generated from pricelees/issue-pr-template
feat: TosspayClient 테스트 추가
This commit is contained in:
parent
9ce66d8074
commit
c057fa85e2
165
src/test/kotlin/roomescape/payment/SampleTosspayConstant.kt
Normal file
165
src/test/kotlin/roomescape/payment/SampleTosspayConstant.kt
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package roomescape.payment
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
object SampleTosspayConstant {
|
||||||
|
const val PAYMENT_KEY: String = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1"
|
||||||
|
const val ORDER_ID: String = "MC4wODU4ODQwMzg4NDk0"
|
||||||
|
const val AMOUNT: Int = 100_000
|
||||||
|
const val CANCEL_REASON: String = "테스트 결제 취소"
|
||||||
|
|
||||||
|
val cancelRequestJson: String = """
|
||||||
|
{
|
||||||
|
"cancelReason": "$CANCEL_REASON"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val tossPaymentErrorJson: String = """
|
||||||
|
{
|
||||||
|
"code": "ERROR_CODE",
|
||||||
|
"message": "Error message"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val confirmJson: String = """
|
||||||
|
{
|
||||||
|
"mId": "tgen_docs",
|
||||||
|
"lastTransactionKey": "txrd_a01k4mtanmyx0hm6hmmbvfvmhky",
|
||||||
|
"paymentKey": "$PAYMENT_KEY",
|
||||||
|
"orderId": "$ORDER_ID",
|
||||||
|
"orderName": "Sonya Aguirre 예약 결제",
|
||||||
|
"taxExemptionAmount": 0,
|
||||||
|
"status": "DONE",
|
||||||
|
"requestedAt": "${OffsetDateTime.now()}",
|
||||||
|
"approvedAt": "${OffsetDateTime.now().plusSeconds(5)}",
|
||||||
|
"useEscrow": false,
|
||||||
|
"cultureExpense": false,
|
||||||
|
"card": {
|
||||||
|
"issuerCode": "41",
|
||||||
|
"acquirerCode": "41",
|
||||||
|
"number": "12748273****675*",
|
||||||
|
"installmentPlanMonths": 0,
|
||||||
|
"isInterestFree": false,
|
||||||
|
"interestPayer": null,
|
||||||
|
"approveNo": "00000000",
|
||||||
|
"useCardPoint": false,
|
||||||
|
"cardType": "신용",
|
||||||
|
"ownerType": "개인",
|
||||||
|
"acquireStatus": "READY",
|
||||||
|
"amount": $AMOUNT
|
||||||
|
},
|
||||||
|
"virtualAccount": null,
|
||||||
|
"transfer": null,
|
||||||
|
"mobilePhone": null,
|
||||||
|
"giftCertificate": null,
|
||||||
|
"cashReceipt": null,
|
||||||
|
"cashReceipts": null,
|
||||||
|
"discount": null,
|
||||||
|
"cancels": null,
|
||||||
|
"secret": "ps_6BYq7GWPVvGNAwZeRBDLrNE5vbo1",
|
||||||
|
"type": "NORMAL",
|
||||||
|
"easyPay": {
|
||||||
|
"provider": "토스페이",
|
||||||
|
"amount": 0,
|
||||||
|
"discountAmount": 0
|
||||||
|
},
|
||||||
|
"country": "KR",
|
||||||
|
"failure": null,
|
||||||
|
"isPartialCancelable": true,
|
||||||
|
"receipt": {
|
||||||
|
"url": "https://dashboard-sandbox.tosspayments.com/receipt/redirection?transactionId=tgen_20250908230510v2rY2&ref=PX"
|
||||||
|
},
|
||||||
|
"checkout": {
|
||||||
|
"url": "https://api.tosspayments.com/v1/payments/tgen_20250908230510v2rY2/checkout"
|
||||||
|
},
|
||||||
|
"currency": "KRW",
|
||||||
|
"totalAmount": $AMOUNT,
|
||||||
|
"balanceAmount": $AMOUNT,
|
||||||
|
"suppliedAmount": ${(AMOUNT * 0.9).toInt()},
|
||||||
|
"vat": ${(AMOUNT * 0.1).toInt()},
|
||||||
|
"taxFreeAmount": 0,
|
||||||
|
"method": "간편결제",
|
||||||
|
"version": "2022-11-16",
|
||||||
|
"metadata": null
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val cancelJson: String = """
|
||||||
|
{
|
||||||
|
"mId": "tgen_docs",
|
||||||
|
"lastTransactionKey": "txrd_a01k4mtgh26vgrn1evbdckyqmdr",
|
||||||
|
"paymentKey": "$PAYMENT_KEY",
|
||||||
|
"orderId": "$ORDER_ID",
|
||||||
|
"orderName": "Sonya Aguirre 예약 결제",
|
||||||
|
"taxExemptionAmount": 0,
|
||||||
|
"status": "CANCELED",
|
||||||
|
"requestedAt": "${OffsetDateTime.now()}",
|
||||||
|
"approvedAt": "${OffsetDateTime.now().plusSeconds(5)}",
|
||||||
|
"useEscrow": false,
|
||||||
|
"cultureExpense": false,
|
||||||
|
"card": {
|
||||||
|
"issuerCode": "41",
|
||||||
|
"acquirerCode": "41",
|
||||||
|
"number": "12748273****675*",
|
||||||
|
"installmentPlanMonths": 0,
|
||||||
|
"isInterestFree": false,
|
||||||
|
"interestPayer": null,
|
||||||
|
"approveNo": "00000000",
|
||||||
|
"useCardPoint": false,
|
||||||
|
"cardType": "신용",
|
||||||
|
"ownerType": "개인",
|
||||||
|
"acquireStatus": "READY",
|
||||||
|
"amount": $AMOUNT
|
||||||
|
},
|
||||||
|
"virtualAccount": null,
|
||||||
|
"transfer": null,
|
||||||
|
"mobilePhone": null,
|
||||||
|
"giftCertificate": null,
|
||||||
|
"cashReceipt": null,
|
||||||
|
"cashReceipts": null,
|
||||||
|
"discount": null,
|
||||||
|
"cancels": [
|
||||||
|
{
|
||||||
|
"transactionKey": "txrd_a01k4mtgh26vgrn1evbdckyqmdr",
|
||||||
|
"cancelReason": "$CANCEL_REASON",
|
||||||
|
"taxExemptionAmount": 0,
|
||||||
|
"canceledAt": "${OffsetDateTime.now().plusMinutes(1)}",
|
||||||
|
"cardDiscountAmount": 0,
|
||||||
|
"transferDiscountAmount": 0,
|
||||||
|
"easyPayDiscountAmount": 0,
|
||||||
|
"receiptKey": null,
|
||||||
|
"cancelStatus": "DONE",
|
||||||
|
"cancelRequestId": null,
|
||||||
|
"cancelAmount": $AMOUNT,
|
||||||
|
"taxFreeAmount": 0,
|
||||||
|
"refundableAmount": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret": "ps_6BYq7GWPVvGNAwZeRBDLrNE5vbo1",
|
||||||
|
"type": "NORMAL",
|
||||||
|
"easyPay": {
|
||||||
|
"provider": "토스페이",
|
||||||
|
"amount": 0,
|
||||||
|
"discountAmount": 0
|
||||||
|
},
|
||||||
|
"country": "KR",
|
||||||
|
"failure": null,
|
||||||
|
"isPartialCancelable": true,
|
||||||
|
"receipt": {
|
||||||
|
"url": "https://dashboard-sandbox.tosspayments.com/receipt/redirection?transactionId=tgen_20250908230510v2rY2&ref=PX"
|
||||||
|
},
|
||||||
|
"checkout": {
|
||||||
|
"url": "https://api.tosspayments.com/v1/payments/tgen_20250908230510v2rY2/checkout"
|
||||||
|
},
|
||||||
|
"currency": "KRW",
|
||||||
|
"totalAmount": ${AMOUNT},
|
||||||
|
"balanceAmount": 0,
|
||||||
|
"suppliedAmount": 0,
|
||||||
|
"vat": 0,
|
||||||
|
"taxFreeAmount": 0,
|
||||||
|
"method": "간편결제",
|
||||||
|
"version": "2022-11-16",
|
||||||
|
"metadata": null
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
159
src/test/kotlin/roomescape/payment/TosspayClientTest.kt
Normal file
159
src/test/kotlin/roomescape/payment/TosspayClientTest.kt
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package roomescape.payment
|
||||||
|
|
||||||
|
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.infrastructure.client.PaymentClientCancelResponse
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentClientConfirmResponse
|
||||||
|
import roomescape.payment.infrastructure.client.TosspayClient
|
||||||
|
import roomescape.payment.infrastructure.common.PaymentStatus
|
||||||
|
|
||||||
|
@RestClientTest(TosspayClient::class)
|
||||||
|
@MockkBean(JpaMetamodelMappingContext::class)
|
||||||
|
class TosspayClientTest(
|
||||||
|
@Autowired val client: TosspayClient,
|
||||||
|
@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(SampleTosspayConstant.confirmJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("성공 응답") {
|
||||||
|
commonAction().andRespond {
|
||||||
|
withSuccess()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(SampleTosspayConstant.confirmJson)
|
||||||
|
.createResponse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentResponse: PaymentClientConfirmResponse = client.confirm(
|
||||||
|
SampleTosspayConstant.PAYMENT_KEY,
|
||||||
|
SampleTosspayConstant.ORDER_ID,
|
||||||
|
SampleTosspayConstant.AMOUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
assertSoftly(paymentResponse) {
|
||||||
|
this.paymentKey shouldBe SampleTosspayConstant.PAYMENT_KEY
|
||||||
|
this.totalAmount shouldBe SampleTosspayConstant.AMOUNT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("실패 응답") {
|
||||||
|
fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
|
||||||
|
commonAction().andRespond {
|
||||||
|
withStatus(httpStatus)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(SampleTosspayConstant.tossPaymentErrorJson)
|
||||||
|
.createResponse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val exception = shouldThrow<PaymentException> {
|
||||||
|
client.confirm(
|
||||||
|
SampleTosspayConstant.PAYMENT_KEY,
|
||||||
|
SampleTosspayConstant.ORDER_ID,
|
||||||
|
SampleTosspayConstant.AMOUNT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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/${SampleTosspayConstant.PAYMENT_KEY}/cancel")
|
||||||
|
}.andExpect {
|
||||||
|
method(HttpMethod.POST)
|
||||||
|
}.andExpect {
|
||||||
|
content().contentType(MediaType.APPLICATION_JSON)
|
||||||
|
}.andExpect {
|
||||||
|
content().json(SampleTosspayConstant.cancelRequestJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("성공 응답") {
|
||||||
|
commonAction().andRespond {
|
||||||
|
withSuccess()
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(SampleTosspayConstant.cancelJson)
|
||||||
|
.createResponse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cancelResponse: PaymentClientCancelResponse = client.cancel(
|
||||||
|
SampleTosspayConstant.PAYMENT_KEY,
|
||||||
|
SampleTosspayConstant.AMOUNT,
|
||||||
|
SampleTosspayConstant.CANCEL_REASON
|
||||||
|
)
|
||||||
|
|
||||||
|
assertSoftly(cancelResponse) {
|
||||||
|
this.status shouldBe PaymentStatus.CANCELED
|
||||||
|
with (this.cancels) {
|
||||||
|
this.cancelAmount shouldBe SampleTosspayConstant.AMOUNT
|
||||||
|
this.cancelReason shouldBe SampleTosspayConstant.CANCEL_REASON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("실패 응답") {
|
||||||
|
fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
|
||||||
|
commonAction().andRespond {
|
||||||
|
withStatus(httpStatus)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(SampleTosspayConstant.tossPaymentErrorJson)
|
||||||
|
.createResponse(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val exception = shouldThrow<PaymentException> {
|
||||||
|
client.cancel(
|
||||||
|
SampleTosspayConstant.PAYMENT_KEY,
|
||||||
|
SampleTosspayConstant.AMOUNT,
|
||||||
|
SampleTosspayConstant.CANCEL_REASON
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user