test: 스케쥴링 작업으로 인해 발생 가능한 문제 시나리오 테스트

This commit is contained in:
이상진 2025-10-03 15:25:35 +09:00
parent dbc6847877
commit 11000f3f3d

View File

@ -0,0 +1,83 @@
package com.sangdol.roomescape.reservation
import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.reservation.business.ReservationService
import com.sangdol.roomescape.reservation.business.scheduler.IncompletedReservationScheduler
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.web.PendingReservationCreateResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import org.springframework.data.repository.findByIdOrNull
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import java.time.LocalDateTime
class ReservationConcurrencyTest(
private val transactionManager: PlatformTransactionManager,
private val incompletedReservationScheduler: IncompletedReservationScheduler,
private val reservationService: ReservationService,
private val reservationRepository: ReservationRepository,
private val scheduleRepository: ScheduleRepository
) : FunSpecSpringbootTest() {
init {
test("Pending 예약 생성시, Schedule 상태 검증 이후부터 커밋 이전 사이에 시작된 schedule 처리 배치 작업은 반영되지 않는다.") {
val user = testAuthUtil.defaultUserLogin().first
val schedule = dummyInitializer.createSchedule().also {
it.status = ScheduleStatus.HOLD
it.holdExpiredAt = LocalDateTime.now().minusMinutes(1)
scheduleRepository.save(it)
}
lateinit var response: PendingReservationCreateResponse
withContext(Dispatchers.IO) {
val createPendingReservationJob = async {
response = TransactionTemplate(transactionManager).execute {
val response = reservationService.createPendingReservation(
user = CurrentUserContext(id = user.id, name = user.name),
request = PendingReservationCreateRequest(
scheduleId = schedule.id,
reserverName = user.name,
reserverContact = user.phone,
participantCount = 3,
requirement = "없어요!"
)
)
Thread.sleep(200)
response
}!!
}
val updateScheduleJob = async {
TransactionTemplate(transactionManager).execute {
incompletedReservationScheduler.processExpiredHoldSchedule()
}
}
listOf(createPendingReservationJob, updateScheduleJob).awaitAll()
}
assertSoftly(scheduleRepository.findByIdOrNull(schedule.id)!!) {
this.status shouldBe ScheduleStatus.HOLD
this.holdExpiredAt.shouldNotBeNull()
}
assertSoftly(reservationRepository.findByIdOrNull(response.id)) {
this.shouldNotBeNull()
this.status shouldBe ReservationStatus.PENDING
}
}
}
}