generated from pricelees/issue-pr-template
[#16] Reservation 도메인 코드 코틀린 마이그레이션 #17
@ -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() {
|
fun sameDate(date: LocalDate?): ReservationSearchSpecification = andIfNotNull(date?.let {
|
||||||
this.spec = Specification.where(null);
|
Specification { root, _, cb ->
|
||||||
}
|
cb.equal(root.get<LocalDate>("date"), date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
public ReservationSearchSpecification sameThemeId(Long themeId) {
|
fun confirmed(): ReservationSearchSpecification = andIfNotNull { root, _, cb ->
|
||||||
if (themeId != null) {
|
cb.or(
|
||||||
this.spec = this.spec.and(
|
cb.equal(
|
||||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("theme").get("id"), themeId));
|
root.get<ReservationStatus>("reservationStatus"),
|
||||||
}
|
ReservationStatus.CONFIRMED
|
||||||
return this;
|
),
|
||||||
}
|
cb.equal(
|
||||||
|
root.get<ReservationStatus>("reservationStatus"),
|
||||||
|
ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public ReservationSearchSpecification sameMemberId(Long memberId) {
|
fun waiting(): ReservationSearchSpecification = andIfNotNull { root, _, cb ->
|
||||||
if (memberId != null) {
|
cb.equal(
|
||||||
this.spec = this.spec.and(
|
root.get<ReservationStatus>("reservationStatus"),
|
||||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("member").get("id"), memberId));
|
ReservationStatus.WAITING
|
||||||
}
|
)
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public ReservationSearchSpecification sameTimeId(Long timeId) {
|
fun dateStartFrom(dateFrom: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateFrom?.let {
|
||||||
if (timeId != null) {
|
Specification { root, _, cb ->
|
||||||
this.spec = this.spec.and(
|
cb.greaterThanOrEqualTo(root.get("date"), dateFrom)
|
||||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("reservationTime").get("id"),
|
}
|
||||||
timeId));
|
})
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReservationSearchSpecification sameDate(LocalDate date) {
|
fun dateEndAt(dateTo: LocalDate?): ReservationSearchSpecification = andIfNotNull(dateTo?.let {
|
||||||
if (date != null) {
|
Specification { root, _, cb ->
|
||||||
this.spec = this.spec.and(
|
cb.lessThanOrEqualTo(root.get("date"), dateTo)
|
||||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("date"), date));
|
}
|
||||||
}
|
})
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReservationSearchSpecification confirmed() {
|
fun build(): Specification<Reservation> {
|
||||||
this.spec = this.spec.and(
|
return this.spec
|
||||||
(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() {
|
private fun andIfNotNull(condition: Specification<Reservation>?): ReservationSearchSpecification {
|
||||||
this.spec = this.spec.and(
|
condition?.let { this.spec = this.spec.and(condition) }
|
||||||
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("reservationStatus"),
|
return this
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,173 +1,179 @@
|
|||||||
package roomescape.reservation.infrastructure.persistence;
|
package roomescape.reservation.infrastructure.persistence
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import io.kotest.assertions.assertSoftly
|
||||||
|
import io.kotest.core.spec.style.StringSpec
|
||||||
import java.time.LocalDate;
|
import io.kotest.matchers.collections.shouldContainExactly
|
||||||
import java.time.LocalDateTime;
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
import java.util.List;
|
import jakarta.persistence.EntityManager
|
||||||
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import org.junit.jupiter.api.Test;
|
import roomescape.util.MemberFixture
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import roomescape.util.ReservationFixture
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
import roomescape.util.ReservationTimeFixture
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import roomescape.util.ThemeFixture
|
||||||
|
import java.time.LocalDate
|
||||||
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;
|
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
class ReservationSearchSpecificationTest {
|
class ReservationSearchSpecificationTest(
|
||||||
|
val entityManager: EntityManager,
|
||||||
|
val reservationRepository: ReservationRepository
|
||||||
|
) : StringSpec() {
|
||||||
|
|
||||||
@Autowired
|
init {
|
||||||
private ReservationRepository reservationRepository;
|
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
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
private ThemeRepository themeRepository;
|
|
||||||
|
|
||||||
@Autowired
|
assertSoftly(results) {
|
||||||
private MemberRepository memberRepository;
|
this shouldHaveSize 3
|
||||||
|
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
"동일한 회원의 예약을 조회한다" {
|
||||||
* 시간은 모두 현재 시간(LocalTime.now()), 테마, 회원은 동일 확정된 예약은 오늘, 결제 대기인 예약은 어제, 대기 상태인 예약은 내일
|
val spec = ReservationSearchSpecification()
|
||||||
*/
|
.sameMemberId(member.id)
|
||||||
// 현재 시간으로 확정 예약
|
.build()
|
||||||
private Reservation reservation1;
|
|
||||||
// 확정되었으나 결제 대기인 하루 전 예약
|
|
||||||
private Reservation reservation2;
|
|
||||||
// 대기 상태인 내일 예약
|
|
||||||
private Reservation reservation3;
|
|
||||||
|
|
||||||
@BeforeEach
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
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(
|
assertSoftly(results) {
|
||||||
new Reservation(dateTime.toLocalDate(), time, theme, member, ReservationStatus.CONFIRMED));
|
this shouldHaveSize 3
|
||||||
reservation2 = reservationRepository.save(
|
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||||
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("동일한 테마의 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchByThemeId() {
|
.sameTimeId(reservationTime.id)
|
||||||
// given
|
.build()
|
||||||
Long themeId = reservation1.getTheme().getId();
|
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameThemeId(themeId).build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
this shouldHaveSize 3
|
||||||
}
|
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
"동일한 날짜의 예약을 조회한다" {
|
||||||
@DisplayName("동일한 회원의 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchByMemberId() {
|
.sameDate(LocalDate.now())
|
||||||
// given
|
.build()
|
||||||
Long memberId = reservation1.getMember().getId();
|
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameMemberId(memberId).build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
this shouldHaveSize 1
|
||||||
}
|
this shouldContainExactly listOf(confirmedNow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
"확정 상태인 예약을 조회한다" {
|
||||||
@DisplayName("동일한 시간의 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchByTimeId() {
|
.confirmed()
|
||||||
// given
|
.build()
|
||||||
Long timeId = reservation1.getReservationTime().getId();
|
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameTimeId(timeId).build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
this shouldHaveSize 2
|
||||||
}
|
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
"대기 상태인 예약을 조회한다" {
|
||||||
@DisplayName("동일한 날짜의 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchByDate() {
|
.waiting()
|
||||||
// given
|
.build()
|
||||||
LocalDate date = reservation1.getDate();
|
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().sameDate(date).build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation1);
|
this shouldHaveSize 1
|
||||||
}
|
this shouldContainExactly listOf(waitingTomorrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
"예약 날짜가 오늘 이후인 예약을 조회한다" {
|
||||||
@DisplayName("확정 상태인 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchConfirmedReservation() {
|
.dateStartFrom(LocalDate.now())
|
||||||
// given
|
.build()
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().confirmed().build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation1, reservation2);
|
this shouldHaveSize 2
|
||||||
}
|
this shouldContainExactly listOf(confirmedNow, waitingTomorrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
"예약 날짜가 내일 이전인 예약을 조회한다" {
|
||||||
@DisplayName("대기 중인 예약을 찾는다.")
|
val spec = ReservationSearchSpecification()
|
||||||
void searchWaitingReservation() {
|
.dateEndAt(LocalDate.now().plusDays(1))
|
||||||
// given
|
.build()
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().waiting().build();
|
|
||||||
|
|
||||||
// when
|
val results: List<Reservation> = reservationRepository.findAll(spec)
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
|
||||||
|
|
||||||
// then
|
assertSoftly(results) {
|
||||||
assertThat(found).containsExactly(reservation3);
|
this shouldHaveSize 3
|
||||||
}
|
this shouldContainExactly listOf(confirmedNow, confirmedNotPaidYesterday, waitingTomorrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
beforeTest {
|
||||||
@DisplayName("특정 날짜 이후의 예약을 찾는다.")
|
member = MemberFixture.create().also {
|
||||||
void searchDateStartFrom() {
|
entityManager.persist(it)
|
||||||
// given : 어제 이후의 예약을 조회하면, 모든 예약이 조회되어야 한다.
|
}
|
||||||
LocalDate date = LocalDate.now().minusDays(1L);
|
reservationTime = ReservationTimeFixture.create().also {
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().dateStartFrom(date).build();
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
theme = ThemeFixture.create().also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
confirmedNow = ReservationFixture.create(
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
reservationTime = reservationTime,
|
||||||
|
member = member,
|
||||||
|
theme = theme,
|
||||||
|
date = LocalDate.now(),
|
||||||
|
status = ReservationStatus.CONFIRMED
|
||||||
|
).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
confirmedNotPaidYesterday = ReservationFixture.create(
|
||||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
reservationTime = reservationTime,
|
||||||
}
|
member = member,
|
||||||
|
theme = theme,
|
||||||
|
date = LocalDate.now().minusDays(1),
|
||||||
|
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||||
|
).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
waitingTomorrow = ReservationFixture.create(
|
||||||
@DisplayName("특정 날짜 이전의 예약을 찾는다.")
|
reservationTime = reservationTime,
|
||||||
void searchDateEndAt() {
|
member = member,
|
||||||
// given : 내일 이전의 예약을 조회하면, 모든 예약이 조회되어야 한다.
|
theme = theme,
|
||||||
LocalDate date = LocalDate.now().plusDays(1L);
|
date = LocalDate.now().plusDays(1),
|
||||||
Specification<Reservation> spec = new ReservationSearchSpecification().dateEndAt(date).build();
|
status = ReservationStatus.WAITING
|
||||||
|
).also {
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
entityManager.flush()
|
||||||
List<Reservation> found = reservationRepository.findAll(spec);
|
}
|
||||||
|
}
|
||||||
// then
|
|
||||||
assertThat(found).containsExactly(reservation1, reservation2, reservation3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ object TestThemeCreateUtil {
|
|||||||
|
|
||||||
ReservationFixture.create(
|
ReservationFixture.create(
|
||||||
date = date,
|
date = date,
|
||||||
themeEntity = themeEntity,
|
theme = themeEntity,
|
||||||
member = member,
|
member = member,
|
||||||
reservationTime = time,
|
reservationTime = time,
|
||||||
status = ReservationStatus.CONFIRMED
|
status = ReservationStatus.CONFIRMED
|
||||||
|
|||||||
@ -71,11 +71,11 @@ object ReservationFixture {
|
|||||||
fun create(
|
fun create(
|
||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||||
themeEntity: ThemeEntity = ThemeFixture.create(),
|
theme: ThemeEntity = ThemeFixture.create(),
|
||||||
reservationTime: ReservationTime = ReservationTimeFixture.create(),
|
reservationTime: ReservationTime = ReservationTimeFixture.create(),
|
||||||
member: MemberEntity = MemberFixture.create(),
|
member: MemberEntity = MemberFixture.create(),
|
||||||
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||||
): Reservation = Reservation(id, date, reservationTime, themeEntity, member, status)
|
): Reservation = Reservation(id, date, reservationTime, theme, member, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
object JwtFixture {
|
object JwtFixture {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user