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"); + }); + } +}