package roomescape.reservation.business 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.repository.findByIdOrNull import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.Role import roomescape.reservation.exception.ReservationErrorCode import roomescape.reservation.exception.ReservationException import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.theme.business.ThemeService import roomescape.time.business.TimeService import roomescape.util.MemberFixture import roomescape.util.ReservationFixture import roomescape.util.TsidFactory import roomescape.util.TimeFixture import java.time.LocalDate import java.time.LocalTime class ReservationServiceTest : FunSpec({ val reservationRepository: ReservationRepository = mockk() val timeService: TimeService = mockk() val memberService: MemberService = mockk() val themeService: ThemeService = mockk() val reservationService = ReservationService( TsidFactory, reservationRepository, timeService, memberService, themeService ) context("예약을 추가할 때") { test("이미 예약이 있으면 예외를 던진다.") { every { reservationRepository.exists(any()) } returns true val reservationRequest = ReservationFixture.createRequest() shouldThrow { reservationService.createConfirmedReservation(reservationRequest, 1L) }.also { it.errorCode shouldBe ReservationErrorCode.RESERVATION_DUPLICATED } } context("날짜, 시간이 잘못 입력되면 예외를 던진다.") { every { reservationRepository.exists(any()) } returns false every { themeService.findById(any()) } returns mockk() every { memberService.findById(any()) } returns mockk() test("지난 날짜이면 예외를 던진다.") { val reservationRequest = ReservationFixture.createRequest().copy( date = LocalDate.now().minusDays(1) ) every { timeService.findById(any()) } returns TimeFixture.create() shouldThrow { reservationService.createConfirmedReservation(reservationRequest, 1L) }.also { it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME } } test("지난 시간이면 예외를 던진다.") { val reservationRequest = ReservationFixture.createRequest().copy( date = LocalDate.now(), ) every { timeService.findById(reservationRequest.timeId) } returns TimeFixture.create( startAt = LocalTime.now().minusMinutes(1) ) shouldThrow { reservationService.createConfirmedReservation(reservationRequest, 1L) }.also { it.errorCode shouldBe ReservationErrorCode.PAST_REQUEST_DATETIME } } } } context("예약 대기를 걸 때") { test("이미 예약한 회원이 같은 날짜와 테마로 대기를 걸면 예외를 던진다.") { val reservationRequest = ReservationFixture.createRequest().copy( date = LocalDate.now(), themeId = 1L, timeId = 1L, ) every { reservationRepository.exists(any()) } returns true shouldThrow { val waitingRequest = ReservationFixture.createWaitingRequest( date = reservationRequest.date, themeId = reservationRequest.themeId, timeId = reservationRequest.timeId ) reservationService.createWaiting(waitingRequest, 1L) }.also { it.errorCode shouldBe ReservationErrorCode.ALREADY_RESERVE } } } context("예약 대기를 취소할 때") { val reservationId = 1L val member = MemberFixture.create(id = 1L, role = Role.MEMBER) test("예약을 찾을 수 없으면 예외를 던진다.") { every { reservationRepository.findByIdOrNull(reservationId) } returns null shouldThrow { reservationService.deleteWaiting(reservationId, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND } } test("대기중인 해당 예약이 이미 확정된 상태라면 예외를 던진다.") { val alreadyConfirmed = ReservationFixture.create( id = reservationId, status = ReservationStatus.CONFIRMED ) every { reservationRepository.findByIdOrNull(reservationId) } returns alreadyConfirmed shouldThrow { reservationService.deleteWaiting(reservationId, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED } } test("타인의 대기를 취소하려고 하면 예외를 던진다.") { val otherMembersWaiting = ReservationFixture.create( id = reservationId, member = MemberFixture.create(id = member.id!! + 1L), status = ReservationStatus.WAITING ) every { reservationRepository.findByIdOrNull(reservationId) } returns otherMembersWaiting shouldThrow { reservationService.deleteWaiting(reservationId, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.NOT_RESERVATION_OWNER } } } context("예약을 조회할 때") { test("종료 날짜가 시작 날짜보다 이전이면 예외를 던진다.") { val startFrom = LocalDate.now() val endAt = startFrom.minusDays(1) shouldThrow { reservationService.searchReservations( null, null, startFrom, endAt ) }.also { it.errorCode shouldBe ReservationErrorCode.INVALID_SEARCH_DATE_RANGE } } } context("대기중인 예약을 승인할 때") { test("관리자가 아니면 예외를 던진다.") { val member = MemberFixture.create(id = 1L, role = Role.MEMBER) every { memberService.findById(any()) } returns member shouldThrow { reservationService.confirmWaiting(1L, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION } } test("이미 확정된 예약이 있으면 예외를 던진다.") { val member = MemberFixture.create(id = 1L, role = Role.ADMIN) val reservationId = 1L every { memberService.findById(any()) } returns member every { reservationRepository.isExistConfirmedReservation(reservationId) } returns true shouldThrow { reservationService.confirmWaiting(reservationId, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS } } } context("대기중인 예약을 거절할 때") { test("관리자가 아니면 예외를 던진다.") { val member = MemberFixture.create(id = 1L, role = Role.MEMBER) every { memberService.findById(any()) } returns member shouldThrow { reservationService.rejectWaiting(1L, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.NO_PERMISSION } } test("예약을 찾을 수 없으면 예외를 던진다.") { val member = MemberFixture.create(id = 1L, role = Role.ADMIN) val reservationId = 1L every { memberService.findById(member.id!!) } returns member every { reservationRepository.findByIdOrNull(reservationId) } returns null shouldThrow { reservationService.rejectWaiting(reservationId, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.RESERVATION_NOT_FOUND } } test("이미 확정된 예약이면 예외를 던진다.") { val member = MemberFixture.create(id = 1L, role = Role.ADMIN) val reservation = ReservationFixture.create( id = 1L, status = ReservationStatus.CONFIRMED ) every { memberService.findById(member.id!!) } returns member every { reservationRepository.findByIdOrNull(reservation.id!!) } returns reservation shouldThrow { reservationService.rejectWaiting(reservation.id!!, member.id!!) }.also { it.errorCode shouldBe ReservationErrorCode.ALREADY_CONFIRMED } } } })