diff --git a/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt new file mode 100644 index 00000000..111c14e1 --- /dev/null +++ b/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt @@ -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 + } + } + } +}