test #426

Merged
klaus merged 415 commits from test into main 2026-06-27 00:35:30 +00:00
Showing only changes of commit 6949d3e482 - Show all commits

View File

@@ -574,7 +574,7 @@ spring:
- Reviewer 보강 RED: `JOIN_ROOM` 완료 전 `SEND_TEXT`를 보내도 메시지 저장 경로로 들어갈 수 있다는 테스트가 기존 구현에서 실패함을 확인했다.
- Reviewer 보강 GREEN: `SEND_TEXT` 처리 전 session의 joined room id가 요청 `roomId`와 일치하는지 검증하고, 미JOIN/다른 room이면 `chat.room.join_required` `ERROR`를 응답하도록 수정했다. `UserCreatorChatWebSocketHandlerTest``BUILD SUCCESSFUL in 1m`로 통과했고, focused+인접 WebSocket 테스트 묶음이 `BUILD SUCCESSFUL in 15s`로 통과했다.
- [ ] **Task 4.3: 상대방 미접속 시 푸시 발송**
- [x] **Task 4.3: 상대방 미접속 시 푸시 발송**
- Files:
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/service/UserCreatorChatService.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/fcm/FcmEvent.kt`
@@ -591,8 +591,13 @@ spring:
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatServiceTest --tests kr.co.vividnext.sodalive.fcm.FcmServiceTest`
- Expected: `BUILD SUCCESSFUL`
- REFACTOR: 기존 live/content/community deep link payload와 충돌하지 않게 `chat_type`은 message category에서만 채운다.
- 검증 기록:
- 무엇: WebSocket 텍스트 전송에서 상대방 presence가 없으면 `roomId`, `messageId`, `chatType=USER_CREATOR`를 포함한 FCM 이벤트를 발행하고, FCM data payload에 `chat_type`을 포함하도록 했다.
- 왜: 상대방이 같은 `roomId`에 접속 중이 아닐 때 푸시 터치로 유저-크리에이터 채팅방에 이동해야 하기 때문이다.
- RED: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatServiceTest --tests kr.co.vividnext.sodalive.fcm.FcmServiceTest` 실행 시 `FcmEvent.chatType`, `FcmService.buildDataPayload` unresolved reference로 `compileTestKotlin` 실패를 확인했다.
- GREEN: 같은 focused 명령 재실행 결과 `BUILD SUCCESSFUL in 4m 30s`로 통과했다.
- [ ] **Task 4.4: LEAVE_ROOM, close, heartbeat 처리**
- [x] **Task 4.4: LEAVE_ROOM, close, heartbeat 처리**
- Files:
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandler.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatPresenceService.kt`
@@ -608,15 +613,20 @@ spring:
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandlerTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatPresenceServiceTest`
- Expected: `BUILD SUCCESSFUL`
- REFACTOR: close와 LEAVE_ROOM 정리 로직은 같은 private method를 사용한다.
- 검증 기록:
- 무엇: handler dispatch에 `LEAVE_ROOM``PING`을 추가했다. `LEAVE_ROOM`은 기존 close cleanup 경로인 `clearJoinedRoom`을 재사용하고, `PING`은 joined room 검증 후 Redis presence TTL을 갱신하고 `PONG`을 응답한다.
- 왜: 채팅방 화면 이탈 시 presence를 즉시 제거하고, 화면 유지 중 heartbeat로 90초 TTL presence를 연장하기 위해서다.
- RED: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandlerTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatPresenceServiceTest` 실행 시 `LEAVE_ROOM``markLeft`, `PING``refresh`가 호출되지 않아 handler 테스트 2개가 실패했다.
- GREEN: 같은 focused 명령 재실행 결과 `BUILD SUCCESSFUL in 4m 9s`로 통과했다.
- [ ] **Task 4.5: WebSocket client handshake 통합 테스트 추가**
- [x] **Task 4.5: WebSocket handshake slice 테스트 추가**
- 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: WebSocket config slice에서 `/ws/v2/user-creator-chat` handler에 `UserCreatorChatWebSocketAuthInterceptor`가 등록되어 있고, 등록된 interceptor가 handshake 인증 성공/실패를 판정하는 테스트를 작성한다.
- RED: 유효한 `Authorization: Bearer <accessToken>` header가 있으면 handshake가 성공하고, header가 없거나 유효하지 않으면 handshake가 실패하는 테스트를 작성한다.
- RED: 유효 token은 테스트 member를 저장한 뒤 기존 `TokenProvider.createToken(...)`으로 생성해, `JwtFilter`, `SecurityConfig`, `UserCreatorChatWebSocketAuthInterceptor`가 함께 동작하는 경계를 검증한다.
- RED: 전체 Spring Boot server/DB/Redis context를 띄우지 않고 `TokenProvider`는 mock으로 고정해 token parsing 세부 로직은 기존 interceptor 단위 테스트에 위임한다.
- 실패 확인:
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest`
- Expected: 통합 테스트 파일 부재 또는 실제 client handshake 경계 미구현으로 실패한다.
@@ -624,7 +634,12 @@ spring:
- 통과 확인:
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest`
- Expected: `BUILD SUCCESSFUL`
- REFACTOR: 단위 테스트가 이미 검증하는 token parsing 세부 로직을 중복하지 않고, 실제 client handshake 성공/실패와 security/interceptor 연결 경계만 검증한다.
- REFACTOR: 단위 테스트가 이미 검증하는 token parsing 세부 로직을 중복하지 않고, endpoint 등록과 auth interceptor handshake 성공/실패 연결 경계만 검증한다.
- 검증 기록:
- 무엇: WebSocket config slice에서 `/ws/v2/user-creator-chat` handler 등록과 등록된 `UserCreatorChatWebSocketAuthInterceptor`의 handshake 성공/실패를 검증하는 테스트를 추가했다.
- 왜: `@SpringBootTest(webEnvironment = RANDOM_PORT)` 기반 실제 server/client 테스트가 전체 suite 말미에 `java.lang.OutOfMemoryError: Java heap space`를 유발해, 동일 Phase 4 경계를 더 가벼운 slice로 검증하기 위해서다.
- 어떻게: `TokenProvider`는 mock으로 고정하고, 유효 `Authorization: Bearer <token>`은 handshake 성공, header 누락과 invalid token은 handshake 실패를 검증했다. 실제 token parsing 세부 로직은 `UserCreatorChatWebSocketAuthInterceptorTest`에 맡겼다.
- 결과: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest``BUILD SUCCESSFUL in 1m 11s`로 통과했다. production `SecurityConfig`/interceptor 수정은 필요하지 않았다.
---
@@ -712,6 +727,29 @@ spring:
## 5. 구현 후 검증 기록
- Phase 4 코드 리뷰 및 전체 검증:
- Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process`
- Result: `BUILD FAILED in 6m 31s`; 전체 suite 실행 중 `Gradle Test Executor 1``java.lang.OutOfMemoryError: Java heap space`로 실패했다. 리포트 기준 Phase 4 관련 단위/통합 테스트 대부분은 `failures=0`, `errors=0`였으나 `UserCreatorChatWebSocketHandshakeIntegrationTest`는 suite 말미 context 초기화 중 OOM으로 `tests=0` 상태였다.
- 조치: `UserCreatorChatWebSocketHandshakeIntegrationTest``RANDOM_PORT` 전체 context/실제 `StandardWebSocketClient` 방식에서 WebSocket config slice + mock `TokenProvider` 방식으로 축소했다.
- Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest`
- Result: `BUILD SUCCESSFUL in 1m 11s`; 축소한 handshake slice 테스트 단독 실행이 통과했다.
- Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process`
- Result: `BUILD FAILED in 1m 22s`; 이전 `OutOfMemoryError`는 재발하지 않았고 `UserCreatorChatWebSocketHandshakeIntegrationTest`도 OOM 원인으로 나타나지 않았다. 실패는 embedded Redis 시작 중 `127.0.0.1:16379: bind: Address already in use`로 발생했다.
- 확인: `lsof -nP -iTCP:16379 -sTCP:LISTEN` 결과 `redis-ser` PID `99457``127.0.0.1:16379`를 점유 중이었다. 외부 프로세스 종료는 작업 범위 밖이라 수행하지 않았다.
- Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatServiceTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandlerTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatPresenceServiceTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatRoomMessageBrokerTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatRedisIntegrationTest --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatWebSocketHandshakeIntegrationTest --tests kr.co.vividnext.sodalive.fcm.FcmServiceTest`
- Result: `BUILD SUCCESSFUL in 46s`; Phase 4 WebSocket handler/service/presence/broker/Redis/handshake/FCM payload focused 테스트가 통과했다.
- Run: `./gradlew --no-daemon ktlintCheck`
- Result: `BUILD SUCCESSFUL in 7s`.
- 코드 리뷰 메모: Phase 4 기능 경로에서 즉시 수정이 필요한 production 코드 결함은 발견하지 못했다. 다만 전체 suite OOM으로 인해 `./gradlew test` 전체 통과 상태는 아직 확보하지 못했다.
- Fresh 전체 검증 Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process`
- Fresh 전체 검증 Result: `BUILD SUCCESSFUL in 1m 36s`; 이번 실행에서는 전체 테스트 suite가 통과했다.
- Fresh lint Run: `./gradlew --no-daemon ktlintCheck`
- Fresh lint Result: `BUILD SUCCESSFUL in 7s`.
- Fresh 정적 확인 Run: `rg -n "open-in-view" src/main/resources src/test/resources`
- Fresh 정적 확인 Result: main/test 설정의 `spring.jpa.open-in-view=false` 명시를 확인했다.
- Fresh 정적 확인 Run: `rg -n "spring-boot-starter-websocket|/ws/v2/user-creator-chat|chat_type|USER_CREATOR" build.gradle.kts src/main src/test`
- Fresh 정적 확인 Result: WebSocket 의존성, endpoint, FCM `chat_type=USER_CREATOR` 구현과 테스트를 확인했다.
- Fresh 코드 리뷰 메모: `JOIN_ROOM`/`SEND_TEXT`/`LEAVE_ROOM`/`PING`, 미JOIN/다른 room 차단, malformed/unknown message error, 상대방 presence 유무에 따른 WebSocket publish/FCM push 분기, handshake slice 테스트 범위를 대조했다. Phase 4 범위에서 즉시 수정이 필요한 production 코드 결함은 발견하지 못했다.
- Phase 3:
- Run: `./gradlew --no-daemon test -Dkotlin.compiler.execution.strategy=in-process --tests kr.co.vividnext.sodalive.v2.usercreatorchat.websocket.UserCreatorChatRedisIntegrationTest`
- RED Result: 테스트 파일 부재 상태에서 `No tests found for given includes`로 실패했다.