Skip to content

[FIX] 그룹 채팅 퇴장/강퇴 unread 집계 보정 및 테스트 안정화#87

Merged
ydking0911 merged 7 commits into
developfrom
fix/#86/그룹-채팅-멤버-퇴장추방-시-미읽음-카운트-집계-오류
Apr 2, 2026

Hidden character warning

The head ref may contain hidden characters: "fix/#86/\uadf8\ub8f9-\ucc44\ud305-\uba64\ubc84-\ud1f4\uc7a5\ucd94\ubc29-\uc2dc-\ubbf8\uc77d\uc74c-\uce74\uc6b4\ud2b8-\uc9d1\uacc4-\uc624\ub958"
Merged

[FIX] 그룹 채팅 퇴장/강퇴 unread 집계 보정 및 테스트 안정화#87
ydking0911 merged 7 commits into
developfrom
fix/#86/그룹-채팅-멤버-퇴장추방-시-미읽음-카운트-집계-오류

Conversation

@ydking0911

Copy link
Copy Markdown
Member

📝 Pull Request Template

📌 제목

그룹 채팅 퇴장/강퇴 unread 집계 보정 및 테스트 안정화


📢 요약

룸메이트가 그룹 채팅방에서 퇴장하거나 강퇴될 때 미확인 메시지(unreadCount)가 남아 있어 지표가 왜곡되는 문제를 해결했습니다. 함께, Docker가 없는 환경에서도 통합 테스트가 실패하지 않도록 Testcontainers 감지 유틸리티를 추가했습니다.

구현/수정 사항

1. 퇴장/강퇴 시 unreadCount 정합성 보장

  • LeaveChatRoomUseCase / RoommateKickedEventListener에서 ChatRoomMember의 lastReadAt 혹은 joinedAt을 기준으로
    ChatMessageService.decreaseUnreadCount()를 먼저 호출한 뒤 멤버를 삭제하도록 순서를 보장했습니다.
  • 마지막 남은 1인 방에서는 unread 감소가 필요 없으므로 조건부로 실행합니다.
  • 강퇴 이벤트 역시 동일 로직을 공유해, kicked 유저의 모든 미확인 메시지가 즉시 정리됩니다.

2. 단위 테스트 보강

  • LeaveChatRoomUseCaseTest, RoommateKickedEventListenerTest에 InOrder 검증을 추가해 unread 감소 → leave 순서가 강제
    되는지, lastReadAt/최소 참여 인원 조건이 만족되는지 확인합니다.
  • joinedAt 기반 fall-back, lastReadAt 기반 업데이트 등 주요 분기 모두 테스트했습니다.

3. Testcontainers 테스트 안정화

  • TestcontainersSupport 헬퍼를 추가해 한 번만 Docker 사용 가능 여부를 감지합니다.
  • ChatRoomSchemaInitializationTest, ChatTransactionAtomicityIntegrationTest의 @BeforeAll에서
    Assumptions.assumeTrue(...)를 호출해 Docker가 없으면 테스트를 자동 스킵하도록 만들었습니다. 로컬/CI에서 Docker가
    있을 때는 기존과 동일하게 실행됩니다.

🔗 연관 이슈: Resolves #86


🚀 PR 유형

해당하는 항목에 체크해주세요.

  • ✨ 새로운 기능 추가
  • 🐛 버그 수정
  • 🎨 CSS/UI 디자인 변경
  • 🔧 코드에 영향 없는 변경(오타 수정, 탭 사이즈 변경, 변수명 변경 등)
  • 🔨 코드 리팩토링
  • 📝 주석 추가 및 수정
  • 📄 문서 수정
  • 🧪 테스트 추가 또는 리팩토링
  • 🏗️ 빌드 및 패키지 매니저 수정
  • 📂 파일 또는 폴더명 수정
  • 🗑️ 파일 또는 폴더 삭제

✅ PR 체크리스트

PR이 다음 요구 사항을 충족하는지 확인해주세요.

  • 🔹 커밋 메시지 컨벤션을 준수했습니다. (Commit message convention 참고)
  • 🔹 변경 사항에 대한 테스트를 수행했습니다. (버그 수정/기능 테스트)
  • 🔹 관련 문서를 업데이트했습니다. (필요한 경우)

📜 기타

리뷰어가 알면 좋을 추가 사항을 적어주세요.

  • 기능 개선 아이디어
  • 코드 리팩토링 필요 여부 등

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c9797f32-318e-4a4d-9fd4-61195f22a740

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/#86/그룹-채팅-멤버-퇴장추방-시-미읽음-카운트-집계-오류

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

그룹 채팅에서 멤버가 퇴장/강퇴될 때 남아 있던 unreadCount를 정리해 지표 왜곡을 방지하고, 읽음 처리 시 read-receipt 브로드캐스트 및 Docker 없는 환경에서의 통합 테스트 안정화를 함께 다루는 PR입니다.

Changes:

  • 퇴장/강퇴 시 decreaseUnreadCount(...)를 선행 호출하도록 순서 보장(기준 시각은 lastReadAt → 없으면 joinedAt)
  • WebSocket 응답 모델에 unreadCount 포함 및 읽음 처리 read-receipt 브로드캐스트 추가
  • Docker/Testcontainers 감지 유틸 및 CI 워크플로우/테스트 보강(스킵 로직 포함)

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/main/java/com/project/dorumdorum/domain/chat/application/usecase/LeaveChatRoomUseCase.java 퇴장 시 unread 감소를 멤버 삭제보다 먼저 수행하도록 로직 순서 보정, WS 응답에 unreadCount 포함
src/main/java/com/project/dorumdorum/domain/chat/application/event/RoommateKickedEventListener.java 강퇴 시 unread 감소 후 멤버 삭제로 순서 보정, WS 응답에 unreadCount 포함
src/main/java/com/project/dorumdorum/domain/chat/application/usecase/MarkChatRoomReadUseCase.java 읽음 처리 후 read-receipt를 WebSocket으로 브로드캐스트
src/main/java/com/project/dorumdorum/domain/chat/application/usecase/SendGroupChatMessageUseCase.java 메시지 WS 응답에 unreadCount 포함
src/main/java/com/project/dorumdorum/domain/chat/application/usecase/JoinChatRoomUseCase.java 시스템 메시지 WS 응답에 unreadCount 포함
src/main/java/com/project/dorumdorum/domain/chat/application/dto/response/ChatMessageResponse.java WS 메시지 응답 record에 unreadCount 필드 추가
src/main/java/com/project/dorumdorum/domain/chat/application/dto/response/ChatReadReceiptResponse.java 읽음 브로드캐스트용 DTO 추가
src/test/java/com/project/dorumdorum/domain/chat/unit/usecase/LeaveChatRoomUseCaseTest.java unread 감소 → leave 호출 순서 및 분기 테스트 보강(InOrder 포함)
src/test/java/com/project/dorumdorum/domain/chat/unit/usecase/RoommateKickedEventListenerTest.java 강퇴 시 unread 감소 → leave 순서 검증(InOrder)
src/test/java/com/project/dorumdorum/domain/chat/unit/usecase/MarkChatRoomReadUseCaseTest.java read-receipt 브로드캐스트 검증 추가
src/test/java/com/project/dorumdorum/testsupport/TestcontainersSupport.java Docker 가용성 1회 감지 헬퍼 추가
src/test/java/com/project/dorumdorum/domain/chat/integration/ChatTransactionAtomicityIntegrationTest.java Docker 미가용 시 스킵 의도 로직 추가(현재 방식에는 이슈 존재)
src/test/java/com/project/dorumdorum/domain/chat/integration/ChatRoomSchemaInitializationTest.java Docker 미가용 시 스킵 의도 로직 추가(현재 방식에는 이슈 존재)
.github/workflows/cicd.yml CI에서 Docker daemon 확인 단계 추가
src/main/resources/application-dev.yml dev 환경에서 devtools restart 비활성화
.gitignore .env 정리 및 macOS .DS_Store 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +70 to +76
@BeforeAll
static void requireDocker() {
Assumptions.assumeTrue(
TestcontainersSupport.requireDockerOrSkip("ChatTransactionAtomicityIntegrationTest"),
"Docker is required for ChatTransactionAtomicityIntegrationTest"
);
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

@SpringBootTest 컨텍스트가 JUnit @BeforeAll 실행 전에 먼저 로드되기 때문에, 여기의 Assumptions.assumeTrue(...)는 Docker 없는 환경에서 Testcontainers JDBC 드라이버가 컨텍스트 초기화 중 컨테이너를 띄우다 실패하는 것을 막지 못합니다. Docker 감지는 클래스 실행 전에 평가되는 조건(예: JUnit5 ExecutionCondition 확장, 또는 Testcontainers JUnit의 @Testcontainers(disabledWithoutDocker = true)/Spring test의 조건부 실행 애너테이션)으로 옮겨서 컨텍스트 자체가 로드되지 않도록 해주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +31
@BeforeAll
static void requireDocker() {
Assumptions.assumeTrue(
TestcontainersSupport.requireDockerOrSkip("ChatRoomSchemaInitializationTest"),
"Docker is required for ChatRoomSchemaInitializationTest"
);
}

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

@SpringBootTest는 JUnit @BeforeAll보다 먼저 Spring 컨텍스트를 초기화하므로, 이 Assumptions.assumeTrue(...)로는 Docker 없는 환경에서 Testcontainers datasource 초기화 실패를 방지할 수 없습니다. Docker 필요 여부를 컨텍스트 로딩 이전에 평가되는 방식(예: JUnit5 ExecutionCondition 확장/조건부 실행 애너테이션, 또는 @Testcontainers(disabledWithoutDocker = true) 등)으로 전환해 테스트 클래스 자체가 스킵되도록 해주세요.

Copilot uses AI. Check for mistakes.
try {
DockerClientFactory.instance().client();
return true;
} catch (Throwable ignored) {

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

Docker 감지 로직에서 catch (Throwable ...)Error(예: OutOfMemoryError)까지 삼켜 디버깅을 어렵게 만들 수 있습니다. Docker 미가용 판별이 목적이라면 Exception(또는 Testcontainers가 던지는 구체 예외 타입)만 잡도록 범위를 좁혀주세요.

Suggested change
} catch (Throwable ignored) {
} catch (Exception ignored) {

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +118
ChatRoomMember member = ChatRoomMember.builder().chatRoom(chatRoom).userNo("user-kicked").build();
User mockUser = mock(User.class);
ChatMessage mockMessage = mock(ChatMessage.class);
when(chatRoomService.findByRoomNo("room-1")).thenReturn(Optional.of(chatRoom));
when(chatRoomMemberService.isMember(chatRoom, "user-kicked")).thenReturn(true);
when(chatRoomMemberService.findByChatRoomAndUserNo(chatRoom, "user-kicked")).thenReturn(member);
when(userService.findById("user-kicked")).thenReturn(mockUser);

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

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

이 테스트에서 ChatRoomMember를 builder로 생성하면서 joinedAt/lastReadAt가 모두 null인 상태입니다. 실제 엔티티는 @PrePersistjoinedAt이 항상 채워지므로, 여기서도 joinedAt을 명시적으로 세팅하고 decreaseUnreadCountfromTime이 해당 값으로 계산되는지까지 함께 검증하면 테스트 신뢰도가 올라갑니다.

Copilot uses AI. Check for mistakes.
@ydking0911 ydking0911 merged commit 37dbf0b into develop Apr 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants