From 45c1ba654e1f12deda0469a722dd4ae9cbaa68cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 10:59:10 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?DTO=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatRoomMemberResponse: 개별 멤버 정보 (userId, name, imageUrl, isOwner, joinedAt) - ChatRoomMembersResponse: 멤버 목록 래퍼 클라이언트가 채팅방 참여자 정보를 확인할 수 있도록 응답 객체 정의 --- .../domain/chat/dto/ChatRoomMemberResponse.java | 12 ++++++++++++ .../domain/chat/dto/ChatRoomMembersResponse.java | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java create mode 100644 src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java diff --git a/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java b/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java new file mode 100644 index 000000000..7a1fc1309 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMemberResponse.java @@ -0,0 +1,12 @@ +package gg.agit.konect.domain.chat.dto; + +import java.time.LocalDateTime; + +public record ChatRoomMemberResponse( + Integer userId, + String name, + String profileImageUrl, + boolean isOwner, + LocalDateTime joinedAt +) { +} diff --git a/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java b/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java new file mode 100644 index 000000000..3534d197c --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/chat/dto/ChatRoomMembersResponse.java @@ -0,0 +1,8 @@ +package gg.agit.konect.domain.chat.dto; + +import java.util.List; + +public record ChatRoomMembersResponse( + List members +) { +} From a8e60e3a8a19ad45c3c9c885690decd6043c5809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 10:59:35 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=ED=99=9C=EC=84=B1=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20Repository=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findActiveMembersByChatRoomId: 채팅방 ID로 활성 멤버 조회 - JOIN FETCH로 User 정보를 한 번에 로딩 (N+1 방지) - leftAt IS NULL 조건으로 나간 멤버 필터링 --- .../domain/chat/repository/ChatRoomMemberRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java index 1b7252648..3ddc0537e 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java @@ -120,6 +120,15 @@ List countUnreadByRoomIdsAndUserId( @Param("userId") Integer userId ); + @Query(""" + SELECT crm + FROM ChatRoomMember crm + JOIN FETCH crm.user + WHERE crm.id.chatRoomId = :chatRoomId + AND crm.leftAt IS NULL + """) + List findActiveMembersByChatRoomId(@Param("chatRoomId") Integer chatRoomId); + List saveAll(Iterable chatRoomMembers); } From a51dfe435a1a14a39fc3f9895a82876be20567d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:00:34 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20Service=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getChatRoomMembers: 멤버 목록 조회 비즈니스 로직 - validateMembership: existsByChatRoomIdAndUserId로 권한 검증 - toMemberResponse: Entity → DTO 변환 (createdAt을 joinedAt으로 매핑) 비멤버 접근 시 FORBIDDEN_CHAT_ROOM_ACCESS 예외 발생 --- .../service/ChatRoomMembershipService.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 3dd25d68c..7ed197dd6 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -15,6 +15,8 @@ import gg.agit.konect.domain.chat.model.ChatRoom; import gg.agit.konect.domain.chat.model.ChatRoomMember; +import gg.agit.konect.domain.chat.dto.ChatRoomMemberResponse; +import gg.agit.konect.domain.chat.dto.ChatRoomMembersResponse; import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository; import gg.agit.konect.domain.chat.repository.ChatRoomRepository; import gg.agit.konect.domain.club.model.Club; @@ -39,6 +41,36 @@ public class ChatRoomMembershipService { private final ClubMemberRepository clubMemberRepository; private final UserRepository userRepository; + @Transactional(readOnly = true) + public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer currentUserId) { + // 1. 현재 사용자가 채팅방 멤버인지 확인 (존재 + 나가지 않음) + validateMembership(chatRoomId, currentUserId); + + // 2. 활성 멤버 목록 조회 (leftAt IS NULL) + List members = chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId); + + // 3. DTO 변환 및 반환 + return new ChatRoomMembersResponse(members.stream() + .map(this::toMemberResponse) + .toList()); + } + + private void validateMembership(Integer chatRoomId, Integer userId) { + if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, userId)) { + throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); + } + } + + private ChatRoomMemberResponse toMemberResponse(ChatRoomMember member) { + return new ChatRoomMemberResponse( + member.getUser().getId(), + member.getUser().getName(), + member.getUser().getImageUrl(), // getProfileImageUrl -> getImageUrl + member.isOwner(), + member.getCreatedAt() + ); + } + @Transactional public void addClubMember(ClubMember clubMember) { LocalDateTime baseline = Objects.requireNonNull(clubMember.getCreatedAt()); From 6646b81ca6ec27b6b6b6802feed65f6fef153dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:01:29 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /chats/rooms/{chatRoomId}/members 엔드포인트 추가 - ChatApi 인터페이스: Swagger 문서화 (설명, 에러 코드) - ChatController 구현체: @UserId로 사용자 ID 주입 채팅방 접속 시 해당 방의 멤버 목록을 조회할 수 있는 API 제공 --- .../domain/chat/controller/ChatApi.java | 27 +++++++++++++++++-- .../chat/controller/ChatController.java | 13 +++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java b/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java index b42c87be9..fd31aff2e 100644 --- a/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java +++ b/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java @@ -16,6 +16,8 @@ import gg.agit.konect.domain.chat.dto.ChatMessageSendRequest; import gg.agit.konect.domain.chat.dto.ChatMuteResponse; import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest; +import gg.agit.konect.domain.chat.dto.ChatRoomMemberResponse; +import gg.agit.konect.domain.chat.dto.ChatRoomMembersResponse; import gg.agit.konect.domain.chat.dto.ChatRoomNameUpdateRequest; import gg.agit.konect.domain.chat.dto.ChatRoomResponse; import gg.agit.konect.domain.chat.dto.ChatRoomsSummaryResponse; @@ -23,6 +25,8 @@ import gg.agit.konect.domain.chat.enums.ChatInviteSortBy; import gg.agit.konect.global.auth.annotation.UserId; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; @@ -253,11 +257,11 @@ ResponseEntity kickMember( @Operation(summary = "그룹 채팅방을 생성한다.", description = """ ## 설명 - 여러 유저를 초대하여 그룹 채팅방을 생성합니다. - + ## 로직 - 요청자(방장)를 포함하여 선택된 모든 유저가 참여하는 그룹 채팅방을 생성합니다. - 방장은 채팅방을 생성한 사용자입니다. - + ## 에러 - CANNOT_CREATE_CHAT_ROOM_WITH_SELF (400): 자기 자신만으로는 채팅방을 만들 수 없습니다. - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. @@ -267,4 +271,23 @@ ResponseEntity createGroupChatRoom( @Valid @RequestBody ChatRoomCreateRequest.Group request, @UserId Integer userId ); + + @Operation(summary = "채팅방 멤버 목록 조회", description = """ + ## 설명 + - 특정 채팅방의 모든 멤버 목록을 조회합니다. + + ## 로직 + - 채팅방에 참여 중인 멤버만 조회할 수 있습니다. + - 나간 멤버(leftAt이 설정된 멤버)는 목록에 포함되지 않습니다. + - 각 멤버의 userId, 이름, 프로필 이미지, 방장 여부, 참여 시간을 반환합니다. + + ## 에러 + - FORBIDDEN_CHAT_ROOM_ACCESS (403): 채팅방에 접근할 권한이 없습니다. + - NOT_FOUND_CHAT_ROOM (404): 채팅방을 찾을 수 없습니다. + """) + @GetMapping("/rooms/{chatRoomId}/members") + ResponseEntity getChatRoomMembers( + @Parameter(description = "채팅방 ID") @PathVariable Integer chatRoomId, + @UserId Integer userId + ); } diff --git a/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java b/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java index 844a25f53..c14f013cb 100644 --- a/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java +++ b/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java @@ -2,6 +2,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,11 +15,13 @@ import gg.agit.konect.domain.chat.dto.ChatMessageSendRequest; import gg.agit.konect.domain.chat.dto.ChatMuteResponse; import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest; +import gg.agit.konect.domain.chat.dto.ChatRoomMembersResponse; import gg.agit.konect.domain.chat.dto.ChatRoomNameUpdateRequest; import gg.agit.konect.domain.chat.dto.ChatRoomResponse; import gg.agit.konect.domain.chat.dto.ChatRoomsSummaryResponse; import gg.agit.konect.domain.chat.dto.ChatSearchResponse; import gg.agit.konect.domain.chat.enums.ChatInviteSortBy; +import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; import gg.agit.konect.domain.chat.service.ChatService; import gg.agit.konect.global.auth.annotation.UserId; import jakarta.validation.Valid; @@ -31,6 +34,7 @@ public class ChatController implements ChatApi { private final ChatService chatService; + private final ChatRoomMembershipService chatRoomMembershipService; @Override public ResponseEntity createOrGetChatRoom( @@ -147,4 +151,13 @@ public ResponseEntity createGroupChatRoom( ChatRoomResponse response = chatService.createGroupChatRoom(userId, request); return ResponseEntity.ok(response); } + + @Override + @GetMapping("/rooms/{chatRoomId}/members") + public ResponseEntity getChatRoomMembers( + @PathVariable Integer chatRoomId, + @UserId Integer userId) { + ChatRoomMembersResponse response = chatRoomMembershipService.getChatRoomMembers(chatRoomId, userId); + return ResponseEntity.ok(response); + } } From 6918bd1afb37953f1082f3a3c3724ad2f29c49af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:02:25 +0900 Subject: [PATCH 05/11] =?UTF-8?q?test:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20Service=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 멤버 조회 성공 케이스 검증 - 비멤버 접근 시 FORBIDDEN 예외 발생 검증 - 나간 멤버는 조회되지 않음 검증 Mockito를 사용하여 Repository 의존성 모킹 --- .../ChatRoomMembershipServiceTest.java | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java new file mode 100644 index 000000000..9645daea0 --- /dev/null +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java @@ -0,0 +1,152 @@ +package gg.agit.konect.unit.domain.chat.service; + +import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import gg.agit.konect.domain.chat.dto.ChatRoomMemberResponse; +import gg.agit.konect.domain.chat.dto.ChatRoomMembersResponse; +import gg.agit.konect.domain.chat.model.ChatRoom; +import gg.agit.konect.domain.chat.model.ChatRoomMember; +import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository; +import gg.agit.konect.domain.chat.repository.ChatRoomRepository; +import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; +import gg.agit.konect.domain.club.repository.ClubMemberRepository; +import gg.agit.konect.domain.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import gg.agit.konect.global.exception.CustomException; + +@ExtendWith(MockitoExtension.class) +class ChatRoomMembershipServiceTest { + + @Mock + private ChatRoomRepository chatRoomRepository; + + @Mock + private ChatRoomMemberRepository chatRoomMemberRepository; + + @Mock + private ClubMemberRepository clubMemberRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private ChatRoomMembershipService chatRoomMembershipService; + + @Test + @DisplayName("채팅방 멤버 목록 조회 성공") + void getChatRoomMembers_success() { + // given + Integer chatRoomId = 1; + Integer currentUserId = 100; + + User user1 = User.builder() + .id(100) + .name("User1") + .imageUrl("image1.jpg") + .build(); + + User user2 = User.builder() + .id(200) + .name("User2") + .imageUrl("image2.jpg") + .build(); + + ChatRoom chatRoom = ChatRoom.builder() + .id(chatRoomId) + .build(); + + ChatRoomMember member1 = ChatRoomMember.ofOwner(chatRoom, user1, LocalDateTime.now()); + ChatRoomMember member2 = ChatRoomMember.of(chatRoom, user2, LocalDateTime.now()); + + given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + .willReturn(true); + given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) + .willReturn(List.of(member1, member2)); + + // when + ChatRoomMembersResponse response = chatRoomMembershipService.getChatRoomMembers(chatRoomId, currentUserId); + + // then + assertThat(response.members()).hasSize(2); + + ChatRoomMemberResponse firstMember = response.members().get(0); + assertThat(firstMember.userId()).isEqualTo(100); + assertThat(firstMember.name()).isEqualTo("User1"); + assertThat(firstMember.isOwner()).isTrue(); + + ChatRoomMemberResponse secondMember = response.members().get(1); + assertThat(secondMember.userId()).isEqualTo(200); + assertThat(secondMember.name()).isEqualTo("User2"); + assertThat(secondMember.isOwner()).isFalse(); + } + + @Test + @DisplayName("비멤버가 조회 시도 시 FORBIDDEN 예외 발생") + void getChatRoomMembers_forbiddenWhenNotMember() { + // given + Integer chatRoomId = 1; + Integer currentUserId = 100; + + given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + .willReturn(false); + + // when & then + assertThatThrownBy(() -> chatRoomMembershipService.getChatRoomMembers(chatRoomId, currentUserId)) + .isInstanceOf(CustomException.class) + .hasFieldOrPropertyWithValue("errorCode", FORBIDDEN_CHAT_ROOM_ACCESS); + } + + @Test + @DisplayName("나간 멤버는 조회되지 않음") + void getChatRoomMembers_excludesLeftMembers() { + // given + Integer chatRoomId = 1; + Integer currentUserId = 100; + + User user1 = User.builder() + .id(100) + .name("User1") + .imageUrl("image1.jpg") + .build(); + + User user2 = User.builder() + .id(200) + .name("User2") + .imageUrl("image2.jpg") + .build(); + + ChatRoom chatRoom = ChatRoom.builder() + .id(chatRoomId) + .build(); + + ChatRoomMember member1 = ChatRoomMember.of(chatRoom, user1, LocalDateTime.now()); + ChatRoomMember member2 = ChatRoomMember.of(chatRoom, user2, LocalDateTime.now()); + member2.leaveDirectRoom(LocalDateTime.now()); // member2는 나간 상태 + + given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + .willReturn(true); + given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) + .willReturn(List.of(member1)); // 나간 멤버는 조회되지 않음 + + // when + ChatRoomMembersResponse response = chatRoomMembershipService.getChatRoomMembers(chatRoomId, currentUserId); + + // then + assertThat(response.members()).hasSize(1); + assertThat(response.members().get(0).userId()).isEqualTo(100); + } +} From 737d1f7f46ee2ff10a9b667109b432616eaace35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:09:31 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=EA=B3=BC=20import=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Service: WHAT 설명 주석 제거 (메서드명이 충분히 표현) - Service: 코드 변경 이력 주석 제거 - Test: 사용되지 않는 any import 제거 코드 리뷰 피드백 반영하여 가독성 개선 --- .../domain/chat/service/ChatRoomMembershipService.java | 5 +---- .../domain/chat/service/ChatRoomMembershipServiceTest.java | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 7ed197dd6..79c1d27c0 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -43,13 +43,10 @@ public class ChatRoomMembershipService { @Transactional(readOnly = true) public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer currentUserId) { - // 1. 현재 사용자가 채팅방 멤버인지 확인 (존재 + 나가지 않음) validateMembership(chatRoomId, currentUserId); - // 2. 활성 멤버 목록 조회 (leftAt IS NULL) List members = chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId); - // 3. DTO 변환 및 반환 return new ChatRoomMembersResponse(members.stream() .map(this::toMemberResponse) .toList()); @@ -65,7 +62,7 @@ private ChatRoomMemberResponse toMemberResponse(ChatRoomMember member) { return new ChatRoomMemberResponse( member.getUser().getId(), member.getUser().getName(), - member.getUser().getImageUrl(), // getProfileImageUrl -> getImageUrl + member.getUser().getImageUrl(), member.isOwner(), member.getCreatedAt() ); diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java index 9645daea0..6356f8eb0 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java @@ -3,7 +3,6 @@ import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import java.time.LocalDateTime; From 40623f1472f26a5946f6badfa7d3dba854d5854d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:13:36 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=ED=83=88=ED=87=B4=ED=95=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=ED=95=84=ED=84=B0=EB=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getChatRoomMembers: 탈퇴한 유저(deletedAt != null) 제외 - Stream filter로 deletedAt == null인 멤버만 변환 탈퇴한 사용자는 채팅방 멤버 목록에 노출되지 않음 --- .../konect/domain/chat/service/ChatRoomMembershipService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 79c1d27c0..9bd0302df 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -48,6 +48,7 @@ public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer cu List members = chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId); return new ChatRoomMembersResponse(members.stream() + .filter(member -> member.getUser().getDeletedAt() == null) .map(this::toMemberResponse) .toList()); } From 7e8874f0497e9db1b1688f349ead038afa5fffc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:18:35 +0900 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=B0=A9=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getChatRoomMembers: 현재 사용자 정보를 UserRepository에서 조회 - validateMembership: 어드민이 시스템 어드민 방 조회 시 권한 검증 스킵 - NOT_FOUND_USER import 추가 - 테스트: UserRepository mock 설정 추가 어드민은 시스템 어드민 방의 멤버 목록을 조회할 수 있음 --- .../chat/service/ChatRoomMembershipService.java | 15 ++++++++++++--- .../service/ChatRoomMembershipServiceTest.java | 12 ++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 9bd0302df..53b3ba097 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -2,6 +2,7 @@ import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS; import static gg.agit.konect.global.code.ApiResponseCode.NOT_FOUND_CHAT_ROOM; +import static gg.agit.konect.global.code.ApiResponseCode.NOT_FOUND_USER; import java.time.LocalDateTime; import java.util.List; @@ -43,7 +44,10 @@ public class ChatRoomMembershipService { @Transactional(readOnly = true) public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer currentUserId) { - validateMembership(chatRoomId, currentUserId); + User currentUser = userRepository.findById(currentUserId) + .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); + + validateMembership(chatRoomId, currentUser); List members = chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId); @@ -53,8 +57,13 @@ public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer cu .toList()); } - private void validateMembership(Integer chatRoomId, Integer userId) { - if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, userId)) { + private void validateMembership(Integer chatRoomId, User currentUser) { + // 어드민은 시스템 어드민 방의 멤버를 조회할 수 있음 + if (currentUser.isAdmin() && isSystemAdminRoom(chatRoomId)) { + return; + } + + if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUser.getId())) { throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); } } diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java index 6356f8eb0..c111d07e3 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java @@ -71,6 +71,8 @@ void getChatRoomMembers_success() { ChatRoomMember member1 = ChatRoomMember.ofOwner(chatRoom, user1, LocalDateTime.now()); ChatRoomMember member2 = ChatRoomMember.of(chatRoom, user2, LocalDateTime.now()); + given(userRepository.findById(currentUserId)) + .willReturn(java.util.Optional.of(user1)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) @@ -100,6 +102,14 @@ void getChatRoomMembers_forbiddenWhenNotMember() { Integer chatRoomId = 1; Integer currentUserId = 100; + User user1 = User.builder() + .id(100) + .name("User1") + .imageUrl("image1.jpg") + .build(); + + given(userRepository.findById(currentUserId)) + .willReturn(java.util.Optional.of(user1)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(false); @@ -136,6 +146,8 @@ void getChatRoomMembers_excludesLeftMembers() { ChatRoomMember member2 = ChatRoomMember.of(chatRoom, user2, LocalDateTime.now()); member2.leaveDirectRoom(LocalDateTime.now()); // member2는 나간 상태 + given(userRepository.findById(currentUserId)) + .willReturn(java.util.Optional.of(user1)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) From 2e68109eda3216c12698d6e2018e60dd7117044c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:25:31 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix:=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20404=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getChatRoomMembers: 채팅방 존재 여부를 멤버십 검증 전에 확인 - 존재하지 않는 채팅방: NOT_FOUND_CHAT_ROOM (404) - 비멤버 접근: FORBIDDEN_CHAT_ROOM_ACCESS (403) - validateMembership 파라미터를 ChatRoom으로 변경 - 테스트: ChatRoomRepository mock 설정 추가 잘못된 순서로 인해 존재하지 않는 방도 403이 뜨던 문제 해결 --- .../chat/service/ChatRoomMembershipService.java | 12 ++++++++---- .../chat/service/ChatRoomMembershipServiceTest.java | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 53b3ba097..51806d244 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -47,7 +47,11 @@ public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer cu User currentUser = userRepository.findById(currentUserId) .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); - validateMembership(chatRoomId, currentUser); + // 채팅방 존재 여부 먼저 확인 + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + + validateMembership(chatRoom, currentUser); List members = chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId); @@ -57,13 +61,13 @@ public ChatRoomMembersResponse getChatRoomMembers(Integer chatRoomId, Integer cu .toList()); } - private void validateMembership(Integer chatRoomId, User currentUser) { + private void validateMembership(ChatRoom chatRoom, User currentUser) { // 어드민은 시스템 어드민 방의 멤버를 조회할 수 있음 - if (currentUser.isAdmin() && isSystemAdminRoom(chatRoomId)) { + if (currentUser.isAdmin() && isSystemAdminRoom(chatRoom.getId())) { return; } - if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUser.getId())) { + if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoom.getId(), currentUser.getId())) { throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); } } diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java index c111d07e3..f4748575a 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java @@ -73,6 +73,8 @@ void getChatRoomMembers_success() { given(userRepository.findById(currentUserId)) .willReturn(java.util.Optional.of(user1)); + given(chatRoomRepository.findById(chatRoomId)) + .willReturn(java.util.Optional.of(chatRoom)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) @@ -108,8 +110,14 @@ void getChatRoomMembers_forbiddenWhenNotMember() { .imageUrl("image1.jpg") .build(); + ChatRoom chatRoom = ChatRoom.builder() + .id(chatRoomId) + .build(); + given(userRepository.findById(currentUserId)) .willReturn(java.util.Optional.of(user1)); + given(chatRoomRepository.findById(chatRoomId)) + .willReturn(java.util.Optional.of(chatRoom)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(false); @@ -148,6 +156,8 @@ void getChatRoomMembers_excludesLeftMembers() { given(userRepository.findById(currentUserId)) .willReturn(java.util.Optional.of(user1)); + given(chatRoomRepository.findById(chatRoomId)) + .willReturn(java.util.Optional.of(chatRoom)); given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) From da85394031f98874ade538a4c1fee16751fe7c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:31:02 +0900 Subject: [PATCH 10/11] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agit/konect/domain/chat/controller/ChatApi.java | 10 ++++------ .../konect/domain/chat/controller/ChatController.java | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java b/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java index fd31aff2e..2dd9c3488 100644 --- a/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java +++ b/src/main/java/gg/agit/konect/domain/chat/controller/ChatApi.java @@ -16,7 +16,6 @@ import gg.agit.konect.domain.chat.dto.ChatMessageSendRequest; import gg.agit.konect.domain.chat.dto.ChatMuteResponse; import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest; -import gg.agit.konect.domain.chat.dto.ChatRoomMemberResponse; import gg.agit.konect.domain.chat.dto.ChatRoomMembersResponse; import gg.agit.konect.domain.chat.dto.ChatRoomNameUpdateRequest; import gg.agit.konect.domain.chat.dto.ChatRoomResponse; @@ -26,7 +25,6 @@ import gg.agit.konect.global.auth.annotation.UserId; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; @@ -257,11 +255,11 @@ ResponseEntity kickMember( @Operation(summary = "그룹 채팅방을 생성한다.", description = """ ## 설명 - 여러 유저를 초대하여 그룹 채팅방을 생성합니다. - + ## 로직 - 요청자(방장)를 포함하여 선택된 모든 유저가 참여하는 그룹 채팅방을 생성합니다. - 방장은 채팅방을 생성한 사용자입니다. - + ## 에러 - CANNOT_CREATE_CHAT_ROOM_WITH_SELF (400): 자기 자신만으로는 채팅방을 만들 수 없습니다. - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. @@ -275,12 +273,12 @@ ResponseEntity createGroupChatRoom( @Operation(summary = "채팅방 멤버 목록 조회", description = """ ## 설명 - 특정 채팅방의 모든 멤버 목록을 조회합니다. - + ## 로직 - 채팅방에 참여 중인 멤버만 조회할 수 있습니다. - 나간 멤버(leftAt이 설정된 멤버)는 목록에 포함되지 않습니다. - 각 멤버의 userId, 이름, 프로필 이미지, 방장 여부, 참여 시간을 반환합니다. - + ## 에러 - FORBIDDEN_CHAT_ROOM_ACCESS (403): 채팅방에 접근할 권한이 없습니다. - NOT_FOUND_CHAT_ROOM (404): 채팅방을 찾을 수 없습니다. diff --git a/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java b/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java index c14f013cb..10a18059f 100644 --- a/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java +++ b/src/main/java/gg/agit/konect/domain/chat/controller/ChatController.java @@ -155,8 +155,8 @@ public ResponseEntity createGroupChatRoom( @Override @GetMapping("/rooms/{chatRoomId}/members") public ResponseEntity getChatRoomMembers( - @PathVariable Integer chatRoomId, - @UserId Integer userId) { + @PathVariable Integer chatRoomId, + @UserId Integer userId) { ChatRoomMembersResponse response = chatRoomMembershipService.getChatRoomMembers(chatRoomId, userId); return ResponseEntity.ok(response); } From 8598eb6f8f8653af74a2ddbddf6d2d3632a1a3fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 15 Apr 2026 11:42:33 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=EB=82=98=EA=B0=84=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EA=B6=8C=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - existsActiveByChatRoomIdAndUserId 메서드 추가 (leftAt IS NULL 조건) - validateMembership에서 활성 멤버만 조회 가능하도록 변경 - 나간 멤버 접근 시 FORBIDDEN 예외 발생 테스트 추가 나간 멤버가 여전히 멤버로 인식되는 문제 해결 --- .../repository/ChatRoomMemberRepository.java | 12 +++++++ .../service/ChatRoomMembershipService.java | 2 +- .../ChatRoomMembershipServiceTest.java | 36 +++++++++++++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java index 3ddc0537e..05135e668 100644 --- a/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java +++ b/src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomMemberRepository.java @@ -120,6 +120,18 @@ List countUnreadByRoomIdsAndUserId( @Param("userId") Integer userId ); + @Query(""" + SELECT COUNT(crm) > 0 + FROM ChatRoomMember crm + WHERE crm.id.chatRoomId = :chatRoomId + AND crm.id.userId = :userId + AND crm.leftAt IS NULL + """) + boolean existsActiveByChatRoomIdAndUserId( + @Param("chatRoomId") Integer chatRoomId, + @Param("userId") Integer userId + ); + @Query(""" SELECT crm FROM ChatRoomMember crm diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java index 51806d244..8d8687c36 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomMembershipService.java @@ -67,7 +67,7 @@ private void validateMembership(ChatRoom chatRoom, User currentUser) { return; } - if (!chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoom.getId(), currentUser.getId())) { + if (!chatRoomMemberRepository.existsActiveByChatRoomIdAndUserId(chatRoom.getId(), currentUser.getId())) { throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); } } diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java index f4748575a..2726fdefd 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatRoomMembershipServiceTest.java @@ -75,7 +75,7 @@ void getChatRoomMembers_success() { .willReturn(java.util.Optional.of(user1)); given(chatRoomRepository.findById(chatRoomId)) .willReturn(java.util.Optional.of(chatRoom)); - given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + given(chatRoomMemberRepository.existsActiveByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) .willReturn(List.of(member1, member2)); @@ -118,7 +118,7 @@ void getChatRoomMembers_forbiddenWhenNotMember() { .willReturn(java.util.Optional.of(user1)); given(chatRoomRepository.findById(chatRoomId)) .willReturn(java.util.Optional.of(chatRoom)); - given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + given(chatRoomMemberRepository.existsActiveByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(false); // when & then @@ -127,6 +127,36 @@ void getChatRoomMembers_forbiddenWhenNotMember() { .hasFieldOrPropertyWithValue("errorCode", FORBIDDEN_CHAT_ROOM_ACCESS); } + @Test + @DisplayName("나간 멤버가 조회 시도 시 FORBIDDEN 예외 발생") + void getChatRoomMembers_forbiddenWhenLeftMember() { + // given + Integer chatRoomId = 1; + Integer currentUserId = 100; + + User user1 = User.builder() + .id(100) + .name("User1") + .imageUrl("image1.jpg") + .build(); + + ChatRoom chatRoom = ChatRoom.builder() + .id(chatRoomId) + .build(); + + given(userRepository.findById(currentUserId)) + .willReturn(java.util.Optional.of(user1)); + given(chatRoomRepository.findById(chatRoomId)) + .willReturn(java.util.Optional.of(chatRoom)); + given(chatRoomMemberRepository.existsActiveByChatRoomIdAndUserId(chatRoomId, currentUserId)) + .willReturn(false); // 나간 멤버는 활성 멤버가 아님 + + // when & then + assertThatThrownBy(() -> chatRoomMembershipService.getChatRoomMembers(chatRoomId, currentUserId)) + .isInstanceOf(CustomException.class) + .hasFieldOrPropertyWithValue("errorCode", FORBIDDEN_CHAT_ROOM_ACCESS); + } + @Test @DisplayName("나간 멤버는 조회되지 않음") void getChatRoomMembers_excludesLeftMembers() { @@ -158,7 +188,7 @@ void getChatRoomMembers_excludesLeftMembers() { .willReturn(java.util.Optional.of(user1)); given(chatRoomRepository.findById(chatRoomId)) .willReturn(java.util.Optional.of(chatRoom)); - given(chatRoomMemberRepository.existsByChatRoomIdAndUserId(chatRoomId, currentUserId)) + given(chatRoomMemberRepository.existsActiveByChatRoomIdAndUserId(chatRoomId, currentUserId)) .willReturn(true); given(chatRoomMemberRepository.findActiveMembersByChatRoomId(chatRoomId)) .willReturn(List.of(member1)); // 나간 멤버는 조회되지 않음