test: tosspay-mock 모듈 API 테스트 추가

This commit is contained in:
이상진 2025-10-01 12:42:39 +09:00
parent 8f42bd6054
commit 7898a93182

View File

@ -0,0 +1,188 @@
package com.sangdol.tosspaymock
import com.sangdol.common.persistence.IDGenerator
import com.sangdol.tosspaymock.exception.code.TosspayCancelErrorCode
import com.sangdol.tosspaymock.exception.code.TosspayConfirmErrorCode
import com.sangdol.tosspaymock.infrastructure.persistence.OrderAmountEntity
import com.sangdol.tosspaymock.infrastructure.persistence.OrderAmountRepository
import com.sangdol.tosspaymock.web.dto.PaymentCancelRequest
import com.sangdol.tosspaymock.web.dto.PaymentConfirmRequest
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.restassured.RestAssured
import io.restassured.module.kotlin.extensions.Extract
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import org.hamcrest.CoreMatchers
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TosspayApiTest(
@LocalServerPort private val port: Int,
private val orderAmountRepository: OrderAmountRepository,
private val idGenerator: IDGenerator
) : FunSpec({
beforeSpec {
RestAssured.port = port
}
afterTest {
orderAmountRepository.deleteAll()
}
val authorizationKey = "dGVzdF9nc2tfZG9jc19PYVB6OEw1S2RtUVhrelJ6M3k0N0JNdzY6"
val paymentConfirmRequest = PaymentConfirmRequest(
paymentKey = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1",
orderId = "MC4wODU4ODQwMzg4NDk0",
amount = 100_000,
)
val paymentCancelRequest = PaymentCancelRequest(
cancelAmount = paymentConfirmRequest.amount,
cancelReason = "그냥.."
)
context("결제 승인") {
val paymentConfirmRequest = PaymentConfirmRequest(
paymentKey = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1",
orderId = "MC4wODU4ODQwMzg4NDk0",
amount = 100_000,
)
test("Basic Authorization 헤더가 없으면 실패한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
body(paymentConfirmRequest)
} When {
post("/v1/payments/confirm")
} Then {
statusCode(HttpStatus.BAD_REQUEST.value())
body("code", CoreMatchers.equalTo(TosspayConfirmErrorCode.INVALID_API_KEY.name))
}
}
test("Basic Authorization 헤더가 Base64 형식이 아니면 실패한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic hello-world")
body(paymentConfirmRequest)
} When {
post("/v1/payments/confirm")
} Then {
statusCode(HttpStatus.UNAUTHORIZED.value())
body("code", CoreMatchers.equalTo(TosspayConfirmErrorCode.UNAUTHORIZED_KEY.name))
}
}
test("임의의 결제 정보를 반환하며, 금액은 별도로 저장한다.") {
val paymentKey = Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic $authorizationKey")
body(paymentConfirmRequest)
} When {
post("/v1/payments/confirm")
} Then {
statusCode(HttpStatus.OK.value())
} Extract {
path<String>("paymentKey")
}
paymentKey shouldBe paymentConfirmRequest.paymentKey
orderAmountRepository.findByPaymentKey(paymentKey).shouldNotBeNull()
}
}
context("결제 취소") {
test("Basic Authorization 헤더가 없으면 실패한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
body(paymentCancelRequest)
} When {
post("/v1/payments/${paymentConfirmRequest.paymentKey}/cancel")
} Then {
statusCode(HttpStatus.BAD_REQUEST.value())
body("code", CoreMatchers.equalTo(TosspayConfirmErrorCode.INVALID_API_KEY.name))
}
}
test("Basic Authorization 헤더가 Base64 형식이 아니면 실패한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic hello-world")
body(paymentCancelRequest)
} When {
post("/v1/payments/${paymentConfirmRequest.paymentKey}/cancel")
} Then {
statusCode(HttpStatus.UNAUTHORIZED.value())
body("code", CoreMatchers.equalTo(TosspayConfirmErrorCode.UNAUTHORIZED_KEY.name))
}
}
context("정상 응답") {
lateinit var orderAmount: OrderAmountEntity
beforeTest {
orderAmount = OrderAmountEntity(
id = idGenerator.create(),
paymentKey = paymentConfirmRequest.paymentKey,
approvedAmount = (paymentConfirmRequest.amount - 1000),
easypayDiscountAmount = 1000,
cardDiscountAmount = 0,
transferDiscountAmount = 0
).also {
orderAmountRepository.saveAndFlush(it)
}
}
test("요청에 cancelAmount를 포함하지 않으면 전체 금액을 취소한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic $authorizationKey")
body(PaymentCancelRequest(cancelReason = "그냥!"))
} When {
post("/v1/payments/${paymentConfirmRequest.paymentKey}/cancel")
} Then {
statusCode(HttpStatus.OK.value())
body("paymentKey", CoreMatchers.equalTo(paymentConfirmRequest.paymentKey))
body("cancels.easyPayDiscountAmount", CoreMatchers.equalTo(orderAmount.easypayDiscountAmount))
body("cancels.cancelAmount", CoreMatchers.equalTo(orderAmount.totalAmount()))
}
}
test("이전 결제 정보의 할인 금액을 가져온 뒤 반환한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic $authorizationKey")
body(paymentCancelRequest)
} When {
post("/v1/payments/${paymentConfirmRequest.paymentKey}/cancel")
} Then {
statusCode(HttpStatus.OK.value())
body("paymentKey", CoreMatchers.equalTo(paymentConfirmRequest.paymentKey))
body("cancels.easyPayDiscountAmount", CoreMatchers.equalTo(orderAmount.easypayDiscountAmount))
body("cancels.cancelAmount", CoreMatchers.equalTo(paymentCancelRequest.cancelAmount))
}
}
}
test("이전 결제 정보가 없으면 실패한다.") {
Given {
contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Basic $authorizationKey")
body(paymentCancelRequest)
} When {
post("/v1/payments/notExistPaymentKey/cancel")
} Then {
statusCode(HttpStatus.NOT_FOUND.value())
body("code", CoreMatchers.equalTo(TosspayCancelErrorCode.NOT_FOUND_PAYMENT.name))
}
}
}
})