diff --git a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt b/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt index 888d62f2..8bd9c6bf 100644 --- a/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt +++ b/src/main/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecification.kt @@ -1,84 +1,75 @@ -package roomescape.reservation.infrastructure.persistence; +package roomescape.reservation.infrastructure.persistence -import java.time.LocalDate; +import org.springframework.data.jpa.domain.Specification +import roomescape.member.infrastructure.persistence.MemberEntity +import roomescape.theme.infrastructure.persistence.ThemeEntity +import java.time.LocalDate -import org.springframework.data.jpa.domain.Specification; +class ReservationSearchSpecification( + private var spec: Specification = Specification { _, _, _ -> null } +) { + fun sameThemeId(themeId: Long?): ReservationSearchSpecification = andIfNotNull(themeId?.let { + Specification { root, _, cb -> + cb.equal(root.get("theme").get("id"), themeId) + } + }) -public class ReservationSearchSpecification { + fun sameMemberId(memberId: Long?): ReservationSearchSpecification = andIfNotNull(memberId?.let { + Specification { root, _, cb -> + cb.equal(root.get("member").get("id"), memberId) + } + }) - private Specification spec; + fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let { + Specification { root, _, cb -> + cb.equal(root.get("reservationTime").get("id"), timeId) + } + }) - public ReservationSearchSpecification() { - this.spec = Specification.where(null); - } + fun sameDate(date: LocalDate?): ReservationSearchSpecification = andIfNotNull(date?.let { + Specification { root, _, cb -> + cb.equal(root.get("date"), date) + } + }) - public ReservationSearchSpecification sameThemeId(Long themeId) { - if (themeId != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("theme").get("id"), themeId)); - } - return this; - } + fun confirmed(): ReservationSearchSpecification = andIfNotNull { root, _, cb -> + cb.or( + cb.equal( + root.get("reservationStatus"), + ReservationStatus.CONFIRMED + ), + cb.equal( + root.get("reservationStatus"), + ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + ) + ) + } - public ReservationSearchSpecification sameMemberId(Long memberId) { - if (memberId != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("member").get("id"), memberId)); - } - return this; - } + fun waiting(): ReservationSearchSpecification = andIfNotNull { root, _, cb -> + cb.equal( + root.get("reservationStatus"), + ReservationStatus.WAITING + ) + } - public ReservationSearchSpecification sameTimeId(Long timeId) { - if (timeId != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("reservationTime").get("id"), - timeId)); - } - return this; - } + fun dateStartFrom(dateFrom: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateFrom?.let { + Specification { root, _, cb -> + cb.greaterThanOrEqualTo(root.get("date"), dateFrom) + } + }) - public ReservationSearchSpecification sameDate(LocalDate date) { - if (date != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("date"), date)); - } - return this; - } + fun dateEndAt(dateTo: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateTo?.let { + Specification { root, _, cb -> + cb.lessThanOrEqualTo(root.get("date"), dateTo) + } + }) - public ReservationSearchSpecification confirmed() { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.or( - criteriaBuilder.equal(root.get("reservationStatus"), ReservationStatus.CONFIRMED), - criteriaBuilder.equal(root.get("reservationStatus"), - ReservationStatus.CONFIRMED_PAYMENT_REQUIRED) - )); - return this; - } + fun build(): Specification { + return this.spec + } - public ReservationSearchSpecification waiting() { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("reservationStatus"), - ReservationStatus.WAITING)); - return this; - } - - public ReservationSearchSpecification dateStartFrom(LocalDate dateFrom) { - if (dateFrom != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get("date"), dateFrom)); - } - return this; - } - - public ReservationSearchSpecification dateEndAt(LocalDate toDate) { - if (toDate != null) { - this.spec = this.spec.and( - (root, query, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get("date"), toDate)); - } - return this; - } - - public Specification build() { - return this.spec; - } + private fun andIfNotNull(condition: Specification?): ReservationSearchSpecification { + condition?.let { this.spec = this.spec.and(condition) } + return this + } } diff --git a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt b/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt index 4b37df53..be918324 100644 --- a/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt +++ b/src/test/java/roomescape/reservation/infrastructure/persistence/ReservationSearchSpecificationTest.kt @@ -1,173 +1,179 @@ -package roomescape.reservation.infrastructure.persistence; +package roomescape.reservation.infrastructure.persistence -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.data.jpa.domain.Specification; - -import roomescape.member.infrastructure.persistence.MemberEntity; -import roomescape.member.infrastructure.persistence.MemberRepository; -import roomescape.member.infrastructure.persistence.Role; -import roomescape.theme.infrastructure.persistence.ThemeEntity; -import roomescape.theme.infrastructure.persistence.ThemeRepository; +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldHaveSize +import jakarta.persistence.EntityManager +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import roomescape.member.infrastructure.persistence.MemberEntity +import roomescape.theme.infrastructure.persistence.ThemeEntity +import roomescape.util.MemberFixture +import roomescape.util.ReservationFixture +import roomescape.util.ReservationTimeFixture +import roomescape.util.ThemeFixture +import java.time.LocalDate @DataJpaTest -class ReservationSearchSpecificationTest { +class ReservationSearchSpecificationTest( + val entityManager: EntityManager, + val reservationRepository: ReservationRepository +) : StringSpec() { - @Autowired - private ReservationRepository reservationRepository; + init { + lateinit var confirmedNow: Reservation + lateinit var confirmedNotPaidYesterday: Reservation + lateinit var waitingTomorrow: Reservation + lateinit var member: MemberEntity + lateinit var reservationTime: ReservationTime + lateinit var theme: ThemeEntity - @Autowired - private ReservationTimeRepository timeRepository; + "동일한 테마의 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .sameThemeId(theme.id) + .build() - @Autowired - private ThemeRepository themeRepository; + val results: List = reservationRepository.findAll(spec) - @Autowired - private MemberRepository memberRepository; + assertSoftly(results) { + this shouldHaveSize 3 + this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow) + } + } - /** - * 시간은 모두 현재 시간(LocalTime.now()), 테마, 회원은 동일 확정된 예약은 오늘, 결제 대기인 예약은 어제, 대기 상태인 예약은 내일 - */ - // 현재 시간으로 확정 예약 - private Reservation reservation1; - // 확정되었으나 결제 대기인 하루 전 예약 - private Reservation reservation2; - // 대기 상태인 내일 예약 - private Reservation reservation3; + "동일한 회원의 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .sameMemberId(member.id) + .build() - @BeforeEach - void setUp() { - LocalDateTime dateTime = LocalDateTime.now(); - MemberEntity member = memberRepository.save( - new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER)); - ReservationTime time = timeRepository.save(new ReservationTime(dateTime.toLocalTime())); - ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "description", "thumbnail")); + val results: List = reservationRepository.findAll(spec) - reservation1 = reservationRepository.save( - new Reservation(dateTime.toLocalDate(), time, theme, member, ReservationStatus.CONFIRMED)); - reservation2 = reservationRepository.save( - new Reservation(dateTime.toLocalDate().minusDays(1), time, theme, member, - ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)); - reservation3 = reservationRepository.save( - new Reservation(dateTime.toLocalDate().plusDays(1), time, theme, member, ReservationStatus.WAITING)); - } + assertSoftly(results) { + this shouldHaveSize 3 + this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow) + } + } - @Test - @DisplayName("동일한 테마의 예약을 찾는다.") - void searchByThemeId() { - // given - Long themeId = reservation1.getTheme().getId(); - Specification spec = new ReservationSearchSpecification().sameThemeId(themeId).build(); + "동일한 예약 시간의 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .sameTimeId(reservationTime.id) + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation1, reservation2, reservation3); - } + assertSoftly(results) { + this shouldHaveSize 3 + this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow) + } + } - @Test - @DisplayName("동일한 회원의 예약을 찾는다.") - void searchByMemberId() { - // given - Long memberId = reservation1.getMember().getId(); - Specification spec = new ReservationSearchSpecification().sameMemberId(memberId).build(); + "동일한 날짜의 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .sameDate(LocalDate.now()) + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation1, reservation2, reservation3); - } + assertSoftly(results) { + this shouldHaveSize 1 + this shouldContainExactly listOf(confirmedNow) + } + } - @Test - @DisplayName("동일한 시간의 예약을 찾는다.") - void searchByTimeId() { - // given - Long timeId = reservation1.getReservationTime().getId(); - Specification spec = new ReservationSearchSpecification().sameTimeId(timeId).build(); + "확정 상태인 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .confirmed() + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation1, reservation2, reservation3); - } + assertSoftly(results) { + this shouldHaveSize 2 + this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday) + } + } - @Test - @DisplayName("동일한 날짜의 예약을 찾는다.") - void searchByDate() { - // given - LocalDate date = reservation1.getDate(); - Specification spec = new ReservationSearchSpecification().sameDate(date).build(); + "대기 상태인 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .waiting() + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation1); - } + assertSoftly(results) { + this shouldHaveSize 1 + this shouldContainExactly listOf(waitingTomorrow) + } + } - @Test - @DisplayName("확정 상태인 예약을 찾는다.") - void searchConfirmedReservation() { - // given - Specification spec = new ReservationSearchSpecification().confirmed().build(); + "예약 날짜가 오늘 이후인 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .dateStartFrom(LocalDate.now()) + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation1, reservation2); - } + assertSoftly(results) { + this shouldHaveSize 2 + this shouldContainExactly listOf(confirmedNow, waitingTomorrow) + } + } - @Test - @DisplayName("대기 중인 예약을 찾는다.") - void searchWaitingReservation() { - // given - Specification spec = new ReservationSearchSpecification().waiting().build(); + "예약 날짜가 내일 이전인 예약을 조회한다" { + val spec = ReservationSearchSpecification() + .dateEndAt(LocalDate.now().plusDays(1)) + .build() - // when - List found = reservationRepository.findAll(spec); + val results: List = reservationRepository.findAll(spec) - // then - assertThat(found).containsExactly(reservation3); - } + assertSoftly(results) { + this shouldHaveSize 3 + this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow) + } + } - @Test - @DisplayName("특정 날짜 이후의 예약을 찾는다.") - void searchDateStartFrom() { - // given : 어제 이후의 예약을 조회하면, 모든 예약이 조회되어야 한다. - LocalDate date = LocalDate.now().minusDays(1L); - Specification spec = new ReservationSearchSpecification().dateStartFrom(date).build(); + beforeTest { + member = MemberFixture.create().also { + entityManager.persist(it) + } + reservationTime = ReservationTimeFixture.create().also { + entityManager.persist(it) + } + theme = ThemeFixture.create().also { + entityManager.persist(it) + } - // when - List found = reservationRepository.findAll(spec); + confirmedNow = ReservationFixture.create( + reservationTime = reservationTime, + member = member, + theme = theme, + date = LocalDate.now(), + status = ReservationStatus.CONFIRMED + ).also { + entityManager.persist(it) + } - // then - assertThat(found).containsExactly(reservation1, reservation2, reservation3); - } + confirmedNotPaidYesterday = ReservationFixture.create( + reservationTime = reservationTime, + member = member, + theme = theme, + date = LocalDate.now().minusDays(1), + status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED + ).also { + entityManager.persist(it) + } - @Test - @DisplayName("특정 날짜 이전의 예약을 찾는다.") - void searchDateEndAt() { - // given : 내일 이전의 예약을 조회하면, 모든 예약이 조회되어야 한다. - LocalDate date = LocalDate.now().plusDays(1L); - Specification spec = new ReservationSearchSpecification().dateEndAt(date).build(); + waitingTomorrow = ReservationFixture.create( + reservationTime = reservationTime, + member = member, + theme = theme, + date = LocalDate.now().plusDays(1), + status = ReservationStatus.WAITING + ).also { + entityManager.persist(it) + } - // when - List found = reservationRepository.findAll(spec); - - // then - assertThat(found).containsExactly(reservation1, reservation2, reservation3); - } + entityManager.flush() + } + } } diff --git a/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt b/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt index c3765df7..33756ae6 100644 --- a/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt +++ b/src/test/java/roomescape/theme/util/TestThemeCreateUtil.kt @@ -29,7 +29,7 @@ object TestThemeCreateUtil { ReservationFixture.create( date = date, - themeEntity = themeEntity, + theme = themeEntity, member = member, reservationTime = time, status = ReservationStatus.CONFIRMED diff --git a/src/test/java/roomescape/util/Fixtures.kt b/src/test/java/roomescape/util/Fixtures.kt index 6343eab1..07aca7c2 100644 --- a/src/test/java/roomescape/util/Fixtures.kt +++ b/src/test/java/roomescape/util/Fixtures.kt @@ -71,11 +71,11 @@ object ReservationFixture { fun create( id: Long? = null, date: LocalDate = LocalDate.now().plusWeeks(1), - themeEntity: ThemeEntity = ThemeFixture.create(), + theme: ThemeEntity = ThemeFixture.create(), reservationTime: ReservationTime = ReservationTimeFixture.create(), member: MemberEntity = MemberFixture.create(), status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED - ): Reservation = Reservation(id, date, reservationTime, themeEntity, member, status) + ): Reservation = Reservation(id, date, reservationTime, theme, member, status) } object JwtFixture {