docs(chat): DM Phase 6 검증을 기록한다

This commit is contained in:
2026-06-11 12:06:03 +09:00
parent ff728af7cc
commit f47a9d6d93

View File

@@ -328,7 +328,7 @@
### Phase 6: DI, Manifest, 문서 갱신
- [ ] **Task 6.1: Koin DI 등록**
- [x] **Task 6.1: Koin DI 등록**
- Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- 작업:
@@ -338,7 +338,7 @@
- 검증:
- import 추가 외 기존 DI 등록 순서를 불필요하게 재정렬하지 않는다.
- [ ] **Task 6.2: Manifest 등록**
- [x] **Task 6.2: Manifest 등록**
- Files:
- Modify: `app/src/main/AndroidManifest.xml`
- 작업:
@@ -347,7 +347,7 @@
- 검증:
- activity는 exported를 명시하지 않는 기존 내부 Activity 패턴을 따른다.
- [ ] **Task 6.3: 테스트 실행 가이드 갱신**
- [x] **Task 6.3: 테스트 실행 가이드 갱신**
- Files:
- Modify: `docs/agent-guides/build-test-style.md`
- 작업:
@@ -356,7 +356,7 @@
- 검증:
- 문서 변경은 신규 테스트 명령 예시 추가로만 제한한다.
- [ ] **Task 6.4: SSE 전용 read timeout 제거**
- [x] **Task 6.4: SSE 전용 read timeout 제거**
- Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatEventClient.kt`
@@ -369,7 +369,7 @@
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest"`
- Expected: SSE 전용 client의 read timeout이 0으로 설정되고, 기존 SSE parsing/cancel/failure 동작은 유지된다.
- [ ] **Task 6.5: realtime callback scheduling Disposable 누적 방지**
- [x] **Task 6.5: realtime callback scheduling Disposable 누적 방지**
- Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt`
- Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt`
@@ -381,9 +381,33 @@
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"`
- Expected: SSE callback은 main thread에서 처리되고, 메시지 수신 횟수만큼 완료된 `Disposable`이 누적되지 않는다.
- [x] **Task 6.6: ViewModel onCleared 정리 보장**
- Files:
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt`
- Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt`
- 작업:
- `DmChatRoomViewModel``onCleared()` 오버라이드를 추가한다.
- ViewModel 소멸 후 `mainHandler.post { action() }`로 예약된 realtime callback이 실행되지 않도록 `mainHandler.removeCallbacksAndMessages(null)`를 호출한다.
- `compositeDisposable.clear()` 또는 `dispose()`, `reconnectDisposable.dispose()`, `realtimeClient.cancel()` 정리가 `onCleared()`에서도 보장되는지 확인하고 누락된 정리를 추가한다.
- Activity `onStop`/`onDestroy` 경로에만 의존하지 않고 ViewModel lifecycle 종료만으로 realtime 연결과 예약 작업이 정리되도록 한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"`
- Expected: `onCleared()` 이후 예약된 main callback이 상태를 갱신하지 않고, composite/reconnect disposable 및 realtime client cancel 정리가 호출된다.
- [x] **Task 6.7: DmChatEventClientTest reflection 의존 제거 검토**
- Files:
- Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatEventClientTest.kt`
- 작업:
- `getDeclaredField("okHttpClient")`로 내부 필드를 읽는 검증을 동작 기반 검증으로 대체할 수 있는지 확인한다.
- 최소 변경으로 가능하면 `MockWebServer` 또는 실제 request/stream 동작을 통해 SSE 전용 read timeout 정책과 기존 parsing/cancel/failure 동작을 검증한다.
- reflection 제거가 과도한 테스트 구조 변경을 요구하면 현재 검증은 유지하되, 필드명 변경에 취약한 낮은 우선순위 테스트 부채로 문서에 남긴다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest"`
- Expected: 내부 필드명에 직접 의존하지 않거나, 유지 사유가 명확히 기록되고 기존 SSE 전용 client 동작 검증은 유지된다.
### Phase 7: 최종 검증과 기록
- [ ] **Task 7.1: 단위 테스트 실행**
- [x] **Task 7.1: 단위 테스트 실행**
- Files:
- Check: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/*Test.kt`
- Run:
@@ -391,7 +415,7 @@
- Expected:
- 신규 DM 채팅 단위 테스트가 모두 PASS.
- [ ] **Task 7.2: 앱 빌드 확인**
- [x] **Task 7.2: 앱 빌드 확인**
- Files:
- Check: Gradle project
- Run:
@@ -399,7 +423,7 @@
- Expected:
- Debug APK 빌드 PASS.
- [ ] **Task 7.3: 린트/스타일 확인**
- [x] **Task 7.3: 린트/스타일 확인**
- Files:
- Check: Kotlin/XML 변경 파일
- Run:
@@ -457,3 +481,13 @@
- 2026-06-11: Phase 5 코드리뷰 권장 변경사항 A-D를 각각 후속 Task로 추가했다. `Task 5.5`는 자동 재연결 실행 스레드 race 제거, `Task 5.6`은 disconnect와 예약 재연결 경합 방지, `Task 5.7`은 SSE 재연결 backoff 또는 시도 제한 검토, `Task 5.8``roomOpenedEventLiveData` 스티키 재전달 방지를 다룬다. 이번 변경은 문서 갱신만 수행했으며 Android 빌드/테스트는 실행하지 않았다.
- 2026-06-11: Phase 5.5~5.8 범위로 예약 재연결 실행 시 `connectRealtime()`를 scheduler thread에서 직접 호출하지 않고 main callback 경로로 전달하도록 수정했다. disconnect 이후 예약 재연결이 실행되어도 `shouldReconnectRealtime` 재확인으로 새 SSE 연결이 남지 않도록 했고, `roomOpenedEventLiveData``DmChatEvent<Boolean>` 소비형 이벤트로 바꿔 observer 재등록만으로 realtime connect가 반복 트리거되지 않도록 했다. SSE 재연결 정책은 PRD의 서버 `reconnectTime=3000`ms 및 foreground 한정 조건을 우선해 backoff/최대 횟수 제한을 추가하지 않고 3초 반복 재시도 유지로 테스트 고정했다.
- 2026-06-11: Phase 5.5~5.8 검증으로 RED 단계에서 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"``consume` API 부재로 실패함을 확인했고, 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`, `./gradlew :app:assembleDebug` PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig``disabled_rules` deprecation warning이 출력됐지만 실패는 없었다.
- 2026-06-11: Phase 6.1~6.5 범위로 `AppDI.kt``DmChatApi`, `DmChatRealtimeClient`/`DmChatEventClient`, `DmChatRepository`, `DmChatRoomViewModel` DI 등록을 추가하고, `AndroidManifest.xml``.v2.main.chat.dm.DmChatRoomActivity``stateAlwaysHidden|adjustResize`로 등록했다. `docs/agent-guides/build-test-style.md`에는 DM 채팅 단위 테스트 wildcard 실행 예시를 추가했다.
- 2026-06-11: Phase 6.4 RED 단계에서 `DmChatEventClientTest`에 공유 `OkHttpClient`의 60초 read timeout이 SSE 전용 client에서 0으로 제거되어야 한다는 테스트를 추가했고, 기존 구현에서 `assertEquals(0, sseClient.readTimeoutMillis)` 실패를 확인했다. 이후 `DmChatEventClient`가 전달받은 client에서 `readTimeout(0, TimeUnit.MILLISECONDS)`를 적용한 전용 client를 생성하도록 수정했고, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest"` PASS를 확인했다.
- 2026-06-11: Phase 6.5 RED 단계에서 `DmChatRoomViewModelTest`에 반복 realtime message callback 후 `compositeDisposable.size()`가 증가하지 않아야 한다는 테스트를 추가했고, 기존 `AndroidSchedulers.mainThread().scheduleDirect` disposable 누적으로 실패를 확인했다. 이후 realtime callback 전달을 main thread에서는 즉시 실행, background thread에서는 `Handler(Looper.getMainLooper()).post { ... }`로 전달하도록 수정해 완료된 callback `Disposable` 누적을 제거했고, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"` PASS를 확인했다.
- 2026-06-11: Phase 7.1~7.3 자동 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:assembleDebug`, `./gradlew :app:ktlintCheck`를 순차 실행해 모두 PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig``disabled_rules` deprecation warning이 출력됐지만 실패는 없었다. Phase 7.4 수동 화면 확인은 현재 연결된 기기/에뮬레이터 실기 실행 증거가 없어 완료 처리하지 않았다.
- 2026-06-11: Phase 6, 7 코드리뷰 개선사항을 후속 Task로 추가했다. `Task 6.6``DmChatRoomViewModel.onCleared()`에서 main handler callback, disposable, realtime client 정리를 보장하는 작업이고, `Task 6.7``DmChatEventClientTest``getDeclaredField("okHttpClient")` reflection 의존을 동작 기반 검증으로 대체하거나 유지 사유를 명확히 남기는 작업이다. 이번 변경은 문서 갱신만 수행했으며 Android 빌드/테스트는 실행하지 않았다.
- 2026-06-11: Phase 6.6 RED 단계에서 `DmChatRoomViewModelTest``onCleared()` 정리 테스트를 추가했고, 기존 구현에서 `onCleared()` 오버라이드 및 `mainHandler.removeCallbacksAndMessages(null)` 부재로 실패를 확인했다. 이후 `DmChatRoomViewModel.onCleared()`에서 예약된 main handler callback 제거, `reconnectDisposable` dispose, realtime client cancel을 수행하고 `super.onCleared()`로 기존 `compositeDisposable.dispose()` 정리를 유지하도록 수정했다.
- 2026-06-11: Phase 6.7에서 `DmChatEventClientTest``getDeclaredField("okHttpClient")` reflection 검증을 제거하고, OkHttp interceptor의 `chain.readTimeoutMillis()`를 실제 SSE 요청 경로에서 관찰하는 동작 기반 검증으로 대체했다. 비동기 요청 실행을 기다리기 위해 `CountDownLatch`를 사용했고, 공유 client의 60초 read timeout은 유지되며 SSE 요청 경로에서는 0ms read timeout이 적용됨을 확인했다.
- 2026-06-11: Phase 6.6~6.7 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`, `./gradlew :app:compileDebugKotlin --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1`, `git diff --check` PASS를 확인했다. 최초 병렬 Gradle 실행에서는 Kotlin incremental cache 동시 접근으로 timeout/daemon fallback이 발생해 순차 실행으로 재검증했다. `ktlintCheck`에서는 기존 `.editorconfig``disabled_rules` deprecation warning이 출력됐지만 실패는 없었다.
- 2026-06-11: Phase 6.6 리뷰 후속으로 `onCleared()` 테스트를 source 문자열 검증에서 런타임 동작 검증으로 보강했다. 예약된 realtime 재연결이 `onCleared()` 후 실행되지 않는지, background thread에서 예약된 `mainHandler.post { action() }` callback이 `onCleared()` 후 상태를 갱신하지 않는지 확인하도록 수정했다. Phase 6.7 테스트에는 `requestLatch.await(2, TimeUnit.SECONDS)` 반환값 assertion을 추가해 timeout 검증 의도를 명확히 했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest" --max-workers=1`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*" --max-workers=1`, `./gradlew :app:ktlintCheck --max-workers=1` PASS를 확인했다.