diff --git a/.gitattributes b/.gitattributes index 3b41682..44153cd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,9 @@ /mvnw text eol=lf *.cmd text eol=crlf +* text eol=lf +*.bat eol=crlf +*.htm text diff=html +*.html text diff=html +*.xhtml text diff=html +*.java text diff=java +*.css text diff=css \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2b97fb0..9694756 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,66 @@ + + org.flywaydb + flyway-maven-plugin + + sa + password + jdbc:h2:mem:todoapp + false + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.2 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.5.2 + + + verify + + report-only + + + + + true + + + + com.mysema.maven + apt-maven-plugin + 1.1.3 + + + + process + + + target/generated-sources/java + com.mysema.query.apt.jpa.JPAAnnotationProcessor + + + + org.apache.maven.plugins maven-surefire-plugin @@ -144,5 +205,4 @@ - diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java new file mode 100644 index 0000000..429151b --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/config/DataaccessConfiguration.java @@ -0,0 +1,10 @@ +package com.capgemini.training.appointmentbooking.dataaccess.config; + +import com.capgemini.training.appointmentbooking.dataaccess.repository.impl.BaseJpaRepositoryImpl; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class, basePackages = "com.capgemini.training.appointmentbooking.dataaccess.repository") +public class DataaccessConfiguration { +} diff --git a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java index 28e888f..12eeb0c 100644 --- a/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/entity/AppointmentEntity.java @@ -1,6 +1,7 @@ package com.capgemini.training.appointmentbooking.dataaccess.entity; import java.time.Instant; +import java.util.Objects; import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus; @@ -38,7 +39,30 @@ public class AppointmentEntity extends BaseEntity { @Column(name = "DATE_TIME") private Instant dateTime; + @Column(name = "END_DATE_TIME") + private Instant endsAt; + @Enumerated(EnumType.STRING) private AppointmentStatus status; + @Override + public void prePersist() { + super.prePersist(); + validateDates(); + } + + @Override + public void preUpdate() { + super.preUpdate(); + validateDates(); + } + + private void validateDates() { + Objects.requireNonNull(this.dateTime); + Objects.requireNonNull(this.endsAt); + if (!this.endsAt.isAfter(this.dateTime)) { + throw new IllegalStateException( + String.format("Starting date: %s must be before end date: %s", this.dateTime, this.endsAt)); + } + } } 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 63f8ead..4281a83 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 25239cd..0cc936c 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 @@ -7,6 +7,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedQuery; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; import lombok.Getter; @@ -16,6 +17,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..03541a8 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepository.java @@ -0,0 +1,86 @@ +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 + AND a.status <> com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus.CANCELLED + """) + 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..9acca13 --- /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.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface BaseJpaRepository extends JpaRepository { + + 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..550b5b3 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepository.java @@ -0,0 +1,24 @@ +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..bd0efb7 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepository.java @@ -0,0 +1,31 @@ +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..9911a91 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepository.java @@ -0,0 +1,43 @@ +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 findAllByNameStartingWithIgnoringCase(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..be8eeb6 --- /dev/null +++ b/src/main/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepository.java @@ -0,0 +1,43 @@ +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..17ae7ef --- /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..32a2270 --- /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/main/resources/application.properties b/src/main/resources/application.properties index b4e3d67..141d23b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,3 +13,7 @@ management.endpoints.web.exposure.include=* spring.flyway.locations=classpath:db/migration spring.flyway.enabled=true spring.flyway.clean-on-validation-error=true + +spring.jpa.hibernate.ddl-auto=none + +spring.banner.location=classpath:/banner/jbf_banner.txt diff --git a/src/main/resources/banner/jbf_banner.txt b/src/main/resources/banner/jbf_banner.txt new file mode 100644 index 0000000..32241af --- /dev/null +++ b/src/main/resources/banner/jbf_banner.txt @@ -0,0 +1,8 @@ + ,--. ,-----. ,--. ,--. ,------. ,--. ,--. ,--. + | | ,--,--.,--. ,--.,--,--. | |) /_ ,--,--.,---.| |,-. ,---. ,--,--, ,-| | | .---',--.,--.,--,--, ,-| | ,--,--.,-' '-.`--' ,---. ,--,--, +,--. | |' ,-. | \ `' /' ,-. | | .-. \' ,-. | .--'| /| .-. :| \' .-. | | `--, | || || \' .-. |' ,-. |'-. .-',--.| .-. || \ +| '-' /\ '-' | \ / \ '-' | | '--' /\ '-' \ `--.| \ \\ --.| || |\ `-' | | |` ' '' '| || |\ `-' |\ '-' | | | | |' '-' '| || | + `-----' `--`--' `--' `--`--' `------' `--`--'`---'`--'`--'`----'`--''--' `---' `--' `----' `--''--' `---' `--`--' `--' `--' `---' `--''--' + +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} \ No newline at end of file diff --git a/src/main/resources/db/migration/1.0/V0001__Create_schema.sql b/src/main/resources/db/migration/1.0/V0001__Create_schema.sql index cea91c1..b3ed7d7 100644 --- a/src/main/resources/db/migration/1.0/V0001__Create_schema.sql +++ b/src/main/resources/db/migration/1.0/V0001__Create_schema.sql @@ -49,6 +49,7 @@ CREATE TABLE APPOINTMENT ( ID NUMBER(19,0) NOT NULL, VERSION INTEGER NOT NULL, DATE_TIME TIMESTAMP NOT NULL, + END_DATE_TIME TIMESTAMP NOT NULL, STATUS VARCHAR(128) NOT NULL DEFAULT 'SCHEDULED', CLIENT_ID NUMBER(19,0) NOT NULL, TREATMENT_ID NUMBER(19,0) NOT NULL, @@ -69,6 +70,6 @@ CREATE SEQUENCE APPOINTMENT_SEQ START WITH 1 INCREMENT BY 100 NOCYCLE; -- CREATING INDEX CREATE INDEX IDX_CLIENT_USER ON CLIENT(USER_ID); CREATE INDEX IDX_SPECIAL_USER ON SPECIALIST(USER_ID); -CREATE INDEX IDX_TREATM_SPECIAL ON TREATMENT(SPECIALIST_ID); +CREATE INDEX IDX_TREATM_SPECIAL ON TREATMENT(SPECIALIST_ID, ID); CREATE INDEX IDX_APPOINT_CLIENT ON APPOINTMENT(CLIENT_ID); -CREATE INDEX IDX_APPOINT_TREATM ON APPOINTMENT(TREATMENT_ID); \ No newline at end of file +CREATE INDEX IDX_APPOINT_TREATM ON APPOINTMENT(TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS); \ No newline at end of file diff --git a/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql b/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql index 24c169f..ffe8296 100644 --- a/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql +++ b/src/main/resources/db/migration/1.0/V0002__Create_mockdata.sql @@ -37,23 +37,23 @@ INSERT INTO TREATMENT(ID, VERSION, NAME, DESCRIPTION, DURATION_MINUTES, SPECIALI INSERT INTO TREATMENT(ID, VERSION, NAME, DESCRIPTION, DURATION_MINUTES, SPECIALIST_ID, CREATED, LAST_UPDATED) VALUES (-12, 0, 'Rekonstrukcja więzadła ACL', 'Rekonstrukcją więzadła krzyżowego przedniego (ACL) z zastąpieniem uszkodzonego więzadła nowym więzadłem ze ścięgien pacjenta.', 180, -4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -- APPOINTMENTS -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-1, 0, -1, -1, '2024-03-01 09:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-2, 0, -2, -3, '2024-03-02 10:30:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-3, 0, -3, -5, '2024-03-03 14:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-4, 0, -4, -10, '2024-03-04 08:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-5, 0, -1, -2, '2024-03-05 11:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-6, 0, -2, -4, '2024-03-06 16:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-7, 0, -3, -6, '2024-03-07 09:30:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-8, 0, -4, -11, '2024-03-08 13:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-9, 0, -1, -7, '2024-03-09 10:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-10, 0, -2, -8, '2024-03-10 12:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-11, 0, -3, -9, '2024-03-11 15:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-12, 0, -4, -12, '2024-03-12 17:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-13, 0, -1, -1, '2024-03-13 08:30:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-14, 0, -2, -3, '2024-03-14 11:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-15, 0, -3, -5, '2024-03-15 13:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-16, 0, -4, -10, '2024-03-16 09:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-17, 0, -1, -2, '2024-03-17 14:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-18, 0, -2, -4, '2024-03-18 16:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-19, 0, -3, -6, '2024-03-19 10:45:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); -INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-20, 0, -4, -11, '2024-03-20 12:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); \ No newline at end of file +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-1, 0, -1, -1, '2024-03-01 09:00:00', '2024-03-01 09:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-2, 0, -2, -3, '2024-03-02 10:30:00', '2024-03-02 10:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-3, 0, -3, -5, '2024-03-03 14:00:00', '2024-03-03 14:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-4, 0, -4, -10, '2024-03-04 08:15:00', '2024-03-04 08:30:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-5, 0, -1, -2, '2024-03-05 11:45:00', '2024-03-05 12:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-6, 0, -2, -4, '2024-03-06 16:30:00', '2024-03-06 16:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-7, 0, -3, -6, '2024-03-07 09:30:00', '2024-03-07 09:45:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-8, 0, -4, -11, '2024-03-08 13:45:00', '2024-03-08 14:00:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-9, 0, -1, -7, '2024-03-09 10:00:00', '2024-03-09 10:15:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-10, 0, -2, -8, '2024-03-10 12:30:00', '2024-03-10 12:45:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-11, 0, -3, -9, '2024-03-11 15:00:00', '2024-03-11 15:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-12, 0, -4, -12,'2024-03-12 17:15:00', '2024-03-12 17:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-13, 0, -1, -1, '2024-03-13 08:30:00', '2024-03-13 08:45:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-14, 0, -2, -3, '2024-03-14 11:00:00', '2024-03-14 11:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-15, 0, -3, -5, '2024-03-15 13:00:00', '2024-03-15 13:15:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-16, 0, -4, -10,'2024-03-16 09:15:00', '2024-03-16 09:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-17, 0, -1, -2, '2024-03-17 14:45:00', '2024-03-17 15:00:00', 'COMPLETED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-18, 0, -2, -4, '2024-03-18 16:00:00', '2024-03-18 16:15:00', 'SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-19, 0, -3, -6, '2024-03-19 10:45:00', '2024-03-19 11:00:00', 'CANCELLED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); +INSERT INTO APPOINTMENT(ID, VERSION, CLIENT_ID, TREATMENT_ID, DATE_TIME, END_DATE_TIME, STATUS, CREATED, LAST_UPDATED) VALUES (-20, 0, -4, -11,'2024-03-20 12:15:00', '2024-03-20 12:30:00','SCHEDULED', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); \ No newline at end of file diff --git a/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java new file mode 100644 index 0000000..aebe70e --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/common/BaseDataJpaTest.java @@ -0,0 +1,10 @@ +package com.capgemini.training.appointmentbooking.common; + +import com.capgemini.training.appointmentbooking.dataaccess.config.DataaccessConfiguration; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +@DataJpaTest +@Import(DataaccessConfiguration.class) +public class BaseDataJpaTest extends BaseTest { +} 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..a6a0e2a --- /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/EntitySmokeIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java index 3b7a4a9..b6ad16f 100644 --- a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/entity/EntitySmokeIT.java @@ -10,8 +10,9 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import org.springframework.data.repository.config.BootstrapMode; -@DataJpaTest +@DataJpaTest(bootstrapMode = BootstrapMode.LAZY) public class EntitySmokeIT { @PersistenceContext diff --git a/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java new file mode 100644 index 0000000..eaf926a --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/AppointmentRepositoryIT.java @@ -0,0 +1,176 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest; +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 java.time.Instant; +import java.util.List; +import java.util.Optional; + +public class AppointmentRepositoryIT extends BaseDataJpaTest { + + @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).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/ClientRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryIT.java new file mode 100644 index 0000000..2340b77 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/ClientRepositoryIT.java @@ -0,0 +1,32 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest; +import com.capgemini.training.appointmentbooking.dataaccess.entity.ClientEntity; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class ClientRepositoryIT extends BaseDataJpaTest { + + @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/SpecialistRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryIT.java new file mode 100644 index 0000000..7b88e04 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/SpecialistRepositoryIT.java @@ -0,0 +1,46 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest; +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 java.util.List; + +public class SpecialistRepositoryIT extends BaseDataJpaTest { + + @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/TreatmentRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryIT.java new file mode 100644 index 0000000..9d4f7bb --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/TreatmentRepositoryIT.java @@ -0,0 +1,149 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest; +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 java.util.List; +import java.util.Optional; + +public class TreatmentRepositoryIT extends BaseDataJpaTest { + + @Inject + private TreatmentRepository treatmentRepository; + + @Inject + private SpecialistRepository specialistRepository; + + @Test + void shouldFindTreatmentsByName() { + // given + String treatmentName = "konsUltacja de"; + + // when + List result = treatmentRepository.findAllByNameStartingWithIgnoringCase(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).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).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()).isOne(); + 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/UserRepositoryIT.java b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryIT.java new file mode 100644 index 0000000..3edbdb1 --- /dev/null +++ b/src/test/java/com/capgemini/training/appointmentbooking/dataaccess/repository/UserRepositoryIT.java @@ -0,0 +1,33 @@ +package com.capgemini.training.appointmentbooking.dataaccess.repository; + +import com.capgemini.training.appointmentbooking.common.BaseDataJpaTest; +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 java.util.List; + +public class UserRepositoryIT extends BaseDataJpaTest { + + @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"); + }); + } +}