From ccaab93e19bc71a93b0192db6f8d3555760ce2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 1 Apr 2026 17:25:35 +0900 Subject: [PATCH 1/7] =?UTF-8?q?test:=20=EA=B7=B8=EB=A3=B9=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=20=EA=B0=95=ED=87=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/domain/chat/ChatApiTest.java | 426 +++++++++++++++++- 1 file changed, 418 insertions(+), 8 deletions(-) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index e31174f5..60f51cdb 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -243,8 +243,8 @@ void setUpInvitableUsersFixture() { .clubPosition(ClubPosition.MEMBER) .build()); - ChatRoom bcsdRoom = persist(ChatRoom.groupOf(bcsd)); - ChatRoom cseRoom = persist(ChatRoom.groupOf(cse)); + ChatRoom bcsdRoom = persist(ChatRoom.clubGroupOf(bcsd)); + ChatRoom cseRoom = persist(ChatRoom.clubGroupOf(cse)); createDirectChatRoom(normalUser, directOnlyUser); createDirectChatRoom(normalUser, adminCandidate); @@ -658,7 +658,7 @@ void cannotOperateHiddenDirectRoomBeforeNewMessage() throws Exception { @DisplayName("동아리 채팅방은 나갈 수 없다") void leaveGroupChatRoomFails() throws Exception { Club club = persist(ClubFixture.create(university)); - ChatRoom groupRoom = persist(ChatRoom.groupOf(club)); + ChatRoom groupRoom = persist(ChatRoom.clubGroupOf(club)); ChatRoom managedGroupRoom = entityManager.getReference(ChatRoom.class, groupRoom.getId()); User managedNormalUser = entityManager.getReference(User.class, normalUser.getId()); persist(ChatRoomMember.of(managedGroupRoom, managedNormalUser, groupRoom.getCreatedAt())); @@ -754,7 +754,7 @@ void setUpSearchFixture() { void searchChatsReturnsRoomMatchesForDirectAndGroupRooms() throws Exception { // given ChatRoom directRoom = createDirectChatRoom(normalUser, targetUser); - ChatRoom groupRoom = persist(ChatRoom.groupOf(developmentClub)); + ChatRoom groupRoom = persist(ChatRoom.clubGroupOf(developmentClub)); addRoomMember(groupRoom, normalUser); persistChatMessage(directRoom, normalUser, "안녕하세요"); mockLoginUser(normalUser.getId()); @@ -769,7 +769,7 @@ void searchChatsReturnsRoomMatchesForDirectAndGroupRooms() throws Exception { .andExpect(jsonPath("$.roomMatches.rooms[0].roomName").value("개발팀")) .andExpect(jsonPath("$.roomMatches.rooms[0].chatType").value("DIRECT")) .andExpect(jsonPath("$.roomMatches.rooms[1].roomName").value("개발동아리")) - .andExpect(jsonPath("$.roomMatches.rooms[1].chatType").value("GROUP")) + .andExpect(jsonPath("$.roomMatches.rooms[1].chatType").value("CLUB_GROUP")) .andExpect(jsonPath("$.messageMatches.totalCount").value(0)) .andExpect(jsonPath("$.messageMatches.currentCount").value(0)); } @@ -876,7 +876,7 @@ void searchChatsAppliesPaginationToRoomMatches() throws Exception { // given createDirectChatRoom(normalUser, targetUser); createDirectChatRoom(normalUser, secondTargetUser); - ChatRoom groupRoom = persist(ChatRoom.groupOf(developmentClub)); + ChatRoom groupRoom = persist(ChatRoom.clubGroupOf(developmentClub)); addRoomMember(groupRoom, normalUser); mockLoginUser(normalUser.getId()); @@ -895,7 +895,7 @@ void searchChatsAppliesPaginationToRoomMatches() throws Exception { void searchChatsWithVeryLargePageReturnsEmptyResult() throws Exception { // given createDirectChatRoom(normalUser, targetUser); - ChatRoom groupRoom = persist(ChatRoom.groupOf(developmentClub)); + ChatRoom groupRoom = persist(ChatRoom.clubGroupOf(developmentClub)); addRoomMember(groupRoom, normalUser); mockLoginUser(normalUser.getId()); @@ -956,6 +956,403 @@ void toggleMuteSuccessAndDuplicateProcessing() throws Exception { } } + @Nested + @DisplayName("POST /chats/rooms/group - 그룹 채팅방 생성") + class CreateGroupChatRoom { + + private User memberA; + private User memberB; + + @BeforeEach + void setUpGroupChatFixture() { + memberA = createUser("멤버A", "2021136002"); + memberB = createUser("멤버B", "2021136003"); + clearPersistenceContext(); + } + + @Test + @DisplayName("그룹 채팅방을 생성하면 방장과 멤버가 모두 참여한다") + void createGroupChatRoomSuccess() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when & then + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.chatRoomId").isNumber()); + + // then - 방장(owner) 확인 + clearPersistenceContext(); + Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) + .stream().findFirst().orElseThrow().getId(); + + ChatRoomMember ownerMember = chatRoomMemberRepository + .findByChatRoomIdAndUserId(roomId, normalUser.getId()).orElseThrow(); + assertThat(ownerMember.isOwner()).isTrue(); + + // then - 일반 멤버 확인 + ChatRoomMember memberARecord = chatRoomMemberRepository + .findByChatRoomIdAndUserId(roomId, memberA.getId()).orElseThrow(); + assertThat(memberARecord.isOwner()).isFalse(); + + ChatRoomMember memberBRecord = chatRoomMemberRepository + .findByChatRoomIdAndUserId(roomId, memberB.getId()).orElseThrow(); + assertThat(memberBRecord.isOwner()).isFalse(); + + assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(3); + } + + @Test + @DisplayName("userIds에 자신만 포함되면 400을 반환한다") + void createGroupChatRoomWithSelfOnlyFails() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when & then + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(normalUser.getId()) + )) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("CANNOT_CREATE_CHAT_ROOM_WITH_SELF")); + } + + @Test + @DisplayName("userIds가 빈 리스트이면 validation 에러를 반환한다") + void createGroupChatRoomWithEmptyUserIdsFails() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when & then + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group(List.of())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("INVALID_REQUEST_BODY")); + } + + @Test + @DisplayName("존재하지 않는 userId가 포함되면 404를 반환한다") + void createGroupChatRoomWithNonExistentUserFails() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when & then + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), 99999) + )) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("NOT_FOUND_USER")); + } + + @Test + @DisplayName("중복 userId는 무시하고 정상 생성한다") + void createGroupChatRoomWithDuplicateUserIds() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberA.getId(), memberB.getId()) + )) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.chatRoomId").isNumber()); + + // then - 멤버 수가 중복 제거된 3명(방장 + A + B)이어야 한다 + clearPersistenceContext(); + Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) + .stream().findFirst().orElseThrow().getId(); + assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(3); + } + + @Test + @DisplayName("userIds에 자신이 포함되어도 무시하고 정상 생성한다") + void createGroupChatRoomWithSelfInUserIds() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(normalUser.getId(), memberA.getId()) + )) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.chatRoomId").isNumber()); + + // then - 멤버 수가 2명(방장 + A)이어야 한다 + clearPersistenceContext(); + Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) + .stream().findFirst().orElseThrow().getId(); + assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(2); + } + + @Test + @DisplayName("생성된 그룹 채팅방에 메시지를 전송할 수 있다") + void canSendMessageToCreatedGroupChatRoom() throws Exception { + // given + mockLoginUser(normalUser.getId()); + Integer roomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); + + // when & then + performPost( + "/chats/rooms/" + roomId + "/messages", + new ChatMessageSendRequest("그룹방 첫 메시지") + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.messageId").isNumber()) + .andExpect(jsonPath("$.senderId").value(normalUser.getId())) + .andExpect(jsonPath("$.senderName").value(normalUser.getName())) + .andExpect(jsonPath("$.content").value("그룹방 첫 메시지")) + .andExpect(jsonPath("$.isMine").value(true)); + } + } + + @Nested + @DisplayName("DELETE /chats/rooms/{chatRoomId}/members/{targetUserId} - 멤버 강퇴") + class KickMember { + + private User ownerUser; + private User memberUser; + private User anotherMember; + private ChatRoom groupRoom; + + @BeforeEach + void setUpKickFixture() { + ownerUser = createUser("방장", "2021136002"); + memberUser = createUser("멤버", "2021136003"); + anotherMember = createUser("다른멤버", "2021136004"); + groupRoom = createGroupChatRoomWithOwner(ownerUser, memberUser, anotherMember); + clearPersistenceContext(); + } + + @Test + @DisplayName("방장이 일반 멤버를 강퇴한다") + void kickMemberSuccess() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isNoContent()); + + // then + clearPersistenceContext(); + assertThat(chatRoomMemberRepository.findByChatRoomIdAndUserId( + groupRoom.getId(), memberUser.getId() + )).isEmpty(); + assertThat(chatRoomMemberRepository.findByChatRoomIdAndUserId( + groupRoom.getId(), ownerUser.getId() + )).isPresent(); + assertThat(chatRoomMemberRepository.findByChatRoomId(groupRoom.getId())).hasSize(2); + } + + @Test + @DisplayName("방장이 아닌 멤버가 강퇴하면 403을 반환한다") + void kickMemberByNonOwnerFails() throws Exception { + // given + mockLoginUser(memberUser.getId()); + + // when & then + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + anotherMember.getId()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_KICK")); + } + + @Test + @DisplayName("자기 자신을 강퇴하면 400을 반환한다") + void kickSelfFails() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + ownerUser.getId()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("CANNOT_KICK_SELF")); + } + + @Test + @DisplayName("1:1 채팅방에서 강퇴하면 400을 반환한다") + void kickInDirectRoomFails() throws Exception { + // given + ChatRoom directRoom = createDirectChatRoom(ownerUser, memberUser); + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + directRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("CANNOT_KICK_IN_NON_GROUP_ROOM")); + } + + @Test + @DisplayName("동아리 채팅방에서 강퇴하면 400을 반환한다") + void kickInClubRoomFails() throws Exception { + // given + Club club = persist(ClubFixture.create(university)); + ChatRoom clubRoom = persist(ChatRoom.clubGroupOf(club)); + addRoomMember(clubRoom, ownerUser); + addRoomMember(clubRoom, memberUser); + clearPersistenceContext(); + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + clubRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("CANNOT_KICK_IN_NON_GROUP_ROOM")); + } + + @Test + @DisplayName("다른 방장(owner) 멤버를 강퇴하면 400을 반환한다") + void kickAnotherOwnerFails() throws Exception { + // given - 방장 2명인 그룹 방을 수동 생성 + ChatRoom room = persist(ChatRoom.groupOf()); + ChatRoom managedRoom = entityManager.getReference(ChatRoom.class, room.getId()); + User managedOwner = entityManager.getReference(User.class, ownerUser.getId()); + User managedMember = entityManager.getReference(User.class, memberUser.getId()); + persist(ChatRoomMember.ofOwner(managedRoom, managedOwner, room.getCreatedAt())); + persist(ChatRoomMember.ofOwner(managedRoom, managedMember, room.getCreatedAt())); + clearPersistenceContext(); + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + room.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("CANNOT_KICK_ROOM_OWNER")); + } + + @Test + @DisplayName("채팅방에 없는 멤버를 강퇴하면 403을 반환한다") + void kickNonMemberTargetFails() throws Exception { + // given + User outsiderUser = createUser("외부인", "2021136005"); + clearPersistenceContext(); + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + outsiderUser.getId()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + + @Test + @DisplayName("강퇴된 멤버는 채팅방 이름을 수정할 수 없다") + void kickedMemberCannotUpdateRoomName() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isNoContent()); + + // when & then + mockLoginUser(memberUser.getId()); + performPatch( + "/chats/rooms/" + groupRoom.getId() + "/name", + new ChatRoomNameUpdateRequest("강퇴 후 이름") + ) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + + @Test + @DisplayName("강퇴된 멤버는 메시지를 보낼 수 없다") + void kickedMemberCannotSendMessage() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isNoContent()); + + // when & then + mockLoginUser(memberUser.getId()); + performPost( + "/chats/rooms/" + groupRoom.getId() + "/messages", + new ChatMessageSendRequest("강퇴 후 메시지") + ) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + + @Test + @DisplayName("강퇴된 멤버의 방 목록에서 해당 방이 제거된다") + void kickedMemberRoomRemovedFromList() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isNoContent()); + + // when & then + mockLoginUser(memberUser.getId()); + performGet("/chats/rooms") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rooms[?(@.roomId==" + groupRoom.getId() +")]").doesNotExist()); + } + + @Test + @DisplayName("존재하지 않는 채팅방에서 강퇴하면 404를 반환한다") + void kickInNonExistentRoomFails() throws Exception { + // given + mockLoginUser(ownerUser.getId()); + + // when & then + performDelete("/chats/rooms/99999/members/" + memberUser.getId()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("NOT_FOUND_CHAT_ROOM")); + } + + @Test + @DisplayName("채팅방 멤버가 아닌 사용자가 강퇴를 시도하면 403을 반환한다") + void kickByOutsiderRequesterFails() throws Exception { + // given + User outsiderUser = createUser("외부인", "2021136005"); + clearPersistenceContext(); + mockLoginUser(outsiderUser.getId()); + + // when & then + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + + @Test + @DisplayName("이미 나간 멤버를 강퇴하면 403을 반환한다") + void kickAlreadyLeftMemberFails() throws Exception { + // given - member leaves the group room + mockLoginUser(memberUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId()) + .andExpect(status().isNoContent()); + + // when & then - owner tries to kick the left member + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + + @Test + @DisplayName("마지막 멤버를 강퇴하면 방에 방장만 남는다") + void kickLastMemberLeavesOwnerOnly() throws Exception { + // given - room with only owner and one member + User soleMember = createUser("유일멤버", "2021136005"); + ChatRoom twoPersonRoom = createGroupChatRoomWithOwner(ownerUser, soleMember); + clearPersistenceContext(); + + mockLoginUser(ownerUser.getId()); + + // when + performDelete("/chats/rooms/" + twoPersonRoom.getId() + "/members/" + soleMember.getId()) + .andExpect(status().isNoContent()); + + // then - only owner remains + clearPersistenceContext(); + assertThat(chatRoomMemberRepository.findByChatRoomId(twoPersonRoom.getId())).hasSize(1); + assertThat(chatRoomMemberRepository.findByChatRoomIdAndUserId(twoPersonRoom.getId(), ownerUser.getId())) + .isPresent() + .get() + .extracting(ChatRoomMember::isOwner) + .isEqualTo(true); + } + } + private ChatRoom createDirectChatRoom(User firstUser, User secondUser) { ChatRoom chatRoom = persist(ChatRoom.directOf()); LocalDateTime joinedAt = chatRoom.getCreatedAt(); @@ -969,6 +1366,19 @@ private ChatRoom createDirectChatRoom(User firstUser, User secondUser) { return chatRoom; } + private ChatRoom createGroupChatRoomWithOwner(User owner, User... members) { + ChatRoom groupRoom = persist(ChatRoom.groupOf()); + ChatRoom managedRoom = entityManager.getReference(ChatRoom.class, groupRoom.getId()); + User managedOwner = entityManager.getReference(User.class, owner.getId()); + persist(ChatRoomMember.ofOwner(managedRoom, managedOwner, groupRoom.getCreatedAt())); + for (User member : members) { + User managedMember = entityManager.getReference(User.class, member.getId()); + persist(ChatRoomMember.of(managedRoom, managedMember, groupRoom.getCreatedAt())); + } + clearPersistenceContext(); + return groupRoom; + } + private User createUser(String name, String studentId) { return persist(UserFixture.createUser(university, name, studentId)); } @@ -1013,7 +1423,7 @@ private void createGroupedInviteCandidates(String clubName, String namePrefix, i .clubPosition(ClubPosition.MEMBER) .build()); - ChatRoom groupRoom = persist(ChatRoom.groupOf(club)); + ChatRoom groupRoom = persist(ChatRoom.clubGroupOf(club)); addRoomMember(groupRoom, normalUser); for (int index = 1; index <= count; index++) { From c14d90cb3421352623f577debe03c76946e90e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 1 Apr 2026 17:30:33 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20=EA=B7=B8=EB=A3=B9=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/domain/chat/ChatApiTest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index 60f51cdb..2ae1175f 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -1084,6 +1084,61 @@ void createGroupChatRoomWithSelfInUserIds() throws Exception { assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(2); } + @Test + @DisplayName("초대받은 멤버도 그룹 채팅방에 메시지를 전송할 수 있다") + void invitedMemberCanSendMessageToGroupChatRoom() throws Exception { + // given + mockLoginUser(normalUser.getId()); + Integer roomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); + + // when & then - memberA(초대받은 멤버)가 메시지 전송 + mockLoginUser(memberA.getId()); + performPost( + "/chats/rooms/" + roomId + "/messages", + new ChatMessageSendRequest("초대받은 멤버의 메시지") + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.senderId").value(memberA.getId())) + .andExpect(jsonPath("$.senderName").value(memberA.getName())) + .andExpect(jsonPath("$.content").value("초대받은 멤버의 메시지")) + .andExpect(jsonPath("$.isMine").value(true)); + } + + @Test + @DisplayName("같은 유저들로 그룹 채팅방을 여러 개 생성할 수 있다") + void createMultipleGroupChatRoomsWithSameUsers() throws Exception { + // given + mockLoginUser(normalUser.getId()); + + // when - 같은 유저들로 두 번째 그룹방 생성 + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.chatRoomId").isNumber()); + + Integer firstRoomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) + .stream().findFirst().orElseThrow().getId(); + + // when - 같은 유저들로 두 번째 그룹방 생성 + Integer secondRoomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); + + // then - 서로 다른 방이 생성됨 (DIRECT와 달리 중복 허용) + assertThat(secondRoomId).isNotEqualTo(firstRoomId); + clearPersistenceContext(); + assertThat(chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId())).hasSize(2); + } + @Test @DisplayName("생성된 그룹 채팅방에 메시지를 전송할 수 있다") void canSendMessageToCreatedGroupChatRoom() throws Exception { @@ -1351,6 +1406,22 @@ void kickLastMemberLeavesOwnerOnly() throws Exception { .extracting(ChatRoomMember::isOwner) .isEqualTo(true); } + + @Test + @DisplayName("방장이 나간 그룹방에서 일반 멤버가 강퇴할 수 없다") + void kickFailsAfterOwnerLeaves() throws Exception { + // given - 3인 그룹방: owner + memberUser + anotherMember + // owner가 나가면 방장이 없는 상태가 됨 + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId()) + .andExpect(status().isNoContent()); + + // when & then - 남은 일반 멤버가 다른 멤버를 강퇴 시도 + mockLoginUser(memberUser.getId()); + performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + anotherMember.getId()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_KICK")); + } } private ChatRoom createDirectChatRoom(User firstUser, User secondUser) { From f44f631380d76034b3385c8f6eac944320edf44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 1 Apr 2026 17:30:42 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agit/konect/integration/domain/chat/ChatApiTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index 2ae1175f..d49e5d19 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -1339,7 +1339,7 @@ void kickedMemberRoomRemovedFromList() throws Exception { mockLoginUser(memberUser.getId()); performGet("/chats/rooms") .andExpect(status().isOk()) - .andExpect(jsonPath("$.rooms[?(@.roomId==" + groupRoom.getId() +")]").doesNotExist()); + .andExpect(jsonPath("$.rooms[?(@.roomId==" + groupRoom.getId() + ")]").doesNotExist()); } @Test From 6f5209dd6ef52d887442477581f87cd78f2ff42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 1 Apr 2026 19:16:10 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=EA=B7=B8=EB=A3=B9=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20=EC=83=9D=EC=84=B1=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/domain/chat/ChatApiTest.java | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index d49e5d19..1f6baa5d 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -976,18 +976,20 @@ void createGroupChatRoomSuccess() throws Exception { // given mockLoginUser(normalUser.getId()); - // when & then - performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( - List.of(memberA.getId(), memberB.getId()) - )) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.chatRoomId").isNumber()); + // when + int roomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); - // then - 방장(owner) 확인 + // then - 채팅방 타입이 GROUP인지 확인 clearPersistenceContext(); - Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) - .stream().findFirst().orElseThrow().getId(); + ChatRoom chatRoom = chatRoomRepository.findById(roomId).orElseThrow(); + assertThat(chatRoom.getRoomType()).isEqualTo(ChatType.GROUP); + // then - 방장(owner) 확인 ChatRoomMember ownerMember = chatRoomMemberRepository .findByChatRoomIdAndUserId(roomId, normalUser.getId()).orElseThrow(); assertThat(ownerMember.isOwner()).isTrue(); @@ -1051,16 +1053,15 @@ void createGroupChatRoomWithDuplicateUserIds() throws Exception { mockLoginUser(normalUser.getId()); // when - performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( - List.of(memberA.getId(), memberA.getId(), memberB.getId()) - )) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.chatRoomId").isNumber()); + int roomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); // then - 멤버 수가 중복 제거된 3명(방장 + A + B)이어야 한다 clearPersistenceContext(); - Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) - .stream().findFirst().orElseThrow().getId(); assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(3); } @@ -1071,16 +1072,15 @@ void createGroupChatRoomWithSelfInUserIds() throws Exception { mockLoginUser(normalUser.getId()); // when - performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( - List.of(normalUser.getId(), memberA.getId()) - )) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.chatRoomId").isNumber()); + int roomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(normalUser.getId(), memberA.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); // then - 멤버 수가 2명(방장 + A)이어야 한다 clearPersistenceContext(); - Integer roomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) - .stream().findFirst().orElseThrow().getId(); assertThat(chatRoomMemberRepository.findByChatRoomId(roomId)).hasSize(2); } @@ -1089,7 +1089,7 @@ void createGroupChatRoomWithSelfInUserIds() throws Exception { void invitedMemberCanSendMessageToGroupChatRoom() throws Exception { // given mockLoginUser(normalUser.getId()); - Integer roomId = objectMapper.readTree( + int roomId = objectMapper.readTree( performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( List.of(memberA.getId(), memberB.getId()) )) @@ -1115,18 +1115,16 @@ void createMultipleGroupChatRoomsWithSameUsers() throws Exception { // given mockLoginUser(normalUser.getId()); - // when - 같은 유저들로 두 번째 그룹방 생성 - performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( - List.of(memberA.getId(), memberB.getId()) - )) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.chatRoomId").isNumber()); - - Integer firstRoomId = chatRoomRepository.findGroupRoomsByMemberUserId(normalUser.getId()) - .stream().findFirst().orElseThrow().getId(); + // when - 같은 유저들로 첫 번째 그룹방 생성 + int firstRoomId = objectMapper.readTree( + performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( + List.of(memberA.getId(), memberB.getId()) + )) + .andReturn().getResponse().getContentAsString() + ).get("chatRoomId").asInt(); // when - 같은 유저들로 두 번째 그룹방 생성 - Integer secondRoomId = objectMapper.readTree( + int secondRoomId = objectMapper.readTree( performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( List.of(memberA.getId(), memberB.getId()) )) @@ -1144,7 +1142,7 @@ void createMultipleGroupChatRoomsWithSameUsers() throws Exception { void canSendMessageToCreatedGroupChatRoom() throws Exception { // given mockLoginUser(normalUser.getId()); - Integer roomId = objectMapper.readTree( + int roomId = objectMapper.readTree( performPost("/chats/rooms/group", new ChatRoomCreateRequest.Group( List.of(memberA.getId(), memberB.getId()) )) From 1cf66e875510d243f9ae6d5a010bb29e47f91983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Wed, 1 Apr 2026 19:27:59 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=EA=B0=95=ED=87=B4=EB=90=9C=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gg/agit/konect/integration/domain/chat/ChatApiTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index 1f6baa5d..82d82a1d 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -1311,14 +1311,15 @@ void kickedMemberCannotUpdateRoomName() throws Exception { @DisplayName("강퇴된 멤버는 메시지를 보낼 수 없다") void kickedMemberCannotSendMessage() throws Exception { // given + Integer roomId = groupRoom.getId(); mockLoginUser(ownerUser.getId()); - performDelete("/chats/rooms/" + groupRoom.getId() + "/members/" + memberUser.getId()) + performDelete("/chats/rooms/" + roomId + "/members/" + memberUser.getId()) .andExpect(status().isNoContent()); // when & then mockLoginUser(memberUser.getId()); performPost( - "/chats/rooms/" + groupRoom.getId() + "/messages", + "/chats/rooms/" + roomId + "/messages", new ChatMessageSendRequest("강퇴 후 메시지") ) .andExpect(status().isForbidden()) From eb683c4a578e7ccec2605a114564d7b180e15265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Thu, 2 Apr 2026 22:20:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20=EA=B0=95=ED=87=B4=EB=90=9C=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/domain/chat/ChatApiTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index 82d82a1d..8f191f68 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -1326,6 +1326,25 @@ void kickedMemberCannotSendMessage() throws Exception { .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); } + @Test + @DisplayName("강퇴된 멤버는 메시지를 조회할 수 없다") + void kickedMemberCannotGetMessages() throws Exception { + // given + Integer roomId = groupRoom.getId(); + System.out.println("DEBUG: roomId = " + roomId); + mockLoginUser(ownerUser.getId()); + performDelete("/chats/rooms/" + roomId + "/members/" + memberUser.getId()) + .andExpect(status().isNoContent()); + + clearPersistenceContext(); + + // when & then + mockLoginUser(memberUser.getId()); + performGet("/chats/rooms/" + roomId + "?page=1&limit=20") + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("FORBIDDEN_CHAT_ROOM_ACCESS")); + } + @Test @DisplayName("강퇴된 멤버의 방 목록에서 해당 방이 제거된다") void kickedMemberRoomRemovedFromList() throws Exception { From 165801de450aa612e5b733ecab28d76e1a2ac13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Thu, 2 Apr 2026 22:42:05 +0900 Subject: [PATCH 7/7] =?UTF-8?q?test:=20=EC=B1=84=ED=8C=85=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B6=8C=ED=95=9C=20=EC=BC=80=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=BB=A4=EB=B0=8B=EB=90=9C=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/integration/domain/chat/ChatApiTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java index 8f191f68..aacb3cec 100644 --- a/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java @@ -14,7 +14,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.transaction.TestTransaction; import gg.agit.konect.domain.chat.dto.ChatMessageSendRequest; import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest; @@ -719,10 +721,15 @@ void getMessagesNotFound() throws Exception { } @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) @DisplayName("참여하지 않은 사용자가 조회하면 403을 반환한다") void getMessagesForbidden() throws Exception { // given ChatRoom chatRoom = createDirectChatRoom(normalUser, targetUser); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + mockLoginUser(outsiderUser.getId()); // when & then @@ -1327,16 +1334,17 @@ void kickedMemberCannotSendMessage() throws Exception { } @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) @DisplayName("강퇴된 멤버는 메시지를 조회할 수 없다") void kickedMemberCannotGetMessages() throws Exception { // given Integer roomId = groupRoom.getId(); - System.out.println("DEBUG: roomId = " + roomId); mockLoginUser(ownerUser.getId()); performDelete("/chats/rooms/" + roomId + "/members/" + memberUser.getId()) .andExpect(status().isNoContent()); - clearPersistenceContext(); + TestTransaction.flagForCommit(); + TestTransaction.end(); // when & then mockLoginUser(memberUser.getId());