From 8d7fb4a96a915f7995842fdb7e542f5faed0f7fd Mon Sep 17 00:00:00 2001 From: Jansoon Date: Wed, 17 Dec 2025 01:17:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20API=20=EA=B5=AC=ED=98=84=20(#128)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/controller/ChatApi.java | 9 ++- .../chat/controller/ChatController.java | 17 +++- .../domain/chat/dto/response/ChatsRes.java | 34 ++++++++ .../domain/chat/facade/ChatFacade.java | 29 +++++++ .../chat/repository/ChatRepository.java | 4 + .../domain/chat/service/ChatFacade.java | 15 ---- .../domain/chat/service/ChatQueryService.java | 8 ++ .../domain/chat/service/ChatService.java | 9 +++ .../domain/chat/facade/ChatFacadeTest.java | 56 +++++++++++++ .../ChatControllerIntegrationTest.java | 79 +++++++++++++++++++ .../chat/repository/ChatRepositoryTest.java | 53 +++++++++++++ .../chat/service/ChatQueryServiceTest.java | 44 +++++++++++ .../domain/chat/service/ChatServiceTest.java | 46 +++++++++++ 13 files changed, 386 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/sofa/linkiving/domain/chat/dto/response/ChatsRes.java create mode 100644 src/main/java/com/sofa/linkiving/domain/chat/facade/ChatFacade.java delete mode 100644 src/main/java/com/sofa/linkiving/domain/chat/service/ChatFacade.java create mode 100644 src/test/java/com/sofa/linkiving/domain/chat/facade/ChatFacadeTest.java create mode 100644 src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java create mode 100644 src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java create mode 100644 src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java create mode 100644 src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java diff --git a/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatApi.java b/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatApi.java index f2a98db2..df9fe42e 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatApi.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatApi.java @@ -1,7 +1,14 @@ package com.sofa.linkiving.domain.chat.controller; +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes; +import com.sofa.linkiving.domain.member.entity.Member; +import com.sofa.linkiving.global.common.BaseResponse; + +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -@Tag(name = "Chat", description = "링크 관리 API") +@Tag(name = "Chat", description = "채팅 관리 API") public interface ChatApi { + @Operation(summary = "채팅방 목록 조회", description = "사용자의 채팅방 목록 정보(채팅방 Id, 제목)을 조회합니다.") + BaseResponse getChats(Member member); } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatController.java b/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatController.java index 63e7bd5b..2c1c60a8 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatController.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/controller/ChatController.java @@ -1,12 +1,27 @@ package com.sofa.linkiving.domain.chat.controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes; +import com.sofa.linkiving.domain.chat.facade.ChatFacade; +import com.sofa.linkiving.domain.member.entity.Member; +import com.sofa.linkiving.global.common.BaseResponse; +import com.sofa.linkiving.security.annotation.AuthMember; + import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/v1/chat") +@RequestMapping("/v1/chats") @RequiredArgsConstructor public class ChatController implements ChatApi { + private final ChatFacade chatFacade; + + @Override + @GetMapping + public BaseResponse getChats(@AuthMember Member member) { + ChatsRes res = chatFacade.getChats(member); + return BaseResponse.success(res, "채팅방 목록 조회를 성공했습니다."); + } } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/dto/response/ChatsRes.java b/src/main/java/com/sofa/linkiving/domain/chat/dto/response/ChatsRes.java new file mode 100644 index 00000000..225cf388 --- /dev/null +++ b/src/main/java/com/sofa/linkiving/domain/chat/dto/response/ChatsRes.java @@ -0,0 +1,34 @@ +package com.sofa.linkiving.domain.chat.dto.response; + +import java.util.List; + +import com.sofa.linkiving.domain.chat.entity.Chat; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record ChatsRes( + @Schema(description = "채팅방 목록") + List chats +) { + + public static ChatsRes from(List chatEntities) { + List summaries = chatEntities.stream() + .map(ChatSummary::from) + .toList(); + return new ChatsRes(summaries); + } + + public record ChatSummary( + @Schema(description = "채팅방 Id") + Long id, + @Schema(description = "채팅방 제목") + String title + ) { + public static ChatSummary from(Chat chat) { + return new ChatSummary( + chat.getId(), + chat.getTitle() + ); + } + } +} diff --git a/src/main/java/com/sofa/linkiving/domain/chat/facade/ChatFacade.java b/src/main/java/com/sofa/linkiving/domain/chat/facade/ChatFacade.java new file mode 100644 index 00000000..82d869dc --- /dev/null +++ b/src/main/java/com/sofa/linkiving/domain/chat/facade/ChatFacade.java @@ -0,0 +1,29 @@ +package com.sofa.linkiving.domain.chat.facade; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes; +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.chat.service.ChatService; +import com.sofa.linkiving.domain.chat.service.FeedbackService; +import com.sofa.linkiving.domain.chat.service.MessageService; +import com.sofa.linkiving.domain.member.entity.Member; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ChatFacade { + private final ChatService chatService; + private final MessageService messageService; + private final FeedbackService feedbackService; + + public ChatsRes getChats(Member member) { + List chats = chatService.getChats(member); + return ChatsRes.from(chats); + } +} diff --git a/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java b/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java index aefdbc3a..481d8bd6 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java @@ -1,10 +1,14 @@ package com.sofa.linkiving.domain.chat.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.member.entity.Member; @Repository public interface ChatRepository extends JpaRepository { + List findAllByMemberOrderByCreatedAtDesc(Member member); } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatFacade.java b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatFacade.java deleted file mode 100644 index bde2875c..00000000 --- a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatFacade.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sofa.linkiving.domain.chat.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class ChatFacade { - private final ChatService chatService; - private final MessageService messageService; - private final FeedbackService feedbackService; -} diff --git a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java index b79cf4f5..7a460e0f 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java @@ -1,8 +1,12 @@ package com.sofa.linkiving.domain.chat.service; +import java.util.List; + import org.springframework.stereotype.Service; +import com.sofa.linkiving.domain.chat.entity.Chat; import com.sofa.linkiving.domain.chat.repository.ChatRepository; +import com.sofa.linkiving.domain.member.entity.Member; import lombok.RequiredArgsConstructor; @@ -10,4 +14,8 @@ @RequiredArgsConstructor public class ChatQueryService { private final ChatRepository chatRepository; + + public List findAll(Member member) { + return chatRepository.findAllByMemberOrderByCreatedAtDesc(member); + } } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java index 8566c080..f5bea5a3 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java @@ -1,7 +1,12 @@ package com.sofa.linkiving.domain.chat.service; +import java.util.List; + import org.springframework.stereotype.Service; +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.member.entity.Member; + import lombok.RequiredArgsConstructor; @Service @@ -9,4 +14,8 @@ public class ChatService { private final ChatCommandService chatCommandService; private final ChatQueryService chatQueryService; + + public List getChats(Member member) { + return chatQueryService.findAll(member); + } } diff --git a/src/test/java/com/sofa/linkiving/domain/chat/facade/ChatFacadeTest.java b/src/test/java/com/sofa/linkiving/domain/chat/facade/ChatFacadeTest.java new file mode 100644 index 00000000..3e827c99 --- /dev/null +++ b/src/test/java/com/sofa/linkiving/domain/chat/facade/ChatFacadeTest.java @@ -0,0 +1,56 @@ +package com.sofa.linkiving.domain.chat.facade; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes; +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.chat.service.ChatService; +import com.sofa.linkiving.domain.chat.service.FeedbackService; +import com.sofa.linkiving.domain.chat.service.MessageService; +import com.sofa.linkiving.domain.member.entity.Member; + +@ExtendWith(MockitoExtension.class) +public class ChatFacadeTest { + @InjectMocks + private ChatFacade chatFacade; + + @Mock + private ChatService chatService; + + @Mock + private MessageService messageService; + @Mock + private FeedbackService feedbackService; + @Mock + private Member member; + + @Test + @DisplayName("ChatService.getChats 호출 및 ChatsRes 변환 반환") + void shouldReturnChatsResWhenGetChats() { + // given + Chat chat = mock(Chat.class); + given(chat.getId()).willReturn(1L); + given(chat.getTitle()).willReturn("Title"); + + given(chatService.getChats(member)).willReturn(List.of(chat)); + + // when + ChatsRes result = chatFacade.getChats(member); + + // then + assertThat(result.chats()).hasSize(1); + assertThat(result.chats().get(0).title()).isEqualTo("Title"); + + verify(chatService).getChats(member); + } +} diff --git a/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java b/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java new file mode 100644 index 00000000..822653b7 --- /dev/null +++ b/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java @@ -0,0 +1,79 @@ +package com.sofa.linkiving.domain.chat.integration; + +import static org.mockito.BDDMockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes; +import com.sofa.linkiving.domain.chat.dto.response.ChatsRes.ChatSummary; +import com.sofa.linkiving.domain.chat.facade.ChatFacade; +import com.sofa.linkiving.domain.member.entity.Member; +import com.sofa.linkiving.domain.member.enums.Role; +import com.sofa.linkiving.domain.member.repository.MemberRepository; +import com.sofa.linkiving.infra.redis.RedisService; +import com.sofa.linkiving.security.userdetails.CustomMemberDetail; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +@ActiveProfiles("test") +public class ChatControllerIntegrationTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private MemberRepository memberRepository; + + @MockitoBean + private ChatFacade chatFacade; + @MockitoBean + private RedisService redisService; + + private Member testMember; + private UserDetails testUserDetails; + + @BeforeEach + void setUp() { + testMember = memberRepository.save(Member.builder() + .email("list@test.com") + .password("password") + .build()); + testUserDetails = new CustomMemberDetail(testMember, Role.USER); + } + + @Test + @DisplayName("채팅방 목록 조회 API 호출 및 성공 응답 반환") + void shouldReturnChatListWhenGetChats() throws Exception { + // given + ChatsRes mockResponse = new ChatsRes(List.of( + new ChatSummary(1L, "Chat 1"), + new ChatSummary(2L, "Chat 2") + )); + + given(chatFacade.getChats(any(Member.class))).willReturn(mockResponse); + + // when & then + mockMvc.perform(get("/v1/chats") + .with(user(testUserDetails))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data.chats").isArray()) + .andExpect(jsonPath("$.data.chats[0].title").value("Chat 1")); + } +} diff --git a/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java b/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java new file mode 100644 index 00000000..060689e0 --- /dev/null +++ b/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java @@ -0,0 +1,53 @@ +package com.sofa.linkiving.domain.chat.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; + +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.member.entity.Member; +import com.sofa.linkiving.domain.member.repository.MemberRepository; + +@DataJpaTest +@ActiveProfiles("test") +public class ChatRepositoryTest { + @Autowired + private ChatRepository chatRepository; + @Autowired + private MemberRepository memberRepository; + + @Test + @DisplayName("회원별 채팅방 목록 조회 및 생성일 내림차순 정렬") + void shouldReturnChatsDescByCreatedAtWhenFindAllByMember() throws InterruptedException { + // given + Member member = memberRepository.save( + Member.builder() + .email("test@list.com") + .password("password") + .build()); + + Chat oldChat = chatRepository.save(Chat.builder() + .member(member) + .title("Old Chat") + .build()); + Thread.sleep(100); + Chat newChat = chatRepository.save(Chat.builder() + .member(member) + .title("New Chat") + .build()); + + // when + List result = chatRepository.findAllByMemberOrderByCreatedAtDesc(member); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getTitle()).isEqualTo("New Chat"); + assertThat(result.get(1).getTitle()).isEqualTo("Old Chat"); + } +} diff --git a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java new file mode 100644 index 00000000..eb7202f8 --- /dev/null +++ b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java @@ -0,0 +1,44 @@ +package com.sofa.linkiving.domain.chat.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.chat.repository.ChatRepository; +import com.sofa.linkiving.domain.member.entity.Member; + +@ExtendWith(MockitoExtension.class) +public class ChatQueryServiceTest { + @InjectMocks + private ChatQueryService chatQueryService; + + @Mock + private ChatRepository chatRepository; + + @Mock + private Member member; + + @Test + @DisplayName("ChatRepository.findAllByMemberOrderByCreatedAtDesc 호출 및 반환") + void shouldReturnChatListWhenFindAll() { + // given + List chats = List.of(mock(Chat.class)); + given(chatRepository.findAllByMemberOrderByCreatedAtDesc(member)).willReturn(chats); + + // when + List result = chatQueryService.findAll(member); + + // then + assertThat(result).isEqualTo(chats); + verify(chatRepository).findAllByMemberOrderByCreatedAtDesc(member); + } +} diff --git a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java new file mode 100644 index 00000000..8bb46d63 --- /dev/null +++ b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java @@ -0,0 +1,46 @@ +package com.sofa.linkiving.domain.chat.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.member.entity.Member; + +@ExtendWith(MockitoExtension.class) +public class ChatServiceTest { + @InjectMocks + private ChatService chatService; + + @Mock + private ChatQueryService chatQueryService; + + @Mock + private ChatCommandService chatCommandService; + + @Mock + private Member member; + + @Test + @DisplayName("채팅방 목록 조회 시 ChatQueryService.findAll을 호출하고 결과 반환") + void shouldReturnChatsWhenGetChats() { + // given + List expectedChats = List.of(mock(Chat.class)); + given(chatQueryService.findAll(member)).willReturn(expectedChats); + + // when + List result = chatService.getChats(member); + + // then + assertThat(result).isEqualTo(expectedChats); + verify(chatQueryService).findAll(member); + } +} From 46276bc2ea6cbc67f7927eed5ea2d2c534e0a6af Mon Sep 17 00:00:00 2001 From: Jansoon Date: Wed, 17 Dec 2025 10:07:49 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EC=88=9C=EC=84=9C=EB=A5=BC=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/repository/ChatRepository.java | 12 +++- .../domain/chat/service/ChatQueryService.java | 4 +- .../domain/chat/service/ChatService.java | 2 +- ...tionTest.java => ChatIntegrationTest.java} | 2 +- .../chat/repository/ChatRepositoryTest.java | 69 +++++++++++++++---- .../chat/service/ChatQueryServiceTest.java | 8 +-- .../domain/chat/service/ChatServiceTest.java | 4 +- 7 files changed, 76 insertions(+), 25 deletions(-) rename src/test/java/com/sofa/linkiving/domain/chat/integration/{ChatControllerIntegrationTest.java => ChatIntegrationTest.java} (98%) diff --git a/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java b/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java index 481d8bd6..506b675c 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/repository/ChatRepository.java @@ -3,6 +3,8 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.sofa.linkiving.domain.chat.entity.Chat; @@ -10,5 +12,13 @@ @Repository public interface ChatRepository extends JpaRepository { - List findAllByMemberOrderByCreatedAtDesc(Member member); + @Query(""" + SELECT c + FROM Chat c + JOIN Message m ON m.chat = c + WHERE c.member = :member + GROUP BY c + ORDER BY MAX(m.createdAt) DESC + """) + List findAllByMemberOrderByLastMessageDesc(@Param("member") Member member); } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java index 7a460e0f..f2b43a06 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatQueryService.java @@ -15,7 +15,7 @@ public class ChatQueryService { private final ChatRepository chatRepository; - public List findAll(Member member) { - return chatRepository.findAllByMemberOrderByCreatedAtDesc(member); + public List findAllOrderByLastMessageDesc(Member member) { + return chatRepository.findAllByMemberOrderByLastMessageDesc(member); } } diff --git a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java index f5bea5a3..df440ced 100644 --- a/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java +++ b/src/main/java/com/sofa/linkiving/domain/chat/service/ChatService.java @@ -16,6 +16,6 @@ public class ChatService { private final ChatQueryService chatQueryService; public List getChats(Member member) { - return chatQueryService.findAll(member); + return chatQueryService.findAllOrderByLastMessageDesc(member); } } diff --git a/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java b/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatIntegrationTest.java similarity index 98% rename from src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java rename to src/test/java/com/sofa/linkiving/domain/chat/integration/ChatIntegrationTest.java index 822653b7..4b0304df 100644 --- a/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatControllerIntegrationTest.java +++ b/src/test/java/com/sofa/linkiving/domain/chat/integration/ChatIntegrationTest.java @@ -33,7 +33,7 @@ @AutoConfigureMockMvc @Transactional @ActiveProfiles("test") -public class ChatControllerIntegrationTest { +public class ChatIntegrationTest { @Autowired private MockMvc mockMvc; @Autowired diff --git a/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java b/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java index 060689e0..4f41718c 100644 --- a/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java +++ b/src/test/java/com/sofa/linkiving/domain/chat/repository/ChatRepositoryTest.java @@ -4,6 +4,7 @@ import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -11,9 +12,13 @@ import org.springframework.test.context.ActiveProfiles; import com.sofa.linkiving.domain.chat.entity.Chat; +import com.sofa.linkiving.domain.chat.entity.Message; +import com.sofa.linkiving.domain.chat.enums.Type; import com.sofa.linkiving.domain.member.entity.Member; import com.sofa.linkiving.domain.member.repository.MemberRepository; +import jakarta.persistence.EntityManager; + @DataJpaTest @ActiveProfiles("test") public class ChatRepositoryTest { @@ -21,33 +26,69 @@ public class ChatRepositoryTest { private ChatRepository chatRepository; @Autowired private MemberRepository memberRepository; + @Autowired + private MessageRepository messageRepository; + @Autowired + private EntityManager em; + + private Member member; + + @BeforeEach + void setUp() { + member = memberRepository.save(Member.builder() + .email("test@example.com") + .password("test") + .build()); + + em.flush(); + em.clear(); + } @Test - @DisplayName("회원별 채팅방 목록 조회 및 생성일 내림차순 정렬") - void shouldReturnChatsDescByCreatedAtWhenFindAllByMember() throws InterruptedException { + @DisplayName("메시지가 있는 채팅방만 조회되며, 최신 메시지 순으로 정렬됨") + void shouldReturnOnlyChatsWithMessagesOrderByLastMessageTime() throws InterruptedException { // given - Member member = memberRepository.save( - Member.builder() - .email("test@list.com") - .password("password") - .build()); - Chat oldChat = chatRepository.save(Chat.builder() + // 메시지 없는 채팅방 -> 조회되지 않아야 함 + chatRepository.save(Chat + .builder() .member(member) - .title("Old Chat") + .title("No Msg Chat") .build()); + Thread.sleep(100); - Chat newChat = chatRepository.save(Chat.builder() + + // 오래된 메시지가 있는 채팅방 + Chat chatOldMsg = chatRepository.save(Chat.builder() + .member(member) + .title("Old Msg Chat") + .build()); + messageRepository.save(Message.builder() + .chat(chatOldMsg) + .content("Old") + .type(Type.USER) + .build()); + + Thread.sleep(100); + + // 최신 메시지가 있는 채팅방 + Chat chatNewMsg = chatRepository.save(Chat.builder() .member(member) - .title("New Chat") + .title("New Msg Chat") + .build()); + messageRepository.save(Message + .builder() + .chat(chatNewMsg) + .content("New") + .type(Type.USER) .build()); // when - List result = chatRepository.findAllByMemberOrderByCreatedAtDesc(member); + List result = chatRepository.findAllByMemberOrderByLastMessageDesc(member); // then assertThat(result).hasSize(2); - assertThat(result.get(0).getTitle()).isEqualTo("New Chat"); - assertThat(result.get(1).getTitle()).isEqualTo("Old Chat"); + assertThat(result.get(0).getTitle()).isEqualTo("New Msg Chat"); + assertThat(result.get(1).getTitle()).isEqualTo("Old Msg Chat"); } } diff --git a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java index eb7202f8..8a7ef177 100644 --- a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java +++ b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatQueryServiceTest.java @@ -29,16 +29,16 @@ public class ChatQueryServiceTest { @Test @DisplayName("ChatRepository.findAllByMemberOrderByCreatedAtDesc 호출 및 반환") - void shouldReturnChatListWhenFindAll() { + void shouldReturnChatListWhenFindAllOrderByLastMessageDesc() { // given List chats = List.of(mock(Chat.class)); - given(chatRepository.findAllByMemberOrderByCreatedAtDesc(member)).willReturn(chats); + given(chatRepository.findAllByMemberOrderByLastMessageDesc(member)).willReturn(chats); // when - List result = chatQueryService.findAll(member); + List result = chatQueryService.findAllOrderByLastMessageDesc(member); // then assertThat(result).isEqualTo(chats); - verify(chatRepository).findAllByMemberOrderByCreatedAtDesc(member); + verify(chatRepository).findAllByMemberOrderByLastMessageDesc(member); } } diff --git a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java index 8bb46d63..d43c256b 100644 --- a/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java +++ b/src/test/java/com/sofa/linkiving/domain/chat/service/ChatServiceTest.java @@ -34,13 +34,13 @@ public class ChatServiceTest { void shouldReturnChatsWhenGetChats() { // given List expectedChats = List.of(mock(Chat.class)); - given(chatQueryService.findAll(member)).willReturn(expectedChats); + given(chatQueryService.findAllOrderByLastMessageDesc(member)).willReturn(expectedChats); // when List result = chatService.getChats(member); // then assertThat(result).isEqualTo(expectedChats); - verify(chatQueryService).findAll(member); + verify(chatQueryService).findAllOrderByLastMessageDesc(member); } }