package roomescape.payment.infrastructure.client import com.ninjasquad.springmockk.MockkBean import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.client.RestClientTest import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext import org.springframework.http.HttpMethod import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.client.ResponseActions import org.springframework.test.web.client.match.MockRestRequestMatchers.* import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess import roomescape.payment.exception.PaymentErrorCode import roomescape.payment.exception.PaymentException import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelResponse @RestClientTest(TossPaymentClient::class) @MockkBean(JpaMetamodelMappingContext::class) class TossPaymentClientTest( @Autowired val client: TossPaymentClient, @Autowired val mockServer: MockRestServiceServer ) : FunSpec() { init { context("결제 승인 요청") { fun commonAction(): ResponseActions = mockServer.expect { requestTo("/v1/payments/confirm") }.andExpect { method(HttpMethod.POST) }.andExpect { content().contentType(MediaType.APPLICATION_JSON) }.andExpect { content().json(SampleTossPaymentConst.paymentRequestJson) } test("성공 응답") { commonAction().andRespond { withSuccess() .contentType(MediaType.APPLICATION_JSON) .body(SampleTossPaymentConst.confirmJson) .createResponse(it) } val paymentRequest = SampleTossPaymentConst.paymentRequest val paymentResponse: PaymentApproveResponse = client.confirm(paymentRequest) assertSoftly(paymentResponse) { this.paymentKey shouldBe paymentRequest.paymentKey this.orderId shouldBe paymentRequest.orderId this.totalAmount shouldBe paymentRequest.amount } } context("실패 응답") { fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) { commonAction().andRespond { withStatus(httpStatus) .contentType(MediaType.APPLICATION_JSON) .body(SampleTossPaymentConst.tossPaymentErrorJson) .createResponse(it) } val paymentRequest = SampleTossPaymentConst.paymentRequest // then val exception = shouldThrow { client.confirm(paymentRequest) } exception.errorCode shouldBe expectedError } test("결제 서버에서 4XX 응답 시") { runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR) } test("결제 서버에서 5XX 응답 시") { runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR) } } } context("결제 취소 요청") { fun commonAction(): ResponseActions = mockServer.expect { requestTo("/v1/payments/${SampleTossPaymentConst.paymentKey}/cancel") }.andExpect { method(HttpMethod.POST) }.andExpect { content().contentType(MediaType.APPLICATION_JSON) }.andExpect { content().json(SampleTossPaymentConst.cancelRequestJson) } test("성공 응답") { commonAction().andRespond { withSuccess() .contentType(MediaType.APPLICATION_JSON) .body(SampleTossPaymentConst.cancelJson) .createResponse(it) } val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest val cancelResponse: PaymentCancelResponse = client.cancel(cancelRequest) assertSoftly(cancelResponse) { this.cancelStatus shouldBe "DONE" this.cancelReason shouldBe cancelRequest.cancelReason this.cancelAmount shouldBe cancelRequest.amount } } context("실패 응답") { fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) { commonAction().andRespond { withStatus(httpStatus) .contentType(MediaType.APPLICATION_JSON) .body(SampleTossPaymentConst.tossPaymentErrorJson) .createResponse(it) } val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest val exception = shouldThrow { client.cancel(cancelRequest) } exception.errorCode shouldBe expectedError } test("결제 서버에서 4XX 응답 시") { runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR) } test("결제 서버에서 5XX 응답 시") { runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR) } } } } }