package roomescape.payment.business import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.mockk.every import io.mockk.mockk import io.mockk.slot import roomescape.payment.exception.PaymentErrorCode import roomescape.payment.exception.PaymentException import roomescape.payment.implement.PaymentFinder import roomescape.payment.implement.PaymentWriter import roomescape.payment.infrastructure.client.PaymentApproveResponse import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelResponse import roomescape.util.PaymentFixture import roomescape.util.ReservationFixture import java.time.LocalDate import java.time.LocalTime import java.time.OffsetDateTime import java.time.ZoneOffset class PaymentServiceTest : FunSpec({ val paymentFinder: PaymentFinder = mockk() val paymentWriter: PaymentWriter = mockk() val paymentService = PaymentService(paymentFinder, paymentWriter) context("createPayment") { val approvedPaymentInfo = PaymentApproveResponse( paymentKey = "paymentKey", orderId = "orderId", totalAmount = 1000L, approvedAt = OffsetDateTime.now(), ) val reservation = ReservationFixture.create(id = 1L) test("정상 응답") { every { paymentWriter.create( paymentKey = approvedPaymentInfo.paymentKey, orderId = approvedPaymentInfo.orderId, totalAmount = approvedPaymentInfo.totalAmount, approvedAt = approvedPaymentInfo.approvedAt, reservation = reservation ) } returns PaymentFixture.create( id = 1L, orderId = approvedPaymentInfo.orderId, paymentKey = approvedPaymentInfo.paymentKey, totalAmount = approvedPaymentInfo.totalAmount, approvedAt = approvedPaymentInfo.approvedAt, reservation = reservation ) val response = paymentService.createPayment(approvedPaymentInfo, reservation) assertSoftly(response) { it.id shouldBe 1L it.paymentKey shouldBe approvedPaymentInfo.paymentKey it.reservationId shouldBe reservation.id } } } context("createCanceledPayment(canceledPaymentInfo)") { val canceledPaymentInfo = PaymentCancelResponse( cancelStatus = "normal", cancelReason = "고객 요청", cancelAmount = 1000L, canceledAt = OffsetDateTime.now(), ) val approvedAt = OffsetDateTime.now() val paymentKey = "paymentKey" test("CanceledPaymentEntity를 응답") { every { paymentWriter.createCanceled( cancelReason = canceledPaymentInfo.cancelReason, cancelAmount = canceledPaymentInfo.cancelAmount, canceledAt = canceledPaymentInfo.canceledAt, approvedAt = approvedAt, paymentKey = paymentKey ) } returns PaymentFixture.createCanceled( id = 1L, paymentKey = paymentKey, cancelAmount = canceledPaymentInfo.cancelAmount ) val response = paymentService.createCanceledPayment(canceledPaymentInfo, approvedAt, paymentKey) response.shouldBeInstanceOf() response.paymentKey shouldBe paymentKey } } context("createCanceledPayment(reservationId)") { val reservationId = 1L test("취소 사유를 '예약 취소'로 하여 PaymentCancelRequest를 응답") { val payment = PaymentFixture.create(id = 1L, paymentKey = "paymentKey", totalAmount = 1000L) every { paymentFinder.findByReservationId(reservationId) } returns payment val cancelReasonSlot = slot() every { paymentWriter.createCanceled(payment, capture(cancelReasonSlot), any()) } returns PaymentFixture.createCanceled( id = 1L, paymentKey = payment.paymentKey, cancelAmount = payment.totalAmount ) val response = paymentService.createCanceledPayment(reservationId) response.shouldBeInstanceOf() cancelReasonSlot.captured shouldBe "예약 취소" } test("결제 정보가 없으면 예외 응답") { every { paymentFinder.findByReservationId(reservationId) } throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) shouldThrow { paymentService.createCanceledPayment(reservationId) }.also { it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND } } } context("updateCanceledTime") { val paymentKey = "paymentKey" val canceledAt = OffsetDateTime.of(LocalDate.of(2025, 8, 5), LocalTime.of(10, 0), ZoneOffset.UTC) test("정상 응답") { val canceled = PaymentFixture.createCanceled(id = 1L) every { paymentFinder.findCanceledByKey(paymentKey) } returns canceled paymentService.updateCanceledTime(paymentKey, canceledAt) canceled.canceledAt shouldBe canceledAt } test("결제 취소 정보가 없으면 예외 응답") { every { paymentFinder.findCanceledByKey(paymentKey) } throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) shouldThrow { paymentService.updateCanceledTime(paymentKey, canceledAt) }.also { it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND } } } })