generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -61,6 +61,7 @@ class PaymentWriterTest : FunSpec({
|
||||
val canceledAt = OffsetDateTime.now()
|
||||
|
||||
afterTest {
|
||||
clearMocks(paymentRepository)
|
||||
clearMocks(canceledPaymentRepository)
|
||||
}
|
||||
|
||||
@ -72,11 +73,14 @@ class PaymentWriterTest : FunSpec({
|
||||
canceledPaymentRepository.save(capture(slot))
|
||||
} returns mockk()
|
||||
|
||||
every {
|
||||
paymentRepository.deleteByPaymentKey(paymentKey)
|
||||
} returns Unit
|
||||
|
||||
paymentWriter.createCanceled(payment, cancelReason, canceledAt)
|
||||
|
||||
verify(exactly = 1) {
|
||||
canceledPaymentRepository.save(any())
|
||||
}
|
||||
verify(exactly = 1) { canceledPaymentRepository.save(any()) }
|
||||
verify(exactly = 1) { paymentRepository.deleteByPaymentKey(any()) }
|
||||
|
||||
slot.captured.also {
|
||||
it.paymentKey shouldBe payment.paymentKey
|
||||
@ -92,6 +96,10 @@ class PaymentWriterTest : FunSpec({
|
||||
canceledPaymentRepository.save(capture(slot))
|
||||
} returns mockk()
|
||||
|
||||
every {
|
||||
paymentRepository.deleteByPaymentKey(paymentKey)
|
||||
} returns Unit
|
||||
|
||||
paymentWriter.createCanceled(
|
||||
cancelReason = cancelReason,
|
||||
cancelAmount = totalAmount,
|
||||
@ -100,9 +108,8 @@ class PaymentWriterTest : FunSpec({
|
||||
paymentKey = paymentKey
|
||||
)
|
||||
|
||||
verify(exactly = 1) {
|
||||
canceledPaymentRepository.save(any())
|
||||
}
|
||||
verify(exactly = 1) { canceledPaymentRepository.save(any()) }
|
||||
verify(exactly = 1) { paymentRepository.deleteByPaymentKey(any()) }
|
||||
|
||||
slot.captured.also {
|
||||
it.paymentKey shouldBe paymentKey
|
||||
|
||||
@ -0,0 +1,285 @@
|
||||
package roomescape.reservation.business
|
||||
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.*
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.exception.ReservationException
|
||||
import roomescape.reservation.implement.ReservationFinder
|
||||
import roomescape.reservation.implement.ReservationWriter
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
|
||||
class ReservationCommandServiceTest : FunSpec({
|
||||
|
||||
val reservationFinder: ReservationFinder = mockk()
|
||||
val reservationWriter: ReservationWriter = mockk()
|
||||
val reservationCommandService = ReservationCommandService(reservationFinder, reservationWriter)
|
||||
|
||||
context("createReservationWithPayment") {
|
||||
val request = ReservationFixture.createRequest()
|
||||
val memberId = 1L
|
||||
|
||||
test("정상 응답") {
|
||||
val createdReservation = ReservationFixture.create(
|
||||
date = request.date,
|
||||
time = TimeFixture.create(id = request.timeId),
|
||||
theme = ThemeFixture.create(id = request.themeId),
|
||||
member = MemberFixture.create(id = memberId),
|
||||
status = ReservationStatus.CONFIRMED
|
||||
)
|
||||
|
||||
every {
|
||||
reservationWriter.create(
|
||||
date = request.date,
|
||||
timeId = request.timeId,
|
||||
themeId = request.themeId,
|
||||
status = ReservationStatus.CONFIRMED,
|
||||
memberId = memberId,
|
||||
requesterId = memberId
|
||||
)
|
||||
} returns createdReservation
|
||||
|
||||
val result = reservationCommandService.createReservationWithPayment(request, memberId)
|
||||
|
||||
assertSoftly(result) {
|
||||
this.date shouldBe request.date
|
||||
this.time.id shouldBe request.timeId
|
||||
this.theme.id shouldBe request.themeId
|
||||
this.member.id shouldBe memberId
|
||||
this.status shouldBe ReservationStatus.CONFIRMED
|
||||
}
|
||||
}
|
||||
|
||||
test("예약 생성에 실패하면 예외 응답") {
|
||||
every {
|
||||
reservationWriter.create(any(), any(), any(), any(), any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.RESERVATION_DUPLICATED)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.createReservationWithPayment(request, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_DUPLICATED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("createReservationByAdmin") {
|
||||
val request = ReservationFixture.createAdminRequest()
|
||||
val adminId = request.memberId + 1
|
||||
|
||||
test("정상 응답") {
|
||||
val createdReservation = ReservationFixture.create(
|
||||
date = request.date,
|
||||
time = TimeFixture.create(id = request.timeId),
|
||||
theme = ThemeFixture.create(id = request.themeId),
|
||||
member = MemberFixture.create(id = request.memberId),
|
||||
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||
)
|
||||
|
||||
every {
|
||||
reservationWriter.create(
|
||||
date = request.date,
|
||||
timeId = request.timeId,
|
||||
themeId = request.themeId,
|
||||
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED,
|
||||
memberId = request.memberId,
|
||||
requesterId = adminId
|
||||
)
|
||||
} returns createdReservation
|
||||
|
||||
val response = reservationCommandService.createReservationByAdmin(request, adminId)
|
||||
|
||||
assertSoftly(response) {
|
||||
this.date shouldBe request.date
|
||||
this.time.id shouldBe request.timeId
|
||||
this.theme.id shouldBe request.themeId
|
||||
this.member.id shouldBe request.memberId
|
||||
this.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("createWaiting") {
|
||||
val request = ReservationFixture.createWaitingRequest()
|
||||
val memberId = 1L
|
||||
|
||||
test("정상 응답") {
|
||||
val createdWaiting = ReservationFixture.create(
|
||||
date = request.date,
|
||||
time = TimeFixture.create(id = request.timeId),
|
||||
theme = ThemeFixture.create(id = request.themeId),
|
||||
member = MemberFixture.create(id = memberId),
|
||||
status = ReservationStatus.WAITING
|
||||
)
|
||||
|
||||
every {
|
||||
reservationWriter.create(
|
||||
date = request.date,
|
||||
timeId = request.timeId,
|
||||
themeId = request.themeId,
|
||||
status = ReservationStatus.WAITING,
|
||||
memberId = memberId,
|
||||
requesterId = memberId
|
||||
)
|
||||
} returns createdWaiting
|
||||
|
||||
val response = reservationCommandService.createWaiting(request, memberId)
|
||||
|
||||
assertSoftly(response) {
|
||||
this.date shouldBe request.date
|
||||
this.time.id shouldBe request.timeId
|
||||
this.theme.id shouldBe request.themeId
|
||||
this.member.id shouldBe memberId
|
||||
this.status shouldBe ReservationStatus.WAITING
|
||||
}
|
||||
}
|
||||
|
||||
test("이미 예약한 내역이 있으면 예외 응답") {
|
||||
every {
|
||||
reservationWriter.create(any(), any(), any(), any(), any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.ALREADY_RESERVE)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.createWaiting(request, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_RESERVE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("deleteReservation") {
|
||||
val reservationId = 1L
|
||||
val memberId = 1L
|
||||
val reservation = ReservationFixture.create(id = reservationId, member = MemberFixture.create(id = memberId))
|
||||
|
||||
test("정상 응답") {
|
||||
every { reservationFinder.findById(reservationId) } returns reservation
|
||||
every { reservationWriter.deleteConfirmed(reservation, memberId) } just Runs
|
||||
|
||||
shouldNotThrow<Exception> {
|
||||
reservationCommandService.deleteReservation(reservationId, memberId)
|
||||
}
|
||||
|
||||
verify(exactly = 1) { reservationWriter.deleteConfirmed(reservation, memberId) }
|
||||
}
|
||||
|
||||
test("예약을 찾을 수 없으면 예외 응답") {
|
||||
every {
|
||||
reservationFinder.findById(reservationId)
|
||||
} throws ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.deleteReservation(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("삭제하려는 회원이 관리자가 아니고, 대기한 회원과 다르면 예외 응답") {
|
||||
every { reservationFinder.findById(reservationId) } returns reservation
|
||||
every {
|
||||
reservationWriter.deleteConfirmed(reservation, memberId)
|
||||
} throws ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.deleteReservation(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("confirmWaiting") {
|
||||
val reservationId = 1L
|
||||
val memberId = 99L // Admin
|
||||
|
||||
test("정상 응답") {
|
||||
every { reservationWriter.confirm(reservationId) } just Runs
|
||||
|
||||
shouldNotThrow<Exception> {
|
||||
reservationCommandService.confirmWaiting(reservationId, memberId)
|
||||
}
|
||||
|
||||
verify(exactly = 1) { reservationWriter.confirm(reservationId) }
|
||||
}
|
||||
|
||||
test("이미 확정된 예약이 있으면 예외 응답") {
|
||||
every {
|
||||
reservationWriter.confirm(reservationId)
|
||||
} throws ReservationException(ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.confirmWaiting(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("deleteWaiting") {
|
||||
val reservationId = 1L
|
||||
val memberId = 1L
|
||||
val waitingReservation = ReservationFixture.create(
|
||||
id = reservationId,
|
||||
member = MemberFixture.create(id = memberId),
|
||||
status = ReservationStatus.WAITING
|
||||
)
|
||||
|
||||
test("정상 응답") {
|
||||
every { reservationFinder.findById(reservationId) } returns waitingReservation
|
||||
every { reservationWriter.deleteWaiting(waitingReservation, memberId) } just Runs
|
||||
|
||||
shouldNotThrow<Exception> {
|
||||
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||
}
|
||||
|
||||
verify(exactly = 1) { reservationWriter.deleteWaiting(waitingReservation, memberId) }
|
||||
}
|
||||
|
||||
test("예약을 찾을 수 없으면 예외 응답") {
|
||||
every {
|
||||
reservationFinder.findById(reservationId)
|
||||
} throws ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
|
||||
|
||||
shouldThrow< ReservationException> {
|
||||
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("대기 상태가 아니면 예외 응답") {
|
||||
every { reservationFinder.findById(reservationId) } returns waitingReservation
|
||||
every {
|
||||
reservationWriter.deleteWaiting(waitingReservation, memberId)
|
||||
} throws ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED
|
||||
}
|
||||
}
|
||||
|
||||
test("삭제하려는 회원이 관리자가 아니고, 대기한 회원과 다르면 예외 응답") {
|
||||
every { reservationFinder.findById(reservationId) } returns waitingReservation
|
||||
every {
|
||||
reservationWriter.deleteWaiting(waitingReservation, memberId)
|
||||
} throws ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationCommandService.deleteWaiting(reservationId, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,118 @@
|
||||
package roomescape.reservation.business
|
||||
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.exception.ReservationException
|
||||
import roomescape.reservation.implement.ReservationFinder
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import java.time.LocalDate
|
||||
|
||||
class ReservationQueryServiceTest : FunSpec({
|
||||
|
||||
val reservationFinder: ReservationFinder = mockk()
|
||||
val reservationQueryService = ReservationQueryService(reservationFinder)
|
||||
|
||||
context("findReservations") {
|
||||
test("정상 응답") {
|
||||
val confirmedReservations = listOf(
|
||||
ReservationFixture.create(status = ReservationStatus.CONFIRMED),
|
||||
ReservationFixture.create(status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
||||
)
|
||||
every {
|
||||
reservationFinder.findAllByStatuses(*ReservationStatus.confirmedStatus())
|
||||
} returns confirmedReservations
|
||||
|
||||
val response = reservationQueryService.findReservations()
|
||||
|
||||
assertSoftly(response.reservations) {
|
||||
this shouldHaveSize 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findAllWaiting") {
|
||||
test("정상 응답") {
|
||||
val waitingReservations = listOf(
|
||||
ReservationFixture.create(status = ReservationStatus.WAITING),
|
||||
ReservationFixture.create(status = ReservationStatus.WAITING)
|
||||
)
|
||||
every {
|
||||
reservationFinder.findAllByStatuses(ReservationStatus.WAITING)
|
||||
} returns waitingReservations
|
||||
|
||||
val response = reservationQueryService.findAllWaiting()
|
||||
|
||||
assertSoftly(response.reservations) {
|
||||
this shouldHaveSize 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findReservationsByMemberId") {
|
||||
val memberId = 1L
|
||||
test("정상 응답") {
|
||||
val myReservations = listOf<MyReservationRetrieveResponse>(mockk(), mockk())
|
||||
|
||||
every {
|
||||
reservationFinder.findAllByMemberId(memberId)
|
||||
} returns myReservations
|
||||
|
||||
val response = reservationQueryService.findReservationsByMemberId(memberId)
|
||||
|
||||
response.reservations shouldHaveSize 2
|
||||
}
|
||||
}
|
||||
|
||||
context("searchReservations") {
|
||||
val themeId = 1L
|
||||
val memberId = 1L
|
||||
val startFrom = LocalDate.now()
|
||||
val endAt = LocalDate.now().plusDays(1)
|
||||
|
||||
test("정상 응답") {
|
||||
val searchedReservations = listOf(
|
||||
ReservationFixture.create(
|
||||
theme = ThemeFixture.create(themeId),
|
||||
member = MemberFixture.create(memberId),
|
||||
date = startFrom
|
||||
)
|
||||
)
|
||||
|
||||
every {
|
||||
reservationFinder.searchReservations(themeId, memberId, startFrom, endAt)
|
||||
} returns searchedReservations
|
||||
|
||||
val response = reservationQueryService.searchReservations(themeId, memberId, startFrom, endAt)
|
||||
|
||||
assertSoftly(response.reservations) {
|
||||
this shouldHaveSize 1
|
||||
this[0].theme.id shouldBe themeId
|
||||
this[0].member.id shouldBe memberId
|
||||
this[0].date shouldBe startFrom
|
||||
}
|
||||
}
|
||||
|
||||
test("종료 날짜가 시작 날짜 이전이면 예외 응답") {
|
||||
val invalidEndAt = startFrom.minusDays(1)
|
||||
every {
|
||||
reservationFinder.searchReservations(themeId, memberId, startFrom, invalidEndAt)
|
||||
} throws ReservationException(ReservationErrorCode.INVALID_SEARCH_DATE_RANGE)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationQueryService.searchReservations(themeId, memberId, startFrom, invalidEndAt)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.INVALID_SEARCH_DATE_RANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -18,11 +18,11 @@ import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||
import roomescape.util.*
|
||||
|
||||
class ReservationWithPaymentServiceTest : FunSpec({
|
||||
val reservationService: ReservationService = mockk()
|
||||
val reservationService: ReservationCommandService = mockk()
|
||||
val paymentService: PaymentService = mockk()
|
||||
|
||||
val reservationWithPaymentService = ReservationWithPaymentService(
|
||||
reservationService = reservationService,
|
||||
reservationCommandService = reservationService,
|
||||
paymentService = paymentService
|
||||
)
|
||||
|
||||
@ -48,7 +48,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
||||
context("addReservationWithPayment") {
|
||||
test("예약 및 결제 정보를 저장한다.") {
|
||||
every {
|
||||
reservationService.createConfirmedReservation(reservationCreateWithPaymentRequest, memberId)
|
||||
reservationService.createReservationWithPayment(reservationCreateWithPaymentRequest, memberId)
|
||||
} returns reservationEntity
|
||||
|
||||
every {
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package roomescape.reservation.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.exception.ReservationException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationSearchSpecification
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.time.exception.TimeErrorCode
|
||||
import roomescape.time.exception.TimeException
|
||||
import java.time.LocalDate
|
||||
|
||||
class ReservationFinderTest : FunSpec({
|
||||
val reservationRepository: ReservationRepository = mockk()
|
||||
val reservationValidator = ReservationValidator(reservationRepository)
|
||||
|
||||
val reservationFinder = ReservationFinder(reservationRepository, reservationValidator)
|
||||
|
||||
context("findById") {
|
||||
val reservationId = 1L
|
||||
test("동일한 ID인 시간을 찾아 응답한다.") {
|
||||
every {
|
||||
reservationRepository.findByIdOrNull(reservationId)
|
||||
} returns mockk()
|
||||
|
||||
reservationFinder.findById(reservationId)
|
||||
|
||||
verify(exactly = 1) {
|
||||
reservationRepository.findByIdOrNull(reservationId)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 ID인 시간이 없으면 실패한다.") {
|
||||
every {
|
||||
reservationRepository.findByIdOrNull(reservationId)
|
||||
} returns null
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationFinder.findById(reservationId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("searchReservations") {
|
||||
test("시작 날짜가 종료 날짜 이전이면 실패한다.") {
|
||||
shouldThrow<ReservationException> {
|
||||
reservationFinder.searchReservations(1L, 1L, LocalDate.now(), LocalDate.now().minusDays(1))
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.INVALID_SEARCH_DATE_RANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,170 @@
|
||||
package roomescape.reservation.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.springframework.data.jpa.domain.Specification
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.exception.ReservationException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ReservationFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ReservationValidatorTest : FunSpec({
|
||||
val reservationRepository: ReservationRepository = mockk()
|
||||
|
||||
val reservationValidator = ReservationValidator(reservationRepository)
|
||||
|
||||
context("validateIsNotPast") {
|
||||
val today = LocalDate.now()
|
||||
val now = LocalTime.now()
|
||||
|
||||
test("입력된 날짜가 오늘 이전이면 예외를 던진다.") {
|
||||
val requestDate = today.minusDays(1)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateIsPast(requestDate, now)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME
|
||||
}
|
||||
}
|
||||
|
||||
test("오늘 날짜라도 시간이 지났다면 예외를 던진다.") {
|
||||
val requestTime = now.minusMinutes(1)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateIsPast(today, requestTime)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateSearchDateRange") {
|
||||
test("시작 날짜만 입력되면 종료한다.") {
|
||||
shouldNotThrow<ReservationException> {
|
||||
reservationValidator.validateSearchDateRange(LocalDate.now(), null)
|
||||
}
|
||||
}
|
||||
|
||||
test("종료 날짜만 입력되면 종료한다.") {
|
||||
shouldNotThrow<ReservationException> {
|
||||
reservationValidator.validateSearchDateRange(null, LocalDate.now())
|
||||
}
|
||||
}
|
||||
|
||||
test("두 날짜가 같으면 종료한다.") {
|
||||
shouldNotThrow<ReservationException> {
|
||||
reservationValidator.validateSearchDateRange(LocalDate.now(), LocalDate.now())
|
||||
}
|
||||
}
|
||||
|
||||
test("종료 날짜가 시작 날짜 이전이면 예외를 던진다.") {
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateSearchDateRange(LocalDate.now(), LocalDate.now().minusDays(1))
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.INVALID_SEARCH_DATE_RANGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateIsAlreadyExists") {
|
||||
test("동일한 날짜, 시간, 테마를 가지는 예약이 있으면 예외를 던진다.") {
|
||||
every {
|
||||
reservationRepository.exists(any<Specification<ReservationEntity>>())
|
||||
} returns true
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateIsAlreadyExists(
|
||||
LocalDate.now(),
|
||||
TimeFixture.create(),
|
||||
ThemeFixture.create()
|
||||
)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_DUPLICATED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateMemberAlreadyReserve") {
|
||||
test("회원이 동일한 날짜, 시간, 테마인 예약(대기)를 이미 했다면 예외를 던진다.") {
|
||||
every {
|
||||
reservationRepository.exists(any<Specification<ReservationEntity>>())
|
||||
} returns true
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateMemberAlreadyReserve(1L, 1L, LocalDate.now(), 1L)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_RESERVE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateIsWaiting") {
|
||||
test("예약 상태가 WAITING이 아니면 예외를 던진다.") {
|
||||
ReservationStatus.confirmedStatus().forEach { status ->
|
||||
shouldThrow<ReservationException> {
|
||||
val reservation = ReservationFixture.create(status = status)
|
||||
reservationValidator.validateIsWaiting(reservation)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateCreateAuthority") {
|
||||
test("관리자가 아니면 예외를 던진다.") {
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateCreateAuthority(MemberFixture.user())
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateDeleteAuthority") {
|
||||
test("입력된 회원이 관리자이면 종료한다.") {
|
||||
shouldNotThrow<ReservationException> {
|
||||
reservationValidator.validateDeleteAuthority(mockk(), MemberFixture.admin())
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 회원이 관리자가 아니고, 예약한 회원과 다른 회원이면 예외를 던진다.") {
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateDeleteAuthority(
|
||||
ReservationFixture.create(member = MemberFixture.create(id = 1L)),
|
||||
MemberFixture.create(id = 2L, role = Role.MEMBER)
|
||||
)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateAlreadyConfirmed") {
|
||||
val reservationId = 1L
|
||||
|
||||
test("입력된 ID의 예약과 동일한 날짜, 시간, 테마를 가지는 다른 확정 예약이 있으면 예외를 던진다.") {
|
||||
every {
|
||||
reservationRepository.isExistConfirmedReservation(reservationId)
|
||||
} returns true
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationValidator.validateAlreadyConfirmed(reservationId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,246 @@
|
||||
package roomescape.reservation.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.implement.MemberFinder
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.exception.ReservationException
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.implement.ThemeFinder
|
||||
import roomescape.time.exception.TimeErrorCode
|
||||
import roomescape.time.exception.TimeException
|
||||
import roomescape.time.implement.TimeFinder
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TimeFixture
|
||||
import roomescape.util.TsidFactory
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ReservationWriterTest : FunSpec({
|
||||
|
||||
val reservationValidator: ReservationValidator = mockk()
|
||||
val reservationRepository: ReservationRepository = mockk()
|
||||
val memberFinder: MemberFinder = mockk()
|
||||
val timeFinder: TimeFinder = mockk()
|
||||
val themeFinder: ThemeFinder = mockk()
|
||||
|
||||
val reservationWriter = ReservationWriter(
|
||||
reservationValidator, reservationRepository, memberFinder, timeFinder, themeFinder, TsidFactory
|
||||
)
|
||||
|
||||
context("create") {
|
||||
val today = LocalDate.now()
|
||||
val timeId = 1L
|
||||
val themeId = 1L
|
||||
val memberId = 1L
|
||||
val status = ReservationStatus.CONFIRMED
|
||||
val requesterId = 1L
|
||||
|
||||
test("시간을 찾을 수 없으면 실패한다.") {
|
||||
every {
|
||||
timeFinder.findById(any())
|
||||
} throws TimeException(TimeErrorCode.TIME_NOT_FOUND)
|
||||
|
||||
shouldThrow<TimeException> {
|
||||
reservationWriter.create(today, timeId, themeId, memberId, status, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe TimeErrorCode.TIME_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("이전 날짜이면 실패한다.") {
|
||||
every {
|
||||
timeFinder.findById(timeId)
|
||||
} returns TimeFixture.create(id = timeId, startAt = LocalTime.now().plusHours(1))
|
||||
|
||||
every {
|
||||
reservationValidator.validateIsPast(any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.PAST_REQUEST_DATETIME)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.create(today.minusDays(1), timeId, themeId, memberId, status, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME
|
||||
}
|
||||
}
|
||||
|
||||
test("테마를 찾을 수 없으면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
|
||||
every {
|
||||
themeFinder.findById(themeId)
|
||||
} throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
reservationWriter.create(today.plusDays(1), timeId, themeId, memberId, status, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("회원을 찾을 수 없으면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
every { themeFinder.findById(themeId) } returns ThemeFixture.create(id = themeId)
|
||||
|
||||
every {
|
||||
memberFinder.findById(memberId)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
reservationWriter.create(today.plusDays(1), timeId, themeId, memberId, status, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("이미 예약이 있는 회원이 대기를 추가하면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
every { themeFinder.findById(themeId) } returns ThemeFixture.create(id = themeId)
|
||||
every { memberFinder.findById(memberId) } returns MemberFixture.create(id = memberId)
|
||||
|
||||
every {
|
||||
reservationValidator.validateMemberAlreadyReserve(themeId, timeId, today, memberId)
|
||||
} throws ReservationException(ReservationErrorCode.ALREADY_RESERVE)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.create(today, timeId, themeId, memberId, status = ReservationStatus.WAITING, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_RESERVE
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 날짜, 시간, 테마인 예약이 이미 있으면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
every { themeFinder.findById(themeId) } returns ThemeFixture.create(id = themeId)
|
||||
every { memberFinder.findById(memberId) } returns MemberFixture.create(id = memberId)
|
||||
|
||||
every {
|
||||
reservationValidator.validateIsAlreadyExists(today, any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.RESERVATION_DUPLICATED)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.create(today, timeId, themeId, memberId, status, memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.RESERVATION_DUPLICATED
|
||||
}
|
||||
}
|
||||
|
||||
test("예약하려는 회원과 신청한 회원이 다를 때, 신청한 회원을 찾을 수 없으면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
every { themeFinder.findById(themeId) } returns ThemeFixture.create(id = themeId)
|
||||
every { memberFinder.findById(memberId) } returns MemberFixture.create(id = memberId)
|
||||
every { reservationValidator.validateIsAlreadyExists(today, any(), any()) } returns Unit
|
||||
|
||||
every {
|
||||
memberFinder.findById(memberId + 1)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
reservationWriter.create(today, timeId, themeId, memberId = memberId, status, requesterId = (memberId + 1))
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("예약하려는 회원과 신청한 회원이 다를 때, 신청한 회원이 관리자가 아니면 실패한다.") {
|
||||
every { timeFinder.findById(timeId) } returns TimeFixture.create(id = timeId)
|
||||
every { reservationValidator.validateIsPast(any(), any()) } returns Unit
|
||||
every { themeFinder.findById(themeId) } returns ThemeFixture.create(id = themeId)
|
||||
every { memberFinder.findById(memberId) } returns MemberFixture.create(id = memberId)
|
||||
every { reservationValidator.validateIsAlreadyExists(today, any(), any()) } returns Unit
|
||||
|
||||
every {
|
||||
memberFinder.findById(memberId + 1)
|
||||
} returns MemberFixture.create(id = memberId + 1)
|
||||
|
||||
every {
|
||||
reservationValidator.validateCreateAuthority(any())
|
||||
} throws ReservationException(ReservationErrorCode.NO_PERMISSION)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.create(today, timeId, themeId, memberId = memberId, status, requesterId = (memberId + 1))
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("deleteWaiting") {
|
||||
val reservation: ReservationEntity = mockk()
|
||||
val requesterId = 1L
|
||||
|
||||
test("대기 상태가 아니면 실패한다.") {
|
||||
every {
|
||||
reservationValidator.validateIsWaiting(any())
|
||||
} throws ReservationException(ReservationErrorCode.ALREADY_CONFIRMED)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.deleteWaiting(reservation, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED
|
||||
}
|
||||
}
|
||||
|
||||
test("삭제하려는 회원이 관리자가 아니고, 예약한 회원과 다르면 실패한다.") {
|
||||
every { reservationValidator.validateIsWaiting(any()) } returns Unit
|
||||
every {
|
||||
reservationValidator.validateDeleteAuthority(any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.deleteWaiting(reservation, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("deleteConfirm") {
|
||||
val reservation: ReservationEntity = mockk()
|
||||
val requesterId = 1L
|
||||
|
||||
test("삭제하려는 회원이 관리자가 아니고, 예약한 회원과 다르면 실패한다.") {
|
||||
every { reservationValidator.validateIsWaiting(any()) } returns Unit
|
||||
every {
|
||||
reservationValidator.validateDeleteAuthority(any(), any())
|
||||
} throws ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.deleteConfirmed(reservation, requesterId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("confirm") {
|
||||
val reservationId = 1L
|
||||
|
||||
test("승인하려는 대기와 같은 날짜,시간,테마를 가진 확정 예약이 있으면 실패한다.") {
|
||||
every {
|
||||
reservationValidator.validateAlreadyConfirmed(reservationId)
|
||||
} throws ReservationException(ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS)
|
||||
|
||||
shouldThrow<ReservationException> {
|
||||
reservationWriter.confirm(reservationId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -14,6 +14,7 @@ import roomescape.payment.web.PaymentCancelRequest
|
||||
import roomescape.payment.web.PaymentCancelResponse
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.web.AdminReservationCreateRequest
|
||||
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||
import roomescape.reservation.web.WaitingCreateRequest
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
@ -22,7 +23,6 @@ import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
|
||||
val TsidFactory: TsidFactory = TsidFactory(0)
|
||||
|
||||
object MemberFixture {
|
||||
@ -103,6 +103,18 @@ object ReservationFixture {
|
||||
paymentType = paymentType
|
||||
)
|
||||
|
||||
fun createAdminRequest(
|
||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||
themeId: Long = 1L,
|
||||
timeId: Long = 1L,
|
||||
memberId: Long = 1L
|
||||
): AdminReservationCreateRequest = AdminReservationCreateRequest(
|
||||
date = date,
|
||||
timeId = timeId,
|
||||
themeId = themeId,
|
||||
memberId = memberId
|
||||
)
|
||||
|
||||
fun createWaitingRequest(
|
||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||
themeId: Long = 1L,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user