[#52] 만료 예약 / 일정 스케쥴링 작업 추가 및 동시성 처리를 위한 일부 코드 수정 #53

Merged
pricelees merged 18 commits from refactor/#52 into main 2025-10-04 08:40:37 +00:00
Showing only changes of commit 11000f3f3d - Show all commits

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
}
}
}
}