feat: 예약 확정 EventListener 및 테스트

This commit is contained in:
이상진 2025-10-16 14:19:22 +09:00
parent cce59e522e
commit 66bf68826b
3 changed files with 98 additions and 0 deletions

View File

@ -0,0 +1,34 @@
package com.sangdol.roomescape.reservation.business.event
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.time.Instant
private val log: KLogger = KotlinLogging.logger {}
@Component
class ReservationEventListener(
private val reservationRepository: ReservationRepository
) {
@Async
@EventListener
@Transactional
fun handleReservationConfirmEvent(event: ReservationConfirmEvent) {
val reservationId = event.reservationId
log.info { "[handleReservationConfirmEvent] 예약 확정 이벤트 수신: reservationId=${reservationId}" }
val modifiedRows = reservationRepository.confirmReservation(Instant.now(), reservationId)
if (modifiedRows == 0) {
log.warn { "[handleReservationConfirmEvent] 예상치 못한 예약 확정 실패 - 변경된 row 없음: reservationId=${reservationId}" }
}
log.info { "[handleReservationConfirmEvent] 예약 확정 이벤트 처리 완료" }
}
}

View File

@ -48,4 +48,23 @@ interface ReservationRepository : JpaRepository<ReservationEntity, Long> {
""", nativeQuery = true
)
fun expirePendingReservations(@Param("now") now: Instant, @Param("reservationIds") reservationIds: List<Long>): Int
@Modifying
@Query(
"""
UPDATE
reservation r
JOIN
schedule s ON r.schedule_id = s.id AND s.status = 'HOLD'
SET
r.status = 'CONFIRMED',
r.updated_at = :now,
s.status = 'RESERVED',
s.hold_expired_at = NULL
WHERE
r.id = :id
AND r.status = 'PAYMENT_IN_PROGRESS'
""", nativeQuery = true
)
fun confirmReservation(@Param("now") now: Instant, @Param("id") id: Long): Int
}

View File

@ -0,0 +1,45 @@
package com.sangdol.roomescape.reservation.business.event
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
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.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import org.springframework.data.repository.findByIdOrNull
class ReservationEventListenerTest(
private val reservationEventListener: ReservationEventListener,
private val reservationRepository: ReservationRepository,
private val scheduleRepository: ScheduleRepository
) : FunSpecSpringbootTest() {
init {
test("예약 확정 이벤트를 처리한다.") {
val pendingReservation = dummyInitializer.createPendingReservation(testAuthUtil.defaultUser()).also {
it.status = ReservationStatus.PAYMENT_IN_PROGRESS
reservationRepository.saveAndFlush(it)
}
val reservationConfirmEvent = ReservationConfirmEvent(pendingReservation.id)
reservationEventListener.handleReservationConfirmEvent(reservationConfirmEvent).also {
Thread.sleep(100)
}
assertSoftly(reservationRepository.findByIdOrNull(pendingReservation.id)) {
this.shouldNotBeNull()
this.status shouldBe ReservationStatus.CONFIRMED
}
assertSoftly(scheduleRepository.findByIdOrNull(pendingReservation.scheduleId)) {
this.shouldNotBeNull()
this.status shouldBe ScheduleStatus.RESERVED
this.holdExpiredAt shouldBe null
}
}
}
}