feat(gift): 선물 사용자 API를 추가한다
This commit is contained in:
+153
@@ -0,0 +1,153 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.gift.adapter.`in`.web
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftCancellationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftDeliveryConfirmationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftDetailResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftFormOptionsResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftListResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftRecipientAddressRegistrationRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftRecipientAddressRegistrationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftReviewRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftReviewResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftTrackingRegistrationRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftTrackingRegistrationResponse
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftCommandService
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftQueryService
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftListType
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/gifts")
|
||||
class GiftController(
|
||||
private val queryService: GiftQueryService,
|
||||
private val commandService: GiftCommandService
|
||||
) {
|
||||
@GetMapping("/form-options")
|
||||
fun getFormOptions() = ApiResponse.ok(GiftFormOptionsResponse.from(queryService.getFormOptions()))
|
||||
|
||||
@GetMapping
|
||||
fun getGifts(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@RequestParam(defaultValue = "ALL") type: GiftListType,
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@RequestParam(defaultValue = "20") size: Int
|
||||
) = ApiResponse.ok(
|
||||
GiftListResponse.from(
|
||||
queryService.getGifts(
|
||||
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
type = type,
|
||||
page = page,
|
||||
size = size
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@GetMapping("/{applicationNo}")
|
||||
fun getGiftDetail(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String
|
||||
) = ApiResponse.ok(
|
||||
GiftDetailResponse.from(
|
||||
queryService.getGiftDetail(
|
||||
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping
|
||||
fun registerGift(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@RequestBody request: GiftApplicationRequest
|
||||
) = ApiResponse.ok(
|
||||
GiftApplicationResponse.from(
|
||||
commandService.registerGift(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
command = request.toCommand()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping("/{applicationNo}/cancel")
|
||||
fun cancelGift(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String
|
||||
) = ApiResponse.ok(
|
||||
GiftCancellationResponse.from(
|
||||
commandService.cancelGift(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping("/{applicationNo}/tracking")
|
||||
fun registerTracking(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String,
|
||||
@RequestBody request: GiftTrackingRegistrationRequest
|
||||
) = ApiResponse.ok(
|
||||
GiftTrackingRegistrationResponse.from(
|
||||
commandService.registerTracking(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo,
|
||||
command = request.toCommand()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping("/{applicationNo}/recipient-address")
|
||||
fun registerRecipientAddress(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String,
|
||||
@RequestBody request: GiftRecipientAddressRegistrationRequest
|
||||
) = ApiResponse.ok(
|
||||
GiftRecipientAddressRegistrationResponse.from(
|
||||
commandService.registerRecipientAddress(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo,
|
||||
command = request.toCommand()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping("/{applicationNo}/delivery-complete")
|
||||
fun confirmDelivery(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String
|
||||
) = ApiResponse.ok(
|
||||
GiftDeliveryConfirmationResponse.from(
|
||||
commandService.confirmDelivery(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@PostMapping("/{applicationNo}/review")
|
||||
fun writeReview(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable applicationNo: String,
|
||||
@RequestBody request: GiftReviewRequest
|
||||
) = ApiResponse.ok(
|
||||
GiftReviewResponse.from(
|
||||
commandService.writeReview(
|
||||
member = member ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
applicationNo = applicationNo,
|
||||
command = request.toCommand()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.gift.adapter.`in`.web
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftRecipientAddressRegistrationRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftReviewRequest
|
||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftTrackingRegistrationRequest
|
||||
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
|
||||
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.GiftSizePrice
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftSizePriceRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftApplicationResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftCancellationResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftCommandService
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftDeliveryConfirmationResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftRecipientAddressRegistrationResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftReviewResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.application.GiftTrackingRegistrationResult
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftSize
|
||||
import kr.co.vividnext.sodalive.v2.gift.domain.GiftStatus
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.mock.mockito.MockBean
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@SpringBootTest(
|
||||
properties = [
|
||||
"spring.datasource.url=jdbc:h2:mem:gift-controller;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
|
||||
]
|
||||
)
|
||||
@AutoConfigureMockMvc
|
||||
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||
class GiftControllerTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
private val categoryRepository: GiftCategoryRepository,
|
||||
private val giftRepository: GiftRepository,
|
||||
private val giftDeliveryRepository: GiftDeliveryRepository,
|
||||
private val sizePriceRepository: GiftSizePriceRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val objectMapper: ObjectMapper
|
||||
) {
|
||||
@MockBean
|
||||
private lateinit var commandService: GiftCommandService
|
||||
|
||||
@Test
|
||||
fun `팬은 활성 폼 옵션을 조회하고 categoryCode를 받지 않는다`() {
|
||||
categoryRepository.save(GiftCategory("DOLL", "인형", true, true))
|
||||
categoryRepository.save(GiftCategory("FOOD", "간식", false, false))
|
||||
sizePriceRepository.save(GiftSizePrice(GiftSize.SMALL, 100, 80, true))
|
||||
|
||||
mockMvc.perform(get("/api/v2/gifts/form-options").with(user("fan").roles("USER")))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.sizes[0].sizeCode").value("SMALL"))
|
||||
.andExpect(jsonPath("$.data.sizes[0].name").value("소형"))
|
||||
.andExpect(jsonPath("$.data.sizes[0].basePriceCan").value(100))
|
||||
.andExpect(jsonPath("$.data.sizes[0].salePriceCan").value(80))
|
||||
.andExpect(jsonPath("$.data.categories[0].name").value("인형"))
|
||||
.andExpect(jsonPath("$.data.categories[0].categoryCode").doesNotExist())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("회원은 선물함 목록에서 보낸 선물과 노출 가능한 받은 선물을 페이징 응답으로 조회한다")
|
||||
fun shouldListGifts() {
|
||||
val member = Member(password = "password", nickname = "member").apply { id = 10L }
|
||||
saveGift("G-CONTROLLER-SENT", senderMemberId = 10L, recipientMemberId = 20L, status = GiftStatus.RECEIVED)
|
||||
saveGift("G-CONTROLLER-HIDDEN", senderMemberId = 20L, recipientMemberId = 10L, status = GiftStatus.RECEIVED)
|
||||
saveGift(
|
||||
"G-CONTROLLER-VISIBLE",
|
||||
senderMemberId = 20L,
|
||||
recipientMemberId = 10L,
|
||||
status = GiftStatus.TRACKING_REGISTERED
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/gifts")
|
||||
.param("type", "ALL")
|
||||
.param("page", "-1")
|
||||
.param("size", "0")
|
||||
.with(user(MemberAdapter(member)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.totalCount").value(2))
|
||||
.andExpect(jsonPath("$.data.items.length()").value(2))
|
||||
.andExpect(jsonPath("$.data.page").value(0))
|
||||
.andExpect(jsonPath("$.data.size").value(20))
|
||||
.andExpect(jsonPath("$.data.hasNext").value(false))
|
||||
.andExpect(jsonPath("$.data.items[0].applicationNo").value("G-CONTROLLER-VISIBLE"))
|
||||
.andExpect(jsonPath("$.data.items[0].direction").value("RECEIVED"))
|
||||
.andExpect(jsonPath("$.data.items[0].status").value("TRACKING_REGISTERED"))
|
||||
.andExpect(jsonPath("$.data.items[0].statusName").value("발송 확인"))
|
||||
.andExpect(jsonPath("$.data.items[0].priceCan").value(80))
|
||||
.andExpect(jsonPath("$.data.items[0].categoryName").value("인형"))
|
||||
.andExpect(jsonPath("$.data.items[0].sizeName").value("소형"))
|
||||
.andExpect(jsonPath("$.data.items[0].createdAt").exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("발신자는 인증 정보로 선물 상세와 자신의 발신자 정보를 조회한다")
|
||||
fun shouldGetSentGiftDetail() {
|
||||
val sender = memberRepository.save(Member(password = "password", nickname = "fan"))
|
||||
val recipient = memberRepository.save(Member(password = "password", nickname = "creator"))
|
||||
val gift = saveGift(
|
||||
applicationNo = "G-CONTROLLER-DETAIL",
|
||||
senderMemberId = sender.id!!,
|
||||
recipientMemberId = recipient.id!!,
|
||||
status = GiftStatus.TRACKING_REGISTERED
|
||||
)
|
||||
giftDeliveryRepository.save(
|
||||
GiftDelivery(
|
||||
giftId = gift.id!!,
|
||||
senderName = "홍길동",
|
||||
senderPhoneNumber = "01012345678",
|
||||
senderZipCode = "06234",
|
||||
senderAddress = "서울시 강남구",
|
||||
senderAddressDetail = "101동 1001호",
|
||||
trackingDeadlineAt = LocalDateTime.of(2026, 10, 2, 12, 0)
|
||||
).apply {
|
||||
courierCompanyName = "CJ대한통운"
|
||||
trackingNumber = "1234567890"
|
||||
trackingRegisteredAt = LocalDateTime.of(2026, 9, 30, 12, 0)
|
||||
}
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
get("/api/v2/gifts/{applicationNo}", gift.applicationNo)
|
||||
.with(user(MemberAdapter(sender)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value("G-CONTROLLER-DETAIL"))
|
||||
.andExpect(jsonPath("$.data.direction").value("SENT"))
|
||||
.andExpect(jsonPath("$.data.status").value("TRACKING_REGISTERED"))
|
||||
.andExpect(jsonPath("$.data.giftInfo.recipientCreatorNickname").value("creator"))
|
||||
.andExpect(jsonPath("$.data.giftInfo.applicationNo").value("G-CONTROLLER-DETAIL"))
|
||||
.andExpect(jsonPath("$.data.giftInfo.paidCan").value(80))
|
||||
.andExpect(jsonPath("$.data.giftInfo.tracking").value("CJ대한통운_1234567890"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.name").value("홍길동"))
|
||||
.andExpect(jsonPath("$.data.senderInfo.address").value("(06234) 서울시 강남구, 101동 1001호"))
|
||||
.andExpect(jsonPath("$.data.recipientAddress").isEmpty)
|
||||
.andExpect(jsonPath("$.data.trackingRequired").value(false))
|
||||
.andExpect(jsonPath("$.data.statusTimeline[0].status").value("RECEIVED"))
|
||||
.andExpect(jsonPath("$.data.statusTimeline[1].occurredAt").value("2026-09-30T12:00:00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팬은 POST /api/v2/gifts로 선물을 신청하고 접수 결과를 받는다")
|
||||
fun shouldRegisterGift() {
|
||||
// Given: 로그인한 팬과 선물 신청 요청을 준비한다.
|
||||
val member = Member(password = "password", nickname = "fan").apply { id = SENDER_ID }
|
||||
val request = GiftApplicationRequest(
|
||||
recipientMemberId = RECIPIENT_ID,
|
||||
senderName = "홍길동",
|
||||
senderPhoneNumber = "01012345678",
|
||||
senderZipCode = "06234",
|
||||
senderAddress = "서울시 강남구",
|
||||
senderAddressDetail = "101동 1001호",
|
||||
sizeCode = GiftSize.SMALL,
|
||||
categoryId = CATEGORY_ID,
|
||||
senderTermsAgreed = true,
|
||||
senderPrivacyAgreed = true,
|
||||
damageWaiverAgreed = true
|
||||
)
|
||||
val deadline = LocalDateTime.of(2026, 10, 2, 12, 0)
|
||||
Mockito.`when`(
|
||||
commandService.registerGift(member, request.toCommand())
|
||||
).thenReturn(
|
||||
GiftApplicationResult(
|
||||
applicationNo = "G20260929000001",
|
||||
status = GiftStatus.RECEIVED,
|
||||
statusName = "접수 완료",
|
||||
priceCan = 80,
|
||||
trackingDeadlineAt = deadline
|
||||
)
|
||||
)
|
||||
|
||||
// When: 팬이 선물 신청 API를 호출한다.
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts")
|
||||
.with(user(MemberAdapter(member)))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request))
|
||||
)
|
||||
// Then: 신청번호, 접수 상태, 판매가와 운송장 등록 기한을 반환한다.
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value("G20260929000001"))
|
||||
.andExpect(jsonPath("$.data.status").value("RECEIVED"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("접수 완료"))
|
||||
.andExpect(jsonPath("$.data.priceCan").value(80))
|
||||
.andExpect(jsonPath("$.data.trackingDeadlineAt").value("2026-10-02T12:00:00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팬은 POST /api/v2/gifts/{applicationNo}/cancel로 접수된 선물을 취소하고 환불 결과를 받는다")
|
||||
fun shouldCancelGift() {
|
||||
// Given: 취소할 접수 선물과 로그인한 발신자를 준비한다.
|
||||
val member = Member(password = "password", nickname = "fan").apply { id = SENDER_ID }
|
||||
val canceledAt = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||
Mockito.`when`(commandService.cancelGift(member, APPLICATION_NO)).thenReturn(
|
||||
GiftCancellationResult(
|
||||
applicationNo = APPLICATION_NO,
|
||||
status = GiftStatus.CANCELED,
|
||||
statusName = "신청 취소",
|
||||
priceCan = 80,
|
||||
canceledAt = canceledAt
|
||||
)
|
||||
)
|
||||
|
||||
// When: 발신자 인증으로 취소 API를 호출한다.
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts/{applicationNo}/cancel", APPLICATION_NO)
|
||||
.with(user(MemberAdapter(member)))
|
||||
)
|
||||
// Then: 취소 상태, 환불 금액과 취소 시각을 반환한다.
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value(APPLICATION_NO))
|
||||
.andExpect(jsonPath("$.data.status").value("CANCELED"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("신청 취소"))
|
||||
.andExpect(jsonPath("$.data.priceCan").value(80))
|
||||
.andExpect(jsonPath("$.data.canceledAt").value("2026-09-29T12:00:00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팬은 POST /api/v2/gifts/{applicationNo}/tracking으로 운송장을 등록하고 발송 확인 결과를 받는다")
|
||||
fun shouldRegisterTracking() {
|
||||
// Given: 운송장 등록 요청과 로그인한 발신자를 준비한다.
|
||||
val member = Member(password = "password", nickname = "fan").apply { id = SENDER_ID }
|
||||
val request = GiftTrackingRegistrationRequest(
|
||||
courierCompanyName = "CJ대한통운",
|
||||
trackingNumber = "1234567890"
|
||||
)
|
||||
val trackingRegisteredAt = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||
val recipientAddressDeadlineAt = trackingRegisteredAt.plusDays(7)
|
||||
Mockito.`when`(commandService.registerTracking(member, APPLICATION_NO, request.toCommand())).thenReturn(
|
||||
GiftTrackingRegistrationResult(
|
||||
applicationNo = APPLICATION_NO,
|
||||
status = GiftStatus.TRACKING_REGISTERED,
|
||||
statusName = "발송 확인",
|
||||
courierCompanyName = "CJ대한통운",
|
||||
trackingNumber = "1234567890",
|
||||
trackingRegisteredAt = trackingRegisteredAt,
|
||||
recipientAddressDeadlineAt = recipientAddressDeadlineAt
|
||||
)
|
||||
)
|
||||
|
||||
// When: 발신자 인증으로 운송장 등록 API를 호출한다.
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts/{applicationNo}/tracking", APPLICATION_NO)
|
||||
.with(user(MemberAdapter(member)))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request))
|
||||
)
|
||||
// Then: 신청번호, 발송 확인 상태와 운송장 및 수신자 주소 입력 기한을 반환한다.
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value(APPLICATION_NO))
|
||||
.andExpect(jsonPath("$.data.status").value("TRACKING_REGISTERED"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("발송 확인"))
|
||||
.andExpect(jsonPath("$.data.courierCompanyName").value("CJ대한통운"))
|
||||
.andExpect(jsonPath("$.data.trackingNumber").value("1234567890"))
|
||||
.andExpect(jsonPath("$.data.trackingRegisteredAt").value("2026-09-29T12:00:00"))
|
||||
.andExpect(jsonPath("$.data.recipientAddressDeadlineAt").value("2026-10-06T12:00:00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("받는 크리에이터는 POST recipient-address로 주소 등록 성공 envelope를 받는다")
|
||||
fun shouldRegisterRecipientAddress() {
|
||||
// Given: 로그인한 받는 크리에이터와 주소 등록 요청을 준비한다.
|
||||
val member = Member(password = "password", nickname = "creator").apply { id = RECIPIENT_ID }
|
||||
val request = GiftRecipientAddressRegistrationRequest(
|
||||
recipientName = "김소다",
|
||||
recipientPhoneNumber = "01098765432",
|
||||
recipientZipCode = "04524",
|
||||
recipientAddress = "서울시 중구",
|
||||
recipientAddressDetail = "202호",
|
||||
recipientTermsAgreed = true,
|
||||
recipientPrivacyAgreed = true
|
||||
)
|
||||
val registeredAt = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||
Mockito.`when`(
|
||||
commandService.registerRecipientAddress(member, APPLICATION_NO, request.toCommand())
|
||||
).thenReturn(
|
||||
GiftRecipientAddressRegistrationResult(
|
||||
applicationNo = APPLICATION_NO,
|
||||
status = GiftStatus.TRACKING_REGISTERED,
|
||||
statusName = "발송 확인",
|
||||
recipientAddressRegisteredAt = registeredAt
|
||||
)
|
||||
)
|
||||
|
||||
// When: 받는 크리에이터 인증으로 주소 등록 API를 호출한다.
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts/{applicationNo}/recipient-address", APPLICATION_NO)
|
||||
.with(user(MemberAdapter(member)))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request))
|
||||
)
|
||||
// Then: 성공 envelope에 신청번호, 상태와 주소 등록 시각을 반환한다.
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value(APPLICATION_NO))
|
||||
.andExpect(jsonPath("$.data.status").value("TRACKING_REGISTERED"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("발송 확인"))
|
||||
.andExpect(jsonPath("$.data.recipientAddressRegisteredAt").value("2026-09-29T12:00:00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("받는 크리에이터는 POST delivery-complete로 수령 확인 성공 envelope를 받는다")
|
||||
fun shouldConfirmDelivery() {
|
||||
val member = Member(password = "password", nickname = "creator").apply { id = RECIPIENT_ID }
|
||||
val deliveredAt = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||
Mockito.`when`(commandService.confirmDelivery(member, APPLICATION_NO)).thenReturn(
|
||||
GiftDeliveryConfirmationResult(
|
||||
applicationNo = APPLICATION_NO,
|
||||
status = GiftStatus.DELIVERED,
|
||||
statusName = "전달완료",
|
||||
deliveredAt = deliveredAt
|
||||
)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts/{applicationNo}/delivery-complete", APPLICATION_NO)
|
||||
.with(user(MemberAdapter(member)))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value(APPLICATION_NO))
|
||||
.andExpect(jsonPath("$.data.status").value("DELIVERED"))
|
||||
.andExpect(jsonPath("$.data.statusName").value("전달완료"))
|
||||
.andExpect(jsonPath("$.data.deliveredAt").value("2026-09-29T12:00:00"))
|
||||
|
||||
Mockito.verify(commandService).confirmDelivery(member, APPLICATION_NO)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팬은 POST review로 리뷰를 작성하고 keywords 배열 응답을 받는다")
|
||||
fun shouldWriteReview() {
|
||||
val member = Member(password = "password", nickname = "fan").apply { id = SENDER_ID }
|
||||
val request = GiftReviewRequest(
|
||||
rating = 5,
|
||||
keywords = listOf("배송이 빨라요", "안내가 친절해요"),
|
||||
comment = "선물 전달 과정이 만족스러웠습니다."
|
||||
)
|
||||
val createdAt = LocalDateTime.of(2026, 10, 11, 12, 0)
|
||||
Mockito.`when`(commandService.writeReview(member, APPLICATION_NO, request.toCommand())).thenReturn(
|
||||
GiftReviewResult(
|
||||
reviewId = 1L,
|
||||
applicationNo = APPLICATION_NO,
|
||||
rating = 5,
|
||||
keywords = listOf("배송이 빨라요", "안내가 친절해요"),
|
||||
comment = "선물 전달 과정이 만족스러웠습니다.",
|
||||
createdAt = createdAt
|
||||
)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v2/gifts/{applicationNo}/review", APPLICATION_NO)
|
||||
.with(user(MemberAdapter(member)))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request))
|
||||
)
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.andExpect(jsonPath("$.data.reviewId").value(1L))
|
||||
.andExpect(jsonPath("$.data.applicationNo").value(APPLICATION_NO))
|
||||
.andExpect(jsonPath("$.data.rating").value(5))
|
||||
.andExpect(jsonPath("$.data.keywords[0]").value("배송이 빨라요"))
|
||||
.andExpect(jsonPath("$.data.keywords[1]").value("안내가 친절해요"))
|
||||
.andExpect(jsonPath("$.data.keyword").doesNotExist())
|
||||
.andExpect(jsonPath("$.data.comment").value("선물 전달 과정이 만족스러웠습니다."))
|
||||
.andExpect(jsonPath("$.data.createdAt").value("2026-10-11T12:00:00"))
|
||||
|
||||
Mockito.verify(commandService).writeReview(member, APPLICATION_NO, request.toCommand())
|
||||
}
|
||||
|
||||
private fun saveGift(
|
||||
applicationNo: String,
|
||||
senderMemberId: Long,
|
||||
recipientMemberId: Long,
|
||||
status: GiftStatus
|
||||
) = giftRepository.save(
|
||||
Gift(
|
||||
applicationNo = applicationNo,
|
||||
senderMemberId = senderMemberId,
|
||||
recipientMemberId = recipientMemberId,
|
||||
status = status,
|
||||
sizeCode = GiftSize.SMALL,
|
||||
categoryId = CATEGORY_ID,
|
||||
categoryNameSnapshot = "인형",
|
||||
salePriceCan = 80,
|
||||
canUsageId = 1L,
|
||||
senderTermsAgreed = true,
|
||||
senderPrivacyAgreed = true,
|
||||
senderTermsAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0),
|
||||
senderPrivacyAgreedAt = LocalDateTime.of(2026, 9, 29, 12, 0)
|
||||
)
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val SENDER_ID = 1L
|
||||
const val RECIPIENT_ID = 2L
|
||||
const val CATEGORY_ID = 3L
|
||||
const val APPLICATION_NO = "G20260929000001"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user