diff --git a/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md b/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md index a6859e77..0d54ecb3 100644 --- a/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md +++ b/docs/20260618_유저크리에이터채팅_WebSocket전환/plan-task.md @@ -80,6 +80,7 @@ - WebSocket 텍스트 메시지 저장/전달용 application method를 추가하고 SSE 의존성을 제거한다. - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/UserCreatorChatServiceTest.kt` - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandlerTest.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt` ### 푸시 payload - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt` @@ -340,7 +341,7 @@ spring: ### Phase 1: WebSocket 의존성과 인증 handshake 기반 추가 -- [ ] **Task 1.1: WebSocket 의존성 추가** +- [x] **Task 1.1: WebSocket 의존성 추가** - Files: - Modify: `build.gradle.kts` - RED: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketConfigTest.kt`를 추가해 `/ws/v2/user-creator-chat` handler bean 등록을 기대한다. @@ -352,8 +353,13 @@ spring: - Run: `./gradlew tasks --all` - Expected: `BUILD SUCCESSFUL` - REFACTOR: 의존성 추가 외 다른 dependency 정렬/버전 변경은 하지 않는다. + - 검증 기록: + - 무엇: `spring-boot-starter-websocket` 의존성을 추가했다. + - 왜: raw WebSocket endpoint와 handshake interceptor 타입을 사용하기 위해서다. + - 어떻게: WebSocket 타입 부재로 `compileTestKotlin` RED를 확인한 뒤 의존성을 추가하고 Phase 1 focused 테스트를 재실행했다. + - 결과: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketConfigTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketAuthInterceptorTest`가 `BUILD SUCCESSFUL in 1m 11s`로 통과했다. -- [ ] **Task 1.2: WebSocket config와 handler 등록** +- [x] **Task 1.2: WebSocket config와 handler 등록** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketConfig.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandler.kt` @@ -367,8 +373,13 @@ spring: - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketConfigTest` - Expected: `BUILD SUCCESSFUL` - REFACTOR: CORS origin은 기존 `WebConfig`의 허용 origin 정책과 어긋나지 않게 제한한다. + - 검증 기록: + - 무엇: `/ws/v2/user-creator-chat` endpoint를 `WebSocketConfigurer`로 등록하고 `TextWebSocketHandler` 기반 handler를 추가했다. + - 왜: 채팅방 화면 진입 중 유지되는 raw WebSocket 연결 기반을 만들기 위해서다. + - 어떻게: `UserCreatorChatWebSocketConfigTest`에서 endpoint path 등록을 검증했다. + - 결과: Phase 1 focused 테스트가 `BUILD SUCCESSFUL`로 통과했다. allowed origin은 기존 `WebConfig` origin 목록과 동일하게 제한했다. -- [ ] **Task 1.3: WebSocket handshake JWT 인증 추가** +- [x] **Task 1.3: WebSocket handshake JWT 인증 추가** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketAuthInterceptor.kt` - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketAuthInterceptorTest.kt` @@ -382,6 +393,11 @@ spring: - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketAuthInterceptorTest` - Expected: `BUILD SUCCESSFUL` - REFACTOR: JWT parsing 로직을 새로 만들지 않고 기존 `TokenProvider`를 재사용한다. + - 검증 기록: + - 무엇: `Authorization: Bearer ` handshake 인증 interceptor를 추가했다. + - 왜: WebSocket session attributes에 인증 member id와 authentication을 저장해 이후 room join/message 처리에서 사용할 수 있게 하기 위해서다. + - 어떻게: `TokenProvider.validateToken(token)`과 `TokenProvider.getAuthentication(token)`을 재사용하고, `MemberAdapter.member.id`를 `memberId` attribute로 저장했다. + - 결과: 유효 token 성공, Authorization header 누락 실패, invalid token 실패 테스트가 Phase 1 focused 테스트에서 통과했다. --- @@ -521,6 +537,23 @@ spring: - Expected: `BUILD SUCCESSFUL` - REFACTOR: close와 LEAVE_ROOM 정리 로직은 같은 private method를 사용한다. +- [ ] **Task 4.5: WebSocket client handshake 통합 테스트 추가** + - Files: + - Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt` + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt` (필요한 경우에만) + - Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketAuthInterceptor.kt` (필요한 경우에만) + - RED: `@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)`에서 실제 서버 포트를 띄우고 `StandardWebSocketClient`로 `/ws/v2/user-creator-chat`에 접속하는 테스트를 작성한다. + - RED: 유효한 `Authorization: Bearer ` header가 있으면 handshake가 성공하고, header가 없거나 유효하지 않으면 handshake가 실패하는 테스트를 작성한다. + - RED: 유효 token은 테스트 member를 저장한 뒤 기존 `TokenProvider.createToken(...)`으로 생성해, `JwtFilter`, `SecurityConfig`, `UserCreatorChatWebSocketAuthInterceptor`가 함께 동작하는 경계를 검증한다. + - 실패 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest` + - Expected: 통합 테스트 파일 부재 또는 실제 client handshake 경계 미구현으로 실패한다. + - GREEN: 테스트가 실패하면 최소 수정만 적용한다. 예를 들어 security filter가 `/ws/v2/user-creator-chat` handshake를 interceptor까지 통과시키지 못하는 경우에만 해당 경로 정책을 조정한다. + - 통과 확인: + - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest` + - Expected: `BUILD SUCCESSFUL` + - REFACTOR: 단위 테스트가 이미 검증하는 token parsing 세부 로직을 중복하지 않고, 실제 client handshake 성공/실패와 security/interceptor 연결 경계만 검증한다. + --- ### Phase 5: 기존 SSE 제거와 REST 경계 정리 @@ -590,6 +623,8 @@ spring: - Expected: `BUILD SUCCESSFUL` - Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.*` - Expected: `BUILD SUCCESSFUL` +- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest` + - Expected: `BUILD SUCCESSFUL` - Run: `./gradlew test --tests kr.co.vividnext.sodalive.fcm.FcmServiceTest` - Expected: `BUILD SUCCESSFUL` - Run: `./gradlew ktlintCheck`