[#20] 도메인별 예외 분리 #21

Merged
pricelees merged 37 commits from refactor/#20 into main 2025-07-24 02:48:53 +00:00
2 changed files with 56 additions and 58 deletions
Showing only changes of commit e9fcc31dea - Show all commits

View File

@ -4,17 +4,15 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.http.HttpRequest import org.springframework.http.HttpRequest
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode import org.springframework.http.HttpStatusCode
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.client.ClientHttpResponse import org.springframework.http.client.ClientHttpResponse
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.web.client.RestClient import org.springframework.web.client.RestClient
import roomescape.common.exception.ErrorType import roomescape.payment.exception.PaymentErrorCode
import roomescape.common.exception.RoomescapeException import roomescape.payment.exception.PaymentException
import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.PaymentCancelResponse import roomescape.payment.web.PaymentCancelResponse
import java.io.IOException
import java.util.Map import java.util.Map
@Component @Component
@ -43,7 +41,7 @@ class TossPaymentClient(
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) } { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
) )
.body(PaymentApproveResponse::class.java) .body(PaymentApproveResponse::class.java)
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR) ?: throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
} }
fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse { fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse {
@ -60,7 +58,7 @@ class TossPaymentClient(
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) } { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
) )
.body(PaymentCancelResponse::class.java) .body(PaymentCancelResponse::class.java)
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR) ?: throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
} }
private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) { private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) {
@ -80,32 +78,25 @@ class TossPaymentClient(
private fun handlePaymentError( private fun handlePaymentError(
res: ClientHttpResponse res: ClientHttpResponse
): Nothing { ): Nothing {
val statusCode = res.statusCode getErrorCodeByHttpStatus(res.statusCode).also {
val errorType = getErrorTypeByStatusCode(statusCode) logTossPaymentError(res)
val errorResponse = getErrorResponse(res) throw PaymentException(it)
}
throw RoomescapeException(
errorType,
"[ErrorCode = ${errorResponse.code}, ErrorMessage = ${errorResponse.message}]",
statusCode
)
} }
private fun getErrorResponse( private fun logTossPaymentError(res: ClientHttpResponse): TossPaymentErrorResponse {
res: ClientHttpResponse
): TossPaymentErrorResponse {
val body = res.body val body = res.body
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java) val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
body.close() body.close()
log.error { "결제 실패. response: $errorResponse" }
return errorResponse return errorResponse
} }
private fun getErrorTypeByStatusCode( private fun getErrorCodeByHttpStatus(statusCode: HttpStatusCode): PaymentErrorCode {
statusCode: HttpStatusCode
): ErrorType {
if (statusCode.is4xxClientError) { if (statusCode.is4xxClientError) {
return ErrorType.PAYMENT_ERROR return PaymentErrorCode.PAYMENT_CLIENT_ERROR
} }
return ErrorType.PAYMENT_SERVER_ERROR return PaymentErrorCode.PAYMENT_PROVIDER_ERROR
} }
} }

View File

@ -14,8 +14,8 @@ import org.springframework.test.web.client.ResponseActions
import org.springframework.test.web.client.match.MockRestRequestMatchers.* 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.withStatus
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
import roomescape.common.exception.ErrorType import roomescape.payment.exception.PaymentErrorCode
import roomescape.common.exception.RoomescapeException import roomescape.payment.exception.PaymentException
import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelRequest
import roomescape.payment.web.PaymentCancelResponse import roomescape.payment.web.PaymentCancelResponse
@ -56,28 +56,32 @@ class TossPaymentClientTest(
} }
} }
test("400 에러 발생") { context("실패 응답") {
commonAction().andRespond { fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
withStatus(HttpStatus.BAD_REQUEST) commonAction().andRespond {
.contentType(MediaType.APPLICATION_JSON) withStatus(httpStatus)
.body(SampleTossPaymentConst.tossPaymentErrorJson) .contentType(MediaType.APPLICATION_JSON)
.createResponse(it) .body(SampleTossPaymentConst.tossPaymentErrorJson)
.createResponse(it)
}
// when
val paymentRequest = SampleTossPaymentConst.paymentRequest
// then
val exception = shouldThrow<PaymentException> {
client.confirm(paymentRequest)
}
exception.errorCode shouldBe expectedError
} }
// when test("결제 서버에서 4XX 응답 시") {
val paymentRequest = SampleTossPaymentConst.paymentRequest runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR)
// then
val exception = shouldThrow<RoomescapeException> {
client.confirm(paymentRequest)
} }
assertSoftly(exception) { test("결제 서버에서 5XX 응답 시") {
this.errorType shouldBe ErrorType.PAYMENT_ERROR runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
this.invalidValue shouldBe "[ErrorCode = ERROR_CODE, ErrorMessage = Error message]"
this.httpStatus shouldBe HttpStatus.BAD_REQUEST
} }
} }
} }
@ -111,26 +115,29 @@ class TossPaymentClientTest(
} }
} }
test("500 에러 발생") { context("실패 응답") {
commonAction().andRespond { fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
withStatus(HttpStatus.INTERNAL_SERVER_ERROR) commonAction().andRespond {
.contentType(MediaType.APPLICATION_JSON) withStatus(httpStatus)
.body(SampleTossPaymentConst.tossPaymentErrorJson) .contentType(MediaType.APPLICATION_JSON)
.createResponse(it) .body(SampleTossPaymentConst.tossPaymentErrorJson)
.createResponse(it)
}
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
val exception = shouldThrow<PaymentException> {
client.cancel(cancelRequest)
}
exception.errorCode shouldBe expectedError
} }
// when test("결제 서버에서 4XX 응답 시") {
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR)
// then
val exception = shouldThrow<RoomescapeException> {
client.cancel(cancelRequest)
} }
assertSoftly(exception) { test("결제 서버에서 5XX 응답 시") {
this.errorType shouldBe ErrorType.PAYMENT_SERVER_ERROR runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
this.invalidValue shouldBe "[ErrorCode = ERROR_CODE, ErrorMessage = Error message]"
this.httpStatus shouldBe HttpStatus.INTERNAL_SERVER_ERROR
} }
} }
} }