test #440

Merged
klaus merged 6 commits from test into main 2026-07-15 12:24:50 +00:00
Showing only changes of commit 28dcc4e907 - Show all commits

View File

@@ -0,0 +1,108 @@
# 라이브 예약 LazyInitializationException 수정 Plan/TASK
> **For agentic workers:** REQUIRED SUB-SKILL: `superpowers:executing-plans`를 사용해 각 task를 순서대로 실행한다. 각 단계는 체크박스 상태와 검증 기록을 즉시 갱신한다.
**Goal:** OSIV off 환경에서 라이브 예약 생성이 `LiveRoom.reservations` lazy 컬렉션 초기화 예외 없이 완료되고, 결제와 예약 저장이 하나의 트랜잭션에 참여하게 한다.
**Architecture:** 기존 API와 엔티티 매핑은 유지한다. `LiveReservationService.makeReservation()`을 서비스 계층의 쓰기 트랜잭션 경계로 만들고, 트랜잭션이 없는 통합 테스트에서 실제 Spring 프록시를 호출해 detached `LiveRoom`의 lazy 컬렉션 접근 오류를 재현하고 수정한다.
**Tech Stack:** Kotlin, Java 17, Spring Boot 2.7.14, Spring Data JPA, Hibernate, JUnit 5, H2, Gradle Wrapper
## Global Constraints
- 공개 API URL, 요청 및 응답 스키마를 변경하지 않는다.
- `spring.jpa.open-in-view=false`를 유지한다.
- `LiveRoom.reservations`의 fetch 전략과 `LiveReservation.room` setter를 변경하지 않는다.
- 예약 중복 방지, 결제 정책, 응답 포맷을 변경하지 않는다.
- 변경은 PRD, Plan/TASK, `LiveReservationService.makeReservation()`, 해당 통합 테스트로 제한한다.
---
## 파일 구조 계획
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationServiceIntegrationTest.kt`
- 실제 Spring 서비스 프록시와 JPA 엔티티로 OSIV off 예약 생성 경로를 검증한다.
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationService.kt`
- `makeReservation()`에 쓰기 `@Transactional`을 추가한다.
- Modify: `docs/20260715_라이브_예약_LazyInitializationException_수정/plan-task.md`
- RED/GREEN/회귀 검증 결과를 누적 기록한다.
---
### Phase 1: LazyInitializationException 재현
- [ ] **Task 1.1: 라이브 예약 서비스 통합 실패 테스트 작성**
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationServiceIntegrationTest.kt`
- RED: `@SpringBootTest``@Transactional(propagation = Propagation.NOT_SUPPORTED)`를 사용해 테스트 자체 트랜잭션이 서비스 경계를 가리지 않게 한다.
- RED: `EmbeddedRedisInitializer`를 명시적으로 적용하고 클래스 종료 후 Context를 정리한다.
- RED: `TransactionTemplate` 안에서 예약자, 크리에이터, 가격이 0인 예약 라이브방을 저장하고 `EntityManager.flush()`, `EntityManager.clear()`를 실행한다.
- RED: `MockHttpServletRequest``RequestContextHolder`에 등록해 request-scoped `LangContext`를 사용할 수 있게 한 뒤 실제 Spring 빈 `service.makeReservation(...)`을 호출한다.
- RED 코드의 핵심 검증은 다음과 같다.
```kotlin
val response = service.makeReservation(
request = MakeLiveReservationRequest(
roomId = fixture.roomId,
container = "web",
timezone = "Asia/Seoul"
),
memberId = fixture.memberId
)
val reservation = transactionTemplate.execute {
repository.findById(response.reservationId).orElseThrow()
}!!
assertEquals(fixture.roomId, reservation.room!!.id)
assertEquals(fixture.memberId, reservation.member!!.id)
```
- 실패 확인: `./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest`
- 기대 결과: production code 수정 전 `reservation.room = room`에서 `LiveRoom.reservations`를 초기화하려다 `LazyInitializationException`으로 실패한다.
- GREEN: 이 task에서는 production code를 변경하지 않는다.
- REFACTOR: fixture와 결과 검증용 타입은 테스트 파일 내부 private data class로 제한하고, request context는 `@AfterEach`에서 해제한다.
- 검증 기록: 구현 중 누적한다.
---
### Phase 2: 서비스 쓰기 트랜잭션 적용
- [ ] **Task 2.1: `makeReservation()` 트랜잭션 경계 추가**
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservationService.kt`
- Consumes: `LiveReservationService.makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse`
- Produces: 같은 메서드 시그니처와 응답을 유지하는 transactional 예약 생성 흐름
- GREEN: 기존 import인 `org.springframework.transaction.annotation.Transactional`을 사용해 다음 한 줄만 추가한다.
```kotlin
@Transactional
fun makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse {
```
- 통과 확인: `./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest`
- 기대 결과: `BUILD SUCCESSFUL`이며 저장된 예약의 방 ID와 회원 ID가 fixture와 일치한다.
- REFACTOR: 불필요한 fetch 전략, setter, 응답 로직 변경이 없는지 `git diff`로 확인한다.
- 검증 기록: 구현 중 누적한다.
---
### Phase 3: 회귀 및 문서 검증
- [ ] **Task 3.1: 관련 테스트와 저장소 규칙 검증**
- Verify: `./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.live.reservation.LiveReservationServiceIntegrationTest`
- Verify: `./gradlew --no-daemon ktlintCheck`
- Verify: `./gradlew --no-daemon tasks --all`
- Verify: `git diff --check`
- 기대 결과: 모든 Gradle 명령은 `BUILD SUCCESSFUL`, `git diff --check`는 출력 없이 exit code 0이다.
- RED/GREEN: Phase 1과 Phase 2의 실패 및 통과 결과를 다시 확인한다.
- REFACTOR: 이번 요청과 무관한 코드 및 문서 변경이 없는지 확인한다.
- 검증 기록: 구현 중 누적한다.
---
## 검증 기록
- 계획 작성 시점에는 production code와 테스트를 변경하지 않았다.
- 2026-07-15: 문서 변경 후 `./gradlew --no-daemon tasks --all` 명령 유효성을 확인했다.
- sandbox 실행은 Gradle wrapper lock 파일 접근 제한으로 실패했다.
- 승인 실행은 `BUILD SUCCESSFUL in 11s`로 통과했다.
- 2026-07-15: `git diff --check`가 출력 없이 통과해 문서 공백 오류가 없음을 확인했다.