generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) --> ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #30 ## ✨ 작업 내용 <!-- 어떤 작업을 했는지 알려주세요! --> - ReservationService를 읽기(Find) / 쓰기(Write) 서비스로 분리 - 모든 도메인에 repository를 사용하는 Finder, Writer, Validator 도입 -> ReservationService에 있는 조회, 검증, 쓰기 작업을 별도의 클래스로 분리하기 위함이었고, 이 과정에서 다른 도메인에도 도입함. ## 🧪 테스트 <!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! --> 새로 추가된 기능 & 클래스는 모두 테스트 추가하였고, 작업 후 전체 테스트 완료 ## 📚 참고 자료 및 기타 <!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! --> Reviewed-on: #31 Co-authored-by: pricelees <priceelees@gmail.com> Co-committed-by: pricelees <priceelees@gmail.com>
167 lines
6.1 KiB
Kotlin
167 lines
6.1 KiB
Kotlin
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<CanceledPaymentEntity>()
|
|
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<String>()
|
|
|
|
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<PaymentCancelRequest>()
|
|
cancelReasonSlot.captured shouldBe "예약 취소"
|
|
}
|
|
|
|
test("결제 정보가 없으면 예외 응답") {
|
|
every {
|
|
paymentFinder.findByReservationId(reservationId)
|
|
} throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
|
|
shouldThrow<PaymentException> {
|
|
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<PaymentException> {
|
|
paymentService.updateCanceledTime(paymentKey, canceledAt)
|
|
}.also {
|
|
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
|
|
}
|
|
}
|
|
}
|
|
})
|