generated from pricelees/issue-pr-template
refactor: ReservationSearchSpecification 및 테스트 코틀린 전환
This commit is contained in:
parent
b20220794c
commit
5a919eb3ab
@ -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<Reservation> = Specification { _, _, _ -> null }
|
||||
) {
|
||||
fun sameThemeId(themeId: Long?): ReservationSearchSpecification = andIfNotNull(themeId?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.equal(root.get<ThemeEntity>("theme").get<Long>("id"), themeId)
|
||||
}
|
||||
})
|
||||
|
||||
public class ReservationSearchSpecification {
|
||||
fun sameMemberId(memberId: Long?): ReservationSearchSpecification = andIfNotNull(memberId?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.equal(root.get<MemberEntity>("member").get<Long>("id"), memberId)
|
||||
}
|
||||
})
|
||||
|
||||
private Specification<Reservation> spec;
|
||||
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.equal(root.get<ReservationTime>("reservationTime").get<Long>("id"), timeId)
|
||||
}
|
||||
})
|
||||
|
||||
public ReservationSearchSpecification() {
|
||||
this.spec = Specification.where(null);
|
||||
fun sameDate(date: LocalDate?): ReservationSearchSpecification = andIfNotNull(date?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.equal(root.get<LocalDate>("date"), date)
|
||||
}
|
||||
})
|
||||
|
||||
fun confirmed(): ReservationSearchSpecification = andIfNotNull { root, _, cb ->
|
||||
cb.or(
|
||||
cb.equal(
|
||||
root.get<ReservationStatus>("reservationStatus"),
|
||||
ReservationStatus.CONFIRMED
|
||||
),
|
||||
cb.equal(
|
||||
root.get<ReservationStatus>("reservationStatus"),
|
||||
ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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 waiting(): ReservationSearchSpecification = andIfNotNull { root, _, cb ->
|
||||
cb.equal(
|
||||
root.get<ReservationStatus>("reservationStatus"),
|
||||
ReservationStatus.WAITING
|
||||
)
|
||||
}
|
||||
|
||||
public ReservationSearchSpecification sameMemberId(Long memberId) {
|
||||
if (memberId != null) {
|
||||
this.spec = this.spec.and(
|
||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("member").get("id"), memberId));
|
||||
fun dateStartFrom(dateFrom: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateFrom?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.greaterThanOrEqualTo(root.get("date"), dateFrom)
|
||||
}
|
||||
return this;
|
||||
})
|
||||
|
||||
fun dateEndAt(dateTo: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateTo?.let {
|
||||
Specification { root, _, cb ->
|
||||
cb.lessThanOrEqualTo(root.get("date"), dateTo)
|
||||
}
|
||||
})
|
||||
|
||||
fun build(): Specification<Reservation> {
|
||||
return this.spec
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public ReservationSearchSpecification sameDate(LocalDate date) {
|
||||
if (date != null) {
|
||||
this.spec = this.spec.and(
|
||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("date"), date));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<Reservation> build() {
|
||||
return this.spec;
|
||||
private fun andIfNotNull(condition: Specification<Reservation>?): ReservationSearchSpecification {
|
||||
condition?.let { this.spec = this.spec.and(condition) }
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
@Autowired
|
||||
private MemberRepository memberRepository;
|
||||
|
||||
/**
|
||||
* 시간은 모두 현재 시간(LocalTime.now()), 테마, 회원은 동일 확정된 예약은 오늘, 결제 대기인 예약은 어제, 대기 상태인 예약은 내일
|
||||
*/
|
||||
// 현재 시간으로 확정 예약
|
||||
private Reservation reservation1;
|
||||
// 확정되었으나 결제 대기인 하루 전 예약
|
||||
private Reservation reservation2;
|
||||
// 대기 상태인 내일 예약
|
||||
private Reservation reservation3;
|
||||
|
||||
@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"));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일한 테마의 예약을 찾는다.")
|
||||
void searchByThemeId() {
|
||||
// given
|
||||
Long themeId = reservation1.getTheme().getId();
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameThemeId(themeId).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일한 회원의 예약을 찾는다.")
|
||||
void searchByMemberId() {
|
||||
// given
|
||||
Long memberId = reservation1.getMember().getId();
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameMemberId(memberId).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일한 시간의 예약을 찾는다.")
|
||||
void searchByTimeId() {
|
||||
// given
|
||||
Long timeId = reservation1.getReservationTime().getId();
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameTimeId(timeId).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일한 날짜의 예약을 찾는다.")
|
||||
void searchByDate() {
|
||||
// given
|
||||
LocalDate date = reservation1.getDate();
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameDate(date).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("확정 상태인 예약을 찾는다.")
|
||||
void searchConfirmedReservation() {
|
||||
// given
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().confirmed().build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("대기 중인 예약을 찾는다.")
|
||||
void searchWaitingReservation() {
|
||||
// given
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().waiting().build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("특정 날짜 이후의 예약을 찾는다.")
|
||||
void searchDateStartFrom() {
|
||||
// given : 어제 이후의 예약을 조회하면, 모든 예약이 조회되어야 한다.
|
||||
LocalDate date = LocalDate.now().minusDays(1L);
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().dateStartFrom(date).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("특정 날짜 이전의 예약을 찾는다.")
|
||||
void searchDateEndAt() {
|
||||
// given : 내일 이전의 예약을 조회하면, 모든 예약이 조회되어야 한다.
|
||||
LocalDate date = LocalDate.now().plusDays(1L);
|
||||
Specification<Reservation> spec = new ReservationSearchSpecification().dateEndAt(date).build();
|
||||
|
||||
// when
|
||||
List<Reservation> found = reservationRepository.findAll(spec);
|
||||
|
||||
// then
|
||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 3
|
||||
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
"동일한 회원의 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.sameMemberId(member.id)
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 3
|
||||
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
"동일한 예약 시간의 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.sameTimeId(reservationTime.id)
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 3
|
||||
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
"동일한 날짜의 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.sameDate(LocalDate.now())
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 1
|
||||
this shouldContainExactly listOf(confirmedNow)
|
||||
}
|
||||
}
|
||||
|
||||
"확정 상태인 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.confirmed()
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 2
|
||||
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday)
|
||||
}
|
||||
}
|
||||
|
||||
"대기 상태인 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.waiting()
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 1
|
||||
this shouldContainExactly listOf(waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
"예약 날짜가 오늘 이후인 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.dateStartFrom(LocalDate.now())
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 2
|
||||
this shouldContainExactly listOf(confirmedNow, waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
"예약 날짜가 내일 이전인 예약을 조회한다" {
|
||||
val spec = ReservationSearchSpecification()
|
||||
.dateEndAt(LocalDate.now().plusDays(1))
|
||||
.build()
|
||||
|
||||
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||
|
||||
assertSoftly(results) {
|
||||
this shouldHaveSize 3
|
||||
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||
}
|
||||
}
|
||||
|
||||
beforeTest {
|
||||
member = MemberFixture.create().also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
reservationTime = ReservationTimeFixture.create().also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
theme = ThemeFixture.create().also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
|
||||
confirmedNow = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now(),
|
||||
status = ReservationStatus.CONFIRMED
|
||||
).also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
|
||||
confirmedNotPaidYesterday = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now().minusDays(1),
|
||||
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||
).also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
|
||||
waitingTomorrow = ReservationFixture.create(
|
||||
reservationTime = reservationTime,
|
||||
member = member,
|
||||
theme = theme,
|
||||
date = LocalDate.now().plusDays(1),
|
||||
status = ReservationStatus.WAITING
|
||||
).also {
|
||||
entityManager.persist(it)
|
||||
}
|
||||
|
||||
entityManager.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ object TestThemeCreateUtil {
|
||||
|
||||
ReservationFixture.create(
|
||||
date = date,
|
||||
themeEntity = themeEntity,
|
||||
theme = themeEntity,
|
||||
member = member,
|
||||
reservationTime = time,
|
||||
status = ReservationStatus.CONFIRMED
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user