test(live): 유료 예약 원자성 회귀 테스트를 추가한다
This commit is contained in:
@@ -1,15 +1,26 @@
|
|||||||
package kr.co.vividnext.sodalive.live.reservation
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||||
|
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.Payment
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
|
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||||
|
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
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.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.SpyBean
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException
|
||||||
import org.springframework.mock.web.MockHttpServletRequest
|
import org.springframework.mock.web.MockHttpServletRequest
|
||||||
import org.springframework.test.annotation.DirtiesContext
|
import org.springframework.test.annotation.DirtiesContext
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import org.springframework.test.context.ContextConfiguration
|
||||||
@@ -34,9 +45,15 @@ import javax.persistence.EntityManager
|
|||||||
class LiveReservationServiceIntegrationTest @Autowired constructor(
|
class LiveReservationServiceIntegrationTest @Autowired constructor(
|
||||||
private val service: LiveReservationService,
|
private val service: LiveReservationService,
|
||||||
private val repository: LiveReservationRepository,
|
private val repository: LiveReservationRepository,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val chargeRepository: ChargeRepository,
|
||||||
|
private val useCanRepository: UseCanRepository,
|
||||||
private val transactionTemplate: TransactionTemplate,
|
private val transactionTemplate: TransactionTemplate,
|
||||||
private val entityManager: EntityManager
|
private val entityManager: EntityManager
|
||||||
) {
|
) {
|
||||||
|
@SpyBean
|
||||||
|
private lateinit var repositorySpy: LiveReservationRepository
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUpRequestContext() {
|
fun setUpRequestContext() {
|
||||||
RequestContextHolder.setRequestAttributes(ServletRequestAttributes(MockHttpServletRequest()))
|
RequestContextHolder.setRequestAttributes(ServletRequestAttributes(MockHttpServletRequest()))
|
||||||
@@ -73,7 +90,49 @@ class LiveReservationServiceIntegrationTest @Autowired constructor(
|
|||||||
assertEquals(fixture.memberId, savedReservation.memberId)
|
assertEquals(fixture.memberId, savedReservation.memberId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFixture(): Fixture {
|
@Test
|
||||||
|
@DisplayName("유료 예약 저장이 실패하면 결제와 예약을 함께 롤백한다")
|
||||||
|
fun shouldRollbackPaymentAndReservationWhenPaidReservationSaveFails() {
|
||||||
|
val fixture = createFixture(price = 100)
|
||||||
|
val useCanCountBefore = useCanRepository.count()
|
||||||
|
|
||||||
|
Mockito.doAnswer {
|
||||||
|
assertEquals(useCanCountBefore + 1, useCanRepository.count())
|
||||||
|
throw DataIntegrityViolationException("reservation save failed")
|
||||||
|
}
|
||||||
|
.`when`(repositorySpy)
|
||||||
|
.save(Mockito.any(LiveReservation::class.java))
|
||||||
|
|
||||||
|
val exception = assertThrows(DataIntegrityViolationException::class.java) {
|
||||||
|
service.makeReservation(
|
||||||
|
request = MakeLiveReservationRequest(
|
||||||
|
roomId = fixture.roomId,
|
||||||
|
container = "web",
|
||||||
|
timezone = "Asia/Seoul"
|
||||||
|
),
|
||||||
|
memberId = fixture.memberId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rollbackState = transactionTemplate.execute {
|
||||||
|
val member = memberRepository.findById(fixture.memberId).orElseThrow()
|
||||||
|
val charge = chargeRepository.findById(fixture.chargeId!!).orElseThrow()
|
||||||
|
RollbackState(
|
||||||
|
memberCan = member.getChargeCan("web") + member.getRewardCan("web"),
|
||||||
|
chargeCan = charge.chargeCan + charge.rewardCan,
|
||||||
|
useCanCount = useCanRepository.count(),
|
||||||
|
reservationExists = repository.isExistsReservation(fixture.roomId, fixture.memberId)
|
||||||
|
)
|
||||||
|
}!!
|
||||||
|
|
||||||
|
assertEquals("reservation save failed", exception.message)
|
||||||
|
assertEquals(fixture.price, rollbackState.memberCan)
|
||||||
|
assertEquals(fixture.price, rollbackState.chargeCan)
|
||||||
|
assertEquals(useCanCountBefore, rollbackState.useCanCount)
|
||||||
|
assertEquals(false, rollbackState.reservationExists)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createFixture(price: Int = 0): Fixture {
|
||||||
return transactionTemplate.execute {
|
return transactionTemplate.execute {
|
||||||
val creator = Member(
|
val creator = Member(
|
||||||
email = "live-reservation-creator@test.com",
|
email = "live-reservation-creator@test.com",
|
||||||
@@ -87,14 +146,29 @@ class LiveReservationServiceIntegrationTest @Autowired constructor(
|
|||||||
password = "password",
|
password = "password",
|
||||||
nickname = "live-reservation-member"
|
nickname = "live-reservation-member"
|
||||||
)
|
)
|
||||||
|
member.pgChargeCan = price
|
||||||
entityManager.persist(member)
|
entityManager.persist(member)
|
||||||
|
|
||||||
|
val charge = if (price > 0) {
|
||||||
|
Charge(chargeCan = price, rewardCan = 0).also {
|
||||||
|
it.member = member
|
||||||
|
it.payment = Payment(
|
||||||
|
status = PaymentStatus.COMPLETE,
|
||||||
|
paymentGateway = PaymentGateway.PG
|
||||||
|
)
|
||||||
|
entityManager.persist(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val room = LiveRoom(
|
val room = LiveRoom(
|
||||||
title = "예약 라이브",
|
title = "예약 라이브",
|
||||||
notice = "예약 라이브 안내",
|
notice = "예약 라이브 안내",
|
||||||
beginDateTime = LocalDateTime.now().plusDays(1),
|
beginDateTime = LocalDateTime.now().plusDays(1),
|
||||||
numberOfPeople = 10,
|
numberOfPeople = 10,
|
||||||
isAdult = false
|
isAdult = false,
|
||||||
|
price = price
|
||||||
)
|
)
|
||||||
room.member = creator
|
room.member = creator
|
||||||
entityManager.persist(room)
|
entityManager.persist(room)
|
||||||
@@ -102,7 +176,9 @@ class LiveReservationServiceIntegrationTest @Autowired constructor(
|
|||||||
entityManager.flush()
|
entityManager.flush()
|
||||||
val fixture = Fixture(
|
val fixture = Fixture(
|
||||||
roomId = room.id!!,
|
roomId = room.id!!,
|
||||||
memberId = member.id!!
|
memberId = member.id!!,
|
||||||
|
chargeId = charge?.id,
|
||||||
|
price = price
|
||||||
)
|
)
|
||||||
entityManager.clear()
|
entityManager.clear()
|
||||||
fixture
|
fixture
|
||||||
@@ -111,11 +187,20 @@ class LiveReservationServiceIntegrationTest @Autowired constructor(
|
|||||||
|
|
||||||
private data class Fixture(
|
private data class Fixture(
|
||||||
val roomId: Long,
|
val roomId: Long,
|
||||||
val memberId: Long
|
val memberId: Long,
|
||||||
|
val chargeId: Long?,
|
||||||
|
val price: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class SavedReservation(
|
private data class SavedReservation(
|
||||||
val roomId: Long,
|
val roomId: Long,
|
||||||
val memberId: Long
|
val memberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class RollbackState(
|
||||||
|
val memberCan: Int,
|
||||||
|
val chargeCan: Int,
|
||||||
|
val useCanCount: Long,
|
||||||
|
val reservationExists: Boolean
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user