diff --git a/src/main/java/corecord/dev/domain/chat/dto/request/ChatRequest.java b/src/main/java/corecord/dev/domain/chat/dto/request/ChatRequest.java index 270c1d7..18d7f9d 100644 --- a/src/main/java/corecord/dev/domain/chat/dto/request/ChatRequest.java +++ b/src/main/java/corecord/dev/domain/chat/dto/request/ChatRequest.java @@ -1,11 +1,13 @@ package corecord.dev.domain.chat.dto.request; import jakarta.validation.constraints.NotBlank; +import lombok.Builder; import lombok.Getter; public class ChatRequest { @Getter + @Builder public static class ChatDto { private boolean guide; @NotBlank(message = "채팅을 입력해주세요.") diff --git a/src/test/java/corecord/dev/chat/repository/ChatRepositoryTest.java b/src/test/java/corecord/dev/chat/repository/ChatRepositoryTest.java new file mode 100644 index 0000000..671333f --- /dev/null +++ b/src/test/java/corecord/dev/chat/repository/ChatRepositoryTest.java @@ -0,0 +1,108 @@ +package corecord.dev.chat.repository; + +import corecord.dev.domain.chat.entity.Chat; +import corecord.dev.domain.chat.entity.ChatRoom; +import corecord.dev.domain.chat.repository.ChatRepository; +import corecord.dev.domain.chat.repository.ChatRoomRepository; +import corecord.dev.domain.user.entity.User; +import corecord.dev.domain.user.repository.UserRepository; +import jakarta.persistence.EntityManager; +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@Transactional +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ChatRepositoryTest { + + @Autowired + ChatRepository chatRepository; + + @Autowired + ChatRoomRepository chatRoomRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + EntityManager entityManager; + + @Test + @DisplayName("채팅방 ID로 채팅 삭제 테스트") + void deleteByChatRoomId() { + // Given + User user = createTestUser(); + ChatRoom chatRoom = createTestChatRoom(user); + + createTestChat(chatRoom, "First message"); + createTestChat(chatRoom, "Second message"); + + List chatsBeforeDelete = chatRepository.findByChatRoomOrderByChatId(chatRoom); + assertEquals(2, chatsBeforeDelete.size()); + + // When + chatRepository.deleteByChatRoomId(chatRoom.getChatRoomId()); + entityManager.flush(); + + // Then + List chatsAfterDelete = chatRepository.findByChatRoomOrderByChatId(chatRoom); + assertTrue(chatsAfterDelete.isEmpty()); + } + + @Test + @DisplayName("채팅방에 속한 채팅 조회 테스트") + void findByChatRoomOrderByChatId() { + // Given + User user = createTestUser(); + ChatRoom chatRoom = createTestChatRoom(user); + + createTestChat(chatRoom, "First message"); + createTestChat(chatRoom, "Second message"); + createTestChat(chatRoom, "Third message"); + + // When + List chats = chatRepository.findByChatRoomOrderByChatId(chatRoom); + + // Then + assertEquals(3, chats.size()); + assertEquals("First message", chats.get(0).getContent()); + assertEquals("Second message", chats.get(1).getContent()); + assertEquals("Third message", chats.get(2).getContent()); + } + + private User createTestUser() { + // 사용자 저장 시 persist 호출 + User user = User.builder() + .providerId("testProvider") + .nickName("TestUser") + .status(corecord.dev.domain.user.entity.Status.UNIVERSITY_STUDENT) + .build(); + userRepository.save(user); + return user; + } + + private ChatRoom createTestChatRoom(User user) { + ChatRoom chatRoom = ChatRoom.builder() + .user(user) + .build(); + chatRoomRepository.save(chatRoom); + return chatRoom; + } + + private void createTestChat(ChatRoom chatRoom, String content) { + Chat chat = Chat.builder() + .author(1) + .content(content) + .chatRoom(chatRoom) + .build(); + chatRepository.save(chat); + } +} diff --git a/src/test/java/corecord/dev/chat/repository/ChatRoomRepositoryTest.java b/src/test/java/corecord/dev/chat/repository/ChatRoomRepositoryTest.java new file mode 100644 index 0000000..6b3528c --- /dev/null +++ b/src/test/java/corecord/dev/chat/repository/ChatRoomRepositoryTest.java @@ -0,0 +1,77 @@ +package corecord.dev.chat.repository; + +import corecord.dev.domain.chat.entity.ChatRoom; +import corecord.dev.domain.chat.repository.ChatRoomRepository; +import corecord.dev.domain.user.entity.Status; +import corecord.dev.domain.user.entity.User; +import corecord.dev.domain.user.repository.UserRepository; +import jakarta.persistence.EntityManager; +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@Transactional +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ChatRoomRepositoryTest { + + @Autowired + ChatRoomRepository chatRoomRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + EntityManager entityManager; + + @Test + @DisplayName("ChatRoom ID와 User로 채팅방 조회 테스트") + void findByChatRoomIdAndUser() { + // Given + User user = createTestUser(); + ChatRoom chatRoom = createTestChatRoom(user); + + // When + Optional foundChatRoom = chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user); + + // Then + assertTrue(foundChatRoom.isPresent()); + assertEquals(foundChatRoom.get().getUser(), user); + } + + @Test + @DisplayName("존재하지 않는 채팅방 조회 테스트") + void findByChatRoomIdAndUser_NotFound() { + // Given + User user = createTestUser(); + + // When + Optional foundChatRoom = chatRoomRepository.findByChatRoomIdAndUser(999L, user); + + // Then + assertFalse(foundChatRoom.isPresent()); + } + + private User createTestUser() { + User user = User.builder() + .providerId("testProvider") + .nickName("TestUser") + .status(Status.UNIVERSITY_STUDENT) + .build(); + return userRepository.save(user); + } + + private ChatRoom createTestChatRoom(User user) { + ChatRoom chatRoom = ChatRoom.builder() + .user(user) + .build(); + return chatRoomRepository.save(chatRoom); + } +} diff --git a/src/test/java/corecord/dev/chat/service/ChatServiceTest.java b/src/test/java/corecord/dev/chat/service/ChatServiceTest.java new file mode 100644 index 0000000..71e209e --- /dev/null +++ b/src/test/java/corecord/dev/chat/service/ChatServiceTest.java @@ -0,0 +1,325 @@ +package corecord.dev.chat.service; + +import corecord.dev.domain.chat.dto.request.ChatRequest; +import corecord.dev.domain.chat.dto.response.ChatResponse; +import corecord.dev.domain.chat.entity.Chat; +import corecord.dev.domain.chat.entity.ChatRoom; +import corecord.dev.domain.chat.exception.model.ChatException; +import corecord.dev.domain.chat.repository.ChatRepository; +import corecord.dev.domain.chat.repository.ChatRoomRepository; +import corecord.dev.domain.chat.service.ChatService; +import corecord.dev.domain.chat.service.ClovaRequest; +import corecord.dev.domain.chat.service.ClovaService; +import corecord.dev.domain.user.entity.Status; +import corecord.dev.domain.user.entity.User; +import corecord.dev.domain.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ChatServiceTest { + + @InjectMocks + private ChatService chatService; + + @Mock + private ChatRoomRepository chatRoomRepository; + + @Mock + private ChatRepository chatRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private ClovaService clovaService; + + private User user; + + private ChatRoom chatRoom; + + @BeforeEach + void setUp() { + user = createTestUser(); + chatRoom = createTestChatRoom(); + + } + + @Test + @DisplayName("채팅방 생성 테스트") + void createChatRoom() { + // Given + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + + // When + ChatResponse.ChatRoomDto result = chatService.createChatRoom(user.getUserId()); + + // Then + verify(chatRoomRepository).save(any(ChatRoom.class)); + verify(chatRepository).save(any(Chat.class)); + assertEquals(result.getFirstChat(), "안녕하세요! testUser님\n오늘은 어떤 경험을 했나요?\n저와 함께 정리해보아요!"); + } + + @Test + @DisplayName("채팅 조회 테스트") + void getChatList() throws NoSuchFieldException, IllegalAccessException { + // Given + Chat userChat = createTestChat("userChat", 1); + Chat aiChat = createTestChat("aiChat", 0); + + + when(chatRepository.findByChatRoomOrderByChatId(chatRoom)).thenReturn(List.of(userChat, aiChat)); + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + + // When + ChatResponse.ChatListDto result = chatService.getChatList(user.getUserId(), chatRoom.getChatRoomId()); + + // Then + assertEquals(result.getChats().size(), 2); + assertEquals(result.getChats().get(0).getContent(), "userChat"); + assertEquals(result.getChats().get(1).getContent(), "aiChat"); + } + + @Nested + @DisplayName("채팅 생성하기 테스트") + class ChatServiceAiResponseTests { + + @Test + @DisplayName("가이드 호출 시") + void createChatWithGuide() { + // Given + ChatRequest.ChatDto request = ChatRequest.ChatDto.builder() + .guide(true) + .content("어떤 경험을 말해야 할지 모르겠어요.") + .build(); + + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + + // When + ChatResponse.ChatsDto result = chatService.createChat( + user.getUserId(), + chatRoom.getChatRoomId(), + request + ); + + // Then + verify(chatRepository, times(3)).save(any(Chat.class)); // 사용자 입력 1개, 가이드 2개 + assertEquals(result.getChats().size(), 2); // Guide 메시지는 두 개 생성 + assertEquals(result.getChats().get(0).getContent(), "걱정 마세요!\n저와 대화하다 보면 경험이 정리될 거예요\uD83D\uDCDD"); + assertEquals(result.getChats().get(1).getContent(), "오늘은 어떤 경험을 했나요?\n상황과 해결한 문제를 말해주세요!"); + } + + @Test + @DisplayName("AI 응답 성공 시") + void createChatWithSuccess() { + // Given + ChatRequest.ChatDto request = ChatRequest.ChatDto.builder() + .content("테스트 입력") + .build(); + + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + when(chatRepository.save(any(Chat.class))).thenAnswer(invocation -> invocation.getArgument(0)); // 저장된 Chat 객체 반환 + when(clovaService.generateAiResponse(any(ClovaRequest.class))).thenReturn("AI의 예상 응답"); + + // When + ChatResponse.ChatsDto result = chatService.createChat( + user.getUserId(), + chatRoom.getChatRoomId(), + request + ); + + // Then + verify(chatRepository, times(2)).save(any(Chat.class)); // 사용자 입력 1개, AI 응답 1개 + assertEquals(result.getChats().size(), 1); + assertEquals(result.getChats().getFirst().getContent(), "AI의 예상 응답"); + } + } + + @Nested + @DisplayName("채팅 요약 정보 생성 테스트") + class ChatSummaryTests { + + @Test + @DisplayName("AI 응답 성공 시") + void validAiResponse() throws NoSuchFieldException, IllegalAccessException { + // Given + List chatList = List.of( + createTestChat("userChat1", 1), + createTestChat("aiChat1", 0), + createTestChat("userChat2", 1) + ); + + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + when(chatRepository.findByChatRoomOrderByChatId(chatRoom)).thenReturn(chatList); + when(clovaService.generateAiResponse(any(ClovaRequest.class))) + .thenReturn("{\"title\":\"요약 제목\",\"content\":\"요약 내용\"}"); + + // When + ChatResponse.ChatSummaryDto result = chatService.getChatSummary(user.getUserId(), chatRoom.getChatRoomId()); + + // Then + assertEquals(result.getTitle(), "요약 제목"); + assertEquals(result.getContent(), "요약 내용"); + } + + @Test + @DisplayName("AI 응답이 빈 경우") + void emptyAiResponse() throws NoSuchFieldException, IllegalAccessException { + // Given + List chatList = List.of( + createTestChat("userChat1", 1), + createTestChat("aiChat1", 0) + ); + + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + when(chatRepository.findByChatRoomOrderByChatId(chatRoom)).thenReturn(chatList); + when(clovaService.generateAiResponse(any(ClovaRequest.class))) + .thenReturn("{\"title\":\"\",\"content\":\"\"}"); // 빈 응답 + + // When & Then + assertThrows(ChatException.class, () -> chatService.getChatSummary(user.getUserId(), chatRoom.getChatRoomId())); + } + + @Test + @DisplayName("AI 응답이 긴 경우 예외 발생 (제목 50자 초과)") + void longAiTitle() throws NoSuchFieldException, IllegalAccessException { + // Given + List chatList = List.of( + createTestChat("userChat1", 1), + createTestChat("aiChat1", 0) + ); + + String longTitle = "a".repeat(51); // 51자 제목 생성 + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + when(chatRepository.findByChatRoomOrderByChatId(chatRoom)).thenReturn(chatList); + when(clovaService.generateAiResponse(any(ClovaRequest.class))) + .thenReturn(String.format("{\"title\":\"%s\",\"content\":\"정상 내용\"}", longTitle)); // 50자 초과 제목 + + // When & Then + assertThrows(ChatException.class, () -> chatService.getChatSummary(user.getUserId(), chatRoom.getChatRoomId())); + } + + @Test + @DisplayName("AI 응답이 긴 경우 예외 발생 (내용 500자 초과)") + void longAiResponse() throws NoSuchFieldException, IllegalAccessException { + // Given + List chatList = List.of( + createTestChat("userChat1", 1), + createTestChat("aiChat1", 0) + ); + + String longContent = "a".repeat(501); // 501자 응답 생성 + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + when(chatRepository.findByChatRoomOrderByChatId(chatRoom)).thenReturn(chatList); + when(clovaService.generateAiResponse(any(ClovaRequest.class))) + .thenReturn(String.format("{\"title\":\"정상 제목\",\"content\":\"%s\"}", longContent)); // 500자 초과 내용 + + // When & Then + assertThrows(ChatException.class, () -> chatService.getChatSummary(user.getUserId(), chatRoom.getChatRoomId())); + } + } + + @Nested + @DisplayName("임시 채팅방 테스트") + class ChatTmpTests { + + @Test + @DisplayName("저장 성공") + void saveChatTmp() { + // Given + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + + // When + chatService.saveChatTmp(user.getUserId(), chatRoom.getChatRoomId()); + + // Then + assertEquals(user.getTmpChat(), chatRoom.getChatRoomId()); + verify(userRepository).findById(user.getUserId()); + } + + @Test + @DisplayName("이미 임시 저장된 채팅방이 있을 경우 예외 처리") + void saveChatTmpFailsWhenTmpChatExists() { + // Given + user.updateTmpChat(chatRoom.getChatRoomId()); + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + when(chatRoomRepository.findByChatRoomIdAndUser(chatRoom.getChatRoomId(), user)).thenReturn(Optional.of(chatRoom)); + + // When & Then + assertThrows(ChatException.class, () -> chatService.saveChatTmp(user.getUserId(), chatRoom.getChatRoomId())); + } + + @Test + @DisplayName("조회 성공") + void getChatTmp() { + // Given + user.updateTmpChat(chatRoom.getChatRoomId()); + when(userRepository.findById(user.getUserId())).thenReturn(Optional.of(user)); + + // When + ChatResponse.ChatTmpDto result = chatService.getChatTmp(user.getUserId()); + + // Then + assertEquals(result.getChatRoomId(), chatRoom.getChatRoomId()); + assertTrue(result.isExist()); + verify(userRepository).findById(user.getUserId()); + } + } + + private User createTestUser() { + return User.builder() + .userId(1L) + .providerId("providerId") + .nickName("testUser") + .status(Status.UNIVERSITY_STUDENT) + .abilities(new ArrayList<>()) + .chatRooms(new ArrayList<>()) + .folders(new ArrayList<>()) + .records(new ArrayList<>()) + .build(); + } + + private ChatRoom createTestChatRoom() { + return ChatRoom.builder() + .chatRoomId(1L) + .user(user) + .chatList(new ArrayList<>()) + .build(); + } + + private Chat createTestChat(String content, int isSystem) throws IllegalAccessException, NoSuchFieldException { + Chat chat = Chat.builder() + .chatId(1L) + .author(isSystem) + .content(content) + .chatRoom(chatRoom) + .build(); + Field createdAtField = Chat.class.getSuperclass().getDeclaredField("createdAt"); + createdAtField.setAccessible(true); + createdAtField.set(chat, LocalDateTime.now()); + return chat; + } +}