diff --git a/pom.xml b/pom.xml
index c0be2ba..2cade74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,29 +55,30 @@
org.projectlombok
lombok
- true
+ provided
org.springframework.boot
spring-boot-starter-test
test
+
+ com.querydsl
+ querydsl-apt
+ 5.0.0
+ jakarta
+ provided
+
+
+ com.querydsl
+ querydsl-jpa
+ jakarta
+ 5.0.0
+
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
org.springframework.boot
spring-boot-maven-plugin
@@ -90,6 +91,16 @@
+
+ org.flywaydb
+ flyway-maven-plugin
+
+ sa
+ password
+ jdbc:h2:mem:todoapp
+ false
+
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -124,7 +135,22 @@
true
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.mysema.query.apt.jpa.JPAAnnotationProcessor
+
+
+
+
-
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java b/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java
index 1639ccf..147a512 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/AppointmentBookingAppApplication.java
@@ -1,9 +1,12 @@
package com.capgemini.training.appointmentbooking;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.impl.BaseJpaRepositoryImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
+@EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class)
public class AppointmentBookingAppApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
index 6bf5120..0833bc3 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/SpecialistEntity.java
@@ -1,28 +1,18 @@
package com.capgemini.training.appointmentbooking.dataaccess.entity;
-import java.util.List;
-
import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
import com.capgemini.training.appointmentbooking.dataaccess.converter.SpecializationConverter;
-
-import jakarta.persistence.CascadeType;
-import jakarta.persistence.Convert;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.OneToMany;
-import jakarta.persistence.OneToOne;
-import jakarta.persistence.SequenceGenerator;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
+import java.util.List;
+
@Entity
@Table(name="SPECIALIST")
@Getter
@Setter
+@NamedQuery(name = "SpecialistEntity.findBySpecialization", query = "select s from SpecialistEntity s where specialization =:specialization")
public class SpecialistEntity extends BaseEntity {
@Id
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
index 2ffc416..19011d8 100644
--- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/TreatmentEntity.java
@@ -1,15 +1,6 @@
package com.capgemini.training.appointmentbooking.dataaccess.entity;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.SequenceGenerator;
-import jakarta.persistence.Table;
-import jakarta.persistence.Version;
+import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@@ -17,6 +8,7 @@
@Table(name="TREATMENT")
@Getter
@Setter
+@NamedQuery(name = "TreatmentEntity.findByNameNamedQuery", query = "select t from TreatmentEntity t where name =:name")
public class TreatmentEntity extends BaseEntity {
@Id
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java
new file mode 100644
index 0000000..53927f1
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java
@@ -0,0 +1,83 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
+import jakarta.persistence.criteria.*;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+@Repository
+public interface AppointmentRepository extends BaseJpaRepository {
+
+ default List findByCriteria(AppointmentCriteria criteria) {
+ Objects.requireNonNull(criteria, "criteria must not be null");
+
+ CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(AppointmentEntity.class);
+ Root root = cq.from(AppointmentEntity.class);
+ List predicates = new ArrayList<>();
+
+ if (criteria.treatmentName() != null && !criteria.treatmentName().trim().isEmpty()) {
+ Join treatmentJoin = root.join("treatment", JoinType.LEFT);
+ predicates.add(cb.like(cb.lower(treatmentJoin.get("name")), "%" + criteria.treatmentName().trim().toLowerCase() + "%"));
+ }
+
+ if (criteria.startDate() != null) {
+ predicates.add(cb.greaterThanOrEqualTo(root.get("dateTime"), criteria.startDate()));
+ }
+
+ if (criteria.endDate() != null) {
+ predicates.add(cb.lessThanOrEqualTo(root.get("dateTime"), criteria.endDate()));
+ }
+
+ if (criteria.status() != null) {
+ predicates.add(cb.equal(root.get("status"), criteria.status()));
+ }
+
+ if (criteria.clientId() != null) {
+ Join clientJoin = root.join("client", JoinType.LEFT);
+ predicates.add(cb.equal(clientJoin.get("id"), criteria.clientId()));
+ }
+
+ if (criteria.specialistId() != null) {
+ Join treatmentJoin = root.join("treatment", JoinType.LEFT);
+ Join specialistJoin = treatmentJoin.join("specialist", JoinType.LEFT);
+ predicates.add(cb.equal(specialistJoin.get("id"), criteria.specialistId()));
+ }
+
+ cq.where(predicates.toArray(new Predicate[0]));
+ return getEntityManager().createQuery(cq).getResultList();
+ }
+
+ List findByDateTimeBetweenAndStatus(Instant start, Instant end, AppointmentStatus status);
+
+ @Query("""
+ SELECT a FROM AppointmentEntity a
+ JOIN a.treatment t
+ WHERE t.specialist.id = :specialistId
+ AND a.dateTime < :date
+ ORDER BY a.dateTime DESC
+ """)
+ List findAppointmentsBySpecialistIdBeforeDate(@Param("specialistId") Long specialistId, @Param("date") Instant date);
+
+ @Query("""
+ SELECT CASE WHEN COUNT(a) > 0 THEN TRUE ELSE FALSE END
+ FROM AppointmentEntity a
+ JOIN a.treatment t
+ WHERE t.specialist.id = :specialistId
+ AND a.dateTime = :date
+ """)
+ boolean hasConflictingAppointmentBySpecialistIdAndDateTime(@Param("specialistId") Long specialistId, @Param("date") Instant date);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java
new file mode 100644
index 0000000..1720af1
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/BaseJpaRepository.java
@@ -0,0 +1,12 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import jakarta.persistence.EntityManager;
+import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
+import org.springframework.data.repository.NoRepositoryBean;
+
+@NoRepositoryBean
+public interface BaseJpaRepository extends JpaRepositoryImplementation {
+
+ EntityManager getEntityManager();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java
new file mode 100644
index 0000000..14140ac
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java
@@ -0,0 +1,28 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ClientRepository extends BaseJpaRepository {
+
+ default List findByName(String firstName, String lastName) {
+ JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
+
+ QClientEntity client = QClientEntity.clientEntity;
+ QUserEntity user = QUserEntity.userEntity;
+
+ return queryFactory
+ .selectFrom(client)
+ .leftJoin(client.user, user)
+ .where(user.firstname.eq(firstName)
+ .and(user.lastname.eq(lastName)))
+ .fetch();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java
new file mode 100644
index 0000000..86925ef
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java
@@ -0,0 +1,33 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QUserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface SpecialistRepository extends BaseJpaRepository, QuerydslPredicateExecutor {
+
+ List findBySpecialization(Specialization specialization);
+
+ default List findSpecialistByName(String firstName, String lastName) {
+ JPAQueryFactory queryFactory = new JPAQueryFactory(getEntityManager());
+
+ QSpecialistEntity specialist = QSpecialistEntity.specialistEntity;
+ QUserEntity user = QUserEntity.userEntity;
+
+ return queryFactory
+ .selectFrom(specialist)
+ .leftJoin(specialist.user, user)
+ .where(user.firstname.eq(firstName)
+ .and(user.lastname.eq(lastName)))
+ .fetch();
+
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java
new file mode 100644
index 0000000..8479256
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java
@@ -0,0 +1,45 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QSpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.QTreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.jpa.impl.JPAQuery;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Objects;
+
+@Repository
+public interface TreatmentRepository extends BaseJpaRepository {
+
+ List findAllByName(String name);
+
+ List findByNameNamedQuery(String name);
+
+ default List findByCriteria(TreatmentCriteria treatmentCriteria) {
+ Objects.requireNonNull(treatmentCriteria, "treatmentCriteria cannot be null");
+
+ QTreatmentEntity treatment = QTreatmentEntity.treatmentEntity;
+ QSpecialistEntity specialist = QSpecialistEntity.specialistEntity;
+
+ JPAQuery query = new JPAQuery<>(getEntityManager());
+ BooleanExpression predicate = treatment.isNotNull();
+
+ if (treatmentCriteria.name() != null) {
+ predicate = predicate.and(treatment.name.like(treatmentCriteria.name()));
+ }
+
+ if (treatmentCriteria.specialization() != null) {
+ predicate = predicate.and(specialist.specialization.eq(treatmentCriteria.specialization()));
+ }
+
+ return query.select(treatment)
+ .from(treatment)
+ .leftJoin(treatment.specialist, specialist) // Join with specialist
+ .where(predicate)
+ .fetch();
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java
new file mode 100644
index 0000000..75d3412
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java
@@ -0,0 +1,44 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Repository
+public interface UserRepository extends BaseJpaRepository {
+
+ default List findByCriteria(UserCriteria criteria) {
+ Objects.requireNonNull(criteria, "criteria must not be null");
+
+ EntityManager entityManager = getEntityManager();
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery cq = cb.createQuery(UserEntity.class);
+ Root root = cq.from(UserEntity.class);
+ List predicates = new ArrayList<>();
+
+ if (criteria.firstName() != null) {
+ predicates.add(cb.like(root.get("firstname"), criteria.firstName()));
+ }
+
+ if (criteria.lastName() != null) {
+ predicates.add(cb.like(root.get("lastname"), criteria.lastName()));
+ }
+
+ if (criteria.email() != null) {
+ predicates.add(cb.like(root.get("email"), criteria.email()));
+ }
+
+ cq.where(predicates.toArray(new Predicate[0]));
+ return entityManager.createQuery(cq).getResultList();
+ }
+
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java
new file mode 100644
index 0000000..be9b96d
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/AppointmentCriteria.java
@@ -0,0 +1,11 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import lombok.Builder;
+
+import java.time.Instant;
+
+@Builder
+public record AppointmentCriteria(String treatmentName, Instant startDate, Instant endDate, AppointmentStatus status,
+ Long clientId, Long specialistId) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java
new file mode 100644
index 0000000..032a97f
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/TreatmentCriteria.java
@@ -0,0 +1,8 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import lombok.Builder;
+
+@Builder
+public record TreatmentCriteria(String name, Specialization specialization) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java
new file mode 100644
index 0000000..cdd1638
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/criteria/UserCriteria.java
@@ -0,0 +1,7 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.criteria;
+
+import lombok.Builder;
+
+@Builder
+public record UserCriteria(String firstName, String lastName, String email) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java
new file mode 100644
index 0000000..da6793a
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/impl/BaseJpaRepositoryImpl.java
@@ -0,0 +1,22 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository.impl;
+
+import com.capgemini.training.appointmentbooking.dataaccess.repository.BaseJpaRepository;
+import jakarta.persistence.EntityManager;
+import org.springframework.data.jpa.repository.support.JpaEntityInformation;
+import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
+
+public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository {
+
+ private final EntityManager entityManager;
+
+ BaseJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
+ super(entityInformation, entityManager);
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public EntityManager getEntityManager() {
+ return this.entityManager;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java
new file mode 100644
index 0000000..41eabb7
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseTest.java
@@ -0,0 +1,17 @@
+package com.capgemini.training.appointmentbooking.common;
+
+import org.assertj.core.api.WithAssertions;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+public class BaseTest implements WithAssertions {
+
+ protected Instant toInstant(String date) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ return LocalDateTime.parse(date, formatter).atZone(ZoneId.systemDefault()).toInstant();
+ }
+
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java
similarity index 96%
rename from src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java
rename to src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java
index 68c28a7..2286fa0 100644
--- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTest.java
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeTestIT.java
@@ -12,7 +12,7 @@
import jakarta.persistence.PersistenceContext;
@DataJpaTest
-public class EntitySmokeTest {
+public class EntitySmokeTestIT {
@PersistenceContext
private EntityManager em;
@@ -34,5 +34,4 @@ void loadAllClasses() {
assertThat((List>) em.createQuery("from " + entityType.getSimpleName()).getResultList()).hasSize(expectedCount));
}
-
}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java
new file mode 100644
index 0000000..e9d2e3a
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryTestIT.java
@@ -0,0 +1,196 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseTest;
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.AppointmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+
+@DataJpaTest
+public class AppointmentRepositoryTestIT extends BaseTest {
+
+ @Inject
+ private AppointmentRepository appointmentRepository;
+
+
+ @Test
+ void shouldFindAll() {
+ // given
+ // when
+ List result = appointmentRepository.findAll();
+
+ // then
+ assertThat(result).hasSize(20);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentName() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .treatmentName("Konsultacja pediatryczna")
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndStartDate() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .treatmentName("Konsultacja pediatryczna")
+ .startDate(toInstant("2024-03-03 14:00:00"))
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndBetweenStartDateAndEndDate() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .treatmentName("Konsultacja pediatryczna")
+ .startDate(toInstant("2024-03-03 14:00:00"))
+ .endDate(toInstant("2024-03-03 15:00:00"))
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindAppointmentsByTreatmentNameAndAppointmentStatus() {
+ // given
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .treatmentName("Konsultacja pediatryczna")
+ .status(AppointmentStatus.CANCELLED)
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ void shouldFindAppointmentsByClientId() {
+ // given
+ Long clientId = -1L;
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .clientId(clientId)
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(5);
+ assertThat(result).extracting(AppointmentEntity::getClient).extracting(ClientEntity::getId).containsOnly(clientId);
+ }
+
+ @Test
+ void shouldFindAppointmentsBySpecialistId() {
+ // given
+ Long specialistId = -1L;
+ AppointmentCriteria criteria = AppointmentCriteria
+ .builder()
+ .specialistId(specialistId)
+ .build();
+
+ // when
+ List result = appointmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(4);
+ assertThat(result).extracting(AppointmentEntity::getTreatment).extracting(TreatmentEntity::getSpecialist).extracting(SpecialistEntity::getId).containsOnly(specialistId);
+ }
+
+ @Test
+ void shouldFindAppointmentsByDateTimeBetweenAndStatus() {
+ // given
+ Instant startDate = toInstant("2024-03-10 00:00:00");
+ Instant endDate = toInstant("2024-03-14 23:59:59");
+ AppointmentStatus status = AppointmentStatus.SCHEDULED;
+
+ // when
+ List result = appointmentRepository.findByDateTimeBetweenAndStatus(startDate, endDate, status);
+
+ // then
+ assertThat(result).hasSize(3);
+ assertThat(result).extracting(AppointmentEntity::getStatus).containsOnly(status);
+ }
+
+ @Test
+ void shouldFindAppointmentById() {
+ // given
+ Long appointmentId = -1L;
+
+ // when
+ Optional result = appointmentRepository.findById(appointmentId);
+
+ // then
+ assertThat(result).isPresent().hasValueSatisfying(appointment -> {
+ assertThat(appointment.getId()).isEqualTo(appointmentId);
+ assertThat(appointment.getStatus()).isEqualTo(AppointmentStatus.SCHEDULED);
+ assertThat(appointment.getClient()).isNotNull();
+ assertThat(appointment.getTreatment()).isNotNull();
+ assertThat(appointment.getDateTime()).isEqualTo(toInstant("2024-03-01 09:00:00"));
+ });
+ }
+
+ @Test
+ void shouldFindAppointmentsBySpecialistIdBeforeDate() {
+ // given
+ Long specialistId = -1L;
+ Instant date = toInstant("2024-03-12 09:00:00");
+
+ // when
+ List appointments = appointmentRepository.findAppointmentsBySpecialistIdBeforeDate(specialistId, date);
+
+ // then
+ assertThat(appointments)
+ .hasSize(2)
+ .allMatch(a -> a.getTreatment().getSpecialist().getId().equals(specialistId),
+ "All appointments belong to the correct specialist")
+ .allMatch(a -> a.getDateTime().isBefore(date),
+ "All appointments should be before the given date");
+ }
+
+ @Test
+ void shouldFindConflictedAppointment() {
+ // given
+ Long specialistId = -1L;
+ Instant date = toInstant("2024-03-05 11:45:00");
+
+ // when
+ boolean conflict = appointmentRepository.hasConflictingAppointmentBySpecialistIdAndDateTime(specialistId, date);
+
+ // then
+ assertThat(conflict).isTrue();
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java
new file mode 100644
index 0000000..22e1777
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryTestIT.java
@@ -0,0 +1,35 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseTest;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.util.List;
+
+@DataJpaTest
+public class ClientRepositoryTestIT extends BaseTest {
+
+ @Inject
+ private ClientRepository clientRepository;
+
+ @Test
+ void shouldFindClientsByName() {
+ // given
+ String firstName = "Stefan";
+ String lastName = "Kowalski";
+
+ // when
+ List clients = clientRepository.findByName(firstName, lastName);
+
+ // then
+ assertThat(clients)
+ .hasSize(1)
+ .first()
+ .satisfies(client -> {
+ assertThat(client.getUser().getFirstname()).isEqualTo(firstName).as("Expected client to have the specified first name");
+ assertThat(client.getUser().getLastname()).isEqualTo(lastName).as("Expected client to have the specified last name");
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java
new file mode 100644
index 0000000..b5aec30
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryTestIT.java
@@ -0,0 +1,51 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseTest;
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.util.List;
+
+@DataJpaTest
+public class SpecialistRepositoryTestIT extends BaseTest {
+
+ @Inject
+ private SpecialistRepository specialistRepository;
+
+ @Test
+ void shouldFindSpecialistsBySpecialization() {
+ // given
+ Specialization specialization = Specialization.DENTIST;
+
+ // when
+ List specialists = specialistRepository.findBySpecialization(specialization);
+
+ // then
+ assertThat(specialists)
+ .hasSize(1)
+ .allMatch(specialist -> specialist.getSpecialization() == specialization,
+ "Specialists should all have specialization " + specialization);
+ }
+
+ @Test
+ void shouldFindSpecialistsByName() {
+ // given
+ String firstName = "Dobromir";
+ String lastName = "Zegula";
+
+ // when
+ List specialists = specialistRepository.findSpecialistByName(firstName, lastName);
+
+ // then
+ assertThat(specialists)
+ .hasSize(1)
+ .first()
+ .satisfies(specialist -> {
+ assertThat(specialist.getUser().getFirstname()).isEqualTo(firstName).as("Expected specialist to have the specified first name");
+ assertThat(specialist.getUser().getLastname()).isEqualTo(lastName).as("Expected specialist to have the specified last name");
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java
new file mode 100644
index 0000000..fc53725
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryTestIT.java
@@ -0,0 +1,168 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseTest;
+import com.capgemini.training.appointmentbooking.common.datatype.Specialization;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.SpecialistEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.TreatmentEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.TreatmentCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.util.List;
+import java.util.Optional;
+
+@DataJpaTest
+public class TreatmentRepositoryTestIT extends BaseTest {
+
+ @Inject
+ private TreatmentRepository treatmentRepository;
+
+ @Inject
+ private SpecialistRepository specialistRepository;
+
+ @Test
+ void shouldFindTreatmentsByName() {
+ // given
+ String treatmentName = "Konsultacja dentystyczna";
+
+ // when
+ List result = treatmentRepository.findAllByName(treatmentName);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameNamedQuery() {
+ // given
+ String treatmentName = "Konsultacja dentystyczna";
+
+ // when
+ List result = treatmentRepository.findByNameNamedQuery(treatmentName);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameQueryDSL() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria
+ .builder()
+ .name("Konsultacja kardiologiczna")
+ .build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsByNameAndSpecialization() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria
+ .builder()
+ .name("Konsultacja dentystyczna")
+ .specialization(Specialization.DENTIST)
+ .build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void shouldFindTreatmentsBySpecialization() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria
+ .builder()
+ .specialization(Specialization.PEDIATRICIAN)
+ .build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(5);
+ }
+
+ @Test
+ void shouldFindAllTreatments() {
+ // given
+ TreatmentCriteria criteria = TreatmentCriteria
+ .builder()
+ .build();
+
+ // when
+ List result = treatmentRepository.findByCriteria(criteria);
+
+ // then
+ assertThat(result).hasSize(12);
+ }
+
+ @Test
+ void shouldFindTreatmentById() {
+ // given
+ Long treatmentId = -1L;
+
+ // when
+ Optional result = treatmentRepository.findById(treatmentId);
+
+ // then
+ assertThat(result)
+ .isPresent()
+ .hasValueSatisfying(treatment -> {
+ assertThat(treatment.getId()).isEqualTo(treatmentId);
+ assertThat(treatment.getName()).isEqualTo("Konsultacja dentystyczna");
+ assertThat(treatment.getDescription()).isEqualTo("Konsultacja dentystyczna z diagnostyką i planem leczenia");
+ assertThat(treatment.getDurationMinutes()).isEqualTo(30);
+
+ SpecialistEntity specialist = treatment.getSpecialist();
+ assertThat(specialist.getId()).isEqualTo(-1L);
+ assertThat(specialist.getUser()).isNotNull();
+ assertThat(specialist.getSpecialization()).isEqualTo(Specialization.DENTIST);
+ assertThat(specialist.getTreatments()).isNotNull();
+ });
+ }
+
+ @Test
+ void shouldSaveTreatment() {
+ // given
+ Long treatmentId = -1L;
+ Optional optionalSpecialist = specialistRepository.findById(treatmentId);
+
+ assertThat(optionalSpecialist)
+ .isPresent()
+ .hasValueSatisfying(specialist -> {
+ TreatmentEntity treatmentEntity = new TreatmentEntity();
+ treatmentEntity.setName("Wypełnienie ubytku");
+ treatmentEntity.setDescription("Usunięcie próchnicy i wypełnienie zęba kompozytem");
+ treatmentEntity.setDurationMinutes(45);
+ treatmentEntity.setSpecialist(specialist);
+
+ // when
+ TreatmentEntity result = treatmentRepository.save(treatmentEntity);
+
+ // then
+ assertThat(result.getId()).isEqualTo(1L);
+ assertThat(result.getName()).isEqualTo("Wypełnienie ubytku");
+ assertThat(result.getDescription()).isEqualTo("Usunięcie próchnicy i wypełnienie zęba kompozytem");
+ assertThat(result.getDurationMinutes()).isEqualTo(45);
+
+ SpecialistEntity savedSpecialist = result.getSpecialist();
+ assertThat(savedSpecialist)
+ .isNotNull()
+ .extracting(SpecialistEntity::getId)
+ .isEqualTo(specialist.getId());
+
+ assertThat(savedSpecialist.getUser()).isNotNull();
+ assertThat(savedSpecialist.getSpecialization()).isEqualTo(Specialization.DENTIST);
+ assertThat(savedSpecialist.getTreatments()).isNotNull();
+ });
+ }
+}
diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java
new file mode 100644
index 0000000..58483aa
--- /dev/null
+++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryTestIT.java
@@ -0,0 +1,43 @@
+package com.capgemini.training.appointmentbooking.dataaccess.repository;
+
+import com.capgemini.training.appointmentbooking.common.BaseTest;
+import com.capgemini.training.appointmentbooking.dataaccess.entity.UserEntity;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.UserCriteria;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.util.List;
+
+@DataJpaTest
+public class UserRepositoryTestIT extends BaseTest {
+
+ @Inject
+ private UserRepository userRepository;
+
+ @Test
+ void shouldFindUsersByCriteria() {
+ // given
+ String firstName = "Stefan";
+ String lastName = "Kowalski";
+ String email = "stefan.kowalski@gmail.com";
+ UserCriteria userCriteria = UserCriteria
+ .builder()
+ .firstName(firstName)
+ .lastName(lastName)
+ .email(email)
+ .build();
+
+ // when
+ List users = userRepository.findByCriteria(userCriteria);
+
+ // then
+ assertThat(users)
+ .hasSize(1)
+ .first()
+ .satisfies(user -> {
+ assertThat(user.getFirstname()).isEqualTo(firstName).as("Expected user to have the specified first name");
+ assertThat(user.getLastname()).isEqualTo(lastName).as("Expected user to have the specified last name");
+ });
+ }
+}