fix(can-coupon): 쿠폰 사용 본인인증 예외를 성인 노출 정책에 맞춘다

This commit is contained in:
2026-04-02 11:28:21 +09:00
parent 38fd826fe4
commit 8705389ddd
3 changed files with 136 additions and 1 deletions

View File

@@ -8,6 +8,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.repository.findByIdOrNull
@@ -29,6 +30,7 @@ class CanCouponService(
private val couponNumberRepository: CanCouponNumberRepository,
private val memberRepository: MemberRepository,
private val memberContentPreferenceService: MemberContentPreferenceService,
private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher,
@@ -133,7 +135,8 @@ class CanCouponService(
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (member.auth == null) throw SodaException(messageKey = "can.coupon.auth_required")
val viewerContentPreference = memberContentPreferenceService.getStoredPreference(member)
if (!viewerContentPreference.isAdult) throw SodaException(messageKey = "can.coupon.auth_required")
issueService.validateAvailableUseCoupon(couponNumber, memberId)

View File

@@ -0,0 +1,122 @@
package kr.co.vividnext.sodalive.can.coupon
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.can.charge.ChargeService
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.member.contentpreference.ViewerContentPreference
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.springframework.context.ApplicationEventPublisher
import java.util.Optional
class CanCouponServiceTest {
private lateinit var issueService: CanCouponIssueService
private lateinit var chargeService: ChargeService
private lateinit var repository: CanCouponRepository
private lateinit var couponNumberRepository: CanCouponNumberRepository
private lateinit var memberRepository: MemberRepository
private lateinit var memberContentPreferenceService: MemberContentPreferenceService
private lateinit var objectMapper: ObjectMapper
private lateinit var applicationEventPublisher: ApplicationEventPublisher
private lateinit var messageSource: SodaMessageSource
private lateinit var langContext: LangContext
private lateinit var service: CanCouponService
@BeforeEach
fun setUp() {
issueService = mock(CanCouponIssueService::class.java)
chargeService = mock(ChargeService::class.java)
repository = mock(CanCouponRepository::class.java)
couponNumberRepository = mock(CanCouponNumberRepository::class.java)
memberRepository = mock(MemberRepository::class.java)
memberContentPreferenceService = mock(MemberContentPreferenceService::class.java)
objectMapper = mock(ObjectMapper::class.java)
applicationEventPublisher = mock(ApplicationEventPublisher::class.java)
messageSource = mock(SodaMessageSource::class.java)
langContext = LangContext()
service = CanCouponService(
issueService = issueService,
chargeService = chargeService,
repository = repository,
couponNumberRepository = couponNumberRepository,
memberRepository = memberRepository,
memberContentPreferenceService = memberContentPreferenceService,
objectMapper = objectMapper,
applicationEventPublisher = applicationEventPublisher,
messageSource = messageSource,
langContext = langContext
)
}
@Test
@DisplayName("비한국 사용자는 성인 노출 설정이 true이면 본인인증 없이 쿠폰을 사용할 수 있다")
fun shouldUseCouponWithoutAuthForNonKrAdultVisibleMember() {
val member = createMember(memberId = 1L)
val couponNumber = "COUPON1234"
`when`(memberRepository.findById(1L)).thenReturn(Optional.of(member))
`when`(memberContentPreferenceService.getStoredPreference(member)).thenReturn(
ViewerContentPreference(
countryCode = "US",
isAdultContentVisible = true,
contentType = kr.co.vividnext.sodalive.content.ContentType.ALL,
isAdult = true
)
)
`when`(chargeService.chargeByCoupon(couponNumber, member)).thenReturn("charged")
val result = service.useCanCoupon(couponNumber = couponNumber, memberId = 1L)
assertEquals("charged", result)
verify(issueService).validateAvailableUseCoupon(couponNumber, 1L)
verify(chargeService).chargeByCoupon(couponNumber, member)
}
@Test
@DisplayName("한국 사용자는 본인인증이 없으면 기존처럼 쿠폰 사용이 불가능하다")
fun shouldThrowWhenAdultPolicyIsFalse() {
val member = createMember(memberId = 2L)
`when`(memberRepository.findById(2L)).thenReturn(Optional.of(member))
`when`(memberContentPreferenceService.getStoredPreference(member)).thenReturn(
ViewerContentPreference(
countryCode = "KR",
isAdultContentVisible = true,
contentType = kr.co.vividnext.sodalive.content.ContentType.ALL,
isAdult = false
)
)
val exception = assertThrows(SodaException::class.java) {
service.useCanCoupon(couponNumber = "COUPON5678", memberId = 2L)
}
assertEquals("can.coupon.auth_required", exception.messageKey)
verify(issueService, never()).validateAvailableUseCoupon("COUPON5678", 2L)
verify(chargeService, never()).chargeByCoupon("COUPON5678", member)
}
private fun createMember(memberId: Long): Member {
return Member(
email = "member$memberId@test.com",
password = "password",
nickname = "member$memberId"
).apply {
id = memberId
}
}
}