fix(charge): 쿠폰 충전 회원 락을 적용한다
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.can.CanRepository
|
||||
import kr.co.vividnext.sodalive.can.coupon.CanCoupon
|
||||
import kr.co.vividnext.sodalive.can.coupon.CanCouponNumber
|
||||
import kr.co.vividnext.sodalive.can.coupon.CanCouponNumberRepository
|
||||
import kr.co.vividnext.sodalive.can.coupon.CouponType
|
||||
import kr.co.vividnext.sodalive.google.GooglePlayService
|
||||
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.point.MemberPoint
|
||||
import kr.co.vividnext.sodalive.point.MemberPointRepository
|
||||
import kr.co.vividnext.sodalive.point.PointGrantLog
|
||||
import kr.co.vividnext.sodalive.point.PointGrantLogRepository
|
||||
import kr.co.vividnext.sodalive.v2.can.charge.event.ChargeEventJobService
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mockito
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class ChargeServiceTest {
|
||||
private lateinit var chargeRepository: ChargeRepository
|
||||
private lateinit var canRepository: CanRepository
|
||||
private lateinit var memberRepository: MemberRepository
|
||||
private lateinit var couponNumberRepository: CanCouponNumberRepository
|
||||
private lateinit var grantLogRepository: PointGrantLogRepository
|
||||
private lateinit var memberPointRepository: MemberPointRepository
|
||||
private lateinit var messageSource: SodaMessageSource
|
||||
private lateinit var service: ChargeService
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
chargeRepository = Mockito.mock(ChargeRepository::class.java)
|
||||
canRepository = Mockito.mock(CanRepository::class.java)
|
||||
memberRepository = Mockito.mock(MemberRepository::class.java)
|
||||
couponNumberRepository = Mockito.mock(CanCouponNumberRepository::class.java)
|
||||
grantLogRepository = Mockito.mock(PointGrantLogRepository::class.java)
|
||||
memberPointRepository = Mockito.mock(MemberPointRepository::class.java)
|
||||
messageSource = Mockito.mock(SodaMessageSource::class.java)
|
||||
|
||||
Mockito.`when`(chargeRepository.save(Mockito.any(Charge::class.java))).thenAnswer { it.arguments[0] }
|
||||
Mockito.`when`(messageSource.getMessage("can.charge.title", LangContext().lang)).thenReturn("%d 캔")
|
||||
Mockito.`when`(messageSource.getMessage("can.coupon.use_complete", LangContext().lang)).thenReturn("%d 캔 충전 완료")
|
||||
Mockito.`when`(messageSource.getMessage("can.coupon.use_complete_point", LangContext().lang))
|
||||
.thenReturn("%d 포인트 충전 완료")
|
||||
|
||||
service = ChargeService(
|
||||
chargeRepository = chargeRepository,
|
||||
canRepository = canRepository,
|
||||
memberRepository = memberRepository,
|
||||
couponNumberRepository = couponNumberRepository,
|
||||
grantLogRepository = grantLogRepository,
|
||||
memberPointRepository = memberPointRepository,
|
||||
objectMapper = Mockito.mock(ObjectMapper::class.java),
|
||||
okHttpClient = Mockito.mock(OkHttpClient::class.java),
|
||||
chargeEventJobService = Mockito.mock(ChargeEventJobService::class.java),
|
||||
googlePlayService = Mockito.mock(GooglePlayService::class.java),
|
||||
messageSource = messageSource,
|
||||
langContext = LangContext(),
|
||||
bootpayApplicationId = "bootpayApplicationId",
|
||||
bootpayPrivateKey = "bootpayPrivateKey",
|
||||
bootpayHectoApplicationId = "bootpayHectoApplicationId",
|
||||
bootpayHectoPrivateKey = "bootpayHectoPrivateKey",
|
||||
appleInAppVerifySandBoxUrl = "https://sandbox.example.com",
|
||||
appleInAppVerifyUrl = "https://apple.example.com",
|
||||
payverseMid = "payverseMid",
|
||||
payverseClientKey = "payverseClientKey",
|
||||
payverseSecretKey = "payverseSecretKey",
|
||||
payverseUsdMid = "payverseUsdMid",
|
||||
payverseUsdClientKey = "payverseUsdClientKey",
|
||||
payverseUsdSecretKey = "payverseUsdSecretKey",
|
||||
payverseJpyMid = "payverseJpyMid",
|
||||
payverseJpyClientKey = "payverseJpyClientKey",
|
||||
payverseJpySecretKey = "payverseJpySecretKey",
|
||||
payverseHost = "https://payverse.example.com",
|
||||
serverEnv = "test"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldUseLockedMemberWhenChargingCanCoupon() {
|
||||
val originalMember = member(id = 10L)
|
||||
val lockedMember = member(id = 10L)
|
||||
val couponNumber = canCouponNumber(couponType = CouponType.CAN, can = 300)
|
||||
|
||||
Mockito.`when`(couponNumberRepository.findByCouponNumber("COUPON1234")).thenReturn(couponNumber)
|
||||
Mockito.`when`(memberRepository.findByIdForUpdate(10L)).thenReturn(lockedMember)
|
||||
|
||||
service.chargeByCoupon("COUPON1234", originalMember)
|
||||
|
||||
Mockito.verify(memberRepository).findByIdForUpdate(10L)
|
||||
assertEquals(300, lockedMember.pgRewardCan)
|
||||
assertEquals(0, originalMember.pgRewardCan)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldSaveCouponChargeWithLockedMember() {
|
||||
val originalMember = member(id = 11L)
|
||||
val lockedMember = member(id = 11L)
|
||||
val couponNumber = canCouponNumber(couponType = CouponType.CAN, can = 150)
|
||||
val captor = ArgumentCaptor.forClass(Charge::class.java)
|
||||
|
||||
Mockito.`when`(couponNumberRepository.findByCouponNumber("COUPON5678")).thenReturn(couponNumber)
|
||||
Mockito.`when`(memberRepository.findByIdForUpdate(11L)).thenReturn(lockedMember)
|
||||
|
||||
service.chargeByCoupon("COUPON5678", originalMember)
|
||||
|
||||
Mockito.verify(chargeRepository).save(captor.capture())
|
||||
assertEquals(lockedMember, captor.value.member)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldNotLockMemberWhenCanCouponAlreadyUsed() {
|
||||
val member = member(id = 12L)
|
||||
val couponNumber = canCouponNumber(couponType = CouponType.CAN, can = 100).also {
|
||||
it.member = member(id = 99L)
|
||||
}
|
||||
|
||||
Mockito.`when`(couponNumberRepository.findByCouponNumber("USED1234")).thenReturn(couponNumber)
|
||||
|
||||
org.junit.jupiter.api.assertThrows<kr.co.vividnext.sodalive.common.SodaException> {
|
||||
service.chargeByCoupon("USED1234", member)
|
||||
}
|
||||
|
||||
Mockito.verify(memberRepository, Mockito.never()).findByIdForUpdate(Mockito.anyLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shouldKeepPointCouponBehaviorWithoutMemberLock() {
|
||||
val member = member(id = 13L)
|
||||
val couponNumber = canCouponNumber(couponType = CouponType.POINT, can = 500)
|
||||
|
||||
Mockito.`when`(couponNumberRepository.findByCouponNumber("POINT1234")).thenReturn(couponNumber)
|
||||
|
||||
val result = service.chargeByCoupon("POINT1234", member)
|
||||
|
||||
assertEquals("500 포인트 충전 완료", result)
|
||||
Mockito.verify(memberRepository, Mockito.never()).findByIdForUpdate(Mockito.anyLong())
|
||||
Mockito.verify(grantLogRepository).save(Mockito.any(PointGrantLog::class.java))
|
||||
Mockito.verify(memberPointRepository).save(Mockito.any(MemberPoint::class.java))
|
||||
Mockito.verify(chargeRepository, Mockito.never()).save(Mockito.any(Charge::class.java))
|
||||
}
|
||||
|
||||
private fun canCouponNumber(couponType: CouponType, can: Int): CanCouponNumber {
|
||||
val coupon = CanCoupon(
|
||||
couponName = "테스트 쿠폰",
|
||||
couponType = couponType,
|
||||
can = can,
|
||||
couponCount = 1,
|
||||
validity = LocalDateTime.now().plusDays(1),
|
||||
isActive = true,
|
||||
isMultipleUse = false
|
||||
)
|
||||
return CanCouponNumber("COUPON").also { it.canCoupon = coupon }
|
||||
}
|
||||
|
||||
private fun member(id: Long): Member {
|
||||
return Member(password = "password", nickname = "member$id").also { it.id = id }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user