fix(gift): 수령확인 시각을 초 단위로 정규화한다

This commit is contained in:
2026-10-26 12:02:34 +09:00
parent 52e5c97423
commit c97b028dd5
2 changed files with 55 additions and 1 deletions
@@ -18,6 +18,7 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
data class GiftApplicationCommand(
val recipientMemberId: Long,
@@ -297,7 +298,7 @@ class GiftCommandService(
throw invalidRequest()
}
val delivery = deliveryRepository.findByGiftIdForUpdate(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
val deliveredAt = now()
val deliveredAt = now().truncatedTo(ChronoUnit.SECONDS)
gift.status = GiftStatus.DELIVERED
delivery.deliveredAt = deliveredAt
@@ -22,6 +22,7 @@ import kr.co.vividnext.sodalive.member.MemberAdapter
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftDeliveryConfirmationResponse
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategory
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
@@ -29,11 +30,13 @@ import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDelivery
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDeliveryRepository
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftRepository
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftReview
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftReviewRepository
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePrice
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePriceRepository
import kr.co.vividnext.sodalive.v2.gift.adapter.out.scheduler.GiftScheduler
import kr.co.vividnext.sodalive.v2.gift.application.GiftAdminService
import kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationCommand
import kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationNoGenerator
import kr.co.vividnext.sodalive.v2.gift.application.GiftCommandService
import kr.co.vividnext.sodalive.v2.gift.application.GiftPushService
import kr.co.vividnext.sodalive.v2.gift.application.GiftRecipientAddressRegistrationCommand
@@ -55,6 +58,7 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource
import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.ValueSource
import org.mockito.Mockito
import org.springframework.aop.framework.Advised
import org.springframework.aop.support.AopUtils
@@ -121,6 +125,8 @@ class AdminGiftControllerTest @Autowired constructor(
private val sizePriceRepository: GiftSizePriceRepository,
private val giftRepository: GiftRepository,
private val giftDeliveryRepository: GiftDeliveryRepository,
private val giftReviewRepository: GiftReviewRepository,
private val applicationNoGenerator: GiftApplicationNoGenerator,
private val memberRepository: MemberRepository,
private val chargeRepository: ChargeRepository,
private val useCanRepository: UseCanRepository,
@@ -592,6 +598,53 @@ class AdminGiftControllerTest @Autowired constructor(
println("P16_CONFIRM_HTTP status=200 success=true rejected=true repeated=false senderPush=1 recipientPush=0")
}
@ParameterizedTest
@ValueSource(ints = [0, 123456789, 999999999])
@DisplayName("수령 확인 시각은 나노초 입력에도 초 단위 DB 저장값과 응답이 일치한다")
fun shouldPersistAndReturnSameSecondPrecisionDeliveryTime(nanos: Int) {
val (gift, _) = saveAdminGift(
applicationNo = APPLICATION_NO,
status = GiftStatus.INSPECTION_COMPLETED,
senderNickname = "precision-sender",
recipientNickname = "precision-recipient",
withTracking = true
)
val giftId = requireNotNull(gift.id)
val fixedNow = LocalDateTime.of(2026, 10, 22, 23, 59, 59, nanos)
val service = GiftCommandService(
giftRepository = giftRepository,
deliveryRepository = giftDeliveryRepository,
reviewRepository = giftReviewRepository,
categoryRepository = categoryRepository,
sizePriceRepository = sizePriceRepository,
memberRepository = memberRepository,
applicationNoGenerator = applicationNoGenerator,
canPaymentService = canPaymentService,
giftPushService = giftPushService,
now = { fixedNow }
)
val result = requireNotNull(
TransactionTemplate(transactionManager).execute {
val recipient = memberRepository.findById(gift.recipientMemberId).orElseThrow()
service.confirmDelivery(recipient, gift.applicationNo).also {
entityManager.flush()
entityManager.clear()
}
}
)
val persisted = requireNotNull(giftDeliveryRepository.findByGiftId(giftId)?.deliveredAt)
println(
"P16_CONFIRM_PRECISION nanos=$nanos input=$fixedNow returned=${result.deliveredAt} " +
"reloaded=$persisted expected=2026-10-22T23:59:59"
)
assertEquals(persisted, result.deliveredAt)
assertEquals(LocalDateTime.of(2026, 10, 22, 23, 59, 59), persisted)
assertEquals(0, persisted.nano)
assertEquals("2026-10-22T23:59:59Z", GiftDeliveryConfirmationResponse.from(result).deliveredAt)
assertEquals(GiftStatus.DELIVERED, giftRepository.findById(giftId).orElseThrow().status)
}
@Test
@DisplayName("실제 transactional 스케줄러 배송지 만료는 발신자만 한 번 알리고 재실행은 추가 알림을 보내지 않는다")
fun shouldExpireRecipientAddressThroughTransactionalSchedulerAndNotifyOnlySenderOnce() {