test: reservation에 도입된 Finder, Writer, Validator 테스트 및 Query / Command Service 테스트 추가

This commit is contained in:
이상진 2025-08-06 18:53:26 +09:00
parent 7b059fc92f
commit f36504e220
8 changed files with 912 additions and 10 deletions

View File

@ -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

View File

@ -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
}
}
}
})

View File

@ -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
}
}
}
})

View File

@ -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 {

View File

@ -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
}
}
}
})

View File

@ -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
}
}
}
})

View File

@ -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
}
}
}
})

View File

@ -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,