From 5f7999d0a585ef0517f234dff35f59e524a61817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 10:24:59 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B2=80=EC=A6=9D=20=EC=B1=85=EC=9E=84=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatService에 남아 있던 direct, club group, group 접근 검증 흐름을 ChatRoomAccessService로 이동 - direct SYSTEM_ADMIN 문의방 예외와 club group 멤버 보장 정책을 기존 경로 그대로 유지 - 뮤트, 방 이름 변경, 메시지 조회 접근 검증을 한 서비스로 모아 후속 멤버 명령 분리 범위를 줄임 --- .../chat/service/ChatRoomAccessService.java | 57 +++++++++++++++++++ .../domain/chat/service/ChatService.java | 40 ++----------- .../domain/chat/service/ChatServiceTest.java | 9 +++ 3 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java new file mode 100644 index 00000000..39996f89 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -0,0 +1,57 @@ +package gg.agit.konect.domain.chat.service; + +import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +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.user.model.User; +import gg.agit.konect.domain.user.repository.UserRepository; +import gg.agit.konect.global.exception.CustomException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional +public class ChatRoomAccessService { + + private final ChatRoomMemberRepository chatRoomMemberRepository; + private final UserRepository userRepository; + private final ChatRoomMembershipService chatRoomMembershipService; + private final ChatRoomSystemAdminService chatRoomSystemAdminService; + private final ChatDirectRoomAccessService chatDirectRoomAccessService; + + public ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId) { + if (room.isClubGroupRoom()) { + chatRoomMembershipService.ensureClubRoomMember(room.getId(), userId); + return getRoomMember(room.getId(), userId); + } + + if (room.isDirectRoom()) { + User user = userRepository.getById(userId); + return chatDirectRoomAccessService.getAccessibleMember(room, user); + } + + ChatRoomMember member = getRoomMember(room.getId(), userId); + if (member.hasLeft()) { + throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); + } + return member; + } + + public void ensureMuteAccess(ChatRoom room, User user) { + if (room.isDirectRoom() && user.isAdmin() && chatRoomSystemAdminService.isSystemAdminRoom(room.getId())) { + return; + } + + getAccessibleMember(room, user.getId()); + } + + private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { + return chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId) + .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); + } +} diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatService.java index 6b55a035..8c848467 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatService.java @@ -83,6 +83,7 @@ public class ChatService { private final ChatRoomSummaryService chatRoomSummaryService; private final ChatSearchService chatSearchService; private final ChatMessagePageResolver chatMessagePageResolver; + private final ChatRoomAccessService chatRoomAccessService; private final ChatRoomSystemAdminService chatRoomSystemAdminService; private final ChatDirectRoomAccessService chatDirectRoomAccessService; private final NotificationService notificationService; @@ -375,7 +376,7 @@ public ChatMessagePageResponse getMessages( return getClubMessagesByRoomId(roomId, userId, page, limit); } - getAccessibleRoomMember(room, userId); + chatRoomAccessService.getAccessibleMember(room, userId); chatRoomMembershipService.updateLastReadAt(roomId, userId, readAt); recordPresenceSafely(roomId, userId); return getGroupMessagesByRoomId(roomId, userId, page, limit); @@ -403,19 +404,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); User user = userRepository.getById(userId); - if (room.isClubGroupRoom()) { - ClubMember member = clubMemberRepository.getByClubIdAndUserId(room.getClub().getId(), userId); - ensureRoomMember(room, member.getUser(), member.getCreatedAt()); - } else if (room.isDirectRoom()) { - // 어드민이 SYSTEM_ADMIN 방에 접근하는 경우는 멤버십 체크를 건너뜀 - boolean isAdminAccessingSystemAdminRoom = user.isAdmin() - && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); - if (!isAdminAccessingSystemAdminRoom) { - chatDirectRoomAccessService.getAccessibleMember(room, user); - } - } else { - getAccessibleRoomMember(room, userId); - } + chatRoomAccessService.ensureMuteAccess(room, user); Boolean isMuted = notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId( NotificationTargetType.CHAT_ROOM, roomId, @@ -444,7 +433,7 @@ public void updateChatRoomName(Integer userId, Integer roomId, ChatRoomNameUpdat ChatRoom room = chatRoomRepository.findById(roomId) .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); - ChatRoomMember roomMember = getAccessibleRoomMember(room, userId); + ChatRoomMember roomMember = chatRoomAccessService.getAccessibleMember(room, userId); roomMember.updateCustomRoomName(normalizeCustomRoomName(request.roomName())); } @@ -978,27 +967,6 @@ private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); } - private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) { - if (room.isClubGroupRoom()) { - ClubMember member = clubMemberRepository.getByClubIdAndUserId(room.getClub().getId(), userId); - ensureRoomMember(room, member.getUser(), member.getCreatedAt()); - return getRoomMember(room.getId(), userId); - } - - if (room.isDirectRoom()) { - User user = userRepository.getById(userId); - return chatDirectRoomAccessService.getAccessibleMember(room, user); - } - - ChatRoomMember member = getRoomMember(room.getId(), userId); - - if (member.hasLeft()) { - throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); - } - - return member; - } - private void ensureRoomMember(ChatRoom room, User user, LocalDateTime joinedAt) { chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()) .ifPresentOrElse(member -> { diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java index dba00c64..66cdfb74 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java @@ -54,6 +54,7 @@ import gg.agit.konect.domain.chat.service.ChatDirectRoomAccessService; import gg.agit.konect.domain.chat.service.ChatMessagePageResolver; import gg.agit.konect.domain.chat.service.ChatPresenceService; +import gg.agit.konect.domain.chat.service.ChatRoomAccessService; import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; import gg.agit.konect.domain.chat.service.ChatRoomSummaryService; import gg.agit.konect.domain.chat.service.ChatSearchService; @@ -136,6 +137,13 @@ void setUp() { clubMemberRepository, chatRoomSystemAdminService ); + ChatRoomAccessService chatRoomAccessService = new ChatRoomAccessService( + chatRoomMemberRepository, + userRepository, + chatRoomMembershipService, + chatRoomSystemAdminService, + chatDirectRoomAccessService + ); chatService = new ChatService( chatRoomRepository, chatRoomQueryRepository, @@ -150,6 +158,7 @@ void setUp() { chatRoomSummaryService, chatSearchService, chatMessagePageResolver, + chatRoomAccessService, chatRoomSystemAdminService, chatDirectRoomAccessService, notificationService, From bff0719a8ba496d6466266f8e6b9832ca03b3729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:22:48 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B2=80=EC=A6=9D=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A1=B0=ED=9A=8C=20=EC=9E=AC=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 뮤트 접근 검증에서 이미 조회한 User를 재사용해 불필요한 getById 호출을 피함 - 일반 room 접근 검증은 userId 기반으로 유지해 기존 테스트 스텁과 정책을 보존 - group 방 이름 변경 경로에서 불필요한 사용자 조회가 발생하지 않도록 접근 검증 분기를 정리 --- .../konect/domain/chat/service/ChatRoomAccessService.java | 8 ++++++-- .../konect/unit/domain/chat/service/ChatServiceTest.java | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java index 39996f89..bde5ea13 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor -@Transactional +@Transactional(readOnly = true) public class ChatRoomAccessService { private final ChatRoomMemberRepository chatRoomMemberRepository; @@ -42,12 +42,16 @@ public ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId) { return member; } + public ChatRoomMember getAccessibleMember(ChatRoom room, User user) { + return getAccessibleMember(room, user.getId()); + } + public void ensureMuteAccess(ChatRoom room, User user) { if (room.isDirectRoom() && user.isAdmin() && chatRoomSystemAdminService.isSystemAdminRoom(room.getId())) { return; } - getAccessibleMember(room, user.getId()); + getAccessibleMember(room, user); } private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java index 66cdfb74..dd313533 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java @@ -469,6 +469,7 @@ void toggleMuteTogglesFromUnmutedToMuted() { // then assertThat(response.isMuted()).isTrue(); assertThat(setting.getIsMuted()).isTrue(); + verify(userRepository, times(1)).getById(userId); verify(notificationMuteSettingRepository).save(setting); } From 39c9ece5ea12828a8e42400bc29a0f03c185df26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:27:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20User=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B2=80=EC=A6=9D=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9E=AC=EC=A1=B0=ED=9A=8C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 받은 User 객체로 direct 방 접근 검증을 수행해 중복 사용자 조회를 피함 - club/group 접근 검증은 기존 userId 기반 정책과 예외 처리를 동일하게 유지 - 뮤트 접근 검증에서 사용자 조회 재사용 의도를 코드 경로에 직접 반영 --- .../chat/service/ChatRoomAccessService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java index bde5ea13..e73d78ee 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -43,7 +43,22 @@ public ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId) { } public ChatRoomMember getAccessibleMember(ChatRoom room, User user) { - return getAccessibleMember(room, user.getId()); + Integer userId = user.getId(); + + if (room.isClubGroupRoom()) { + chatRoomMembershipService.ensureClubRoomMember(room.getId(), userId); + return getRoomMember(room.getId(), userId); + } + + if (room.isDirectRoom()) { + return chatDirectRoomAccessService.getAccessibleMember(room, user); + } + + ChatRoomMember member = getRoomMember(room.getId(), userId); + if (member.hasLeft()) { + throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); + } + return member; } public void ensureMuteAccess(ChatRoom room, User user) { From b7c60d422c2bae30d544f3dc76976c3d07bb020e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:32:07 +0900 Subject: [PATCH 4/7] =?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 --- .../agit/konect/unit/domain/chat/service/ChatServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java index d2a10bf0..7d4a16d5 100644 --- a/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java @@ -60,8 +60,8 @@ import gg.agit.konect.domain.chat.service.ChatRoomCreationService; import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; import gg.agit.konect.domain.chat.service.ChatRoomSummaryService; -import gg.agit.konect.domain.chat.service.ChatSearchService; import gg.agit.konect.domain.chat.service.ChatRoomSystemAdminService; +import gg.agit.konect.domain.chat.service.ChatSearchService; import gg.agit.konect.domain.chat.service.ChatService; import gg.agit.konect.domain.club.model.Club; import gg.agit.konect.domain.club.model.ClubMember; From 9d8733ad3f3d8618ff9b10b18cc530d266f8f8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:34:28 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20=EB=A9=A4=EB=B2=84=20=EA=B2=80=EC=A6=9D=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - userId 기반 호출과 User 기반 호출이 같은 방 유형 분기를 공유하도록 정리 - direct 방에서만 User 재사용 여부가 달라지는 조건을 helper 안으로 모아 불필요한 중복 분기를 줄임 - 기존 접근 검증 정책과 direct 방 사용자 재조회 회피 동작은 그대로 유지 --- .../chat/service/ChatRoomAccessService.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java index e73d78ee..05f5d8ac 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -25,32 +25,21 @@ public class ChatRoomAccessService { private final ChatDirectRoomAccessService chatDirectRoomAccessService; public ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId) { - if (room.isClubGroupRoom()) { - chatRoomMembershipService.ensureClubRoomMember(room.getId(), userId); - return getRoomMember(room.getId(), userId); - } - - if (room.isDirectRoom()) { - User user = userRepository.getById(userId); - return chatDirectRoomAccessService.getAccessibleMember(room, user); - } - - ChatRoomMember member = getRoomMember(room.getId(), userId); - if (member.hasLeft()) { - throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS); - } - return member; + return getAccessibleMember(room, userId, null); } public ChatRoomMember getAccessibleMember(ChatRoom room, User user) { - Integer userId = user.getId(); + return getAccessibleMember(room, user.getId(), user); + } + private ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId, User directRoomUser) { if (room.isClubGroupRoom()) { chatRoomMembershipService.ensureClubRoomMember(room.getId(), userId); return getRoomMember(room.getId(), userId); } if (room.isDirectRoom()) { + User user = directRoomUser != null ? directRoomUser : userRepository.getById(userId); return chatDirectRoomAccessService.getAccessibleMember(room, user); } From 647a89e49648f23dd67142382d13c77410a30b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:47:12 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9F=BD=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20=EC=A0=91=EA=B7=BC=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=9E=AC=EC=A1=B0=ED=9A=8C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 조회한 ChatRoom을 가진 접근 검증 경로에서는 roomId 기반 재조회 대신 ChatRoom 기반 멤버 보장 경로를 사용합니다. - 기존 roomId 기반 API는 유지해 다른 호출부 호환성을 보존합니다. - 재조회 없이 멤버 보장이 이뤄지는 회귀 테스트를 추가해 같은 성능 회귀가 반복되지 않게 했습니다. --- .../chat/service/ChatRoomAccessService.java | 2 +- .../service/ChatRoomMembershipService.java | 5 +++++ .../ChatRoomMembershipServiceTest.java | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java index 05f5d8ac..912cec17 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -34,7 +34,7 @@ public ChatRoomMember getAccessibleMember(ChatRoom room, User user) { private ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId, User directRoomUser) { if (room.isClubGroupRoom()) { - chatRoomMembershipService.ensureClubRoomMember(room.getId(), userId); + chatRoomMembershipService.ensureClubRoomMember(room, userId); return getRoomMember(room.getId(), userId); } 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 012e11df..987f1386 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 @@ -125,6 +125,11 @@ public void updateDirectRoomLastReadAt(Integer roomId, User user, LocalDateTime public void ensureClubRoomMember(Integer roomId, Integer userId) { ChatRoom room = chatRoomRepository.findById(roomId) .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + ensureClubRoomMember(room, userId); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void ensureClubRoomMember(ChatRoom room, Integer userId) { if (!room.isGroupRoom() || room.getClub() == null) { throw CustomException.of(NOT_FOUND_CHAT_ROOM); } 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 c8eed343..41afc706 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 @@ -418,6 +418,28 @@ void ensureClubRoomMemberCreatesOrUpdatesMemberFromClubMemberBaseline() { verify(chatRoomMemberRepository).save(any(ChatRoomMember.class)); } + @Test + @DisplayName("ensureClubRoomMember는 이미 조회한 club group room을 재조회하지 않고 멤버를 보장한다") + void ensureClubRoomMemberUsesProvidedRoomWithoutRefetch() { + // given + Club club = createClub(10); + ChatRoom room = createRoom(30, ChatType.CLUB_GROUP, LocalDateTime.of(2026, 4, 11, 9, 0)); + ReflectionTestUtils.setField(room, "club", club); + User user = createUser(20, "동아리원", UserRole.USER); + ClubMember clubMember = createClubMember(club, user, LocalDateTime.of(2026, 4, 11, 10, 0)); + + given(clubMemberRepository.getByClubIdAndUserId(club.getId(), user.getId())).willReturn(clubMember); + given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId())) + .willReturn(Optional.empty()); + + // when + chatRoomMembershipService.ensureClubRoomMember(room, user.getId()); + + // then + verify(chatRoomRepository, never()).findById(any()); + verify(chatRoomMemberRepository).save(any(ChatRoomMember.class)); + } + @Test @DisplayName("updateLastReadAt는 저장된 값이 더 오래된 경우에만 갱신 쿼리를 위임한다") void updateLastReadAtDelegatesConditionalUpdate() { From 100d6d97a6eee02d2843ccd7adbf3f868699c92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 28 Apr 2026 14:53:53 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B2=80=EC=A6=9D=20=EB=B6=84=EA=B8=B0=20?= =?UTF-8?q?=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nullable User 인자로 직접 채팅방 재조회 여부를 표현하던 흐름을 제거했습니다. - direct 방과 non-direct 방 분기를 public 진입점에서 명확히 나눠 읽기 쉽게 정리했습니다. - 기존 접근 검증 정책과 재조회 제거 동작은 유지했습니다. --- .../chat/service/ChatRoomAccessService.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java index 912cec17..d7dc78b0 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatRoomAccessService.java @@ -25,24 +25,28 @@ public class ChatRoomAccessService { private final ChatDirectRoomAccessService chatDirectRoomAccessService; public ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId) { - return getAccessibleMember(room, userId, null); + if (room.isDirectRoom()) { + User user = userRepository.getById(userId); + return chatDirectRoomAccessService.getAccessibleMember(room, user); + } + + return getAccessibleNonDirectMember(room, userId); } public ChatRoomMember getAccessibleMember(ChatRoom room, User user) { - return getAccessibleMember(room, user.getId(), user); + if (room.isDirectRoom()) { + return chatDirectRoomAccessService.getAccessibleMember(room, user); + } + + return getAccessibleNonDirectMember(room, user.getId()); } - private ChatRoomMember getAccessibleMember(ChatRoom room, Integer userId, User directRoomUser) { + private ChatRoomMember getAccessibleNonDirectMember(ChatRoom room, Integer userId) { if (room.isClubGroupRoom()) { chatRoomMembershipService.ensureClubRoomMember(room, userId); return getRoomMember(room.getId(), userId); } - if (room.isDirectRoom()) { - User user = directRoomUser != null ? directRoomUser : userRepository.getById(userId); - return chatDirectRoomAccessService.getAccessibleMember(room, user); - } - ChatRoomMember member = getRoomMember(room.getId(), userId); if (member.hasLeft()) { throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS);