Skip to content

Conversation

@whqtker
Copy link
Member

@whqtker whqtker commented Aug 11, 2025

관련 이슈

작업 내용

WebSocket + STOMP 관련 테스트 코드와 메시지 송신 테스트를 작성하였습니다.
#423 이 머지되면 rebase 하고 머지하겠습니다 !

특이 사항

ArgumentCaptor 라는 걸 처음 써 봤는데요, 어색한 부분이 있다면 말씀해주세요 ~
WebSocket + STOMP 통합 테스트는 chat 도메인 하위가 아니라 따로 작성하였습니다.

리뷰 요구사항 (선택)

추가할 테스트가 있는지

@whqtker whqtker self-assigned this Aug 11, 2025
@whqtker whqtker requested a review from Gyuhyeok99 as a code owner August 11, 2025 06:23
@whqtker whqtker added the 테스트 Added tests label Aug 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

  1. ChatServiceTest 변경
    • SimpMessagingTemplate를 MockBean으로 추가하고 ArgumentCaptor로 메시지 전송 호출을 검증했습니다.
    • ChatMessageSendRequest/ChatMessageSendResponse DTO를 사용하여 전송 요청과 응답 페이로드를 구성했습니다.
    • 참여자 전송 성공 케이스와 비참여자 전송 시 CustomException(CHAT_PARTICIPANT_NOT_FOUND) 발생 케이스를 추가했습니다.
  2. WebSocketStompIntegrationTest 추가
    • Spring Boot 환경에서 실제 STOMP 핸드셰이크를 검증하는 통합 테스트를 신규 작성했습니다.
    • SockJS+StandardWebSocketClient와 Jackson 메시지 컨버터를 설정하고 ws://localhost:{port}/connect로 연결합니다.
    • AuthTokenProvider와 SiteUserFixture로 AccessToken을 생성하여 인증 성공/미인증 실패 경로를 각각 검증합니다.
    • 세션 핸들러와 transportErrorQueue로 에러 캡처, 세션 연결/해제 루틴을 포함했습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • nayonsoso
  • wibaek
  • lsy1307
  • Gyuhyeok99
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java (2)

71-73: MockBean 주입 선택 잘 하셨습니다.

  1. SimpMessagingTemplate를 MockBean으로 주입해 서비스-메시징 경계 테스트가 깔끔해졌습니다.
    • 실패 케이스에서 상호작용이 전혀 없음을 추가로 검증해 주면 안전망이 더 단단해집니다.

376-423: 전송 테스트 전반 품질 좋습니다. 추가로 있으면 좋은 테스트 아이디어를 공유드립니다.

  1. 입력 검증: 빈 문자열/과도한 길이/금지 단어 전송 시 거절.
  2. 첨부 포함 전송: 이미지/파일 첨부 메시지 전송 흐름과 브로드캐스트 페이로드 구조 검증.
  3. 경계 케이스: 존재하지 않는 채팅방 ID, 비활성화된 채팅방, 차단/숨김 상태.
  4. 사이드이펙트: 메시지 영속화 여부, 마지막 메시지/읽지 않은 수 갱신.
  5. 발신자/수신자 라우팅: 필요 시 convertAndSendToUser 등의 개인 큐 사용 여부 검증.

원하시면 위 시나리오들에 대한 테스트 스켈레톤을 생성해 드리겠습니다.

src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java (2)

70-71: transportErrorQueue는 무제한(또는 여유 있는) 큐로 바꾸는 게 안전합니다.

  1. ArrayBlockingQueue(1)는 1건 초과 에러 발생 시 add에서 블로킹/실패할 수 있습니다.
  2. LinkedBlockingQueue로 바꾸면 예기치 못한 추가 에러에도 안전합니다.
-        private final BlockingQueue<Throwable> transportErrorQueue = new ArrayBlockingQueue<>(1);
+        private final BlockingQueue<Throwable> transportErrorQueue = new java.util.concurrent.LinkedBlockingQueue<>();

67-113: 통합 검증을 한 단계 확장할 수 있는 제안입니다.

  1. CONNECT 이후 SUBSCRIBE/UNSUBSCRIBE까지: 임의 토픽 구독 후 서버에서 테스트용 프레임을 발행해 수신까지 end-to-end 검증.
  2. 토큰 만료/변조: 만료 토큰, 잘못 서명된 토큰으로 401/403 경계값 검증.
  3. Origin/CORS 제약: 서버가 오리진 제한을 둔다면 허용/비허용 오리진으로 행위 검증.
  4. Heartbeat 설정 유효성: 서버/클라이언트 간 heartbeat 협상(미설정 시에도 정상 동작 확인).

필요하시다면 STOMP subscribe/send/receipt를 포함한 시나리오 테스트 템플릿을 제공하겠습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e230e4 and d05cc9f.

📒 Files selected for processing (2)
  • src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java (4 hunks)
  • src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java (1 hunks)
🔇 Additional comments (2)
src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java (1)

407-408: senderId의 도메인 의미를 확인해 주세요 (ChatParticipant ID vs SiteUser ID).

  1. 현재 테스트는 senderId가 ChatParticipant의 ID라고 가정하고 있습니다.
  2. API 계약이 “사용자 ID”를 의미한다면, 테스트 기대값은 sender.getId()가 되어야 합니다.

가능한 경우 아래와 같이 조정해 주세요.

-                    () -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
+                    () -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(sender.getId())
src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java (1)

52-58: 전반적인 STOMP 클라이언트 설정이 명확하고 적절합니다.

  1. SockJS(WebSocket 전용) + Jackson 컨버터 설정이 목적에 부합합니다.
  2. 테스트 가독성이 좋고, 5초 타임아웃도 현실적입니다.

Comment on lines +399 to +409
// then
ArgumentCaptor<String> destinationCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);

BDDMockito.verify(simpMessagingTemplate).convertAndSend(destinationCaptor.capture(), payloadCaptor.capture());

assertAll(
() -> assertThat(destinationCaptor.getValue()).isEqualTo("/topic/chat/" + chatRoom.getId()),
() -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
() -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

검증을 인자 기반(BDD then + eq)으로 바꿔, 호출 횟수/추가 호출에 둔감하게 만듭니다.

  1. 현재 captor로 destination을 직접 캡처하면, convertAndSend가 추가로 더 호출되는 구조로 변경될 때 테스트가 불필요하게 깨질 수 있습니다.
  2. 목적지(destination)는 eq로 검증하고, payload만 captor로 캡처해 필드 단언으로 이어지게 하면 테스트 탄성이 좋아집니다.

아래처럼 수정 제안드립니다.

-            ArgumentCaptor<String> destinationCaptor = ArgumentCaptor.forClass(String.class);
-            ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);
-
-            BDDMockito.verify(simpMessagingTemplate).convertAndSend(destinationCaptor.capture(), payloadCaptor.capture());
-
-            assertAll(
-                    () -> assertThat(destinationCaptor.getValue()).isEqualTo("/topic/chat/" + chatRoom.getId()),
-                    () -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
-                    () -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
-            );
+            ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);
+            BDDMockito.then(simpMessagingTemplate)
+                    .should()
+                    .convertAndSend(
+                            org.mockito.ArgumentMatchers.eq("/topic/chat/" + chatRoom.getId()),
+                            payloadCaptor.capture()
+                    );
+            assertAll(
+                    () -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
+                    () -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
+            );

추가로, eq·any를 자주 쓰신다면 ArgumentMatchers에 대한 static import를 고려해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// then
ArgumentCaptor<String> destinationCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);
BDDMockito.verify(simpMessagingTemplate).convertAndSend(destinationCaptor.capture(), payloadCaptor.capture());
assertAll(
() -> assertThat(destinationCaptor.getValue()).isEqualTo("/topic/chat/" + chatRoom.getId()),
() -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
() -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
);
// then
ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);
BDDMockito.then(simpMessagingTemplate)
.should()
.convertAndSend(
org.mockito.ArgumentMatchers.eq("/topic/chat/" + chatRoom.getId()),
payloadCaptor.capture()
);
assertAll(
() -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
() -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
);
🤖 Prompt for AI Agents
In src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java
around lines 399 to 409, change the verification so the destination is verified
with an eq matcher (eg. eq("/topic/chat/" + chatRoom.getId())) instead of
capturing it, and only capture the payload with
ArgumentCaptor<ChatMessageSendResponse>; update the verify/then call to use the
matcher for the first argument and payloadCaptor.capture() for the second so
extra convertAndSend calls won't break the test, and consider adding a static
import for ArgumentMatchers.eq (and any) to keep the test concise.

Comment on lines +418 to +422
// when & then
assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId()))
.isInstanceOf(CustomException.class)
.hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

실패 케이스에서 메시지 브로커 호출이 전혀 없음을 보장하세요.

  1. 예외 단언 뒤에 no-interaction 검증을 추가하면, 회귀로 인한 오발신을 조기에 잡을 수 있습니다.
             assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId()))
                     .isInstanceOf(CustomException.class)
                     .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
+            org.mockito.Mockito.verifyNoInteractions(simpMessagingTemplate);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// when & then
assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId()))
.isInstanceOf(CustomException.class)
.hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
}
// when & then
assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId()))
.isInstanceOf(CustomException.class)
.hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
org.mockito.Mockito.verifyNoInteractions(simpMessagingTemplate);
}
🤖 Prompt for AI Agents
In src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java
around lines 418 to 422, the failure case asserts the CustomException but does
not verify that the message broker was not called; add a no-interaction
verification for the mocked message broker (e.g., verifyNoInteractions or
verify(messageBroker, never()) semantics) immediately after the exception
assertion to ensure no messages were sent on failure and prevent
regression-induced stray calls.

Comment on lines +60 to +65
@AfterEach
void tearDown() {
if (this.stompSession != null && this.stompSession.isConnected()) {
this.stompSession.disconnect();
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

리소스 정리: 테스트 종료 시 stompClient.stop() 호출을 권장합니다.

  1. 내부 스케줄러/스레드가 남아 다른 테스트에 간섭하는 일을 예방합니다.
     void tearDown() {
         if (this.stompSession != null && this.stompSession.isConnected()) {
             this.stompSession.disconnect();
         }
+        if (this.stompClient != null) {
+            this.stompClient.stop();
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@AfterEach
void tearDown() {
if (this.stompSession != null && this.stompSession.isConnected()) {
this.stompSession.disconnect();
}
}
@AfterEach
void tearDown() {
if (this.stompSession != null && this.stompSession.isConnected()) {
this.stompSession.disconnect();
}
if (this.stompClient != null) {
this.stompClient.stop();
}
}
🤖 Prompt for AI Agents
In
src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java
around lines 60 to 65, the teardown currently only disconnects the stompSession
but does not stop the underlying stompClient, which can leave scheduler/threads
running and interfere with other tests; update tearDown to also call
stompClient.stop() (with a null-check on stompClient and optionally wrapped in
try/catch to avoid masking test failures) after disconnecting the session so the
client's threads are cleaned up between tests.

Comment on lines +106 to +112
assertAll(
() -> assertThat(thrown)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class),
() -> assertThat(transportErrorQueue).hasSize(1)
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

인증 실패 시 transportErrorQueue 크기 단언을 완화하세요.

  1. 전송 계층에서 에러가 발생하지 않고 즉시 HTTP 예외로 반환될 수 있어, 큐 크기 1을 정확히 보장하기 어렵습니다.
  2. 비어있지 않음 정도로만 검증하면 환경차(컨테이너/드라이버)에 덜 민감합니다.
-                    () -> assertThat(transportErrorQueue).hasSize(1)
+                    () -> assertThat(transportErrorQueue).isNotEmpty()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertAll(
() -> assertThat(thrown)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class),
() -> assertThat(transportErrorQueue).hasSize(1)
);
}
assertAll(
() -> assertThat(thrown)
.isInstanceOf(ExecutionException.class)
.hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class),
() -> assertThat(transportErrorQueue).isNotEmpty()
);
🤖 Prompt for AI Agents
In
src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java
around lines 106 to 112, the test currently asserts transportErrorQueue
hasSize(1) which is brittle because authentication failures can be returned
immediately as HTTP exceptions without a transport-layer error; change the
assertion to check the queue is not empty (e.g.,
assertThat(transportErrorQueue).isNotEmpty() or hasSizeGreaterThan(0)) so the
test passes when at least one transport error was recorded while remaining
tolerant of environment differences; update the assertAll accordingly to use the
relaxed assertion.

@whqtker whqtker merged commit d5a9f49 into solid-connection:develop Aug 12, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

테스트 Added tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: WebSocket 관련 테스트 코드 작성

2 participants