fix(gift): 수취인 정보 없는 선물의 전달 완료를 차단한다

This commit is contained in:
2026-10-02 20:06:17 +09:00
parent c6b5f9c867
commit 0bbb9e4fc7
4 changed files with 74 additions and 4 deletions
@@ -84,4 +84,11 @@ class GiftDelivery(
@Column(name = "undeliverable_reason", length = 255) @Column(name = "undeliverable_reason", length = 255)
var undeliverableReason: String? = null var undeliverableReason: String? = null
) : BaseEntity() ) : BaseEntity() {
fun hasCompleteRecipientInformation() = listOf(
recipientName,
recipientPhoneNumber,
recipientZipCode,
recipientAddress
).none { it.isNullOrBlank() }
}
@@ -203,6 +203,7 @@ class GiftAdminService(
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest() val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
if (gift.status != GiftStatus.INSPECTION_COMPLETED) throw invalidRequest() if (gift.status != GiftStatus.INSPECTION_COMPLETED) throw invalidRequest()
val delivery = giftDeliveryRepository.findByGiftId(requireNotNull(gift.id)) ?: throw invalidRequest() val delivery = giftDeliveryRepository.findByGiftId(requireNotNull(gift.id)) ?: throw invalidRequest()
if (!delivery.hasCompleteRecipientInformation()) throw invalidRequest()
gift.status = GiftStatus.DELIVERED gift.status = GiftStatus.DELIVERED
delivery.deliveredAt = now delivery.deliveredAt = now
@@ -332,7 +332,7 @@ class AdminGiftControllerTest @Autowired constructor(
@Test @Test
@DisplayName("관리자는 검수완료 선물을 전달완료 처리한다") @DisplayName("관리자는 검수완료 선물을 전달완료 처리한다")
fun shouldCompleteGiftDelivery() { fun shouldCompleteGiftDelivery() {
saveGift(GiftStatus.INSPECTION_COMPLETED) saveGift(GiftStatus.INSPECTION_COMPLETED, withRecipientAddress = true)
mockMvc.perform(post("/api/v2/admin/gifts/$APPLICATION_NO/complete-delivery").with(user("admin").roles("ADMIN"))) mockMvc.perform(post("/api/v2/admin/gifts/$APPLICATION_NO/complete-delivery").with(user("admin").roles("ADMIN")))
.andExpect(status().isOk) .andExpect(status().isOk)
@@ -342,7 +342,17 @@ class AdminGiftControllerTest @Autowired constructor(
.andExpect(jsonPath("$.data.occurredAt").value(endsWith("Z"))) .andExpect(jsonPath("$.data.occurredAt").value(endsWith("Z")))
} }
private fun saveGift(status: GiftStatus) { @Test
@DisplayName("관리자는 수취인 정보가 없는 검수완료 선물을 전달완료 처리할 수 없다")
fun shouldRejectGiftDeliveryWithoutRecipientAddress() {
saveGift(GiftStatus.INSPECTION_COMPLETED)
mockMvc.perform(post("/api/v2/admin/gifts/$APPLICATION_NO/complete-delivery").with(user("admin").roles("ADMIN")))
.andExpect(status().isOk)
.andExpect(jsonPath("$.success").value(false))
}
private fun saveGift(status: GiftStatus, withRecipientAddress: Boolean = false) {
val gift = giftRepository.save( val gift = giftRepository.save(
Gift( Gift(
applicationNo = APPLICATION_NO, applicationNo = APPLICATION_NO,
@@ -368,6 +378,10 @@ class AdminGiftControllerTest @Autowired constructor(
senderPhoneNumber = "01012345678", senderPhoneNumber = "01012345678",
senderZipCode = "06234", senderZipCode = "06234",
senderAddress = "서울시 강남구", senderAddress = "서울시 강남구",
recipientName = "김소다".takeIf { withRecipientAddress },
recipientPhoneNumber = "01098765432".takeIf { withRecipientAddress },
recipientZipCode = "04524".takeIf { withRecipientAddress },
recipientAddress = "서울시 중구".takeIf { withRecipientAddress },
trackingDeadlineAt = NOW.minusDays(1), trackingDeadlineAt = NOW.minusDays(1),
trackingRegisteredAt = NOW.minusHours(1) trackingRegisteredAt = NOW.minusHours(1)
) )
@@ -16,6 +16,8 @@ import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.Mockito import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
@@ -248,6 +250,7 @@ class GiftAdminServiceTest @Autowired constructor(
@DisplayName("운영자는 검수 완료 선물을 전달 완료 처리한다") @DisplayName("운영자는 검수 완료 선물을 전달 완료 처리한다")
fun shouldCompleteDeliveryForInspectionCompletedGift() { fun shouldCompleteDeliveryForInspectionCompletedGift() {
val (gift, delivery) = saveGift(GiftStatus.INSPECTION_COMPLETED) val (gift, delivery) = saveGift(GiftStatus.INSPECTION_COMPLETED)
populateRecipientInformation(delivery)
val result = service.completeDelivery(APPLICATION_NO, NOW) val result = service.completeDelivery(APPLICATION_NO, NOW)
@@ -262,7 +265,8 @@ class GiftAdminServiceTest @Autowired constructor(
@Test @Test
@DisplayName("검수 완료 상태가 아니면 운영 전달 완료 처리할 수 없다") @DisplayName("검수 완료 상태가 아니면 운영 전달 완료 처리할 수 없다")
fun shouldRejectDeliveryCompletionWhenGiftIsNotInspectionCompleted() { fun shouldRejectDeliveryCompletionWhenGiftIsNotInspectionCompleted() {
saveGift(GiftStatus.ARRIVED_AT_MAILBOX) val (_, delivery) = saveGift(GiftStatus.ARRIVED_AT_MAILBOX)
populateRecipientInformation(delivery)
val exception = assertThrows(SodaException::class.java) { val exception = assertThrows(SodaException::class.java) {
service.completeDelivery(APPLICATION_NO, NOW) service.completeDelivery(APPLICATION_NO, NOW)
@@ -271,6 +275,50 @@ class GiftAdminServiceTest @Autowired constructor(
assertEquals("common.error.invalid_request", exception.messageKey) assertEquals("common.error.invalid_request", exception.messageKey)
} }
@Test
@DisplayName("수취인 정보가 없으면 운영 전달 완료 처리할 수 없다")
fun shouldRejectDeliveryCompletionWithoutRecipientAddress() {
val (gift, delivery) = saveGift(GiftStatus.INSPECTION_COMPLETED)
val exception = assertThrows(SodaException::class.java) {
service.completeDelivery(APPLICATION_NO, NOW)
}
assertEquals("common.error.invalid_request", exception.messageKey)
assertEquals(GiftStatus.INSPECTION_COMPLETED, gift.status)
assertEquals(null, delivery.deliveredAt)
Mockito.verifyNoInteractions(giftPushService)
}
@ParameterizedTest
@ValueSource(strings = ["name", "phoneNumber", "zipCode", "address"])
@DisplayName("수취인 필수 정보에 공백이 있으면 운영 전달 완료 처리할 수 없다")
fun shouldRejectDeliveryCompletionWithBlankRecipientInformation(blankField: String) {
val (gift, delivery) = saveGift(GiftStatus.INSPECTION_COMPLETED)
populateRecipientInformation(delivery)
when (blankField) {
"name" -> delivery.recipientName = " "
"phoneNumber" -> delivery.recipientPhoneNumber = " "
"zipCode" -> delivery.recipientZipCode = " "
"address" -> delivery.recipientAddress = " "
}
assertThrows(SodaException::class.java) {
service.completeDelivery(APPLICATION_NO, NOW)
}
assertEquals(GiftStatus.INSPECTION_COMPLETED, gift.status)
assertEquals(null, delivery.deliveredAt)
Mockito.verifyNoInteractions(giftPushService)
}
private fun populateRecipientInformation(delivery: GiftDelivery) {
delivery.recipientName = "김소다"
delivery.recipientPhoneNumber = "01098765432"
delivery.recipientZipCode = "04524"
delivery.recipientAddress = "서울시 중구"
}
private fun saveGift(status: GiftStatus): Pair<Gift, GiftDelivery> { private fun saveGift(status: GiftStatus): Pair<Gift, GiftDelivery> {
val gift = giftRepository.save( val gift = giftRepository.save(
Gift( Gift(