From fdd07da9aaef18e28c3ed5449802a34c9cd8df68 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:16:30 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20=EC=A1=B0=EB=A6=BD=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatService에 남아 있던 direct, SYSTEM_ADMIN, club group, group 메시지 응답 조립을 ChatMessageReadService로 이동 - readAt 갱신과 presence 기록 순서는 기존 서비스 흐름에 남겨 조회 side effect 순서를 유지 - 메시지 조회 단위/통합 테스트를 재실행해 가시 범위와 페이징 응답 동작을 보존 --- .../chat/service/ChatMessageReadService.java | 295 ++++++++++++++++++ .../domain/chat/service/ChatService.java | 246 +-------------- .../domain/chat/service/ChatServiceTest.java | 8 + 3 files changed, 308 insertions(+), 241 deletions(-) create mode 100644 src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java new file mode 100644 index 00000000..71968a84 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -0,0 +1,295 @@ +package gg.agit.konect.domain.chat.service; + +import static gg.agit.konect.domain.chat.service.ChatRoomMembershipService.SYSTEM_ADMIN_ID; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import gg.agit.konect.domain.chat.dto.ChatMessageDetailResponse; +import gg.agit.konect.domain.chat.dto.ChatMessagePageResponse; +import gg.agit.konect.domain.chat.model.ChatMessage; +import gg.agit.konect.domain.chat.model.ChatRoom; +import gg.agit.konect.domain.chat.model.ChatRoomMember; +import gg.agit.konect.domain.chat.repository.ChatMessageRepository; +import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository; +import gg.agit.konect.domain.user.model.User; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ChatMessageReadService { + + private final ChatMessageRepository chatMessageRepository; + private final ChatRoomMemberRepository chatRoomMemberRepository; + private final ChatRoomSystemAdminService chatRoomSystemAdminService; + private final ChatDirectRoomAccessService chatDirectRoomAccessService; + + public ChatMessagePageResponse getDirectChatRoomMessages( + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt + ) { + Integer roomId = chatRoom.getId(); + List members = chatRoomMemberRepository.findByChatRoomId(roomId); + LocalDateTime visibleMessageFrom = + chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); + + List sortedReadBaselines = toSortedReadBaselines(members); + + return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, + visibleMessageFrom, sortedReadBaselines, null); + } + + public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt + ) { + Integer roomId = chatRoom.getId(); + List members = chatRoomMemberRepository.findByChatRoomId(roomId); + LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); + + List sortedReadBaselines = toAdminChatReadBaselines(members); + Integer maskedAdminId = getMaskedAdminId(user, chatRoom); + + return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, + visibleMessageFrom, sortedReadBaselines, maskedAdminId); + } + + public ChatMessagePageResponse getClubMessagesByRoom( + ChatRoom room, + Integer userId, + Integer page, + Integer limit + ) { + Integer roomId = room.getId(); + PageRequest pageable = PageRequest.of(page - 1, limit); + long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); + Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); + List messages = messagePage.getContent(); + List members = chatRoomMemberRepository.findByChatRoomId(roomId); + List sortedReadBaselines = toSortedReadBaselines(members); + + List responseMessages = messages.stream() + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + return new ChatMessagePageResponse( + totalCount, + responseMessages.size(), + totalPage, + page, + room.getClub().getId(), + responseMessages + ); + } + + public ChatMessagePageResponse getGroupMessagesByRoom( + Integer roomId, + Integer userId, + Integer page, + Integer limit + ) { + PageRequest pageable = PageRequest.of(page - 1, limit); + long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); + Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); + List messages = messagePage.getContent(); + List members = chatRoomMemberRepository.findByChatRoomId(roomId); + List sortedReadBaselines = toSortedReadBaselines(members); + + List responseMessages = messages.stream() + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + return new ChatMessagePageResponse( + totalCount, + responseMessages.size(), + totalPage, + page, + null, + responseMessages + ); + } + + private ChatMessagePageResponse buildDirectChatRoomMessages( + User user, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt, + LocalDateTime visibleMessageFrom, + List sortedReadBaselines, + Integer maskedAdminId + ) { + PageRequest pageable = PageRequest.of(page - 1, limit); + Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); + + List responseMessages = messages.getContent().stream() + .map(message -> { + Integer senderId = maskedAdminId != null + ? resolveDirectSenderId(message, maskedAdminId) + : message.getSender().getId(); + boolean isMine = maskedAdminId != null + ? shouldDisplayAsOwnMessage(user, message, true) + : message.isSentBy(user.getId()); + boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + senderId, + null, + message.getContent(), + message.getCreatedAt(), + isRead, + unreadCount, + isMine + ); + }) + .toList(); + + return new ChatMessagePageResponse( + messages.getTotalElements(), + messages.getNumberOfElements(), + messages.getTotalPages(), + messages.getNumber() + 1, + null, + responseMessages + ); + } + + private List toSortedReadBaselines(List members) { + return members.stream() + .map(ChatRoomMember::getLastReadAt) + .sorted() + .toList(); + } + + private List toAdminChatReadBaselines(List members) { + LocalDateTime adminLastReadAt = null; + LocalDateTime userLastReadAt = null; + + for (ChatRoomMember member : members) { + if (member.getUser().isAdmin()) { + if (adminLastReadAt == null || member.getLastReadAt().isAfter(adminLastReadAt)) { + adminLastReadAt = member.getLastReadAt(); + } + } else { + userLastReadAt = member.getLastReadAt(); + } + } + + List baselines = new ArrayList<>(); + if (adminLastReadAt != null) { + baselines.add(adminLastReadAt); + } + if (userLastReadAt != null) { + baselines.add(userLastReadAt); + } + baselines.sort(Comparator.naturalOrder()); + return baselines; + } + + private int countUnreadSince(LocalDateTime messageCreatedAt, List sortedReadBaselines) { + int left = 0; + int right = sortedReadBaselines.size(); + + while (left < right) { + int mid = (left + right) >>> 1; + LocalDateTime baseline = sortedReadBaselines.get(mid); + + if (baseline.isBefore(messageCreatedAt)) { + left = mid + 1; + } else { + right = mid; + } + } + + return left; + } + + private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members) { + ChatRoomMember systemAdminMember = chatRoomSystemAdminService.findSystemAdminMember(members); + return systemAdminMember != null ? systemAdminMember.getVisibleMessageFrom() : null; + } + + private boolean shouldDisplayAsOwnMessage( + User currentUser, + ChatMessage message, + boolean isAdminViewingSystemRoom + ) { + if (isAdminViewingSystemRoom) { + return message.getSender().isAdmin(); + } + return message.isSentBy(currentUser.getId()); + } + + private Integer resolveDirectSenderId(ChatMessage message, Integer maskedAdminId) { + if (maskedAdminId != null && message.getSender().isAdmin()) { + return maskedAdminId; + } + return message.getSender().getId(); + } + + private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { + if (user.isAdmin()) { + return null; + } + + List memberResults = chatRoomMemberRepository.findRoomMemberIdsByChatRoomIds( + List.of(chatRoom.getId()) + ); + List memberInfos = memberResults.stream() + .map(row -> new MemberInfo((Integer)row[1], (LocalDateTime)row[2])) + .toList(); + + boolean hasSystemAdmin = memberInfos.stream() + .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); + + if (hasSystemAdmin) { + return SYSTEM_ADMIN_ID; + } + + return null; + } + + private record MemberInfo(Integer userId, LocalDateTime createdAt) { + } +} 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..b69db8dc 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 @@ -82,6 +82,7 @@ public class ChatService { private final ChatRoomMembershipService chatRoomMembershipService; private final ChatRoomSummaryService chatRoomSummaryService; private final ChatSearchService chatSearchService; + private final ChatMessageReadService chatMessageReadService; private final ChatMessagePageResolver chatMessagePageResolver; private final ChatRoomSystemAdminService chatRoomSystemAdminService; private final ChatDirectRoomAccessService chatDirectRoomAccessService; @@ -360,25 +361,25 @@ public ChatMessagePageResponse getMessages( if (isAdminViewingSystemRoom) { chatRoomMembershipService.updateLastReadAt(roomId, SYSTEM_ADMIN_ID, readAt); recordPresenceSafely(roomId, userId); - return getAdminSystemDirectChatRoomMessages(user, room, roomId, page, limit, readAt); + return chatMessageReadService.getAdminSystemDirectChatRoomMessages(user, room, page, limit, readAt); } chatRoomMembershipService.updateDirectRoomLastReadAt(roomId, user, readAt, room); recordPresenceSafely(roomId, userId); - return getDirectChatRoomMessages(userId, roomId, page, limit, readAt); + return chatMessageReadService.getDirectChatRoomMessages(user, room, page, limit, readAt); } if (room.isClubGroupRoom()) { chatRoomMembershipService.ensureClubRoomMember(roomId, userId); chatRoomMembershipService.updateLastReadAt(roomId, userId, readAt); recordPresenceSafely(roomId, userId); - return getClubMessagesByRoomId(roomId, userId, page, limit); + return chatMessageReadService.getClubMessagesByRoom(room, userId, page, limit); } getAccessibleRoomMember(room, userId); chatRoomMembershipService.updateLastReadAt(roomId, userId, readAt); recordPresenceSafely(roomId, userId); - return getGroupMessagesByRoomId(roomId, userId, page, limit); + return chatMessageReadService.getGroupMessagesByRoom(roomId, userId, page, limit); } @Transactional @@ -572,89 +573,6 @@ private List getGroupChatRooms(Integer userId) { .toList(); } - private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId - ) { - PageRequest pageable = PageRequest.of(page - 1, limit); - Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); - - List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); - - return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages - ); - } - - private ChatMessagePageResponse getDirectChatRoomMessages( - Integer userId, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt - ) { - ChatRoom chatRoom = getDirectRoom(roomId); - User user = userRepository.getById(userId); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); - - List sortedReadBaselines = toSortedReadBaselines(members); - - return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); - } - - private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt - ) { - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); - - List sortedReadBaselines = toAdminChatReadBaselines(members); - Integer maskedAdminId = getMaskedAdminId(user, chatRoom); - - return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); - } - private ChatMessageDetailResponse sendDirectMessage( Integer userId, Integer roomId, @@ -710,48 +628,6 @@ private ChatMessageDetailResponse sendDirectMessage( ); } - private ChatMessagePageResponse getClubMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit - ) { - ChatRoom room = getClubRoom(roomId); - - PageRequest pageable = PageRequest.of(page - 1, limit); - long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); - Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); - List messages = messagePage.getContent(); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - List sortedReadBaselines = toSortedReadBaselines(members); - - List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; - return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages - ); - } - private ChatMessageDetailResponse sendClubMessageByRoomId(Integer roomId, Integer userId, String content) { ChatRoom room = getClubRoom(roomId); ClubMember member = clubMemberRepository.getByClubIdAndUserId(room.getClub().getId(), userId); @@ -788,48 +664,6 @@ private ChatMessageDetailResponse sendClubMessageByRoomId(Integer roomId, Intege ); } - private ChatMessagePageResponse getGroupMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit - ) { - chatRoomRepository.getById(roomId); - - PageRequest pageable = PageRequest.of(page - 1, limit); - long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); - Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); - List messages = messagePage.getContent(); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - List sortedReadBaselines = toSortedReadBaselines(members); - - List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; - return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages - ); - } - private ChatMessageDetailResponse sendGroupMessageByRoomId(Integer roomId, Integer userId, String content) { ChatRoom room = chatRoomRepository.findById(roomId) .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); @@ -931,28 +765,6 @@ private Map getUnreadCountMap(List chatRoomIds, Integ )); } - private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { - if (user.isAdmin()) { - return null; - } - - List memberResults = chatRoomMemberRepository.findRoomMemberIdsByChatRoomIds( - List.of(chatRoom.getId()) - ); - List memberInfos = memberResults.stream() - .map(row -> new MemberInfo((Integer)row[1], (LocalDateTime)row[2])) - .toList(); - - boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); - - if (hasSystemAdmin) { - return SYSTEM_ADMIN_ID; - } - - return null; - } - private void publishAdminChatEventIfNeeded(boolean isSystemAdminRoom, User sender, String content) { if (isSystemAdminRoom && !sender.isAdmin()) { eventPublisher.publishEvent(AdminChatReceivedEvent.of(sender.getId(), sender.getName(), content)); @@ -1073,31 +885,6 @@ private List toSortedReadBaselines(List members) .toList(); } - private List toAdminChatReadBaselines(List members) { - LocalDateTime adminLastReadAt = null; - LocalDateTime userLastReadAt = null; - - for (ChatRoomMember member : members) { - if (member.getUser().isAdmin()) { - if (adminLastReadAt == null || member.getLastReadAt().isAfter(adminLastReadAt)) { - adminLastReadAt = member.getLastReadAt(); - } - } else { - userLastReadAt = member.getLastReadAt(); - } - } - - List baselines = new ArrayList<>(); - if (adminLastReadAt != null) { - baselines.add(adminLastReadAt); - } - if (userLastReadAt != null) { - baselines.add(userLastReadAt); - } - baselines.sort(Comparator.naturalOrder()); - return baselines; - } - private int countUnreadSince(LocalDateTime messageCreatedAt, List sortedReadBaselines) { int left = 0; int right = sortedReadBaselines.size(); @@ -1140,29 +927,6 @@ private Map getRoomUnreadCountMap(List roomIds, Integ return unreadCountMap; } - private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members) { - ChatRoomMember systemAdminMember = chatRoomSystemAdminService.findSystemAdminMember(members); - return systemAdminMember != null ? systemAdminMember.getVisibleMessageFrom() : null; - } - - private boolean shouldDisplayAsOwnMessage( - User currentUser, - ChatMessage message, - boolean isAdminViewingSystemRoom - ) { - if (isAdminViewingSystemRoom) { - return message.getSender().isAdmin(); - } - return message.isSentBy(currentUser.getId()); - } - - private Integer resolveDirectSenderId(ChatMessage message, Integer maskedAdminId) { - if (maskedAdminId != null && message.getSender().isAdmin()) { - return maskedAdminId; - } - return message.getSender().getId(); - } - private ChatRoomMember findRoomMember(List members, Integer userId) { return members.stream() .filter(member -> member.getUserId().equals(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 dba00c64..d6f8d08f 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 @@ -52,6 +52,7 @@ import gg.agit.konect.domain.chat.repository.ChatRoomQueryRepository; import gg.agit.konect.domain.chat.repository.ChatRoomRepository; import gg.agit.konect.domain.chat.service.ChatDirectRoomAccessService; +import gg.agit.konect.domain.chat.service.ChatMessageReadService; import gg.agit.konect.domain.chat.service.ChatMessagePageResolver; import gg.agit.konect.domain.chat.service.ChatPresenceService; import gg.agit.konect.domain.chat.service.ChatRoomMembershipService; @@ -136,6 +137,12 @@ void setUp() { clubMemberRepository, chatRoomSystemAdminService ); + ChatMessageReadService chatMessageReadService = new ChatMessageReadService( + chatMessageRepository, + chatRoomMemberRepository, + chatRoomSystemAdminService, + chatDirectRoomAccessService + ); chatService = new ChatService( chatRoomRepository, chatRoomQueryRepository, @@ -149,6 +156,7 @@ void setUp() { chatRoomMembershipService, chatRoomSummaryService, chatSearchService, + chatMessageReadService, chatMessagePageResolver, chatRoomSystemAdminService, chatDirectRoomAccessService, From 2a3b87ddce50af8d75c545fefff385711b2ebc5a 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 11:24:24 +0900 Subject: [PATCH 2/6] =?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 --- .../chat/service/ChatMessageReadService.java | 216 +++---- .../domain/chat/service/ChatService.java | 578 +++++++++--------- .../domain/chat/service/ChatServiceTest.java | 466 +++++++------- 3 files changed, 630 insertions(+), 630 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java index eabdc4bd..a31fe5e8 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -34,29 +34,29 @@ public class ChatMessageReadService { @Transactional public ChatMessagePageResponse getDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt ) { Integer roomId = chatRoom.getId(); List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); + chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); List sortedReadBaselines = toSortedReadBaselines(members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); + visibleMessageFrom, sortedReadBaselines, null); } public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt ) { Integer roomId = chatRoom.getId(); List members = chatRoomMemberRepository.findByChatRoomId(roomId); @@ -66,14 +66,14 @@ public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( Integer maskedAdminId = getMaskedAdminId(user, members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); + visibleMessageFrom, sortedReadBaselines, maskedAdminId); } public ChatMessagePageResponse getClubMessagesByRoom( - ChatRoom room, - Integer userId, - Integer page, - Integer limit + ChatRoom room, + Integer userId, + Integer page, + Integer limit ) { Integer roomId = room.getId(); PageRequest pageable = PageRequest.of(page - 1, limit); @@ -84,37 +84,37 @@ public ChatMessagePageResponse getClubMessagesByRoom( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + room.getClub().getId(), + responseMessages ); } public ChatMessagePageResponse getGroupMessagesByRoom( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { PageRequest pageable = PageRequest.of(page - 1, limit); long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); @@ -124,83 +124,83 @@ public ChatMessagePageResponse getGroupMessagesByRoom( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + null, + responseMessages ); } private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId + User user, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt, + LocalDateTime visibleMessageFrom, + List sortedReadBaselines, + Integer maskedAdminId ) { PageRequest pageable = PageRequest.of(page - 1, limit); Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); + .map(message -> { + Integer senderId = maskedAdminId != null + ? resolveDirectSenderId(message, maskedAdminId) + : message.getSender().getId(); + boolean isMine = maskedAdminId != null + ? shouldDisplayAsOwnMessage(user, message, true) + : message.isSentBy(user.getId()); + boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + senderId, + null, + message.getContent(), + message.getCreatedAt(), + isRead, + unreadCount, + isMine + ); + }) + .toList(); return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages + messages.getTotalElements(), + messages.getNumberOfElements(), + messages.getTotalPages(), + messages.getNumber() + 1, + null, + responseMessages ); } private List toSortedReadBaselines(List members) { return members.stream() - .map(ChatRoomMember::getLastReadAt) - .sorted() - .toList(); + .map(ChatRoomMember::getLastReadAt) + .sorted() + .toList(); } private List toAdminChatReadBaselines(List members) { @@ -252,9 +252,9 @@ private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members) { } boolean hasSystemAdmin = members.stream() - .map(ChatRoomMember::getUserId) - .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); + .map(ChatRoomMember::getUserId) + .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return SYSTEM_ADMIN_ID; 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 dbd91945..18e5e40f 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 @@ -95,11 +95,11 @@ public ChatRoomResponse createOrGetChatRoom(Integer currentUserId, ChatRoomCreat } ChatRoom chatRoom = chatRoomRepository.findByTwoUsers( - currentUser.getId(), - targetUser.getId(), - ChatType.DIRECT - ) - .orElseGet(() -> chatRoomRepository.save(ChatRoom.directOf())); + currentUser.getId(), + targetUser.getId(), + ChatType.DIRECT + ) + .orElseGet(() -> chatRoomRepository.save(ChatRoom.directOf())); LocalDateTime joinedAt = Objects.requireNonNull(chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null"); ensureDirectRoomRequester(chatRoom, currentUser, joinedAt); @@ -110,19 +110,19 @@ public ChatRoomResponse createOrGetChatRoom(Integer currentUserId, ChatRoomCreat private ChatRoomResponse getOrCreateSystemAdminChatRoomForUser(User targetUser, User adminUser) { ChatRoom chatRoom = chatRoomRepository.findByTwoUsers(SYSTEM_ADMIN_ID, targetUser.getId(), ChatType.DIRECT) - .orElseGet(() -> { - ChatRoom newRoom = chatRoomRepository.save(ChatRoom.directOf()); - User systemAdmin = userRepository.getById(SYSTEM_ADMIN_ID); - LocalDateTime joinedAt = Objects.requireNonNull( - newRoom.getCreatedAt(), "chatRoom.createdAt must not be null" - ); - ensureRoomMember(newRoom, systemAdmin, joinedAt); - ensureRoomMember(newRoom, targetUser, joinedAt); - return newRoom; - }); + .orElseGet(() -> { + ChatRoom newRoom = chatRoomRepository.save(ChatRoom.directOf()); + User systemAdmin = userRepository.getById(SYSTEM_ADMIN_ID); + LocalDateTime joinedAt = Objects.requireNonNull( + newRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + ); + ensureRoomMember(newRoom, systemAdmin, joinedAt); + ensureRoomMember(newRoom, targetUser, joinedAt); + return newRoom; + }); LocalDateTime joinedAt = Objects.requireNonNull( - chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" ); ensureDirectRoomRequester(chatRoom, adminUser, joinedAt); @@ -132,7 +132,7 @@ private ChatRoomResponse getOrCreateSystemAdminChatRoomForUser(User targetUser, @Transactional public ChatRoomResponse createOrGetAdminChatRoom(Integer currentUserId) { User adminUser = userRepository.findFirstByRoleAndDeletedAtIsNullOrderByIdAsc(UserRole.ADMIN) - .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); return createOrGetChatRoom(currentUserId, new ChatRoomCreateRequest(adminUser.getId())); } @@ -142,9 +142,9 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat User creator = userRepository.getById(currentUserId); List distinctUserIds = request.userIds().stream() - .distinct() - .filter(id -> !id.equals(currentUserId)) - .toList(); + .distinct() + .filter(id -> !id.equals(currentUserId)) + .toList(); if (distinctUserIds.isEmpty()) { throw CustomException.of(CANNOT_CREATE_CHAT_ROOM_WITH_SELF); @@ -157,7 +157,7 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.groupOf()); LocalDateTime joinedAt = Objects.requireNonNull( - chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" ); List members = new ArrayList<>(); @@ -171,7 +171,7 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat @Transactional public void leaveChatRoom(Integer userId, Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); if (room.isClubGroupRoom()) { throw CustomException.of(CANNOT_LEAVE_GROUP_CHAT_ROOM); @@ -189,7 +189,7 @@ public void leaveChatRoom(Integer userId, Integer roomId) { @Transactional public void kickMember(Integer requesterId, Integer roomId, Integer targetUserId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); validateGroupRoomForKick(room); validateNotSelfKick(requesterId, targetUserId); @@ -209,10 +209,10 @@ public ChatRoomsSummaryResponse getChatRooms(Integer userId) { List groupRooms = getGroupChatRooms(userId); List rooms = chatRoomSummaryService.summarizeChatRooms( - userId, - directRooms, - clubRooms, - groupRooms + userId, + directRooms, + clubRooms, + groupRooms ); return new ChatRoomsSummaryResponse(rooms); @@ -221,15 +221,15 @@ public ChatRoomsSummaryResponse getChatRooms(Integer userId) { public ChatSearchResponse searchChats(Integer userId, String keyword, Integer page, Integer limit) { AccessibleChatRooms accessibleChatRooms = getAccessibleChatRooms(userId); return chatSearchService.search(userId, keyword, accessibleChatRooms.rooms(), - accessibleChatRooms.defaultRoomNameMap(), page, limit); + accessibleChatRooms.defaultRoomNameMap(), page, limit); } public ChatInvitableUsersResponse getInvitableUsers( - Integer userId, - String query, - ChatInviteSortBy sortBy, - Integer page, - Integer limit + Integer userId, + String query, + ChatInviteSortBy sortBy, + Integer page, + Integer limit ) { return chatInviteService.getInvitableUsers(userId, query, sortBy, page, limit); } @@ -241,10 +241,10 @@ public ChatMessagePageResponse getMessages(Integer userId, Integer roomId, Integ @Transactional public ChatMessagePageResponse getMessages( - Integer userId, Integer roomId, Integer page, Integer limit, Integer messageId + Integer userId, Integer roomId, Integer page, Integer limit, Integer messageId ) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); User user = userRepository.getById(userId); if (messageId != null) { @@ -255,7 +255,7 @@ public ChatMessagePageResponse getMessages( if (room.isDirectRoom()) { boolean isAdminViewingSystemRoom = user.isAdmin() - && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); + && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); if (isAdminViewingSystemRoom) { chatRoomMembershipService.updateLastReadAt(roomId, SYSTEM_ADMIN_ID, readAt); recordPresenceSafely(roomId, userId); @@ -288,7 +288,7 @@ public ChatMessageDetailResponse sendMessage(Integer userId, Integer roomId, Cha @Transactional public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); User user = userRepository.getById(userId); if (room.isClubGroupRoom()) { @@ -297,7 +297,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { } else if (room.isDirectRoom()) { // 어드민이 SYSTEM_ADMIN 방에 접근하는 경우는 멤버십 체크를 건너뜀 boolean isAdminAccessingSystemAdminRoom = user.isAdmin() - && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); + && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); if (!isAdminAccessingSystemAdminRoom) { chatDirectRoomAccessService.getAccessibleMember(room, user); } @@ -305,24 +305,24 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { getAccessibleRoomMember(room, userId); } Boolean isMuted = notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId( - NotificationTargetType.CHAT_ROOM, - roomId, - userId - ) - .map(setting -> { - setting.toggleMute(); - notificationMuteSettingRepository.save(setting); - return setting.getIsMuted(); - }) - .orElseGet(() -> { - notificationMuteSettingRepository.save(NotificationMuteSetting.of( - NotificationTargetType.CHAT_ROOM, - roomId, - user, - true - )); - return true; - }); + NotificationTargetType.CHAT_ROOM, + roomId, + userId + ) + .map(setting -> { + setting.toggleMute(); + notificationMuteSettingRepository.save(setting); + return setting.getIsMuted(); + }) + .orElseGet(() -> { + notificationMuteSettingRepository.save(NotificationMuteSetting.of( + NotificationTargetType.CHAT_ROOM, + roomId, + user, + true + )); + return true; + }); return new ChatMuteResponse(isMuted); } @@ -330,7 +330,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { @Transactional public void updateChatRoomName(Integer userId, Integer roomId, ChatRoomNameUpdateRequest request) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); ChatRoomMember roomMember = getAccessibleRoomMember(room, userId); roomMember.updateCustomRoomName(normalizeCustomRoomName(request.roomName())); @@ -361,46 +361,46 @@ private List getDirectChatRooms(Integer userId) { } roomSummaries.add(new ChatRoomSummaryResponse( - chatRoom.getId(), - ChatType.DIRECT, - chatPartner.getName(), - chatPartner.getImageUrl(), - getVisibleLastMessageContent(chatRoom, currentMember), - getVisibleLastMessageSentAt(chatRoom, currentMember), - chatRoom.getCreatedAt(), - personalUnreadCountMap.getOrDefault(chatRoom.getId(), 0), - false + chatRoom.getId(), + ChatType.DIRECT, + chatPartner.getName(), + chatPartner.getImageUrl(), + getVisibleLastMessageContent(chatRoom, currentMember), + getVisibleLastMessageSentAt(chatRoom, currentMember), + chatRoom.getCreatedAt(), + personalUnreadCountMap.getOrDefault(chatRoom.getId(), 0), + false )); } roomSummaries.sort(Comparator - .comparing( - (ChatRoomSummaryResponse room) -> - room.lastSentAt() != null ? room.lastSentAt() : room.createdAt(), - Comparator.reverseOrder() - )); + .comparing( + (ChatRoomSummaryResponse room) -> + room.lastSentAt() != null ? room.lastSentAt() : room.createdAt(), + Comparator.reverseOrder() + )); return roomSummaries; } private List getAdminDirectChatRooms(Integer adminUserId) { List projections = chatRoomQueryRepository.findAdminChatRoomsOptimized( - SYSTEM_ADMIN_ID, adminUserId, UserRole.ADMIN, ChatType.DIRECT + SYSTEM_ADMIN_ID, adminUserId, UserRole.ADMIN, ChatType.DIRECT ); return projections.stream() - .map(projection -> new ChatRoomSummaryResponse( - projection.roomId(), - ChatType.DIRECT, - projection.nonAdminUserName(), - projection.nonAdminImageUrl(), - projection.lastMessage(), - projection.lastSentAt(), - projection.createdAt(), - projection.unreadCount().intValue(), - false - )) - .toList(); + .map(projection -> new ChatRoomSummaryResponse( + projection.roomId(), + ChatType.DIRECT, + projection.nonAdminUserName(), + projection.nonAdminImageUrl(), + projection.lastMessage(), + projection.lastSentAt(), + projection.createdAt(), + projection.unreadCount().intValue(), + false + )) + .toList(); } private List getClubChatRooms(Integer userId) { @@ -410,30 +410,30 @@ private List getClubChatRooms(Integer userId) { } List clubIds = memberships.stream() - .map(cm -> cm.getClub().getId()) - .toList(); + .map(cm -> cm.getClub().getId()) + .toList(); List rooms = chatRoomRepository.findByClubIds(new ArrayList<>(clubIds)) - .stream() - .filter(room -> room.getClub() != null) - .toList(); + .stream() + .filter(room -> room.getClub() != null) + .toList(); List roomIds = rooms.stream().map(ChatRoom::getId).toList(); Map unreadCountMap = getRoomUnreadCountMap(roomIds, userId); return rooms.stream() - .map(room -> new ChatRoomSummaryResponse( - room.getId(), - ChatType.CLUB_GROUP, - room.getClub().getName(), - room.getClub().getImageUrl(), - room.getLastMessageContent(), - room.getLastMessageSentAt(), - room.getCreatedAt(), - unreadCountMap.getOrDefault(room.getId(), 0), - false - )) - .toList(); + .map(room -> new ChatRoomSummaryResponse( + room.getId(), + ChatType.CLUB_GROUP, + room.getClub().getName(), + room.getClub().getImageUrl(), + room.getLastMessageContent(), + room.getLastMessageSentAt(), + room.getCreatedAt(), + unreadCountMap.getOrDefault(room.getId(), 0), + false + )) + .toList(); } private List getGroupChatRooms(Integer userId) { @@ -446,92 +446,92 @@ private List getGroupChatRooms(Integer userId) { Map unreadCountMap = getRoomUnreadCountMap(roomIds, userId); return rooms.stream() - .map(room -> new ChatRoomSummaryResponse( - room.getId(), - ChatType.GROUP, - DEFAULT_GROUP_ROOM_NAME, - null, - room.getLastMessageContent(), - room.getLastMessageSentAt(), - room.getCreatedAt(), - unreadCountMap.getOrDefault(room.getId(), 0), - false - )) - .toList(); + .map(room -> new ChatRoomSummaryResponse( + room.getId(), + ChatType.GROUP, + DEFAULT_GROUP_ROOM_NAME, + null, + room.getLastMessageContent(), + room.getLastMessageSentAt(), + room.getCreatedAt(), + unreadCountMap.getOrDefault(room.getId(), 0), + false + )) + .toList(); } private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId + User user, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt, + LocalDateTime visibleMessageFrom, + List sortedReadBaselines, + Integer maskedAdminId ) { PageRequest pageable = PageRequest.of(page - 1, limit); Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); + .map(message -> { + Integer senderId = maskedAdminId != null + ? resolveDirectSenderId(message, maskedAdminId) + : message.getSender().getId(); + boolean isMine = maskedAdminId != null + ? shouldDisplayAsOwnMessage(user, message, true) + : message.isSentBy(user.getId()); + boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + senderId, + null, + message.getContent(), + message.getCreatedAt(), + isRead, + unreadCount, + isMine + ); + }) + .toList(); return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages + messages.getTotalElements(), + messages.getNumberOfElements(), + messages.getTotalPages(), + messages.getNumber() + 1, + null, + responseMessages ); } private ChatMessagePageResponse getDirectChatRoomMessages( - Integer userId, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt + Integer userId, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt ) { ChatRoom chatRoom = getDirectRoom(roomId); User user = userRepository.getById(userId); List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); + chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); List sortedReadBaselines = toSortedReadBaselines(members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); + visibleMessageFrom, sortedReadBaselines, null); } private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt ) { List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); @@ -540,14 +540,14 @@ private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( Integer maskedAdminId = getMaskedAdminId(user, chatRoom); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); + visibleMessageFrom, sortedReadBaselines, maskedAdminId); } private ChatMessagePageResponse getClubMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { ChatRoom room = getClubRoom(roomId); @@ -559,37 +559,37 @@ private ChatMessagePageResponse getClubMessagesByRoomId( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + room.getClub().getId(), + responseMessages ); } private ChatMessagePageResponse getGroupMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { chatRoomRepository.getById(roomId); @@ -601,29 +601,29 @@ private ChatMessagePageResponse getGroupMessagesByRoomId( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + null, + responseMessages ); } @@ -632,20 +632,20 @@ private AccessibleChatRooms getAccessibleChatRooms(Integer userId) { List clubRooms = getClubChatRooms(userId); Map defaultRoomNameMap = chatRoomSummaryService.getDefaultRoomNameMap( - directRooms, - clubRooms + directRooms, + clubRooms ); List rooms = chatRoomSummaryService.summarizeSearchableRooms( - userId, - directRooms, - clubRooms + userId, + directRooms, + clubRooms ); return new AccessibleChatRooms(rooms, defaultRoomNameMap); } private ChatRoom getDirectRoom(Integer roomId) { ChatRoom chatRoom = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); if (!chatRoom.isDirectRoom()) { throw CustomException.of(NOT_FOUND_CHAT_ROOM); @@ -656,7 +656,7 @@ private ChatRoom getDirectRoom(Integer roomId) { private ChatRoom getClubRoom(Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); if (!room.isClubGroupRoom()) { throw CustomException.of(ApiResponseCode.NOT_FOUND_GROUP_CHAT_ROOM); } @@ -665,8 +665,8 @@ private ChatRoom getClubRoom(Integer roomId) { private List extractChatRoomIds(List chatRooms) { return chatRooms.stream() - .map(ChatRoom::getId) - .toList(); + .map(ChatRoom::getId) + .toList(); } private Map getUnreadCountMap(List chatRoomIds, Integer userId) { @@ -675,15 +675,15 @@ private Map getUnreadCountMap(List chatRoomIds, Integ } List unreadMessageCounts = chatMessageRepository.countUnreadMessagesByChatRoomIdsAndUserId( - chatRoomIds, - userId + chatRoomIds, + userId ); return unreadMessageCounts.stream() - .collect(Collectors.toMap( - UnreadMessageCount::chatRoomId, - unreadMessageCount -> unreadMessageCount.unreadCount().intValue() - )); + .collect(Collectors.toMap( + UnreadMessageCount::chatRoomId, + unreadMessageCount -> unreadMessageCount.unreadCount().intValue() + )); } private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { @@ -692,14 +692,14 @@ private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { } List memberResults = chatRoomMemberRepository.findRoomMemberIdsByChatRoomIds( - List.of(chatRoom.getId()) + List.of(chatRoom.getId()) ); List memberInfos = memberResults.stream() - .map(row -> new MemberInfo((Integer)row[1], (LocalDateTime)row[2])) - .toList(); + .map(row -> new MemberInfo((Integer) row[1], (LocalDateTime) row[2])) + .toList(); boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); + .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return SYSTEM_ADMIN_ID; @@ -710,7 +710,7 @@ private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { return chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId) - .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); + .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); } private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) { @@ -736,12 +736,12 @@ private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) { private void ensureRoomMember(ChatRoom room, User user, LocalDateTime joinedAt) { chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()) - .ifPresentOrElse(member -> { - LocalDateTime lastReadAt = member.getLastReadAt(); - if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { - member.updateLastReadAt(joinedAt); - } - }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); + .ifPresentOrElse(member -> { + LocalDateTime lastReadAt = member.getLastReadAt(); + if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { + member.updateLastReadAt(joinedAt); + } + }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); } private void ensureDirectRoomRequester(ChatRoom room, User user, LocalDateTime joinedAt) { @@ -750,17 +750,17 @@ private void ensureDirectRoomRequester(ChatRoom room, User user, LocalDateTime j } chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()) - .ifPresentOrElse(member -> { - if (member.hasLeft()) { - member.reopenDirectRoom(LocalDateTime.now()); - return; - } + .ifPresentOrElse(member -> { + if (member.hasLeft()) { + member.reopenDirectRoom(LocalDateTime.now()); + return; + } - LocalDateTime lastReadAt = member.getLastReadAt(); - if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { - member.updateLastReadAt(joinedAt); - } - }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); + LocalDateTime lastReadAt = member.getLastReadAt(); + if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { + member.updateLastReadAt(joinedAt); + } + }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); } private boolean shouldSkipSystemAdminMembership(ChatRoom room, User user) { @@ -779,9 +779,9 @@ private String normalizeCustomRoomName(String roomName) { private List toSortedReadBaselines(List members) { return members.stream() - .map(ChatRoomMember::getLastReadAt) - .sorted() - .toList(); + .map(ChatRoomMember::getLastReadAt) + .sorted() + .toList(); } private List toAdminChatReadBaselines(List members) { @@ -834,8 +834,8 @@ private Map getRoomUnreadCountMap(List roomIds, Integ Map unreadCountMap = new HashMap<>(); List projections = chatRoomMemberRepository.countUnreadByRoomIdsAndUserId( - roomIds, - userId + roomIds, + userId ); for (RoomUnreadCountProjection projection : projections) { unreadCountMap.put(projection.getRoomId(), projection.getUnreadCount().intValue()); @@ -857,9 +857,9 @@ private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members, Integer userId) { return members.stream() - .filter(member -> member.getUserId().equals(userId)) - .findFirst() - .orElse(null); + .filter(member -> member.getUserId().equals(userId)) + .findFirst() + .orElse(null); } private boolean isDirectRoomVisibleToUser(ChatRoom room, ChatRoomMember member) { @@ -906,7 +906,7 @@ private Map> getRoomMembersMap(List room List roomIds = rooms.stream().map(ChatRoom::getId).toList(); return chatRoomMemberRepository.findByChatRoomIds(roomIds).stream() - .collect(Collectors.groupingBy(ChatRoomMember::getChatRoomId)); + .collect(Collectors.groupingBy(ChatRoomMember::getChatRoomId)); } private Map> getRoomMemberInfoMap(List rooms) { @@ -919,58 +919,58 @@ private Map> getRoomMemberInfoMap(List rooms Map> roomMemberInfoMap = new HashMap<>(); for (Object[] row : results) { - Integer chatRoomId = (Integer)row[0]; - Integer memberId = (Integer)row[1]; - LocalDateTime createdAt = (LocalDateTime)row[2]; + Integer chatRoomId = (Integer) row[0]; + Integer memberId = (Integer) row[1]; + LocalDateTime createdAt = (LocalDateTime) row[2]; roomMemberInfoMap.computeIfAbsent(chatRoomId, k -> new ArrayList<>()) - .add(new MemberInfo(memberId, createdAt)); + .add(new MemberInfo(memberId, createdAt)); } return roomMemberInfoMap; } private User findDirectPartner(List members, Integer userId) { return members.stream() - .map(ChatRoomMember::getUser) - .filter(memberUser -> !memberUser.getId().equals(userId)) - .findFirst() - .orElse(null); + .map(ChatRoomMember::getUser) + .filter(memberUser -> !memberUser.getId().equals(userId)) + .findFirst() + .orElse(null); } private User resolveDirectChatPartner(List members, Integer userId) { boolean hasSystemAdmin = members.stream() - .map(ChatRoomMember::getUserId) - .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); + .map(ChatRoomMember::getUserId) + .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return members.stream() - .map(ChatRoomMember::getUser) - .filter(memberUser -> memberUser.getId().equals(SYSTEM_ADMIN_ID)) - .findFirst() - .orElse(null); + .map(ChatRoomMember::getUser) + .filter(memberUser -> memberUser.getId().equals(SYSTEM_ADMIN_ID)) + .findFirst() + .orElse(null); } return findDirectPartner(members, userId); } private User findDirectPartnerFromMemberInfo( - List memberInfos, - Integer userId, - Map userMap + List memberInfos, + Integer userId, + Map userMap ) { return memberInfos.stream() - .filter(info -> !info.userId().equals(userId)) - .min(Comparator.comparing(MemberInfo::createdAt)) - .map(info -> userMap.get(info.userId())) - .orElse(null); + .filter(info -> !info.userId().equals(userId)) + .min(Comparator.comparing(MemberInfo::createdAt)) + .map(info -> userMap.get(info.userId())) + .orElse(null); } private User resolveDirectChatPartner( - List memberInfos, - Integer userId, - Map userMap + List memberInfos, + Integer userId, + Map userMap ) { boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); + .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return userMap.get(SYSTEM_ADMIN_ID); @@ -1012,8 +1012,8 @@ private void recordPresenceSafely(Integer roomId, Integer userId) { } private record AccessibleChatRooms( - List rooms, - Map defaultRoomNameMap + List rooms, + Map defaultRoomNameMap ) { } 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 a719f908..3ca3573f 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 @@ -132,48 +132,48 @@ class ChatServiceTest extends ServiceTestSupport { @BeforeEach void setUp() { ChatDirectRoomAccessService chatDirectRoomAccessService = - new ChatDirectRoomAccessService(chatRoomMemberRepository); + new ChatDirectRoomAccessService(chatRoomMemberRepository); ChatMessagePageResolver chatMessagePageResolver = new ChatMessagePageResolver( - chatMessageRepository, - chatRoomMemberRepository, - clubMemberRepository, - chatRoomSystemAdminService + chatMessageRepository, + chatRoomMemberRepository, + clubMemberRepository, + chatRoomSystemAdminService ); ChatMessageSendService chatMessageSendService = new ChatMessageSendService( - chatRoomRepository, - chatMessageRepository, - chatRoomMemberRepository, - clubMemberRepository, - userRepository, - chatRoomSystemAdminService, - chatDirectRoomAccessService, - notificationService, - eventPublisher + chatRoomRepository, + chatMessageRepository, + chatRoomMemberRepository, + clubMemberRepository, + userRepository, + chatRoomSystemAdminService, + chatDirectRoomAccessService, + notificationService, + eventPublisher ); ChatMessageReadService chatMessageReadService = new ChatMessageReadService( - chatMessageRepository, - chatRoomMemberRepository, - chatRoomSystemAdminService, - chatDirectRoomAccessService + chatMessageRepository, + chatRoomMemberRepository, + chatRoomSystemAdminService, + chatDirectRoomAccessService ); chatService = new ChatService( - chatRoomRepository, - chatRoomQueryRepository, - chatMessageRepository, - chatRoomMemberRepository, - notificationMuteSettingRepository, - clubMemberRepository, - userRepository, - chatPresenceService, - chatRoomMembershipService, - chatRoomSummaryService, - chatSearchService, - chatInviteService, - chatMessageReadService, - chatMessagePageResolver, - chatRoomSystemAdminService, - chatDirectRoomAccessService, - chatMessageSendService + chatRoomRepository, + chatRoomQueryRepository, + chatMessageRepository, + chatRoomMemberRepository, + notificationMuteSettingRepository, + clubMemberRepository, + userRepository, + chatPresenceService, + chatRoomMembershipService, + chatRoomSummaryService, + chatSearchService, + chatInviteService, + chatMessageReadService, + chatMessagePageResolver, + chatRoomSystemAdminService, + chatDirectRoomAccessService, + chatMessageSendService ); } @@ -187,8 +187,8 @@ void createOrGetChatRoomRejectsSelfChat() { // when & then assertErrorCode( - () -> chatService.createOrGetChatRoom(userId, new ChatRoomCreateRequest(userId)), - CANNOT_CREATE_CHAT_ROOM_WITH_SELF + () -> chatService.createOrGetChatRoom(userId, new ChatRoomCreateRequest(userId)), + CANNOT_CREATE_CHAT_ROOM_WITH_SELF ); verify(chatRoomRepository, never()).save(any(ChatRoom.class)); } @@ -203,23 +203,23 @@ void createOrGetChatRoomReusesExistingDirectRoomAndReopensRequesterMembership() User targetUser = createUser(targetUserId, "상대", UserRole.USER); ChatRoom room = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember requesterMember = createRoomMember(room, currentUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); markMemberLeft(requesterMember, LocalDateTime.of(2026, 4, 11, 11, 0)); ChatRoomMember targetMember = createRoomMember(room, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(userRepository.getById(currentUserId)).willReturn(currentUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(currentUserId, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.of(room)); + .willReturn(Optional.of(room)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), currentUserId)) - .willReturn(Optional.of(requesterMember)); + .willReturn(Optional.of(requesterMember)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), targetUserId)) - .willReturn(Optional.of(targetMember)); + .willReturn(Optional.of(targetMember)); // when ChatRoomResponse response = chatService.createOrGetChatRoom(currentUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(room.getId()); @@ -242,18 +242,18 @@ void createOrGetChatRoomUsesSystemAdminRoomForAdminToUser() { given(userRepository.getById(adminUserId)).willReturn(adminUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(SYSTEM_ADMIN_ID, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomRepository.save(any(ChatRoom.class))).willReturn(room); given(userRepository.getById(SYSTEM_ADMIN_ID)).willReturn(systemAdmin); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), SYSTEM_ADMIN_ID)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), targetUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomSystemAdminService.isSystemAdminRoom(room.getId())).willReturn(true); // when ChatRoomResponse response = chatService.createOrGetChatRoom(adminUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(room.getId()); @@ -278,8 +278,8 @@ void createGroupChatRoomDeduplicatesInviteesAndSavesMembers() { // when ChatRoomResponse response = chatService.createGroupChatRoom( - creatorId, - new ChatRoomCreateRequest.Group(List.of(creatorId, 20, 20, 30)) + creatorId, + new ChatRoomCreateRequest.Group(List.of(creatorId, 20, 20, 30)) ); // then @@ -288,12 +288,12 @@ void createGroupChatRoomDeduplicatesInviteesAndSavesMembers() { verify(chatRoomMemberRepository).saveAll(captor.capture()); assertThat(captor.getValue()).hasSize(3); assertThat(captor.getValue()) - .extracting(ChatRoomMember::getUserId, ChatRoomMember::isOwner) - .containsExactlyInAnyOrder( - org.assertj.core.groups.Tuple.tuple(creatorId, true), - org.assertj.core.groups.Tuple.tuple(20, false), - org.assertj.core.groups.Tuple.tuple(30, false) - ); + .extracting(ChatRoomMember::getUserId, ChatRoomMember::isOwner) + .containsExactlyInAnyOrder( + org.assertj.core.groups.Tuple.tuple(creatorId, true), + org.assertj.core.groups.Tuple.tuple(20, false), + org.assertj.core.groups.Tuple.tuple(30, false) + ); } @Test @@ -307,12 +307,12 @@ void createGroupChatRoomRejectsInvalidInvitees() { // when & then assertErrorCode( - () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(creatorId))), - CANNOT_CREATE_CHAT_ROOM_WITH_SELF + () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(creatorId))), + CANNOT_CREATE_CHAT_ROOM_WITH_SELF ); assertErrorCode( - () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(20, 30))), - NOT_FOUND_USER + () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(20, 30))), + NOT_FOUND_USER ); } @@ -324,12 +324,12 @@ void leaveChatRoomRejectsClubRoomAndMarksDirectRoomLeft() { ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoom clubRoom = createClubRoom(2, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember directMember = createRoomMember(directRoom, createUser(userId, "사용자", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(chatRoomRepository.findById(clubRoom.getId())).willReturn(Optional.of(clubRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(directMember)); + .willReturn(Optional.of(directMember)); // when chatService.leaveChatRoom(userId, directRoom.getId()); @@ -347,11 +347,11 @@ void leaveChatRoomDeletesMembershipForGroupRoom() { Integer userId = 10; ChatRoom groupRoom = createRoom(3, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, createUser(userId, "사용자", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.leaveChatRoom(userId, groupRoom.getId()); @@ -372,7 +372,7 @@ void kickMemberRejectsNonGroupRoom() { // when & then assertErrorCode(() -> chatService.kickMember(requesterId, directRoom.getId(), targetId), - CANNOT_KICK_IN_NON_GROUP_ROOM); + CANNOT_KICK_IN_NON_GROUP_ROOM); } @Test @@ -396,16 +396,16 @@ void kickMemberRejectsNonOwnerRequester() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember nonOwnerRequester = createRoomMember(groupRoom, createUser(requesterId, "요청자", UserRole.USER), - false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + false, + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(nonOwnerRequester)); + .willReturn(Optional.of(nonOwnerRequester)); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_KICK); + FORBIDDEN_CHAT_ROOM_KICK); } @Test @@ -416,15 +416,15 @@ void kickMemberRejectsOwnerTarget() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerRequester = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerTarget = createRoomMember(groupRoom, createUser(targetId, "대상 방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(ownerRequester)); + .willReturn(Optional.of(ownerRequester)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.of(ownerTarget)); + .willReturn(Optional.of(ownerTarget)); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), CANNOT_KICK_ROOM_OWNER); @@ -438,15 +438,15 @@ void kickMemberDeletesTargetMembershipWhenValid() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerRequester = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember target = createRoomMember(groupRoom, createUser(targetId, "멤버", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(ownerRequester)); + .willReturn(Optional.of(ownerRequester)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.of(target)); + .willReturn(Optional.of(target)); // when chatService.kickMember(requesterId, groupRoom.getId(), targetId); @@ -465,14 +465,14 @@ void toggleMuteTogglesFromUnmutedToMuted() { ChatRoom room = createRoom(roomId, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(room, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); NotificationMuteSetting setting = NotificationMuteSetting.of(NotificationTargetType.CHAT_ROOM, roomId, user, - false); + false); given(chatRoomRepository.findById(roomId)).willReturn(Optional.of(room)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.of(setting)); + roomId, userId)) + .willReturn(Optional.of(setting)); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -493,14 +493,14 @@ void toggleMuteTogglesFromMutedToUnmuted() { ChatRoom room = createRoom(roomId, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(room, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); NotificationMuteSetting setting = NotificationMuteSetting.of(NotificationTargetType.CHAT_ROOM, roomId, user, - true); + true); given(chatRoomRepository.findById(roomId)).willReturn(Optional.of(room)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.of(setting)); + roomId, userId)) + .willReturn(Optional.of(setting)); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -525,8 +525,8 @@ void toggleMuteCreatesNewMutedSettingWhenNoneExists() { given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.empty()); + roomId, userId)) + .willReturn(Optional.empty()); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -545,16 +545,16 @@ void getMessagesUsesDirectReadPath() { ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember directMember = createRoomMember(directRoom, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage directMessage = createMessage(100, directRoom, createUser(20, "상대", UserRole.USER), "direct", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())).willReturn(List.of(directMember)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)).willReturn( - Optional.of(directMember)); + Optional.of(directMember)); given(chatMessageRepository.findByChatRoomId(eq(directRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(directMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(directMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, directRoom.getId(), 1, 20); @@ -562,7 +562,7 @@ void getMessagesUsesDirectReadPath() { // then assertThat(response.messages()).hasSize(1); verify(chatRoomMembershipService).updateDirectRoomLastReadAt(eq(directRoom.getId()), eq(user), - any(LocalDateTime.class), eq(directRoom)); + any(LocalDateTime.class), eq(directRoom)); verify(chatPresenceService).recordPresence(directRoom.getId(), userId); } @@ -581,8 +581,8 @@ void getMessagesUsesClubGroupReadPath() { given(chatRoomMemberRepository.findByChatRoomId(clubRoom.getId())).willReturn(List.of(clubRoomMember)); given(chatMessageRepository.countByChatRoomId(clubRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(clubRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(clubMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(clubMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, clubRoom.getId(), 1, 20); @@ -607,12 +607,12 @@ void getMessagesUsesGroupReadPath() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20); @@ -632,12 +632,12 @@ void getMessagesRejectsGroupRoomOutsider() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(createUser(userId, "사용자", UserRole.USER)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.empty()); + Optional.empty()); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20), + FORBIDDEN_CHAT_ROOM_ACCESS ); } @@ -656,16 +656,16 @@ void createOrGetChatRoomCreatesNewDirectRoomWhenNoneExists() { given(userRepository.getById(currentUserId)).willReturn(currentUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(currentUserId, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomRepository.save(any(ChatRoom.class))).willReturn(newRoom); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(newRoom.getId(), currentUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(newRoom.getId(), targetUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when ChatRoomResponse response = chatService.createOrGetChatRoom(currentUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(newRoom.getId()); @@ -688,11 +688,11 @@ void createOrGetChatRoomTreatsAdminToAdminAsNormalDirect() { given(userRepository.getById(adminId1)).willReturn(admin1); given(userRepository.getById(adminId2)).willReturn(admin2); given(chatRoomRepository.findByTwoUsers(adminId1, adminId2, ChatType.DIRECT)) - .willReturn(Optional.of(room)); + .willReturn(Optional.of(room)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), adminId1)) - .willReturn(Optional.of(member1)); + .willReturn(Optional.of(member1)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), adminId2)) - .willReturn(Optional.of(member2)); + .willReturn(Optional.of(member2)); // when ChatRoomResponse response = chatService.createOrGetChatRoom(adminId1, new ChatRoomCreateRequest(adminId2)); @@ -709,7 +709,7 @@ void createOrGetChatRoomTreatsAdminToAdminAsNormalDirect() { void createOrGetAdminChatRoomThrowsWhenNoAdminExists() { // given given(userRepository.findFirstByRoleAndDeletedAtIsNullOrderByIdAsc(UserRole.ADMIN)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.createOrGetAdminChatRoom(10), NOT_FOUND_USER); @@ -736,7 +736,7 @@ void leaveChatRoomThrowsWhenNotMember() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.leaveChatRoom(userId, directRoom.getId()), FORBIDDEN_CHAT_ROOM_ACCESS); @@ -775,11 +775,11 @@ void kickMemberThrowsWhenRequesterNotMember() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_ACCESS); + FORBIDDEN_CHAT_ROOM_ACCESS); } @Test @@ -790,17 +790,17 @@ void kickMemberThrowsWhenTargetNotMember() { Integer targetId = 20; ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember owner = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(owner)); + .willReturn(Optional.of(owner)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_ACCESS); + FORBIDDEN_CHAT_ROOM_ACCESS); } // ===== getMessages additional ===== @@ -825,22 +825,22 @@ void getMessagesReturnsAdminSystemRoomMessages() { User targetUser = createUser(20, "사용자", UserRole.USER); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember targetMember = createRoomMember(systemAdminRoom, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage message = createMessage(100, systemAdminRoom, admin, "문의", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(adminId)).willReturn(admin); given(chatRoomSystemAdminService.isSystemAdminRoom(systemAdminRoom.getId())).willReturn(true); given(chatRoomSystemAdminService.findSystemAdminMember(List.of(systemAdminMember, targetMember))) - .willReturn(systemAdminMember); + .willReturn(systemAdminMember); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, targetMember)); + .willReturn(List.of(systemAdminMember, targetMember)); given(chatMessageRepository.findByChatRoomId(eq(systemAdminRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(message), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(message), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(adminId, systemAdminRoom.getId(), 1, 20); @@ -848,7 +848,7 @@ void getMessagesReturnsAdminSystemRoomMessages() { // then assertThat(response.messages()).hasSize(1); verify(chatRoomMembershipService).updateLastReadAt(eq(systemAdminRoom.getId()), eq(SYSTEM_ADMIN_ID), - any(LocalDateTime.class)); + any(LocalDateTime.class)); verify(chatPresenceService).recordPresence(systemAdminRoom.getId(), adminId); } @@ -874,29 +874,29 @@ void sendMessageInDirectRoomSavesMessageAndSendsNotification() { User receiver = createUser(receiverId, "받는이", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(directRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember receiverMember = createRoomMember(directRoom, receiver, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, directRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())) - .willReturn(List.of(senderMember, receiverMember)); + .willReturn(List.of(senderMember, receiverMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(directRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, directRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); @@ -905,7 +905,7 @@ void sendMessageInDirectRoomSavesMessageAndSendsNotification() { assertThat(response.isMine()).isTrue(); verify(chatMessageRepository).save(any(ChatMessage.class)); verify(notificationService).sendChatNotification(eq(receiverId), eq(directRoom.getId()), eq("보낸이"), - eq("hello")); + eq("hello")); } @Test @@ -917,11 +917,11 @@ void sendMessageDoesNotOverwriteRoomMetadataWhenNewerMessageAlreadyExists() { User receiver = createUser(receiverId, "받는이", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(directRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember receiverMember = createRoomMember(directRoom, receiver, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, directRoom, sender, "older", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); ReflectionTestUtils.setField(directRoom, "lastMessageContent", "newer"); ReflectionTestUtils.setField(directRoom, "lastMessageSentAt", LocalDateTime.of(2026, 4, 11, 10, 2)); @@ -929,16 +929,16 @@ void sendMessageDoesNotOverwriteRoomMetadataWhenNewerMessageAlreadyExists() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())) - .willReturn(List.of(senderMember, receiverMember)); + .willReturn(List.of(senderMember, receiverMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(0); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(directRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); chatService.sendMessage(senderId, directRoom.getId(), new ChatMessageSendRequest("older")); @@ -954,35 +954,35 @@ void sendMessageInGroupRoomSavesMessageAndSendsGroupNotification() { User sender = createUser(senderId, "보낸이", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(groupRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, groupRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - groupRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + groupRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(groupRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())) - .willReturn(List.of(senderMember)); + .willReturn(List.of(senderMember)); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, groupRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); assertThat(response.content()).isEqualTo("hello"); assertThat(response.isMine()).isTrue(); verify(notificationService).sendGroupChatNotification( - eq(groupRoom.getId()), eq(senderId), eq("그룹 채팅"), eq("보낸이"), eq("hello"), - any(List.class) + eq(groupRoom.getId()), eq(senderId), eq("그룹 채팅"), eq("보낸이"), eq("hello"), + any(List.class) ); } @@ -994,18 +994,18 @@ void sendMessageInGroupRoomRejectsLeftMember() { User sender = createUser(senderId, "보낸이", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember leftMember = createRoomMember(groupRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); markMemberLeft(leftMember, LocalDateTime.of(2026, 4, 11, 12, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), senderId)) - .willReturn(Optional.of(leftMember)); + .willReturn(Optional.of(leftMember)); // when & then assertErrorCode( - () -> chatService.sendMessage(senderId, groupRoom.getId(), new ChatMessageSendRequest("hello")), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.sendMessage(senderId, groupRoom.getId(), new ChatMessageSendRequest("hello")), + FORBIDDEN_CHAT_ROOM_ACCESS ); verify(chatMessageRepository, never()).save(any(ChatMessage.class)); } @@ -1023,34 +1023,34 @@ void sendMessageInClubRoomSavesMessageAndSendsGroupNotification() { ClubMember clubMember = ClubMemberFixture.createMember(club, sender); ReflectionTestUtils.setField(clubMember, "createdAt", LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderRoomMember = createRoomMember(clubRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, clubRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(clubRoom.getId())).willReturn(Optional.of(clubRoom)); given(clubMemberRepository.getByClubIdAndUserId(club.getId(), senderId)).willReturn(clubMember); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(clubRoom.getId(), senderId)) - .willReturn(Optional.of(senderRoomMember)); + .willReturn(Optional.of(senderRoomMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - clubRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + clubRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(clubRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); given(chatRoomMemberRepository.findByChatRoomId(clubRoom.getId())) - .willReturn(List.of(senderRoomMember)); + .willReturn(List.of(senderRoomMember)); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, clubRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); assertThat(response.content()).isEqualTo("hello"); verify(notificationService).sendGroupChatNotification( - eq(clubRoom.getId()), eq(senderId), eq("BCSD"), eq("보낸이"), eq("hello"), - any(List.class) + eq(clubRoom.getId()), eq(senderId), eq("BCSD"), eq("보낸이"), eq("hello"), + any(List.class) ); } @@ -1064,25 +1064,25 @@ void sendMessageByUserInSystemAdminRoomPublishesAdminChatEvent() { User systemAdmin = createUser(SYSTEM_ADMIN_ID, "시스템관리자", UserRole.ADMIN); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(systemAdminRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, systemAdminRoom, sender, content, - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(systemAdminRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, senderMember)); + .willReturn(List.of(systemAdminMember, senderMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(systemAdminRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); // when chatService.sendMessage(senderId, systemAdminRoom.getId(), new ChatMessageSendRequest(content)); @@ -1091,12 +1091,12 @@ void sendMessageByUserInSystemAdminRoomPublishesAdminChatEvent() { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AdminChatReceivedEvent.class); verify(eventPublisher).publishEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue()) - .extracting( - AdminChatReceivedEvent::senderId, - AdminChatReceivedEvent::senderName, - AdminChatReceivedEvent::content - ) - .containsExactly(senderId, sender.getName(), content); + .extracting( + AdminChatReceivedEvent::senderId, + AdminChatReceivedEvent::senderName, + AdminChatReceivedEvent::content + ) + .containsExactly(senderId, sender.getName(), content); } @Test @@ -1110,25 +1110,25 @@ void sendMessageAdminBypassesMembershipInSystemAdminRoom() { User targetUser = createUser(targetUserId, "사용자", UserRole.USER); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember targetMember = createRoomMember(systemAdminRoom, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, systemAdminRoom, admin, "문의", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(adminId)).willReturn(admin); given(chatRoomSystemAdminService.isSystemAdminRoom(systemAdminRoom.getId())).willReturn(true); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, targetMember)); + .willReturn(List.of(systemAdminMember, targetMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); // when ChatMessageDetailResponse response = chatService.sendMessage(adminId, systemAdminRoom.getId(), - new ChatMessageSendRequest("문의")); + new ChatMessageSendRequest("문의")); // then assertThat(response.content()).isEqualTo("문의"); @@ -1137,10 +1137,10 @@ void sendMessageAdminBypassesMembershipInSystemAdminRoom() { verify(chatRoomMemberRepository, never()).findByChatRoomIdAndUserId(systemAdminRoom.getId(), adminId); // admin은 lastReadAt 업데이트를 하지 않는다 verify(chatRoomMemberRepository, never()).updateLastReadAtIfOlder(eq(systemAdminRoom.getId()), eq(adminId), - any(LocalDateTime.class)); + any(LocalDateTime.class)); // 비관리자에게 알림이 전송되어야 한다 verify(notificationService).sendChatNotification(eq(targetUserId), eq(systemAdminRoom.getId()), eq("관리자"), - eq("문의")); + eq("문의")); verify(eventPublisher, never()).publishEvent(any(AdminChatReceivedEvent.class)); } @@ -1166,7 +1166,7 @@ void toggleMuteRejectsNonMemberInGroupRoom() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(createUser(userId, "사용자", UserRole.USER)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.toggleMute(userId, groupRoom.getId()), FORBIDDEN_CHAT_ROOM_ACCESS); @@ -1182,11 +1182,11 @@ void updateChatRoomNameUpdatesCustomNameForMember() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("내 채팅방")); @@ -1204,12 +1204,12 @@ void updateChatRoomNameRejectsNonMember() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode( - () -> chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("이름")), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("이름")), + FORBIDDEN_CHAT_ROOM_ACCESS ); } @@ -1221,12 +1221,12 @@ void updateChatRoomNameNormalizesNullName() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); member.updateCustomRoomName("기존 이름"); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest(null)); @@ -1250,12 +1250,12 @@ void getMessagesWithNullMessageIdBehavesIdentically() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); // when — 기존 4-arg 오버로드 호출 ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20); @@ -1274,18 +1274,18 @@ void getMessagesWithMessageIdThrowsWhenMessageNotFound() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(groupMember)); + .willReturn(Optional.of(groupMember)); given(chatMessageRepository.findByIdWithChatRoom(999)).willReturn(Optional.empty()); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 999), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 999), + NOT_FOUND_CHAT_ROOM ); } @@ -1298,21 +1298,21 @@ void getMessagesWithMessageIdThrowsWhenMessageBelongsToOtherRoom() { ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoom otherRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); User sender = createUser(20, "작성자", UserRole.USER); ChatMessage otherRoomMessage = createMessage(100, otherRoom, sender, "다른 방 메시지", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(groupMember)); + .willReturn(Optional.of(groupMember)); given(chatMessageRepository.findByIdWithChatRoom(100)).willReturn(Optional.of(otherRoomMessage)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100), + NOT_FOUND_CHAT_ROOM ); } @@ -1327,25 +1327,25 @@ void getMessagesWithMessageIdCalculatesCorrectPageInGroupRoom() { // 타겟 메시지: roomId=1, id=50, createdAt=14:00 ChatMessage targetMessage = createMessage(50, groupRoom, user, "찾는 메시지", - LocalDateTime.of(2026, 4, 11, 14, 0)); + LocalDateTime.of(2026, 4, 11, 14, 0)); // 타겟 메시지보다 최신인 메시지가 25개 → page = 25/20 + 1 = 2 ChatMessage page2Message = createMessage(30, groupRoom, user, "페이지2 메시지", - LocalDateTime.of(2026, 4, 11, 13, 0)); + LocalDateTime.of(2026, 4, 11, 13, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(targetMessage)); given(chatMessageRepository.countNewerMessagesByChatRoomId( - groupRoom.getId(), 50, targetMessage.getCreatedAt(), null)) - .willReturn(25L); + groupRoom.getId(), 50, targetMessage.getCreatedAt(), null)) + .willReturn(25L); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(100L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(1, 20)))) // page=2이므로 offset=20 - .willReturn(new PageImpl<>(List.of(page2Message, targetMessage), PageRequest.of(1, 20), 100L)); + eq(PageRequest.of(1, 20)))) // page=2이므로 offset=20 + .willReturn(new PageImpl<>(List.of(page2Message, targetMessage), PageRequest.of(1, 20), 100L)); // when — page=1을 보내도 서버가 page=2로 덮어씀 ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20, 50); @@ -1354,7 +1354,7 @@ void getMessagesWithMessageIdCalculatesCorrectPageInGroupRoom() { assertThat(response.currentPage()).isEqualTo(2); assertThat(response.messages().stream().anyMatch(m -> m.messageId().equals(50))).isTrue(); verify(chatMessageRepository).countNewerMessagesByChatRoomId( - groupRoom.getId(), 50, targetMessage.getCreatedAt(), null); + groupRoom.getId(), 50, targetMessage.getCreatedAt(), null); } @Test @@ -1371,18 +1371,18 @@ void getMessagesWithMessageIdThrowsWhenMessageBeforeVisibleMessageFrom() { // 타겟 메시지가 visibleMessageFrom(12:00)보다 이전(10:30)에 작성됨 User partner = createUser(20, "상대", UserRole.USER); ChatMessage oldMessage = createMessage(50, directRoom, partner, "오래된 메시지", - LocalDateTime.of(2026, 4, 11, 10, 30)); + LocalDateTime.of(2026, 4, 11, 10, 30)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(oldMessage)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); } @@ -1396,22 +1396,22 @@ void getMessagesWithMessageIdReturnsPage1ForNewestMessage() { ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage newestMessage = createMessage(100, groupRoom, user, "최신 메시지", - LocalDateTime.of(2026, 4, 11, 15, 0)); + LocalDateTime.of(2026, 4, 11, 15, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(100)).willReturn(Optional.of(newestMessage)); // 최신 메시지보다 더 최신인 메시지가 0개 → page = 0/20 + 1 = 1 given(chatMessageRepository.countNewerMessagesByChatRoomId( - groupRoom.getId(), 100, newestMessage.getCreatedAt(), null)) - .willReturn(0L); + groupRoom.getId(), 100, newestMessage.getCreatedAt(), null)) + .willReturn(0L); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(50L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(newestMessage), PageRequest.of(0, 20), 50L)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(newestMessage), PageRequest.of(0, 20), 50L)); // when ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100); @@ -1433,12 +1433,12 @@ void getMessagesWithMessageIdRejectsNonMemberWithNotFound() { given(userRepository.getById(nonMemberId)).willReturn(nonMember); // 비회원은 멤버십이 없음 given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), nonMemberId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then — 유효한 messageId여도 접근 권한 없음과 동일한 404 assertErrorCode( - () -> chatService.getMessages(nonMemberId, groupRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(nonMemberId, groupRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); // messageId 조회 자체가 실행되지 않아야 함 verify(chatMessageRepository, never()).findByIdWithChatRoom(any()); @@ -1452,7 +1452,7 @@ void getMessagesWithMessageIdRejectsMessageAtExactVisibleMessageFromBoundary() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(directRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); LocalDateTime leftAt = LocalDateTime.of(2026, 4, 11, 12, 0); markMemberLeft(member, leftAt); @@ -1463,19 +1463,19 @@ void getMessagesWithMessageIdRejectsMessageAtExactVisibleMessageFromBoundary() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(boundaryMessage)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); } private User createUser(Integer id, String name, UserRole role) { return UserFixture.createUserWithId(UniversityFixture.createWithId(1), id, name, - "2024" + String.format("%04d", id), role); + "2024" + String.format("%04d", id), role); } private ChatRoom createRoom(Integer id, ChatType type, LocalDateTime createdAt) { @@ -1500,7 +1500,7 @@ private ChatRoom createClubRoom(Integer id, LocalDateTime createdAt) { private ChatRoomMember createRoomMember(ChatRoom room, User user, boolean isOwner, LocalDateTime lastReadAt) { ChatRoomMember member = - isOwner ? ChatRoomMember.ofOwner(room, user, lastReadAt) : ChatRoomMember.of(room, user, lastReadAt); + isOwner ? ChatRoomMember.ofOwner(room, user, lastReadAt) : ChatRoomMember.of(room, user, lastReadAt); ReflectionTestUtils.setField(member, "createdAt", lastReadAt); return member; } @@ -1519,7 +1519,7 @@ private void markMemberLeft(ChatRoomMember member, LocalDateTime leftAt) { private void assertErrorCode(ThrowingCallable callable, ApiResponseCode errorCode) { assertThatThrownBy(callable) - .isInstanceOf(CustomException.class) - .satisfies(exception -> assertThat(((CustomException)exception).getErrorCode()).isEqualTo(errorCode)); + .isInstanceOf(CustomException.class) + .satisfies(exception -> assertThat(((CustomException) exception).getErrorCode()).isEqualTo(errorCode)); } } From b0bc90be9f3894b20f7c54836e959dc0e2f4fec6 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 11:25:04 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Revert=20"chore:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2a3b87ddce50af8d75c545fefff385711b2ebc5a. --- .../chat/service/ChatMessageReadService.java | 216 +++---- .../domain/chat/service/ChatService.java | 578 +++++++++--------- .../domain/chat/service/ChatServiceTest.java | 466 +++++++------- 3 files changed, 630 insertions(+), 630 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java index a31fe5e8..eabdc4bd 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -34,29 +34,29 @@ public class ChatMessageReadService { @Transactional public ChatMessagePageResponse getDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt ) { Integer roomId = chatRoom.getId(); List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); + chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); List sortedReadBaselines = toSortedReadBaselines(members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); + visibleMessageFrom, sortedReadBaselines, null); } public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer page, + Integer limit, + LocalDateTime readAt ) { Integer roomId = chatRoom.getId(); List members = chatRoomMemberRepository.findByChatRoomId(roomId); @@ -66,14 +66,14 @@ public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( Integer maskedAdminId = getMaskedAdminId(user, members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); + visibleMessageFrom, sortedReadBaselines, maskedAdminId); } public ChatMessagePageResponse getClubMessagesByRoom( - ChatRoom room, - Integer userId, - Integer page, - Integer limit + ChatRoom room, + Integer userId, + Integer page, + Integer limit ) { Integer roomId = room.getId(); PageRequest pageable = PageRequest.of(page - 1, limit); @@ -84,37 +84,37 @@ public ChatMessagePageResponse getClubMessagesByRoom( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + room.getClub().getId(), + responseMessages ); } public ChatMessagePageResponse getGroupMessagesByRoom( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { PageRequest pageable = PageRequest.of(page - 1, limit); long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); @@ -124,83 +124,83 @@ public ChatMessagePageResponse getGroupMessagesByRoom( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + null, + responseMessages ); } private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId + User user, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt, + LocalDateTime visibleMessageFrom, + List sortedReadBaselines, + Integer maskedAdminId ) { PageRequest pageable = PageRequest.of(page - 1, limit); Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); + .map(message -> { + Integer senderId = maskedAdminId != null + ? resolveDirectSenderId(message, maskedAdminId) + : message.getSender().getId(); + boolean isMine = maskedAdminId != null + ? shouldDisplayAsOwnMessage(user, message, true) + : message.isSentBy(user.getId()); + boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + senderId, + null, + message.getContent(), + message.getCreatedAt(), + isRead, + unreadCount, + isMine + ); + }) + .toList(); return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages + messages.getTotalElements(), + messages.getNumberOfElements(), + messages.getTotalPages(), + messages.getNumber() + 1, + null, + responseMessages ); } private List toSortedReadBaselines(List members) { return members.stream() - .map(ChatRoomMember::getLastReadAt) - .sorted() - .toList(); + .map(ChatRoomMember::getLastReadAt) + .sorted() + .toList(); } private List toAdminChatReadBaselines(List members) { @@ -252,9 +252,9 @@ private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members) { } boolean hasSystemAdmin = members.stream() - .map(ChatRoomMember::getUserId) - .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); + .map(ChatRoomMember::getUserId) + .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return SYSTEM_ADMIN_ID; 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 18e5e40f..dbd91945 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 @@ -95,11 +95,11 @@ public ChatRoomResponse createOrGetChatRoom(Integer currentUserId, ChatRoomCreat } ChatRoom chatRoom = chatRoomRepository.findByTwoUsers( - currentUser.getId(), - targetUser.getId(), - ChatType.DIRECT - ) - .orElseGet(() -> chatRoomRepository.save(ChatRoom.directOf())); + currentUser.getId(), + targetUser.getId(), + ChatType.DIRECT + ) + .orElseGet(() -> chatRoomRepository.save(ChatRoom.directOf())); LocalDateTime joinedAt = Objects.requireNonNull(chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null"); ensureDirectRoomRequester(chatRoom, currentUser, joinedAt); @@ -110,19 +110,19 @@ public ChatRoomResponse createOrGetChatRoom(Integer currentUserId, ChatRoomCreat private ChatRoomResponse getOrCreateSystemAdminChatRoomForUser(User targetUser, User adminUser) { ChatRoom chatRoom = chatRoomRepository.findByTwoUsers(SYSTEM_ADMIN_ID, targetUser.getId(), ChatType.DIRECT) - .orElseGet(() -> { - ChatRoom newRoom = chatRoomRepository.save(ChatRoom.directOf()); - User systemAdmin = userRepository.getById(SYSTEM_ADMIN_ID); - LocalDateTime joinedAt = Objects.requireNonNull( - newRoom.getCreatedAt(), "chatRoom.createdAt must not be null" - ); - ensureRoomMember(newRoom, systemAdmin, joinedAt); - ensureRoomMember(newRoom, targetUser, joinedAt); - return newRoom; - }); + .orElseGet(() -> { + ChatRoom newRoom = chatRoomRepository.save(ChatRoom.directOf()); + User systemAdmin = userRepository.getById(SYSTEM_ADMIN_ID); + LocalDateTime joinedAt = Objects.requireNonNull( + newRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + ); + ensureRoomMember(newRoom, systemAdmin, joinedAt); + ensureRoomMember(newRoom, targetUser, joinedAt); + return newRoom; + }); LocalDateTime joinedAt = Objects.requireNonNull( - chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" ); ensureDirectRoomRequester(chatRoom, adminUser, joinedAt); @@ -132,7 +132,7 @@ private ChatRoomResponse getOrCreateSystemAdminChatRoomForUser(User targetUser, @Transactional public ChatRoomResponse createOrGetAdminChatRoom(Integer currentUserId) { User adminUser = userRepository.findFirstByRoleAndDeletedAtIsNullOrderByIdAsc(UserRole.ADMIN) - .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_USER)); return createOrGetChatRoom(currentUserId, new ChatRoomCreateRequest(adminUser.getId())); } @@ -142,9 +142,9 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat User creator = userRepository.getById(currentUserId); List distinctUserIds = request.userIds().stream() - .distinct() - .filter(id -> !id.equals(currentUserId)) - .toList(); + .distinct() + .filter(id -> !id.equals(currentUserId)) + .toList(); if (distinctUserIds.isEmpty()) { throw CustomException.of(CANNOT_CREATE_CHAT_ROOM_WITH_SELF); @@ -157,7 +157,7 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat ChatRoom chatRoom = chatRoomRepository.save(ChatRoom.groupOf()); LocalDateTime joinedAt = Objects.requireNonNull( - chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" + chatRoom.getCreatedAt(), "chatRoom.createdAt must not be null" ); List members = new ArrayList<>(); @@ -171,7 +171,7 @@ public ChatRoomResponse createGroupChatRoom(Integer currentUserId, ChatRoomCreat @Transactional public void leaveChatRoom(Integer userId, Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); if (room.isClubGroupRoom()) { throw CustomException.of(CANNOT_LEAVE_GROUP_CHAT_ROOM); @@ -189,7 +189,7 @@ public void leaveChatRoom(Integer userId, Integer roomId) { @Transactional public void kickMember(Integer requesterId, Integer roomId, Integer targetUserId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); validateGroupRoomForKick(room); validateNotSelfKick(requesterId, targetUserId); @@ -209,10 +209,10 @@ public ChatRoomsSummaryResponse getChatRooms(Integer userId) { List groupRooms = getGroupChatRooms(userId); List rooms = chatRoomSummaryService.summarizeChatRooms( - userId, - directRooms, - clubRooms, - groupRooms + userId, + directRooms, + clubRooms, + groupRooms ); return new ChatRoomsSummaryResponse(rooms); @@ -221,15 +221,15 @@ public ChatRoomsSummaryResponse getChatRooms(Integer userId) { public ChatSearchResponse searchChats(Integer userId, String keyword, Integer page, Integer limit) { AccessibleChatRooms accessibleChatRooms = getAccessibleChatRooms(userId); return chatSearchService.search(userId, keyword, accessibleChatRooms.rooms(), - accessibleChatRooms.defaultRoomNameMap(), page, limit); + accessibleChatRooms.defaultRoomNameMap(), page, limit); } public ChatInvitableUsersResponse getInvitableUsers( - Integer userId, - String query, - ChatInviteSortBy sortBy, - Integer page, - Integer limit + Integer userId, + String query, + ChatInviteSortBy sortBy, + Integer page, + Integer limit ) { return chatInviteService.getInvitableUsers(userId, query, sortBy, page, limit); } @@ -241,10 +241,10 @@ public ChatMessagePageResponse getMessages(Integer userId, Integer roomId, Integ @Transactional public ChatMessagePageResponse getMessages( - Integer userId, Integer roomId, Integer page, Integer limit, Integer messageId + Integer userId, Integer roomId, Integer page, Integer limit, Integer messageId ) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); User user = userRepository.getById(userId); if (messageId != null) { @@ -255,7 +255,7 @@ public ChatMessagePageResponse getMessages( if (room.isDirectRoom()) { boolean isAdminViewingSystemRoom = user.isAdmin() - && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); + && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); if (isAdminViewingSystemRoom) { chatRoomMembershipService.updateLastReadAt(roomId, SYSTEM_ADMIN_ID, readAt); recordPresenceSafely(roomId, userId); @@ -288,7 +288,7 @@ public ChatMessageDetailResponse sendMessage(Integer userId, Integer roomId, Cha @Transactional public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); User user = userRepository.getById(userId); if (room.isClubGroupRoom()) { @@ -297,7 +297,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { } else if (room.isDirectRoom()) { // 어드민이 SYSTEM_ADMIN 방에 접근하는 경우는 멤버십 체크를 건너뜀 boolean isAdminAccessingSystemAdminRoom = user.isAdmin() - && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); + && chatRoomSystemAdminService.isSystemAdminRoom(room.getId()); if (!isAdminAccessingSystemAdminRoom) { chatDirectRoomAccessService.getAccessibleMember(room, user); } @@ -305,24 +305,24 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { getAccessibleRoomMember(room, userId); } Boolean isMuted = notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId( - NotificationTargetType.CHAT_ROOM, - roomId, - userId - ) - .map(setting -> { - setting.toggleMute(); - notificationMuteSettingRepository.save(setting); - return setting.getIsMuted(); - }) - .orElseGet(() -> { - notificationMuteSettingRepository.save(NotificationMuteSetting.of( - NotificationTargetType.CHAT_ROOM, - roomId, - user, - true - )); - return true; - }); + NotificationTargetType.CHAT_ROOM, + roomId, + userId + ) + .map(setting -> { + setting.toggleMute(); + notificationMuteSettingRepository.save(setting); + return setting.getIsMuted(); + }) + .orElseGet(() -> { + notificationMuteSettingRepository.save(NotificationMuteSetting.of( + NotificationTargetType.CHAT_ROOM, + roomId, + user, + true + )); + return true; + }); return new ChatMuteResponse(isMuted); } @@ -330,7 +330,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) { @Transactional public void updateChatRoomName(Integer userId, Integer roomId, ChatRoomNameUpdateRequest request) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); ChatRoomMember roomMember = getAccessibleRoomMember(room, userId); roomMember.updateCustomRoomName(normalizeCustomRoomName(request.roomName())); @@ -361,46 +361,46 @@ private List getDirectChatRooms(Integer userId) { } roomSummaries.add(new ChatRoomSummaryResponse( - chatRoom.getId(), - ChatType.DIRECT, - chatPartner.getName(), - chatPartner.getImageUrl(), - getVisibleLastMessageContent(chatRoom, currentMember), - getVisibleLastMessageSentAt(chatRoom, currentMember), - chatRoom.getCreatedAt(), - personalUnreadCountMap.getOrDefault(chatRoom.getId(), 0), - false + chatRoom.getId(), + ChatType.DIRECT, + chatPartner.getName(), + chatPartner.getImageUrl(), + getVisibleLastMessageContent(chatRoom, currentMember), + getVisibleLastMessageSentAt(chatRoom, currentMember), + chatRoom.getCreatedAt(), + personalUnreadCountMap.getOrDefault(chatRoom.getId(), 0), + false )); } roomSummaries.sort(Comparator - .comparing( - (ChatRoomSummaryResponse room) -> - room.lastSentAt() != null ? room.lastSentAt() : room.createdAt(), - Comparator.reverseOrder() - )); + .comparing( + (ChatRoomSummaryResponse room) -> + room.lastSentAt() != null ? room.lastSentAt() : room.createdAt(), + Comparator.reverseOrder() + )); return roomSummaries; } private List getAdminDirectChatRooms(Integer adminUserId) { List projections = chatRoomQueryRepository.findAdminChatRoomsOptimized( - SYSTEM_ADMIN_ID, adminUserId, UserRole.ADMIN, ChatType.DIRECT + SYSTEM_ADMIN_ID, adminUserId, UserRole.ADMIN, ChatType.DIRECT ); return projections.stream() - .map(projection -> new ChatRoomSummaryResponse( - projection.roomId(), - ChatType.DIRECT, - projection.nonAdminUserName(), - projection.nonAdminImageUrl(), - projection.lastMessage(), - projection.lastSentAt(), - projection.createdAt(), - projection.unreadCount().intValue(), - false - )) - .toList(); + .map(projection -> new ChatRoomSummaryResponse( + projection.roomId(), + ChatType.DIRECT, + projection.nonAdminUserName(), + projection.nonAdminImageUrl(), + projection.lastMessage(), + projection.lastSentAt(), + projection.createdAt(), + projection.unreadCount().intValue(), + false + )) + .toList(); } private List getClubChatRooms(Integer userId) { @@ -410,30 +410,30 @@ private List getClubChatRooms(Integer userId) { } List clubIds = memberships.stream() - .map(cm -> cm.getClub().getId()) - .toList(); + .map(cm -> cm.getClub().getId()) + .toList(); List rooms = chatRoomRepository.findByClubIds(new ArrayList<>(clubIds)) - .stream() - .filter(room -> room.getClub() != null) - .toList(); + .stream() + .filter(room -> room.getClub() != null) + .toList(); List roomIds = rooms.stream().map(ChatRoom::getId).toList(); Map unreadCountMap = getRoomUnreadCountMap(roomIds, userId); return rooms.stream() - .map(room -> new ChatRoomSummaryResponse( - room.getId(), - ChatType.CLUB_GROUP, - room.getClub().getName(), - room.getClub().getImageUrl(), - room.getLastMessageContent(), - room.getLastMessageSentAt(), - room.getCreatedAt(), - unreadCountMap.getOrDefault(room.getId(), 0), - false - )) - .toList(); + .map(room -> new ChatRoomSummaryResponse( + room.getId(), + ChatType.CLUB_GROUP, + room.getClub().getName(), + room.getClub().getImageUrl(), + room.getLastMessageContent(), + room.getLastMessageSentAt(), + room.getCreatedAt(), + unreadCountMap.getOrDefault(room.getId(), 0), + false + )) + .toList(); } private List getGroupChatRooms(Integer userId) { @@ -446,92 +446,92 @@ private List getGroupChatRooms(Integer userId) { Map unreadCountMap = getRoomUnreadCountMap(roomIds, userId); return rooms.stream() - .map(room -> new ChatRoomSummaryResponse( - room.getId(), - ChatType.GROUP, - DEFAULT_GROUP_ROOM_NAME, - null, - room.getLastMessageContent(), - room.getLastMessageSentAt(), - room.getCreatedAt(), - unreadCountMap.getOrDefault(room.getId(), 0), - false - )) - .toList(); + .map(room -> new ChatRoomSummaryResponse( + room.getId(), + ChatType.GROUP, + DEFAULT_GROUP_ROOM_NAME, + null, + room.getLastMessageContent(), + room.getLastMessageSentAt(), + room.getCreatedAt(), + unreadCountMap.getOrDefault(room.getId(), 0), + false + )) + .toList(); } private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId + User user, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt, + LocalDateTime visibleMessageFrom, + List sortedReadBaselines, + Integer maskedAdminId ) { PageRequest pageable = PageRequest.of(page - 1, limit); Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); + .map(message -> { + Integer senderId = maskedAdminId != null + ? resolveDirectSenderId(message, maskedAdminId) + : message.getSender().getId(); + boolean isMine = maskedAdminId != null + ? shouldDisplayAsOwnMessage(user, message, true) + : message.isSentBy(user.getId()); + boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + senderId, + null, + message.getContent(), + message.getCreatedAt(), + isRead, + unreadCount, + isMine + ); + }) + .toList(); return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages + messages.getTotalElements(), + messages.getNumberOfElements(), + messages.getTotalPages(), + messages.getNumber() + 1, + null, + responseMessages ); } private ChatMessagePageResponse getDirectChatRoomMessages( - Integer userId, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt + Integer userId, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt ) { ChatRoom chatRoom = getDirectRoom(roomId); User user = userRepository.getById(userId); List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); + chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); List sortedReadBaselines = toSortedReadBaselines(members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); + visibleMessageFrom, sortedReadBaselines, null); } private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt + User user, + ChatRoom chatRoom, + Integer roomId, + Integer page, + Integer limit, + LocalDateTime readAt ) { List members = chatRoomMemberRepository.findByChatRoomId(roomId); LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); @@ -540,14 +540,14 @@ private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( Integer maskedAdminId = getMaskedAdminId(user, chatRoom); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); + visibleMessageFrom, sortedReadBaselines, maskedAdminId); } private ChatMessagePageResponse getClubMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { ChatRoom room = getClubRoom(roomId); @@ -559,37 +559,37 @@ private ChatMessagePageResponse getClubMessagesByRoomId( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + room.getClub().getId(), + responseMessages ); } private ChatMessagePageResponse getGroupMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit + Integer roomId, + Integer userId, + Integer page, + Integer limit ) { chatRoomRepository.getById(roomId); @@ -601,29 +601,29 @@ private ChatMessagePageResponse getGroupMessagesByRoomId( List sortedReadBaselines = toSortedReadBaselines(members); List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int) Math.ceil((double) totalCount / (double) limit) : 0; + .map(message -> { + int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); + return new ChatMessageDetailResponse( + message.getId(), + message.getSender().getId(), + message.getSender().getName(), + message.getContent(), + message.getCreatedAt(), + null, + unreadCount, + message.isSentBy(userId) + ); + }) + .toList(); + + int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages + totalCount, + responseMessages.size(), + totalPage, + page, + null, + responseMessages ); } @@ -632,20 +632,20 @@ private AccessibleChatRooms getAccessibleChatRooms(Integer userId) { List clubRooms = getClubChatRooms(userId); Map defaultRoomNameMap = chatRoomSummaryService.getDefaultRoomNameMap( - directRooms, - clubRooms + directRooms, + clubRooms ); List rooms = chatRoomSummaryService.summarizeSearchableRooms( - userId, - directRooms, - clubRooms + userId, + directRooms, + clubRooms ); return new AccessibleChatRooms(rooms, defaultRoomNameMap); } private ChatRoom getDirectRoom(Integer roomId) { ChatRoom chatRoom = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); if (!chatRoom.isDirectRoom()) { throw CustomException.of(NOT_FOUND_CHAT_ROOM); @@ -656,7 +656,7 @@ private ChatRoom getDirectRoom(Integer roomId) { private ChatRoom getClubRoom(Integer roomId) { ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); if (!room.isClubGroupRoom()) { throw CustomException.of(ApiResponseCode.NOT_FOUND_GROUP_CHAT_ROOM); } @@ -665,8 +665,8 @@ private ChatRoom getClubRoom(Integer roomId) { private List extractChatRoomIds(List chatRooms) { return chatRooms.stream() - .map(ChatRoom::getId) - .toList(); + .map(ChatRoom::getId) + .toList(); } private Map getUnreadCountMap(List chatRoomIds, Integer userId) { @@ -675,15 +675,15 @@ private Map getUnreadCountMap(List chatRoomIds, Integ } List unreadMessageCounts = chatMessageRepository.countUnreadMessagesByChatRoomIdsAndUserId( - chatRoomIds, - userId + chatRoomIds, + userId ); return unreadMessageCounts.stream() - .collect(Collectors.toMap( - UnreadMessageCount::chatRoomId, - unreadMessageCount -> unreadMessageCount.unreadCount().intValue() - )); + .collect(Collectors.toMap( + UnreadMessageCount::chatRoomId, + unreadMessageCount -> unreadMessageCount.unreadCount().intValue() + )); } private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { @@ -692,14 +692,14 @@ private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { } List memberResults = chatRoomMemberRepository.findRoomMemberIdsByChatRoomIds( - List.of(chatRoom.getId()) + List.of(chatRoom.getId()) ); List memberInfos = memberResults.stream() - .map(row -> new MemberInfo((Integer) row[1], (LocalDateTime) row[2])) - .toList(); + .map(row -> new MemberInfo((Integer)row[1], (LocalDateTime)row[2])) + .toList(); boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); + .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return SYSTEM_ADMIN_ID; @@ -710,7 +710,7 @@ private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { return chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId) - .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); + .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); } private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) { @@ -736,12 +736,12 @@ private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) { private void ensureRoomMember(ChatRoom room, User user, LocalDateTime joinedAt) { chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()) - .ifPresentOrElse(member -> { - LocalDateTime lastReadAt = member.getLastReadAt(); - if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { - member.updateLastReadAt(joinedAt); - } - }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); + .ifPresentOrElse(member -> { + LocalDateTime lastReadAt = member.getLastReadAt(); + if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { + member.updateLastReadAt(joinedAt); + } + }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); } private void ensureDirectRoomRequester(ChatRoom room, User user, LocalDateTime joinedAt) { @@ -750,17 +750,17 @@ private void ensureDirectRoomRequester(ChatRoom room, User user, LocalDateTime j } chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()) - .ifPresentOrElse(member -> { - if (member.hasLeft()) { - member.reopenDirectRoom(LocalDateTime.now()); - return; - } + .ifPresentOrElse(member -> { + if (member.hasLeft()) { + member.reopenDirectRoom(LocalDateTime.now()); + return; + } - LocalDateTime lastReadAt = member.getLastReadAt(); - if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { - member.updateLastReadAt(joinedAt); - } - }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); + LocalDateTime lastReadAt = member.getLastReadAt(); + if (lastReadAt == null || lastReadAt.isBefore(joinedAt)) { + member.updateLastReadAt(joinedAt); + } + }, () -> chatRoomMemberRepository.save(ChatRoomMember.of(room, user, joinedAt))); } private boolean shouldSkipSystemAdminMembership(ChatRoom room, User user) { @@ -779,9 +779,9 @@ private String normalizeCustomRoomName(String roomName) { private List toSortedReadBaselines(List members) { return members.stream() - .map(ChatRoomMember::getLastReadAt) - .sorted() - .toList(); + .map(ChatRoomMember::getLastReadAt) + .sorted() + .toList(); } private List toAdminChatReadBaselines(List members) { @@ -834,8 +834,8 @@ private Map getRoomUnreadCountMap(List roomIds, Integ Map unreadCountMap = new HashMap<>(); List projections = chatRoomMemberRepository.countUnreadByRoomIdsAndUserId( - roomIds, - userId + roomIds, + userId ); for (RoomUnreadCountProjection projection : projections) { unreadCountMap.put(projection.getRoomId(), projection.getUnreadCount().intValue()); @@ -857,9 +857,9 @@ private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members, Integer userId) { return members.stream() - .filter(member -> member.getUserId().equals(userId)) - .findFirst() - .orElse(null); + .filter(member -> member.getUserId().equals(userId)) + .findFirst() + .orElse(null); } private boolean isDirectRoomVisibleToUser(ChatRoom room, ChatRoomMember member) { @@ -906,7 +906,7 @@ private Map> getRoomMembersMap(List room List roomIds = rooms.stream().map(ChatRoom::getId).toList(); return chatRoomMemberRepository.findByChatRoomIds(roomIds).stream() - .collect(Collectors.groupingBy(ChatRoomMember::getChatRoomId)); + .collect(Collectors.groupingBy(ChatRoomMember::getChatRoomId)); } private Map> getRoomMemberInfoMap(List rooms) { @@ -919,58 +919,58 @@ private Map> getRoomMemberInfoMap(List rooms Map> roomMemberInfoMap = new HashMap<>(); for (Object[] row : results) { - Integer chatRoomId = (Integer) row[0]; - Integer memberId = (Integer) row[1]; - LocalDateTime createdAt = (LocalDateTime) row[2]; + Integer chatRoomId = (Integer)row[0]; + Integer memberId = (Integer)row[1]; + LocalDateTime createdAt = (LocalDateTime)row[2]; roomMemberInfoMap.computeIfAbsent(chatRoomId, k -> new ArrayList<>()) - .add(new MemberInfo(memberId, createdAt)); + .add(new MemberInfo(memberId, createdAt)); } return roomMemberInfoMap; } private User findDirectPartner(List members, Integer userId) { return members.stream() - .map(ChatRoomMember::getUser) - .filter(memberUser -> !memberUser.getId().equals(userId)) - .findFirst() - .orElse(null); + .map(ChatRoomMember::getUser) + .filter(memberUser -> !memberUser.getId().equals(userId)) + .findFirst() + .orElse(null); } private User resolveDirectChatPartner(List members, Integer userId) { boolean hasSystemAdmin = members.stream() - .map(ChatRoomMember::getUserId) - .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); + .map(ChatRoomMember::getUserId) + .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return members.stream() - .map(ChatRoomMember::getUser) - .filter(memberUser -> memberUser.getId().equals(SYSTEM_ADMIN_ID)) - .findFirst() - .orElse(null); + .map(ChatRoomMember::getUser) + .filter(memberUser -> memberUser.getId().equals(SYSTEM_ADMIN_ID)) + .findFirst() + .orElse(null); } return findDirectPartner(members, userId); } private User findDirectPartnerFromMemberInfo( - List memberInfos, - Integer userId, - Map userMap + List memberInfos, + Integer userId, + Map userMap ) { return memberInfos.stream() - .filter(info -> !info.userId().equals(userId)) - .min(Comparator.comparing(MemberInfo::createdAt)) - .map(info -> userMap.get(info.userId())) - .orElse(null); + .filter(info -> !info.userId().equals(userId)) + .min(Comparator.comparing(MemberInfo::createdAt)) + .map(info -> userMap.get(info.userId())) + .orElse(null); } private User resolveDirectChatPartner( - List memberInfos, - Integer userId, - Map userMap + List memberInfos, + Integer userId, + Map userMap ) { boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); + .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); if (hasSystemAdmin) { return userMap.get(SYSTEM_ADMIN_ID); @@ -1012,8 +1012,8 @@ private void recordPresenceSafely(Integer roomId, Integer userId) { } private record AccessibleChatRooms( - List rooms, - Map defaultRoomNameMap + List rooms, + Map defaultRoomNameMap ) { } 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 3ca3573f..a719f908 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 @@ -132,48 +132,48 @@ class ChatServiceTest extends ServiceTestSupport { @BeforeEach void setUp() { ChatDirectRoomAccessService chatDirectRoomAccessService = - new ChatDirectRoomAccessService(chatRoomMemberRepository); + new ChatDirectRoomAccessService(chatRoomMemberRepository); ChatMessagePageResolver chatMessagePageResolver = new ChatMessagePageResolver( - chatMessageRepository, - chatRoomMemberRepository, - clubMemberRepository, - chatRoomSystemAdminService + chatMessageRepository, + chatRoomMemberRepository, + clubMemberRepository, + chatRoomSystemAdminService ); ChatMessageSendService chatMessageSendService = new ChatMessageSendService( - chatRoomRepository, - chatMessageRepository, - chatRoomMemberRepository, - clubMemberRepository, - userRepository, - chatRoomSystemAdminService, - chatDirectRoomAccessService, - notificationService, - eventPublisher + chatRoomRepository, + chatMessageRepository, + chatRoomMemberRepository, + clubMemberRepository, + userRepository, + chatRoomSystemAdminService, + chatDirectRoomAccessService, + notificationService, + eventPublisher ); ChatMessageReadService chatMessageReadService = new ChatMessageReadService( - chatMessageRepository, - chatRoomMemberRepository, - chatRoomSystemAdminService, - chatDirectRoomAccessService + chatMessageRepository, + chatRoomMemberRepository, + chatRoomSystemAdminService, + chatDirectRoomAccessService ); chatService = new ChatService( - chatRoomRepository, - chatRoomQueryRepository, - chatMessageRepository, - chatRoomMemberRepository, - notificationMuteSettingRepository, - clubMemberRepository, - userRepository, - chatPresenceService, - chatRoomMembershipService, - chatRoomSummaryService, - chatSearchService, - chatInviteService, - chatMessageReadService, - chatMessagePageResolver, - chatRoomSystemAdminService, - chatDirectRoomAccessService, - chatMessageSendService + chatRoomRepository, + chatRoomQueryRepository, + chatMessageRepository, + chatRoomMemberRepository, + notificationMuteSettingRepository, + clubMemberRepository, + userRepository, + chatPresenceService, + chatRoomMembershipService, + chatRoomSummaryService, + chatSearchService, + chatInviteService, + chatMessageReadService, + chatMessagePageResolver, + chatRoomSystemAdminService, + chatDirectRoomAccessService, + chatMessageSendService ); } @@ -187,8 +187,8 @@ void createOrGetChatRoomRejectsSelfChat() { // when & then assertErrorCode( - () -> chatService.createOrGetChatRoom(userId, new ChatRoomCreateRequest(userId)), - CANNOT_CREATE_CHAT_ROOM_WITH_SELF + () -> chatService.createOrGetChatRoom(userId, new ChatRoomCreateRequest(userId)), + CANNOT_CREATE_CHAT_ROOM_WITH_SELF ); verify(chatRoomRepository, never()).save(any(ChatRoom.class)); } @@ -203,23 +203,23 @@ void createOrGetChatRoomReusesExistingDirectRoomAndReopensRequesterMembership() User targetUser = createUser(targetUserId, "상대", UserRole.USER); ChatRoom room = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember requesterMember = createRoomMember(room, currentUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); markMemberLeft(requesterMember, LocalDateTime.of(2026, 4, 11, 11, 0)); ChatRoomMember targetMember = createRoomMember(room, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(userRepository.getById(currentUserId)).willReturn(currentUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(currentUserId, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.of(room)); + .willReturn(Optional.of(room)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), currentUserId)) - .willReturn(Optional.of(requesterMember)); + .willReturn(Optional.of(requesterMember)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), targetUserId)) - .willReturn(Optional.of(targetMember)); + .willReturn(Optional.of(targetMember)); // when ChatRoomResponse response = chatService.createOrGetChatRoom(currentUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(room.getId()); @@ -242,18 +242,18 @@ void createOrGetChatRoomUsesSystemAdminRoomForAdminToUser() { given(userRepository.getById(adminUserId)).willReturn(adminUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(SYSTEM_ADMIN_ID, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomRepository.save(any(ChatRoom.class))).willReturn(room); given(userRepository.getById(SYSTEM_ADMIN_ID)).willReturn(systemAdmin); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), SYSTEM_ADMIN_ID)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), targetUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomSystemAdminService.isSystemAdminRoom(room.getId())).willReturn(true); // when ChatRoomResponse response = chatService.createOrGetChatRoom(adminUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(room.getId()); @@ -278,8 +278,8 @@ void createGroupChatRoomDeduplicatesInviteesAndSavesMembers() { // when ChatRoomResponse response = chatService.createGroupChatRoom( - creatorId, - new ChatRoomCreateRequest.Group(List.of(creatorId, 20, 20, 30)) + creatorId, + new ChatRoomCreateRequest.Group(List.of(creatorId, 20, 20, 30)) ); // then @@ -288,12 +288,12 @@ void createGroupChatRoomDeduplicatesInviteesAndSavesMembers() { verify(chatRoomMemberRepository).saveAll(captor.capture()); assertThat(captor.getValue()).hasSize(3); assertThat(captor.getValue()) - .extracting(ChatRoomMember::getUserId, ChatRoomMember::isOwner) - .containsExactlyInAnyOrder( - org.assertj.core.groups.Tuple.tuple(creatorId, true), - org.assertj.core.groups.Tuple.tuple(20, false), - org.assertj.core.groups.Tuple.tuple(30, false) - ); + .extracting(ChatRoomMember::getUserId, ChatRoomMember::isOwner) + .containsExactlyInAnyOrder( + org.assertj.core.groups.Tuple.tuple(creatorId, true), + org.assertj.core.groups.Tuple.tuple(20, false), + org.assertj.core.groups.Tuple.tuple(30, false) + ); } @Test @@ -307,12 +307,12 @@ void createGroupChatRoomRejectsInvalidInvitees() { // when & then assertErrorCode( - () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(creatorId))), - CANNOT_CREATE_CHAT_ROOM_WITH_SELF + () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(creatorId))), + CANNOT_CREATE_CHAT_ROOM_WITH_SELF ); assertErrorCode( - () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(20, 30))), - NOT_FOUND_USER + () -> chatService.createGroupChatRoom(creatorId, new ChatRoomCreateRequest.Group(List.of(20, 30))), + NOT_FOUND_USER ); } @@ -324,12 +324,12 @@ void leaveChatRoomRejectsClubRoomAndMarksDirectRoomLeft() { ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoom clubRoom = createClubRoom(2, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember directMember = createRoomMember(directRoom, createUser(userId, "사용자", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(chatRoomRepository.findById(clubRoom.getId())).willReturn(Optional.of(clubRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(directMember)); + .willReturn(Optional.of(directMember)); // when chatService.leaveChatRoom(userId, directRoom.getId()); @@ -347,11 +347,11 @@ void leaveChatRoomDeletesMembershipForGroupRoom() { Integer userId = 10; ChatRoom groupRoom = createRoom(3, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, createUser(userId, "사용자", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.leaveChatRoom(userId, groupRoom.getId()); @@ -372,7 +372,7 @@ void kickMemberRejectsNonGroupRoom() { // when & then assertErrorCode(() -> chatService.kickMember(requesterId, directRoom.getId(), targetId), - CANNOT_KICK_IN_NON_GROUP_ROOM); + CANNOT_KICK_IN_NON_GROUP_ROOM); } @Test @@ -396,16 +396,16 @@ void kickMemberRejectsNonOwnerRequester() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember nonOwnerRequester = createRoomMember(groupRoom, createUser(requesterId, "요청자", UserRole.USER), - false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + false, + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(nonOwnerRequester)); + .willReturn(Optional.of(nonOwnerRequester)); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_KICK); + FORBIDDEN_CHAT_ROOM_KICK); } @Test @@ -416,15 +416,15 @@ void kickMemberRejectsOwnerTarget() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerRequester = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerTarget = createRoomMember(groupRoom, createUser(targetId, "대상 방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(ownerRequester)); + .willReturn(Optional.of(ownerRequester)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.of(ownerTarget)); + .willReturn(Optional.of(ownerTarget)); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), CANNOT_KICK_ROOM_OWNER); @@ -438,15 +438,15 @@ void kickMemberDeletesTargetMembershipWhenValid() { Integer targetId = 20; ChatRoom groupRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember ownerRequester = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember target = createRoomMember(groupRoom, createUser(targetId, "멤버", UserRole.USER), false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(ownerRequester)); + .willReturn(Optional.of(ownerRequester)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.of(target)); + .willReturn(Optional.of(target)); // when chatService.kickMember(requesterId, groupRoom.getId(), targetId); @@ -465,14 +465,14 @@ void toggleMuteTogglesFromUnmutedToMuted() { ChatRoom room = createRoom(roomId, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(room, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); NotificationMuteSetting setting = NotificationMuteSetting.of(NotificationTargetType.CHAT_ROOM, roomId, user, - false); + false); given(chatRoomRepository.findById(roomId)).willReturn(Optional.of(room)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.of(setting)); + roomId, userId)) + .willReturn(Optional.of(setting)); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -493,14 +493,14 @@ void toggleMuteTogglesFromMutedToUnmuted() { ChatRoom room = createRoom(roomId, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(room, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); NotificationMuteSetting setting = NotificationMuteSetting.of(NotificationTargetType.CHAT_ROOM, roomId, user, - true); + true); given(chatRoomRepository.findById(roomId)).willReturn(Optional.of(room)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.of(setting)); + roomId, userId)) + .willReturn(Optional.of(setting)); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -525,8 +525,8 @@ void toggleMuteCreatesNewMutedSettingWhenNoneExists() { given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId)).willReturn(Optional.of(member)); given(notificationMuteSettingRepository.findByTargetTypeAndTargetIdAndUserId(NotificationTargetType.CHAT_ROOM, - roomId, userId)) - .willReturn(Optional.empty()); + roomId, userId)) + .willReturn(Optional.empty()); // when ChatMuteResponse response = chatService.toggleMute(userId, roomId); @@ -545,16 +545,16 @@ void getMessagesUsesDirectReadPath() { ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember directMember = createRoomMember(directRoom, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage directMessage = createMessage(100, directRoom, createUser(20, "상대", UserRole.USER), "direct", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())).willReturn(List.of(directMember)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)).willReturn( - Optional.of(directMember)); + Optional.of(directMember)); given(chatMessageRepository.findByChatRoomId(eq(directRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(directMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(directMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, directRoom.getId(), 1, 20); @@ -562,7 +562,7 @@ void getMessagesUsesDirectReadPath() { // then assertThat(response.messages()).hasSize(1); verify(chatRoomMembershipService).updateDirectRoomLastReadAt(eq(directRoom.getId()), eq(user), - any(LocalDateTime.class), eq(directRoom)); + any(LocalDateTime.class), eq(directRoom)); verify(chatPresenceService).recordPresence(directRoom.getId(), userId); } @@ -581,8 +581,8 @@ void getMessagesUsesClubGroupReadPath() { given(chatRoomMemberRepository.findByChatRoomId(clubRoom.getId())).willReturn(List.of(clubRoomMember)); given(chatMessageRepository.countByChatRoomId(clubRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(clubRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(clubMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(clubMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, clubRoom.getId(), 1, 20); @@ -607,12 +607,12 @@ void getMessagesUsesGroupReadPath() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20); @@ -632,12 +632,12 @@ void getMessagesRejectsGroupRoomOutsider() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(createUser(userId, "사용자", UserRole.USER)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.empty()); + Optional.empty()); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20), + FORBIDDEN_CHAT_ROOM_ACCESS ); } @@ -656,16 +656,16 @@ void createOrGetChatRoomCreatesNewDirectRoomWhenNoneExists() { given(userRepository.getById(currentUserId)).willReturn(currentUser); given(userRepository.getById(targetUserId)).willReturn(targetUser); given(chatRoomRepository.findByTwoUsers(currentUserId, targetUserId, ChatType.DIRECT)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomRepository.save(any(ChatRoom.class))).willReturn(newRoom); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(newRoom.getId(), currentUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(newRoom.getId(), targetUserId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when ChatRoomResponse response = chatService.createOrGetChatRoom(currentUserId, - new ChatRoomCreateRequest(targetUserId)); + new ChatRoomCreateRequest(targetUserId)); // then assertThat(response.chatRoomId()).isEqualTo(newRoom.getId()); @@ -688,11 +688,11 @@ void createOrGetChatRoomTreatsAdminToAdminAsNormalDirect() { given(userRepository.getById(adminId1)).willReturn(admin1); given(userRepository.getById(adminId2)).willReturn(admin2); given(chatRoomRepository.findByTwoUsers(adminId1, adminId2, ChatType.DIRECT)) - .willReturn(Optional.of(room)); + .willReturn(Optional.of(room)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), adminId1)) - .willReturn(Optional.of(member1)); + .willReturn(Optional.of(member1)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), adminId2)) - .willReturn(Optional.of(member2)); + .willReturn(Optional.of(member2)); // when ChatRoomResponse response = chatService.createOrGetChatRoom(adminId1, new ChatRoomCreateRequest(adminId2)); @@ -709,7 +709,7 @@ void createOrGetChatRoomTreatsAdminToAdminAsNormalDirect() { void createOrGetAdminChatRoomThrowsWhenNoAdminExists() { // given given(userRepository.findFirstByRoleAndDeletedAtIsNullOrderByIdAsc(UserRole.ADMIN)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.createOrGetAdminChatRoom(10), NOT_FOUND_USER); @@ -736,7 +736,7 @@ void leaveChatRoomThrowsWhenNotMember() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.leaveChatRoom(userId, directRoom.getId()), FORBIDDEN_CHAT_ROOM_ACCESS); @@ -775,11 +775,11 @@ void kickMemberThrowsWhenRequesterNotMember() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_ACCESS); + FORBIDDEN_CHAT_ROOM_ACCESS); } @Test @@ -790,17 +790,17 @@ void kickMemberThrowsWhenTargetNotMember() { Integer targetId = 20; ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember owner = createRoomMember(groupRoom, createUser(requesterId, "방장", UserRole.USER), true, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), requesterId)) - .willReturn(Optional.of(owner)); + .willReturn(Optional.of(owner)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), targetId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.kickMember(requesterId, groupRoom.getId(), targetId), - FORBIDDEN_CHAT_ROOM_ACCESS); + FORBIDDEN_CHAT_ROOM_ACCESS); } // ===== getMessages additional ===== @@ -825,22 +825,22 @@ void getMessagesReturnsAdminSystemRoomMessages() { User targetUser = createUser(20, "사용자", UserRole.USER); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember targetMember = createRoomMember(systemAdminRoom, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage message = createMessage(100, systemAdminRoom, admin, "문의", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(adminId)).willReturn(admin); given(chatRoomSystemAdminService.isSystemAdminRoom(systemAdminRoom.getId())).willReturn(true); given(chatRoomSystemAdminService.findSystemAdminMember(List.of(systemAdminMember, targetMember))) - .willReturn(systemAdminMember); + .willReturn(systemAdminMember); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, targetMember)); + .willReturn(List.of(systemAdminMember, targetMember)); given(chatMessageRepository.findByChatRoomId(eq(systemAdminRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(message), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(message), PageRequest.of(0, 20), 1)); // when ChatMessagePageResponse response = chatService.getMessages(adminId, systemAdminRoom.getId(), 1, 20); @@ -848,7 +848,7 @@ void getMessagesReturnsAdminSystemRoomMessages() { // then assertThat(response.messages()).hasSize(1); verify(chatRoomMembershipService).updateLastReadAt(eq(systemAdminRoom.getId()), eq(SYSTEM_ADMIN_ID), - any(LocalDateTime.class)); + any(LocalDateTime.class)); verify(chatPresenceService).recordPresence(systemAdminRoom.getId(), adminId); } @@ -874,29 +874,29 @@ void sendMessageInDirectRoomSavesMessageAndSendsNotification() { User receiver = createUser(receiverId, "받는이", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(directRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember receiverMember = createRoomMember(directRoom, receiver, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, directRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())) - .willReturn(List.of(senderMember, receiverMember)); + .willReturn(List.of(senderMember, receiverMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(directRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, directRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); @@ -905,7 +905,7 @@ void sendMessageInDirectRoomSavesMessageAndSendsNotification() { assertThat(response.isMine()).isTrue(); verify(chatMessageRepository).save(any(ChatMessage.class)); verify(notificationService).sendChatNotification(eq(receiverId), eq(directRoom.getId()), eq("보낸이"), - eq("hello")); + eq("hello")); } @Test @@ -917,11 +917,11 @@ void sendMessageDoesNotOverwriteRoomMetadataWhenNewerMessageAlreadyExists() { User receiver = createUser(receiverId, "받는이", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(directRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember receiverMember = createRoomMember(directRoom, receiver, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, directRoom, sender, "older", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); ReflectionTestUtils.setField(directRoom, "lastMessageContent", "newer"); ReflectionTestUtils.setField(directRoom, "lastMessageSentAt", LocalDateTime.of(2026, 4, 11, 10, 2)); @@ -929,16 +929,16 @@ void sendMessageDoesNotOverwriteRoomMetadataWhenNewerMessageAlreadyExists() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(directRoom.getId())) - .willReturn(List.of(senderMember, receiverMember)); + .willReturn(List.of(senderMember, receiverMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + directRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(0); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(directRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); chatService.sendMessage(senderId, directRoom.getId(), new ChatMessageSendRequest("older")); @@ -954,35 +954,35 @@ void sendMessageInGroupRoomSavesMessageAndSendsGroupNotification() { User sender = createUser(senderId, "보낸이", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(groupRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, groupRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - groupRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + groupRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(groupRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())) - .willReturn(List.of(senderMember)); + .willReturn(List.of(senderMember)); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, groupRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); assertThat(response.content()).isEqualTo("hello"); assertThat(response.isMine()).isTrue(); verify(notificationService).sendGroupChatNotification( - eq(groupRoom.getId()), eq(senderId), eq("그룹 채팅"), eq("보낸이"), eq("hello"), - any(List.class) + eq(groupRoom.getId()), eq(senderId), eq("그룹 채팅"), eq("보낸이"), eq("hello"), + any(List.class) ); } @@ -994,18 +994,18 @@ void sendMessageInGroupRoomRejectsLeftMember() { User sender = createUser(senderId, "보낸이", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember leftMember = createRoomMember(groupRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); markMemberLeft(leftMember, LocalDateTime.of(2026, 4, 11, 12, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), senderId)) - .willReturn(Optional.of(leftMember)); + .willReturn(Optional.of(leftMember)); // when & then assertErrorCode( - () -> chatService.sendMessage(senderId, groupRoom.getId(), new ChatMessageSendRequest("hello")), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.sendMessage(senderId, groupRoom.getId(), new ChatMessageSendRequest("hello")), + FORBIDDEN_CHAT_ROOM_ACCESS ); verify(chatMessageRepository, never()).save(any(ChatMessage.class)); } @@ -1023,34 +1023,34 @@ void sendMessageInClubRoomSavesMessageAndSendsGroupNotification() { ClubMember clubMember = ClubMemberFixture.createMember(club, sender); ReflectionTestUtils.setField(clubMember, "createdAt", LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderRoomMember = createRoomMember(clubRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, clubRoom, sender, "hello", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(clubRoom.getId())).willReturn(Optional.of(clubRoom)); given(clubMemberRepository.getByClubIdAndUserId(club.getId(), senderId)).willReturn(clubMember); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(clubRoom.getId(), senderId)) - .willReturn(Optional.of(senderRoomMember)); + .willReturn(Optional.of(senderRoomMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - clubRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + clubRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(clubRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); given(chatRoomMemberRepository.findByChatRoomId(clubRoom.getId())) - .willReturn(List.of(senderRoomMember)); + .willReturn(List.of(senderRoomMember)); // when ChatMessageDetailResponse response = chatService.sendMessage(senderId, clubRoom.getId(), - new ChatMessageSendRequest("hello")); + new ChatMessageSendRequest("hello")); // then assertThat(response.messageId()).isEqualTo(savedMessage.getId()); assertThat(response.content()).isEqualTo("hello"); verify(notificationService).sendGroupChatNotification( - eq(clubRoom.getId()), eq(senderId), eq("BCSD"), eq("보낸이"), eq("hello"), - any(List.class) + eq(clubRoom.getId()), eq(senderId), eq("BCSD"), eq("보낸이"), eq("hello"), + any(List.class) ); } @@ -1064,25 +1064,25 @@ void sendMessageByUserInSystemAdminRoomPublishesAdminChatEvent() { User systemAdmin = createUser(SYSTEM_ADMIN_ID, "시스템관리자", UserRole.ADMIN); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember senderMember = createRoomMember(systemAdminRoom, sender, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, systemAdminRoom, sender, content, - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(senderId)).willReturn(sender); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(systemAdminRoom.getId(), senderId)) - .willReturn(Optional.of(senderMember)); + .willReturn(Optional.of(senderMember)); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, senderMember)); + .willReturn(List.of(systemAdminMember, senderMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); given(chatRoomMemberRepository.updateLastReadAtIfOlder(eq(systemAdminRoom.getId()), eq(senderId), - any(LocalDateTime.class))) - .willReturn(1); + any(LocalDateTime.class))) + .willReturn(1); // when chatService.sendMessage(senderId, systemAdminRoom.getId(), new ChatMessageSendRequest(content)); @@ -1091,12 +1091,12 @@ void sendMessageByUserInSystemAdminRoomPublishesAdminChatEvent() { ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(AdminChatReceivedEvent.class); verify(eventPublisher).publishEvent(eventCaptor.capture()); assertThat(eventCaptor.getValue()) - .extracting( - AdminChatReceivedEvent::senderId, - AdminChatReceivedEvent::senderName, - AdminChatReceivedEvent::content - ) - .containsExactly(senderId, sender.getName(), content); + .extracting( + AdminChatReceivedEvent::senderId, + AdminChatReceivedEvent::senderName, + AdminChatReceivedEvent::content + ) + .containsExactly(senderId, sender.getName(), content); } @Test @@ -1110,25 +1110,25 @@ void sendMessageAdminBypassesMembershipInSystemAdminRoom() { User targetUser = createUser(targetUserId, "사용자", UserRole.USER); ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember targetMember = createRoomMember(systemAdminRoom, targetUser, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage savedMessage = createMessage(100, systemAdminRoom, admin, "문의", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(adminId)).willReturn(admin); given(chatRoomSystemAdminService.isSystemAdminRoom(systemAdminRoom.getId())).willReturn(true); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) - .willReturn(List.of(systemAdminMember, targetMember)); + .willReturn(List.of(systemAdminMember, targetMember)); given(chatMessageRepository.save(any(ChatMessage.class))).willReturn(savedMessage); given(chatRoomRepository.updateLastMessageIfLatest( - systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() + systemAdminRoom.getId(), savedMessage.getId(), savedMessage.getContent(), savedMessage.getCreatedAt() )).willReturn(1); // when ChatMessageDetailResponse response = chatService.sendMessage(adminId, systemAdminRoom.getId(), - new ChatMessageSendRequest("문의")); + new ChatMessageSendRequest("문의")); // then assertThat(response.content()).isEqualTo("문의"); @@ -1137,10 +1137,10 @@ void sendMessageAdminBypassesMembershipInSystemAdminRoom() { verify(chatRoomMemberRepository, never()).findByChatRoomIdAndUserId(systemAdminRoom.getId(), adminId); // admin은 lastReadAt 업데이트를 하지 않는다 verify(chatRoomMemberRepository, never()).updateLastReadAtIfOlder(eq(systemAdminRoom.getId()), eq(adminId), - any(LocalDateTime.class)); + any(LocalDateTime.class)); // 비관리자에게 알림이 전송되어야 한다 verify(notificationService).sendChatNotification(eq(targetUserId), eq(systemAdminRoom.getId()), eq("관리자"), - eq("문의")); + eq("문의")); verify(eventPublisher, never()).publishEvent(any(AdminChatReceivedEvent.class)); } @@ -1166,7 +1166,7 @@ void toggleMuteRejectsNonMemberInGroupRoom() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(createUser(userId, "사용자", UserRole.USER)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode(() -> chatService.toggleMute(userId, groupRoom.getId()), FORBIDDEN_CHAT_ROOM_ACCESS); @@ -1182,11 +1182,11 @@ void updateChatRoomNameUpdatesCustomNameForMember() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("내 채팅방")); @@ -1204,12 +1204,12 @@ void updateChatRoomNameRejectsNonMember() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then assertErrorCode( - () -> chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("이름")), - FORBIDDEN_CHAT_ROOM_ACCESS + () -> chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest("이름")), + FORBIDDEN_CHAT_ROOM_ACCESS ); } @@ -1221,12 +1221,12 @@ void updateChatRoomNameNormalizesNullName() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); member.updateCustomRoomName("기존 이름"); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when chatService.updateChatRoomName(userId, groupRoom.getId(), new ChatRoomNameUpdateRequest(null)); @@ -1250,12 +1250,12 @@ void getMessagesWithNullMessageIdBehavesIdentically() { given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(1L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(groupMessage), PageRequest.of(0, 20), 1)); // when — 기존 4-arg 오버로드 호출 ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20); @@ -1274,18 +1274,18 @@ void getMessagesWithMessageIdThrowsWhenMessageNotFound() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(groupMember)); + .willReturn(Optional.of(groupMember)); given(chatMessageRepository.findByIdWithChatRoom(999)).willReturn(Optional.empty()); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 999), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 999), + NOT_FOUND_CHAT_ROOM ); } @@ -1298,21 +1298,21 @@ void getMessagesWithMessageIdThrowsWhenMessageBelongsToOtherRoom() { ChatRoom groupRoom = createRoom(1, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoom otherRoom = createRoom(2, ChatType.GROUP, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); User sender = createUser(20, "작성자", UserRole.USER); ChatMessage otherRoomMessage = createMessage(100, otherRoom, sender, "다른 방 메시지", - LocalDateTime.of(2026, 4, 11, 10, 1)); + LocalDateTime.of(2026, 4, 11, 10, 1)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)) - .willReturn(Optional.of(groupMember)); + .willReturn(Optional.of(groupMember)); given(chatMessageRepository.findByIdWithChatRoom(100)).willReturn(Optional.of(otherRoomMessage)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100), + NOT_FOUND_CHAT_ROOM ); } @@ -1327,25 +1327,25 @@ void getMessagesWithMessageIdCalculatesCorrectPageInGroupRoom() { // 타겟 메시지: roomId=1, id=50, createdAt=14:00 ChatMessage targetMessage = createMessage(50, groupRoom, user, "찾는 메시지", - LocalDateTime.of(2026, 4, 11, 14, 0)); + LocalDateTime.of(2026, 4, 11, 14, 0)); // 타겟 메시지보다 최신인 메시지가 25개 → page = 25/20 + 1 = 2 ChatMessage page2Message = createMessage(30, groupRoom, user, "페이지2 메시지", - LocalDateTime.of(2026, 4, 11, 13, 0)); + LocalDateTime.of(2026, 4, 11, 13, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(targetMessage)); given(chatMessageRepository.countNewerMessagesByChatRoomId( - groupRoom.getId(), 50, targetMessage.getCreatedAt(), null)) - .willReturn(25L); + groupRoom.getId(), 50, targetMessage.getCreatedAt(), null)) + .willReturn(25L); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(100L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(1, 20)))) // page=2이므로 offset=20 - .willReturn(new PageImpl<>(List.of(page2Message, targetMessage), PageRequest.of(1, 20), 100L)); + eq(PageRequest.of(1, 20)))) // page=2이므로 offset=20 + .willReturn(new PageImpl<>(List.of(page2Message, targetMessage), PageRequest.of(1, 20), 100L)); // when — page=1을 보내도 서버가 page=2로 덮어씀 ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20, 50); @@ -1354,7 +1354,7 @@ void getMessagesWithMessageIdCalculatesCorrectPageInGroupRoom() { assertThat(response.currentPage()).isEqualTo(2); assertThat(response.messages().stream().anyMatch(m -> m.messageId().equals(50))).isTrue(); verify(chatMessageRepository).countNewerMessagesByChatRoomId( - groupRoom.getId(), 50, targetMessage.getCreatedAt(), null); + groupRoom.getId(), 50, targetMessage.getCreatedAt(), null); } @Test @@ -1371,18 +1371,18 @@ void getMessagesWithMessageIdThrowsWhenMessageBeforeVisibleMessageFrom() { // 타겟 메시지가 visibleMessageFrom(12:00)보다 이전(10:30)에 작성됨 User partner = createUser(20, "상대", UserRole.USER); ChatMessage oldMessage = createMessage(50, directRoom, partner, "오래된 메시지", - LocalDateTime.of(2026, 4, 11, 10, 30)); + LocalDateTime.of(2026, 4, 11, 10, 30)); given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(oldMessage)); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); } @@ -1396,22 +1396,22 @@ void getMessagesWithMessageIdReturnsPage1ForNewestMessage() { ChatRoomMember groupMember = createRoomMember(groupRoom, user, false, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatMessage newestMessage = createMessage(100, groupRoom, user, "최신 메시지", - LocalDateTime.of(2026, 4, 11, 15, 0)); + LocalDateTime.of(2026, 4, 11, 15, 0)); given(chatRoomRepository.findById(groupRoom.getId())).willReturn(Optional.of(groupRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatMessageRepository.findByIdWithChatRoom(100)).willReturn(Optional.of(newestMessage)); // 최신 메시지보다 더 최신인 메시지가 0개 → page = 0/20 + 1 = 1 given(chatMessageRepository.countNewerMessagesByChatRoomId( - groupRoom.getId(), 100, newestMessage.getCreatedAt(), null)) - .willReturn(0L); + groupRoom.getId(), 100, newestMessage.getCreatedAt(), null)) + .willReturn(0L); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), userId)).willReturn( - Optional.of(groupMember)); + Optional.of(groupMember)); given(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).willReturn(List.of(groupMember)); given(chatMessageRepository.countByChatRoomId(groupRoom.getId(), null)).willReturn(50L); given(chatMessageRepository.findByChatRoomId(eq(groupRoom.getId()), nullable(LocalDateTime.class), - eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(newestMessage), PageRequest.of(0, 20), 50L)); + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(newestMessage), PageRequest.of(0, 20), 50L)); // when ChatMessagePageResponse response = chatService.getMessages(userId, groupRoom.getId(), 1, 20, 100); @@ -1433,12 +1433,12 @@ void getMessagesWithMessageIdRejectsNonMemberWithNotFound() { given(userRepository.getById(nonMemberId)).willReturn(nonMember); // 비회원은 멤버십이 없음 given(chatRoomMemberRepository.findByChatRoomIdAndUserId(groupRoom.getId(), nonMemberId)) - .willReturn(Optional.empty()); + .willReturn(Optional.empty()); // when & then — 유효한 messageId여도 접근 권한 없음과 동일한 404 assertErrorCode( - () -> chatService.getMessages(nonMemberId, groupRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(nonMemberId, groupRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); // messageId 조회 자체가 실행되지 않아야 함 verify(chatMessageRepository, never()).findByIdWithChatRoom(any()); @@ -1452,7 +1452,7 @@ void getMessagesWithMessageIdRejectsMessageAtExactVisibleMessageFromBoundary() { User user = createUser(userId, "사용자", UserRole.USER); ChatRoom directRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember member = createRoomMember(directRoom, user, false, - LocalDateTime.of(2026, 4, 11, 10, 0)); + LocalDateTime.of(2026, 4, 11, 10, 0)); LocalDateTime leftAt = LocalDateTime.of(2026, 4, 11, 12, 0); markMemberLeft(member, leftAt); @@ -1463,19 +1463,19 @@ void getMessagesWithMessageIdRejectsMessageAtExactVisibleMessageFromBoundary() { given(chatRoomRepository.findById(directRoom.getId())).willReturn(Optional.of(directRoom)); given(userRepository.getById(userId)).willReturn(user); given(chatRoomMemberRepository.findByChatRoomIdAndUserId(directRoom.getId(), userId)) - .willReturn(Optional.of(member)); + .willReturn(Optional.of(member)); given(chatMessageRepository.findByIdWithChatRoom(50)).willReturn(Optional.of(boundaryMessage)); // when & then assertErrorCode( - () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), - NOT_FOUND_CHAT_ROOM + () -> chatService.getMessages(userId, directRoom.getId(), 1, 20, 50), + NOT_FOUND_CHAT_ROOM ); } private User createUser(Integer id, String name, UserRole role) { return UserFixture.createUserWithId(UniversityFixture.createWithId(1), id, name, - "2024" + String.format("%04d", id), role); + "2024" + String.format("%04d", id), role); } private ChatRoom createRoom(Integer id, ChatType type, LocalDateTime createdAt) { @@ -1500,7 +1500,7 @@ private ChatRoom createClubRoom(Integer id, LocalDateTime createdAt) { private ChatRoomMember createRoomMember(ChatRoom room, User user, boolean isOwner, LocalDateTime lastReadAt) { ChatRoomMember member = - isOwner ? ChatRoomMember.ofOwner(room, user, lastReadAt) : ChatRoomMember.of(room, user, lastReadAt); + isOwner ? ChatRoomMember.ofOwner(room, user, lastReadAt) : ChatRoomMember.of(room, user, lastReadAt); ReflectionTestUtils.setField(member, "createdAt", lastReadAt); return member; } @@ -1519,7 +1519,7 @@ private void markMemberLeft(ChatRoomMember member, LocalDateTime leftAt) { private void assertErrorCode(ThrowingCallable callable, ApiResponseCode errorCode) { assertThatThrownBy(callable) - .isInstanceOf(CustomException.class) - .satisfies(exception -> assertThat(((CustomException) exception).getErrorCode()).isEqualTo(errorCode)); + .isInstanceOf(CustomException.class) + .satisfies(exception -> assertThat(((CustomException)exception).getErrorCode()).isEqualTo(errorCode)); } } From d748c014e80ddc7d8d3709c5bba34bf4c16f2f0f 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:19:53 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20SYSTEM=5FADMIN=20=EB=A7=88?= =?UTF-8?q?=EC=8A=A4=ED=82=B9=20=EB=B0=98=ED=99=98=20=ED=9D=90=EB=A6=84=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 - members 기반 SYSTEM_ADMIN 포함 여부 판단은 유지하면서 반환 흐름만 한 줄로 정리 - 리뷰 반영 코드의 의도를 바꾸지 않고 중복 분기만 줄임 --- .../konect/domain/chat/service/ChatMessageReadService.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java index eabdc4bd..246bb31b 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -278,10 +278,6 @@ private Integer getMaskedAdminId(User user, List members) { .map(ChatRoomMember::getUserId) .anyMatch(memberUserId -> memberUserId.equals(SYSTEM_ADMIN_ID)); - if (hasSystemAdmin) { - return SYSTEM_ADMIN_ID; - } - - return null; + return hasSystemAdmin ? SYSTEM_ADMIN_ID : null; } } From 5d16a475fc4d3cbafb15877847d320edf7d75531 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:31:08 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EB=AC=B8=EC=9D=98=EB=B0=A9=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=A1=B0=EB=A6=BD=20=EB=8B=A8=EC=9D=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatService에 남아 있던 메시지 응답 조립 메서드를 제거해 ChatMessageReadService가 단일 구현을 갖도록 정리 - 일반 사용자 문의방 조회에서도 관리자 발신자가 SYSTEM_ADMIN으로 보이도록 masking 기준을 read service에서 적용 - 일반 사용자와 관리자 문의방 조회 테스트로 가시 범위, unreadCount, sender 표시 정책 회귀를 막음 - 전체 테스트는 통과했으며 checkstyleTest는 기존 club/notification/chat 테스트 장문 라인 위반이 남아 있어 별도 정리가 필요함 --- .../chat/service/ChatMessageReadService.java | 18 +- .../domain/chat/service/ChatService.java | 285 ------------------ .../domain/chat/service/ChatServiceTest.java | 81 ++++- 3 files changed, 79 insertions(+), 305 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java index 246bb31b..7cb94b1e 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -46,9 +46,10 @@ public ChatMessagePageResponse getDirectChatRoomMessages( chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); List sortedReadBaselines = toSortedReadBaselines(members); + Integer maskedAdminId = getMaskedAdminId(user, members); return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); + visibleMessageFrom, sortedReadBaselines, maskedAdminId); } public ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( @@ -168,9 +169,7 @@ private ChatMessagePageResponse buildDirectChatRoomMessages( Integer senderId = maskedAdminId != null ? resolveDirectSenderId(message, maskedAdminId) : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); + boolean isMine = message.isSentBy(user.getId()); boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); return new ChatMessageDetailResponse( @@ -251,17 +250,6 @@ private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List getGroupChatRooms(Integer userId) { .toList(); } - private ChatMessagePageResponse buildDirectChatRoomMessages( - User user, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt, - LocalDateTime visibleMessageFrom, - List sortedReadBaselines, - Integer maskedAdminId - ) { - PageRequest pageable = PageRequest.of(page - 1, limit); - Page messages = chatMessageRepository.findByChatRoomId(roomId, visibleMessageFrom, pageable); - - List responseMessages = messages.getContent().stream() - .map(message -> { - Integer senderId = maskedAdminId != null - ? resolveDirectSenderId(message, maskedAdminId) - : message.getSender().getId(); - boolean isMine = maskedAdminId != null - ? shouldDisplayAsOwnMessage(user, message, true) - : message.isSentBy(user.getId()); - boolean isRead = isMine || !message.getCreatedAt().isAfter(readAt); - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - senderId, - null, - message.getContent(), - message.getCreatedAt(), - isRead, - unreadCount, - isMine - ); - }) - .toList(); - - return new ChatMessagePageResponse( - messages.getTotalElements(), - messages.getNumberOfElements(), - messages.getTotalPages(), - messages.getNumber() + 1, - null, - responseMessages - ); - } - - private ChatMessagePageResponse getDirectChatRoomMessages( - Integer userId, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt - ) { - ChatRoom chatRoom = getDirectRoom(roomId); - User user = userRepository.getById(userId); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - LocalDateTime visibleMessageFrom = - chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user); - - List sortedReadBaselines = toSortedReadBaselines(members); - - return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, null); - } - - private ChatMessagePageResponse getAdminSystemDirectChatRoomMessages( - User user, - ChatRoom chatRoom, - Integer roomId, - Integer page, - Integer limit, - LocalDateTime readAt - ) { - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - LocalDateTime visibleMessageFrom = resolveAdminSystemRoomVisibleMessageFrom(members); - - List sortedReadBaselines = toAdminChatReadBaselines(members); - Integer maskedAdminId = getMaskedAdminId(user, chatRoom); - - return buildDirectChatRoomMessages(user, roomId, page, limit, readAt, - visibleMessageFrom, sortedReadBaselines, maskedAdminId); - } - - private ChatMessagePageResponse getClubMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit - ) { - ChatRoom room = getClubRoom(roomId); - - PageRequest pageable = PageRequest.of(page - 1, limit); - long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); - Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); - List messages = messagePage.getContent(); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - List sortedReadBaselines = toSortedReadBaselines(members); - - List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; - return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - room.getClub().getId(), - responseMessages - ); - } - - private ChatMessagePageResponse getGroupMessagesByRoomId( - Integer roomId, - Integer userId, - Integer page, - Integer limit - ) { - chatRoomRepository.getById(roomId); - - PageRequest pageable = PageRequest.of(page - 1, limit); - long totalCount = chatMessageRepository.countByChatRoomId(roomId, null); - Page messagePage = chatMessageRepository.findByChatRoomId(roomId, null, pageable); - List messages = messagePage.getContent(); - List members = chatRoomMemberRepository.findByChatRoomId(roomId); - List sortedReadBaselines = toSortedReadBaselines(members); - - List responseMessages = messages.stream() - .map(message -> { - int unreadCount = countUnreadSince(message.getCreatedAt(), sortedReadBaselines); - return new ChatMessageDetailResponse( - message.getId(), - message.getSender().getId(), - message.getSender().getName(), - message.getContent(), - message.getCreatedAt(), - null, - unreadCount, - message.isSentBy(userId) - ); - }) - .toList(); - - int totalPage = limit > 0 ? (int)Math.ceil((double)totalCount / (double)limit) : 0; - return new ChatMessagePageResponse( - totalCount, - responseMessages.size(), - totalPage, - page, - null, - responseMessages - ); - } - private AccessibleChatRooms getAccessibleChatRooms(Integer userId) { List directRooms = getDirectChatRooms(userId); List clubRooms = getClubChatRooms(userId); @@ -643,26 +473,6 @@ private AccessibleChatRooms getAccessibleChatRooms(Integer userId) { return new AccessibleChatRooms(rooms, defaultRoomNameMap); } - private ChatRoom getDirectRoom(Integer roomId) { - ChatRoom chatRoom = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(NOT_FOUND_CHAT_ROOM)); - - if (!chatRoom.isDirectRoom()) { - throw CustomException.of(NOT_FOUND_CHAT_ROOM); - } - - return chatRoom; - } - - private ChatRoom getClubRoom(Integer roomId) { - ChatRoom room = chatRoomRepository.findById(roomId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CHAT_ROOM)); - if (!room.isClubGroupRoom()) { - throw CustomException.of(ApiResponseCode.NOT_FOUND_GROUP_CHAT_ROOM); - } - return room; - } - private List extractChatRoomIds(List chatRooms) { return chatRooms.stream() .map(ChatRoom::getId) @@ -686,28 +496,6 @@ private Map getUnreadCountMap(List chatRoomIds, Integ )); } - private Integer getMaskedAdminId(User user, ChatRoom chatRoom) { - if (user.isAdmin()) { - return null; - } - - List memberResults = chatRoomMemberRepository.findRoomMemberIdsByChatRoomIds( - List.of(chatRoom.getId()) - ); - List memberInfos = memberResults.stream() - .map(row -> new MemberInfo((Integer)row[1], (LocalDateTime)row[2])) - .toList(); - - boolean hasSystemAdmin = memberInfos.stream() - .anyMatch(info -> info.userId().equals(SYSTEM_ADMIN_ID)); - - if (hasSystemAdmin) { - return SYSTEM_ADMIN_ID; - } - - return null; - } - private ChatRoomMember getRoomMember(Integer roomId, Integer userId) { return chatRoomMemberRepository.findByChatRoomIdAndUserId(roomId, userId) .orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS)); @@ -777,56 +565,6 @@ private String normalizeCustomRoomName(String roomName) { return roomName.trim(); } - private List toSortedReadBaselines(List members) { - return members.stream() - .map(ChatRoomMember::getLastReadAt) - .sorted() - .toList(); - } - - private List toAdminChatReadBaselines(List members) { - LocalDateTime adminLastReadAt = null; - LocalDateTime userLastReadAt = null; - - for (ChatRoomMember member : members) { - if (member.getUser().isAdmin()) { - if (adminLastReadAt == null || member.getLastReadAt().isAfter(adminLastReadAt)) { - adminLastReadAt = member.getLastReadAt(); - } - } else { - userLastReadAt = member.getLastReadAt(); - } - } - - List baselines = new ArrayList<>(); - if (adminLastReadAt != null) { - baselines.add(adminLastReadAt); - } - if (userLastReadAt != null) { - baselines.add(userLastReadAt); - } - baselines.sort(Comparator.naturalOrder()); - return baselines; - } - - private int countUnreadSince(LocalDateTime messageCreatedAt, List sortedReadBaselines) { - int left = 0; - int right = sortedReadBaselines.size(); - - while (left < right) { - int mid = (left + right) >>> 1; - LocalDateTime baseline = sortedReadBaselines.get(mid); - - if (baseline.isBefore(messageCreatedAt)) { - left = mid + 1; - } else { - right = mid; - } - } - - return left; - } - private Map getRoomUnreadCountMap(List roomIds, Integer userId) { if (roomIds.isEmpty()) { return Map.of(); @@ -851,29 +589,6 @@ private Map getRoomUnreadCountMap(List roomIds, Integ return unreadCountMap; } - private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List members) { - ChatRoomMember systemAdminMember = chatRoomSystemAdminService.findSystemAdminMember(members); - return systemAdminMember != null ? systemAdminMember.getVisibleMessageFrom() : null; - } - - private boolean shouldDisplayAsOwnMessage( - User currentUser, - ChatMessage message, - boolean isAdminViewingSystemRoom - ) { - if (isAdminViewingSystemRoom) { - return message.getSender().isAdmin(); - } - return message.isSentBy(currentUser.getId()); - } - - private Integer resolveDirectSenderId(ChatMessage message, Integer maskedAdminId) { - if (maskedAdminId != null && message.getSender().isAdmin()) { - return maskedAdminId; - } - return message.getSender().getId(); - } - private ChatRoomMember findRoomMember(List members, Integer userId) { return members.stream() .filter(member -> member.getUserId().equals(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 a719f908..9dfea97b 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 @@ -12,6 +12,7 @@ import static gg.agit.konect.global.code.ApiResponseCode.NOT_FOUND_USER; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -828,8 +829,14 @@ void getMessagesReturnsAdminSystemRoomMessages() { LocalDateTime.of(2026, 4, 11, 10, 0)); ChatRoomMember targetMember = createRoomMember(systemAdminRoom, targetUser, false, LocalDateTime.of(2026, 4, 11, 10, 0)); - ChatMessage message = createMessage(100, systemAdminRoom, admin, "문의", - LocalDateTime.of(2026, 4, 11, 10, 1)); + ReflectionTestUtils.setField(systemAdminMember, "visibleMessageFrom", + LocalDateTime.of(2026, 4, 11, 10, 5)); + systemAdminMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 7)); + targetMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 3)); + ChatMessage adminMessage = createMessage(100, systemAdminRoom, admin, "관리자 답변", + LocalDateTime.of(2026, 4, 11, 10, 6)); + ChatMessage userMessage = createMessage(101, systemAdminRoom, targetUser, "사용자 문의", + LocalDateTime.of(2026, 4, 11, 10, 8)); given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); given(userRepository.getById(adminId)).willReturn(admin); @@ -838,20 +845,84 @@ void getMessagesReturnsAdminSystemRoomMessages() { .willReturn(systemAdminMember); given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) .willReturn(List.of(systemAdminMember, targetMember)); - given(chatMessageRepository.findByChatRoomId(eq(systemAdminRoom.getId()), nullable(LocalDateTime.class), + given(chatMessageRepository.findByChatRoomId(eq(systemAdminRoom.getId()), + eq(systemAdminMember.getVisibleMessageFrom()), eq(PageRequest.of(0, 20)))) - .willReturn(new PageImpl<>(List.of(message), PageRequest.of(0, 20), 1)); + .willReturn(new PageImpl<>(List.of(adminMessage, userMessage), PageRequest.of(0, 20), 2)); // when ChatMessagePageResponse response = chatService.getMessages(adminId, systemAdminRoom.getId(), 1, 20); // then - assertThat(response.messages()).hasSize(1); + assertThat(response.messages()) + .extracting( + ChatMessageDetailResponse::senderId, + ChatMessageDetailResponse::content, + ChatMessageDetailResponse::unreadCount, + ChatMessageDetailResponse::isMine + ) + .containsExactly( + tuple(adminId, "관리자 답변", 1, true), + tuple(targetUser.getId(), "사용자 문의", 2, false) + ); verify(chatRoomMembershipService).updateLastReadAt(eq(systemAdminRoom.getId()), eq(SYSTEM_ADMIN_ID), any(LocalDateTime.class)); verify(chatPresenceService).recordPresence(systemAdminRoom.getId(), adminId); } + @Test + @DisplayName("getMessages는 일반 사용자의 system admin 방 조회에서 가시 범위와 sender masking을 적용한다") + void getMessagesAppliesVisibilityAndSenderMaskingForUserInSystemAdminRoom() { + // given + Integer userId = 20; + Integer adminId = 99; + User user = createUser(userId, "사용자", UserRole.USER); + User admin = createUser(adminId, "관리자", UserRole.ADMIN); + User systemAdmin = createUser(SYSTEM_ADMIN_ID, "시스템관리자", UserRole.ADMIN); + ChatRoom systemAdminRoom = createRoom(1, ChatType.DIRECT, LocalDateTime.of(2026, 4, 11, 10, 0)); + ChatRoomMember userMember = createRoomMember(systemAdminRoom, user, false, + LocalDateTime.of(2026, 4, 11, 10, 0)); + ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, + LocalDateTime.of(2026, 4, 11, 10, 0)); + ReflectionTestUtils.setField(userMember, "visibleMessageFrom", LocalDateTime.of(2026, 4, 11, 10, 5)); + userMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 7)); + systemAdminMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 3)); + ChatMessage adminMessage = createMessage(100, systemAdminRoom, admin, "관리자 답변", + LocalDateTime.of(2026, 4, 11, 10, 6)); + ChatMessage userMessage = createMessage(101, systemAdminRoom, user, "사용자 문의", + LocalDateTime.of(2026, 4, 11, 10, 8)); + + given(chatRoomRepository.findById(systemAdminRoom.getId())).willReturn(Optional.of(systemAdminRoom)); + given(userRepository.getById(userId)).willReturn(user); + given(chatRoomMemberRepository.findByChatRoomIdAndUserId(systemAdminRoom.getId(), userId)) + .willReturn(Optional.of(userMember)); + given(chatRoomMemberRepository.findByChatRoomId(systemAdminRoom.getId())) + .willReturn(List.of(systemAdminMember, userMember)); + given(chatMessageRepository.findByChatRoomId(eq(systemAdminRoom.getId()), + eq(userMember.getVisibleMessageFrom()), + eq(PageRequest.of(0, 20)))) + .willReturn(new PageImpl<>(List.of(adminMessage, userMessage), PageRequest.of(0, 20), 2)); + + // when + ChatMessagePageResponse response = chatService.getMessages(userId, systemAdminRoom.getId(), 1, 20); + + // then + assertThat(response.messages()) + .extracting( + ChatMessageDetailResponse::senderId, + ChatMessageDetailResponse::content, + ChatMessageDetailResponse::unreadCount, + ChatMessageDetailResponse::isMine + ) + .containsExactly( + tuple(SYSTEM_ADMIN_ID, "관리자 답변", 1, false), + tuple(userId, "사용자 문의", 2, true) + ); + verify(chatRoomMembershipService).updateDirectRoomLastReadAt(eq(systemAdminRoom.getId()), eq(user), + any(LocalDateTime.class), eq(systemAdminRoom)); + verify(chatPresenceService).recordPresence(systemAdminRoom.getId(), userId); + } + // ===== sendMessage ===== @Test From 2f9e6b65a3bb54f3c31bc2e448c52528e4b7c38d 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:34 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20direct=20unread=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=97=90=20=EA=B0=80=EC=8B=9C=20=EB=B2=94=EC=9C=84=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - direct 계열 메시지의 unread baseline을 lastReadAt 단독 기준에서 visibleMessageFrom을 함께 보는 기준으로 정리 - 나갔다가 복원된 멤버가 볼 수 없는 과거 메시지가 unreadCount에 포함되지 않도록 방지 - 문의방 조회 테스트에 visibleMessageFrom이 lastReadAt보다 늦은 멤버를 포함해 회귀를 검증 --- .../chat/service/ChatMessageReadService.java | 23 +++++++++++++++---- .../domain/chat/service/ChatServiceTest.java | 8 +++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java index 7cb94b1e..2d1eab08 100644 --- a/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java +++ b/src/main/java/gg/agit/konect/domain/chat/service/ChatMessageReadService.java @@ -197,7 +197,7 @@ private ChatMessagePageResponse buildDirectChatRoomMessages( private List toSortedReadBaselines(List members) { return members.stream() - .map(ChatRoomMember::getLastReadAt) + .map(this::resolveUnreadBaseline) .sorted() .toList(); } @@ -207,12 +207,13 @@ private List toAdminChatReadBaselines(List member LocalDateTime userLastReadAt = null; for (ChatRoomMember member : members) { + LocalDateTime unreadBaseline = resolveUnreadBaseline(member); if (member.getUser().isAdmin()) { - if (adminLastReadAt == null || member.getLastReadAt().isAfter(adminLastReadAt)) { - adminLastReadAt = member.getLastReadAt(); + if (adminLastReadAt == null || unreadBaseline.isAfter(adminLastReadAt)) { + adminLastReadAt = unreadBaseline; } } else { - userLastReadAt = member.getLastReadAt(); + userLastReadAt = unreadBaseline; } } @@ -227,6 +228,20 @@ private List toAdminChatReadBaselines(List member return baselines; } + private LocalDateTime resolveUnreadBaseline(ChatRoomMember member) { + LocalDateTime lastReadAt = member.getLastReadAt(); + LocalDateTime visibleMessageFrom = member.getVisibleMessageFrom(); + + // direct 방에서 다시 보이기 시작한 시각 이전 메시지는 unreadCount에도 포함하지 않는다. + if (visibleMessageFrom == null) { + return lastReadAt; + } + if (lastReadAt == null) { + return visibleMessageFrom; + } + return lastReadAt.isAfter(visibleMessageFrom) ? lastReadAt : visibleMessageFrom; + } + private int countUnreadSince(LocalDateTime messageCreatedAt, List sortedReadBaselines) { int left = 0; int right = sortedReadBaselines.size(); 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 9dfea97b..176e4ba7 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 @@ -831,6 +831,8 @@ void getMessagesReturnsAdminSystemRoomMessages() { LocalDateTime.of(2026, 4, 11, 10, 0)); ReflectionTestUtils.setField(systemAdminMember, "visibleMessageFrom", LocalDateTime.of(2026, 4, 11, 10, 5)); + ReflectionTestUtils.setField(targetMember, "visibleMessageFrom", + LocalDateTime.of(2026, 4, 11, 10, 7)); systemAdminMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 7)); targetMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 3)); ChatMessage adminMessage = createMessage(100, systemAdminRoom, admin, "관리자 답변", @@ -862,7 +864,7 @@ void getMessagesReturnsAdminSystemRoomMessages() { ChatMessageDetailResponse::isMine ) .containsExactly( - tuple(adminId, "관리자 답변", 1, true), + tuple(adminId, "관리자 답변", 0, true), tuple(targetUser.getId(), "사용자 문의", 2, false) ); verify(chatRoomMembershipService).updateLastReadAt(eq(systemAdminRoom.getId()), eq(SYSTEM_ADMIN_ID), @@ -885,6 +887,8 @@ void getMessagesAppliesVisibilityAndSenderMaskingForUserInSystemAdminRoom() { ChatRoomMember systemAdminMember = createRoomMember(systemAdminRoom, systemAdmin, false, LocalDateTime.of(2026, 4, 11, 10, 0)); ReflectionTestUtils.setField(userMember, "visibleMessageFrom", LocalDateTime.of(2026, 4, 11, 10, 5)); + ReflectionTestUtils.setField(systemAdminMember, "visibleMessageFrom", + LocalDateTime.of(2026, 4, 11, 10, 7)); userMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 7)); systemAdminMember.updateLastReadAt(LocalDateTime.of(2026, 4, 11, 10, 3)); ChatMessage adminMessage = createMessage(100, systemAdminRoom, admin, "관리자 답변", @@ -915,7 +919,7 @@ void getMessagesAppliesVisibilityAndSenderMaskingForUserInSystemAdminRoom() { ChatMessageDetailResponse::isMine ) .containsExactly( - tuple(SYSTEM_ADMIN_ID, "관리자 답변", 1, false), + tuple(SYSTEM_ADMIN_ID, "관리자 답변", 0, false), tuple(userId, "사용자 문의", 2, true) ); verify(chatRoomMembershipService).updateDirectRoomLastReadAt(eq(systemAdminRoom.getId()), eq(user),