From f46a6c4c7c893bdabcc53d06d1fb46b9146b913f Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 26 Oct 2026 08:44:44 +0900 Subject: [PATCH] =?UTF-8?q?feat(gift):=20=EB=B0=B0=EC=86=A1=20=EA=B0=9C?= =?UTF-8?q?=EC=9D=B8=EC=A0=95=EB=B3=B4=EC=9D=98=20null=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20=ED=98=B8=ED=99=98?= =?UTF-8?q?=EC=9D=84=20=EC=A7=80=EC=9B=90=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift-schema.sql | 8 ++ .../adapter/out/persistence/GiftDelivery.kt | 18 ++-- .../gift/application/GiftAdminQueryService.kt | 8 +- .../v2/gift/application/GiftQueryService.kt | 8 +- .../gift/adapter/in/web/GiftControllerTest.kt | 81 ++++++++++++++++ .../out/persistence/GiftEntityMappingTest.kt | 62 +++++++++++- .../application/GiftAdminQueryServiceTest.kt | 53 ++++++++++- .../gift/application/GiftQueryServiceTest.kt | 94 ++++++++++++++++++- 8 files changed, 312 insertions(+), 20 deletions(-) diff --git a/docs/20260929_크리에이터_선물하기/gift-schema.sql b/docs/20260929_크리에이터_선물하기/gift-schema.sql index af09cb64..9163dd36 100644 --- a/docs/20260929_크리에이터_선물하기/gift-schema.sql +++ b/docs/20260929_크리에이터_선물하기/gift-schema.sql @@ -128,3 +128,11 @@ CREATE TABLE gift_application_no_sequence ( ) COMMENT='선물 신청번호 날짜별 채번'; ALTER TABLE gift_category DROP INDEX uk_gift_category_receipt_code; + +-- 2026-10-23: 전달 완료 후 배송정보 삭제를 위한 발신자 컬럼 NULL 허용. +-- 기존 DB에는 아래 ALTER TABLE 문만 적용하며, 이 파일 전체를 재실행하지 않는다. +ALTER TABLE gift_delivery + MODIFY COLUMN sender_name VARCHAR(50) NULL COMMENT '발신자 이름', + MODIFY COLUMN sender_phone_number VARCHAR(30) NULL COMMENT '발신자 휴대폰 번호', + MODIFY COLUMN sender_zip_code VARCHAR(20) NULL COMMENT '발신자 우편번호', + MODIFY COLUMN sender_address VARCHAR(255) NULL COMMENT '발신자 주소'; diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftDelivery.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftDelivery.kt index 748e1757..37adce3a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftDelivery.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftDelivery.kt @@ -16,20 +16,20 @@ class GiftDelivery( @Column(name = "gift_id", nullable = false) val giftId: Long, - @Column(name = "sender_name", nullable = false, length = 50) - val senderName: String, + @Column(name = "sender_name", length = 50) + var senderName: String?, - @Column(name = "sender_phone_number", nullable = false, length = 30) - val senderPhoneNumber: String, + @Column(name = "sender_phone_number", length = 30) + var senderPhoneNumber: String?, - @Column(name = "sender_zip_code", nullable = false, length = 20) - val senderZipCode: String, + @Column(name = "sender_zip_code", length = 20) + var senderZipCode: String?, - @Column(name = "sender_address", nullable = false, length = 255) - val senderAddress: String, + @Column(name = "sender_address", length = 255) + var senderAddress: String?, @Column(name = "sender_address_detail", length = 255) - val senderAddressDetail: String? = null, + var senderAddressDetail: String? = null, @Column(name = "recipient_name", length = 50) var recipientName: String? = null, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryService.kt index 79e9c34d..0f2bb43a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryService.kt @@ -115,8 +115,8 @@ class GiftAdminQueryService( applicationNo = gift.applicationNo, senderInfo = AdminGiftMemberInfoResult( nickname = sender.nickname, - name = delivery.senderName, - phoneNumber = delivery.senderPhoneNumber, + name = delivery.senderName.orEmpty(), + phoneNumber = delivery.senderPhoneNumber.orEmpty(), address = formatAddress(delivery.senderZipCode, delivery.senderAddress, delivery.senderAddressDetail) ), recipientInfo = AdminGiftMemberInfoResult( @@ -174,8 +174,8 @@ class GiftAdminQueryService( return formatAddress(zipCode, address, recipientAddressDetail) } - private fun formatAddress(zipCode: String, address: String, detail: String?) = - "($zipCode) $address" + if (detail.isNullOrBlank()) "" else ", $detail" + private fun formatAddress(zipCode: String?, address: String?, detail: String?) = + if (zipCode == null || address == null) "" else "($zipCode) $address" + if (detail.isNullOrBlank()) "" else ", $detail" private fun invalidRequest() = SodaException(messageKey = "common.error.invalid_request") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt index 961060c5..b4e2306e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryService.kt @@ -259,8 +259,8 @@ class GiftQueryService( } private fun GiftDelivery.toSenderInfo() = GiftAddressResult( - name = senderName, - phoneNumber = senderPhoneNumber, + name = senderName.orEmpty(), + phoneNumber = senderPhoneNumber.orEmpty(), address = formatAddress(senderZipCode, senderAddress, senderAddressDetail) ) @@ -305,8 +305,8 @@ class GiftQueryService( ) } - private fun formatAddress(zipCode: String, address: String, detail: String?) = - "($zipCode) $address" + if (detail.isNullOrBlank()) "" else ", $detail" + private fun formatAddress(zipCode: String?, address: String?, detail: String?) = + if (zipCode == null || address == null) "" else "($zipCode) $address" + if (detail.isNullOrBlank()) "" else ", $detail" private fun invalidRequest() = SodaException(messageKey = "common.error.invalid_request") diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt index 2deec4e5..f461254b 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/gift/adapter/in/web/GiftControllerTest.kt @@ -29,8 +29,11 @@ import kr.co.vividnext.sodalive.v2.gift.application.GiftTrackingRegistrationResu import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus import org.hamcrest.Matchers.endsWith +import org.hamcrest.Matchers.nullValue import org.junit.jupiter.api.DisplayName 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.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc @@ -219,6 +222,84 @@ class GiftControllerTest @Autowired constructor( .andExpect(jsonPath("$.data.statusTimeline[1].occurredAt").value("2026-09-30T12:00:00Z")) } + @Test + @DisplayName("배송 개인정보가 지워져도 보낸 선물과 받은 선물의 상세 응답 계약을 유지한다") + fun shouldReturnCompatibleErasedGiftDetailsForSenderAndRecipient() { + val sender = memberRepository.save(Member(password = "password", nickname = "erased-fan")) + val recipient = memberRepository.save(Member(password = "password", nickname = "erased-creator")) + val gift = saveGift("G-CONTROLLER-ERASED", sender.id!!, recipient.id!!, GiftStatus.DELIVERED) + val delivery = GiftDelivery( + giftId = gift.id!!, + senderName = null, + senderPhoneNumber = null, + senderZipCode = null, + senderAddress = null, + trackingDeadlineAt = LocalDateTime.of(2026, 10, 2, 12, 0), + recipientAddressDeadlineAt = LocalDateTime.of(2026, 10, 7, 12, 0), + deliveredAt = LocalDateTime.of(2026, 10, 3, 12, 0) + ) + giftDeliveryRepository.saveAndFlush(delivery) + + mockMvc.perform(get("/api/v2/gifts/{applicationNo}", gift.applicationNo).with(user(MemberAdapter(sender)))) + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.direction").value("SENT")) + .andExpect(jsonPath("$.data.status").value("DELIVERED")) + .andExpect(jsonPath("$.data.senderInfo").isMap) + .andExpect(jsonPath("$.data.senderInfo.name").value("")) + .andExpect(jsonPath("$.data.senderInfo.phoneNumber").value("")) + .andExpect(jsonPath("$.data.senderInfo.address").value("")) + .andExpect(jsonPath("$.data.recipientAddress").value(nullValue())) + .andExpect(jsonPath("$.data.giftInfo.tracking").value(nullValue())) + .andExpect(jsonPath("$.data.giftInfo.recipientCreatorNickname").value("erased-creator")) + .andExpect(jsonPath("$.data.trackingRequired").value(false)) + .andExpect(jsonPath("$.data.recipientAddressRequired").value(false)) + .andExpect(jsonPath("$.data.mailbox").value(nullValue())) + .andExpect(jsonPath("$.data.statusTimeline[4].occurredAt").value("2026-10-03T12:00:00Z")) + + mockMvc.perform(get("/api/v2/gifts/{applicationNo}", gift.applicationNo).with(user(MemberAdapter(recipient)))) + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.direction").value("RECEIVED")) + .andExpect(jsonPath("$.data.senderInfo").value(nullValue())) + .andExpect(jsonPath("$.data.recipientAddress").value(nullValue())) + .andExpect(jsonPath("$.data.giftInfo.tracking").value(nullValue())) + .andExpect(jsonPath("$.data.giftInfo.senderNickname").value("erased-fan")) + .andExpect(jsonPath("$.data.trackingRequired").value(false)) + .andExpect(jsonPath("$.data.recipientAddressRequired").value(false)) + .andExpect(jsonPath("$.data.recipientAddressDeadlineAt").value("2026-10-07T12:00:00Z")) + } + + @ParameterizedTest + @ValueSource(strings = ["senderName", "senderPhoneNumber", "senderZipCode", "senderAddress"]) + @DisplayName("저장 필드가 nullable이어도 신청의 필수 발신자 입력 누락을 거부한다") + fun shouldKeepRequiredSenderRequestValidation(missingField: String) { + val member = Member(password = "password", nickname = "fan").apply { id = SENDER_ID } + val request = objectMapper.createObjectNode() + .put("recipientMemberId", RECIPIENT_ID) + .put("senderName", "sender") + .put("senderPhoneNumber", "phone") + .put("senderZipCode", "zip") + .put("senderAddress", "address") + .put("sizeCode", "SMALL") + .put("categoryId", CATEGORY_ID) + .put("senderTermsAgreed", true) + .put("senderPrivacyAgreed", true) + .put("damageWaiverAgreed", true) + request.remove(missingField) + + mockMvc.perform( + post("/api/v2/gifts") + .with(user(MemberAdapter(member))) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request)) + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$.success").value(false)) + + Mockito.verifyNoInteractions(commandService) + } + @Test @DisplayName("발신자는 전달 완료 전 정상 진행 선물 상세에서 받을 주소를 조회한다") fun shouldGetMailboxForSentNormalProgressGiftDetailBeforeDelivered() { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt index 577bcd03..0abded75 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/adapter/out/persistence/GiftEntityMappingTest.kt @@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -13,6 +14,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import import org.springframework.dao.DataIntegrityViolationException import java.time.LocalDateTime +import javax.persistence.EntityManager @DataJpaTest( properties = [ @@ -28,7 +30,8 @@ class GiftEntityMappingTest @Autowired constructor( private val giftCategoryRepository: GiftCategoryRepository, private val giftSizePriceRepository: GiftSizePriceRepository, private val giftReviewRepository: GiftReviewRepository, - private val giftApplicationNoSequenceRepository: GiftApplicationNoSequenceRepository + private val giftApplicationNoSequenceRepository: GiftApplicationNoSequenceRepository, + private val entityManager: EntityManager ) { @Test @DisplayName("선물 엔티티와 배송 정보를 저장하고 조회한다") @@ -91,6 +94,63 @@ class GiftEntityMappingTest @Autowired constructor( assertNotNull(saved.updatedAt) } + @Test + @DisplayName("발신자와 수신자 및 운송장 개인정보를 모두 null로 저장하고 다시 조회한다") + fun shouldPersistNullForAllSenderFields() { + val category = giftCategoryRepository.save(giftCategory("100", "DOLL", "A")) + val gift = giftRepository.save(gift(category.id!!, "G-NULL-DELIVERY")) + val deadline = LocalDateTime.of(2026, 10, 2, 12, 0) + val delivery = giftDeliveryRepository.saveAndFlush( + GiftDelivery( + giftId = gift.id!!, + senderName = "sender", + senderPhoneNumber = "sender-phone", + senderZipCode = "sender-zip", + senderAddress = "sender-address", + senderAddressDetail = "sender-detail", + recipientName = "recipient", + recipientPhoneNumber = "recipient-phone", + recipientZipCode = "recipient-zip", + recipientAddress = "recipient-address", + recipientAddressDetail = "recipient-detail", + courierCompanyName = "courier", + trackingNumber = "tracking", + trackingDeadlineAt = deadline + ) + ) + delivery.senderName = null + delivery.senderPhoneNumber = null + delivery.senderZipCode = null + delivery.senderAddress = null + delivery.senderAddressDetail = null + delivery.recipientName = null + delivery.recipientPhoneNumber = null + delivery.recipientZipCode = null + delivery.recipientAddress = null + delivery.recipientAddressDetail = null + delivery.courierCompanyName = null + delivery.trackingNumber = null + + entityManager.flush() + entityManager.clear() + + val reloaded = requireNotNull(giftDeliveryRepository.findByGiftId(gift.id!!)) + assertNull(reloaded.senderName) + assertNull(reloaded.senderPhoneNumber) + assertNull(reloaded.senderZipCode) + assertNull(reloaded.senderAddress) + assertNull(reloaded.senderAddressDetail) + assertNull(reloaded.recipientName) + assertNull(reloaded.recipientPhoneNumber) + assertNull(reloaded.recipientZipCode) + assertNull(reloaded.recipientAddress) + assertNull(reloaded.recipientAddressDetail) + assertNull(reloaded.courierCompanyName) + assertNull(reloaded.trackingNumber) + assertEquals(false, reloaded.hasCompleteRecipientInformation()) + assertEquals(deadline, reloaded.trackingDeadlineAt) + } + @Test @DisplayName("신청번호와 카테고리 분류번호 및 코드와 리뷰 선물 ID는 중복 저장할 수 없다") fun shouldRejectDuplicateUniqueValues() { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryServiceTest.kt index ef9568b0..e6407534 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftAdminQueryServiceTest.kt @@ -13,13 +13,18 @@ import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftRepository import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullAndEmptySource +import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import import java.time.LocalDateTime +import javax.persistence.EntityManager @DataJpaTest( properties = [ @@ -33,7 +38,8 @@ class GiftAdminQueryServiceTest @Autowired constructor( private val giftRepository: GiftRepository, private val deliveryRepository: GiftDeliveryRepository, private val categoryRepository: GiftCategoryRepository, - private val memberRepository: MemberRepository + private val memberRepository: MemberRepository, + private val entityManager: EntityManager ) { @Test @DisplayName("관리자는 선물함 목록을 필터링하고 가능한 액션을 조회한다") @@ -124,6 +130,51 @@ class GiftAdminQueryServiceTest @Autowired constructor( assertEquals(listOf("COMPLETE_INSPECTION", "MARK_UNDELIVERABLE"), result.availableActions) } + @Test + @DisplayName("개인정보가 지워진 관리자 선물 응답은 닉네임과 종료 상태 액션을 유지한다") + fun shouldMapErasedDeliveryStringsWithoutChangingActions() { + val sender = saveMember("fan-alpha") + val recipient = saveMember("creator-alpha") + val gift = saveGift("A-ERASED-DETAIL", sender, recipient, saveCategory(), GiftStatus.DELIVERED) + requireNotNull(deliveryRepository.findByGiftId(gift.id!!)).apply { + senderName = null + senderPhoneNumber = null + senderZipCode = null + senderAddress = null + senderAddressDetail = null + } + entityManager.flush() + entityManager.clear() + + val detail = service().getGiftDetail(gift.applicationNo) + val item = service().getGifts(GiftStatus.DELIVERED, null, null, 0, 20).items.single() + + assertEquals(AdminGiftMemberInfoResult("fan-alpha", "", "", ""), detail.senderInfo) + assertEquals(AdminGiftMemberInfoResult("creator-alpha", "", "", ""), detail.recipientInfo) + assertNull(detail.inboundDeliveryInfo.courierCompanyName) + assertNull(detail.inboundDeliveryInfo.trackingNumber) + assertEquals(GiftStatus.DELIVERED, detail.status) + assertEquals(emptyList(), detail.availableActions) + assertEquals("fan-alpha", item.senderNickname) + assertEquals("creator-alpha", item.recipientNickname) + assertNull(item.courierCompanyName) + assertNull(item.trackingNumber) + assertEquals(emptyList(), item.availableActions) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" "]) + @DisplayName("관리자 발신자 주소도 상세주소가 없으면 구분자를 생략한다") + fun shouldOmitAbsentSenderAddressDetail(detail: String?) { + val gift = saveGift("A-NO-DETAIL", saveMember("fan"), saveMember("creator"), saveCategory(), GiftStatus.RECEIVED) + requireNotNull(deliveryRepository.findByGiftId(gift.id!!)).senderAddressDetail = detail + + val result = service().getGiftDetail(gift.applicationNo) + + assertEquals("(06234) 서울시 강남구", result.senderInfo.address) + } + @Test @DisplayName("관리자 선물 목록과 상세는 상태별 가능한 액션을 반환하고 없는 신청번호를 거부한다") fun shouldReturnAvailableActionsByStatusAndRejectUnknownApplicationNo() { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt index e1c06d67..5a46de9e 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/gift/application/GiftQueryServiceTest.kt @@ -27,10 +27,14 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullAndEmptySource +import org.junit.jupiter.params.provider.ValueSource import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.context.annotation.Import import java.time.LocalDateTime +import javax.persistence.EntityManager @DataJpaTest( properties = [ @@ -46,7 +50,8 @@ class GiftQueryServiceTest @Autowired constructor( private val deliveryRepository: GiftDeliveryRepository, private val memberRepository: MemberRepository, private val giftMailboxRepository: GiftMailboxRepository, - private val creatorLocalizedNameService: CreatorLocalizedNameService + private val creatorLocalizedNameService: CreatorLocalizedNameService, + private val entityManager: EntityManager ) { @Test fun `폼 옵션은 활성 카테고리와 활성 사이즈 가격만 반환한다`() { @@ -313,6 +318,93 @@ class GiftQueryServiceTest @Autowired constructor( assertNull(result.statusTimeline[1].occurredAt) } + @Test + @DisplayName("개인정보가 지워진 보낸 선물은 발신자 객체와 빈 문자열을 유지한다") + fun shouldMapErasedSentSenderDetailsToEmptyStrings() { + val sender = saveMember("fan") + val recipient = saveMember("creator", "profile/creator.png") + val gift = saveGift("G-ERASED-SENT", sender.id!!, recipient.id!!, GiftStatus.DELIVERED) + saveDelivery(gift).apply { + applyStatusTimes(GiftStatus.DELIVERED) + recipientAddressDeadlineAt = NOW.plusDays(7) + senderName = null + senderPhoneNumber = null + senderZipCode = null + senderAddress = null + senderAddressDetail = null + } + entityManager.flush() + entityManager.clear() + + val result = queryService().getGiftDetail(sender.id!!, gift.applicationNo, Lang.KO) + val item = queryService().getGifts(sender.id!!, GiftListType.SENT, 0, 20, Lang.KO).items.single() + + assertEquals(GiftDirection.SENT, result.direction) + assertEquals(GiftStatus.DELIVERED, result.status) + assertEquals(GiftAddressResult("", "", ""), result.senderInfo) + assertNull(result.recipientAddress) + assertNull(result.giftInfo.tracking) + assertNull(result.mailbox) + assertEquals("creator", result.giftInfo.recipientCreatorNickname) + assertEquals(false, result.trackingRequired) + assertEquals(false, result.recipientAddressRequired) + assertEquals(NOW.plusHours(4), result.statusTimeline.last().occurredAt) + assertEquals("creator", item.counterpartNickname) + assertEquals("https://cdn.test/profile/creator.png", item.counterpartProfileImageUrl) + assertEquals(false, item.recipientAddressRegistered) + assertEquals(NOW.plusDays(7), item.recipientAddressDeadlineAt) + } + + @Test + @DisplayName("개인정보가 지워진 받은 선물은 발신자 정보와 수신자 배송지를 null로 유지한다") + fun shouldKeepReceivedSenderInfoNullAndRecipientAddressNullWhenErased() { + val sender = saveMember("fan", "profile/fan.png") + val recipient = saveMember("creator") + val gift = saveGift("G-ERASED-RECEIVED", sender.id!!, recipient.id!!, GiftStatus.DELIVERED) + saveDelivery(gift).apply { + applyStatusTimes(GiftStatus.DELIVERED) + recipientAddressDeadlineAt = NOW.plusDays(7) + senderName = null + senderPhoneNumber = null + senderZipCode = null + senderAddress = null + senderAddressDetail = null + } + entityManager.flush() + entityManager.clear() + + val result = queryService().getGiftDetail(recipient.id!!, gift.applicationNo, Lang.KO) + val item = queryService().getGifts(recipient.id!!, GiftListType.RECEIVED, 0, 20, Lang.KO).items.single() + + assertEquals(GiftDirection.RECEIVED, result.direction) + assertNull(result.senderInfo) + assertNull(result.recipientAddress) + assertNull(result.giftInfo.tracking) + assertEquals("fan", result.giftInfo.senderNickname) + assertEquals(false, result.trackingRequired) + assertEquals(false, result.recipientAddressRequired) + assertEquals(NOW.plusDays(7), result.recipientAddressDeadlineAt) + assertEquals("fan", item.counterpartNickname) + assertEquals("https://cdn.test/profile/fan.png", item.counterpartProfileImageUrl) + assertEquals(false, item.recipientAddressRegistered) + assertEquals(NOW.plusDays(7), item.recipientAddressDeadlineAt) + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = [" "]) + @DisplayName("발신자 상세주소가 없으면 주소 뒤 구분자를 생략한다") + fun shouldOmitAbsentSenderAddressDetail(detail: String?) { + val sender = saveMember("fan") + val recipient = saveMember("creator") + val gift = saveGift("G-NO-ADDRESS-DETAIL", sender.id!!, recipient.id!!, GiftStatus.RECEIVED) + saveDelivery(gift).senderAddressDetail = detail + + val result = queryService().getGiftDetail(sender.id!!, gift.applicationNo, Lang.KO) + + assertEquals("(06234) 서울시 강남구", result.senderInfo?.address) + } + @Test @DisplayName("보낸 선물의 전달 완료 전 정상 진행 상세는 받을 주소를 반환한다") fun shouldReturnMailboxForSentNormalProgressGiftDetailBeforeDelivered() {