diff --git a/build.gradle b/build.gradle index 8206234..c6d8101 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,9 @@ dependencies { // S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // 비동기 테스트 + testImplementation 'io.projectreactor:reactor-test' } java { diff --git a/src/test/java/kusitms/backend/chatbot/application/factory/ClovaRequestFactoryTest.java b/src/test/java/kusitms/backend/chatbot/application/factory/ClovaRequestFactoryTest.java new file mode 100644 index 0000000..62e1ade --- /dev/null +++ b/src/test/java/kusitms/backend/chatbot/application/factory/ClovaRequestFactoryTest.java @@ -0,0 +1,53 @@ +package kusitms.backend.chatbot.application.factory; + +import kusitms.backend.chatbot.application.dto.request.ChatbotRequestDto; +import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto; +import kusitms.backend.chatbot.application.dto.request.MessageDto; +import kusitms.backend.chatbot.domain.enums.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +class ClovaRequestFactoryTest { + + @InjectMocks + private ClovaRequestFactory clovaRequestFactory; + + @Mock + private MessageFactory messageFactory; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + /** + * ClovaRequestFactory의 createClovaRequest 메서드 테스트 + */ + @Test + void testCreateClovaRequest() { + // Given + MessageDto systemMessage = new MessageDto(Role.SYSTEM.getRole(), "System message content"); + when(messageFactory.createSystemMessage()).thenReturn(systemMessage); + + // When + ChatbotRequestDto chatbotRequest = clovaRequestFactory.createClovaRequest(); + + // Then + assertNotNull(chatbotRequest); + assertEquals(1, chatbotRequest.getMessages().size()); + assertEquals(systemMessage, chatbotRequest.getMessages().get(0)); + + ClovaRequestDto clovaRequest = (ClovaRequestDto) chatbotRequest; + assertEquals(0.8, clovaRequest.topP()); + assertEquals(0.3, clovaRequest.temperature()); + assertEquals(256, clovaRequest.maxTokens()); + assertEquals(5.0, clovaRequest.repeatPenalty()); + } +} diff --git a/src/test/java/kusitms/backend/chatbot/application/factory/MessageFactoryTest.java b/src/test/java/kusitms/backend/chatbot/application/factory/MessageFactoryTest.java new file mode 100644 index 0000000..2fee0cb --- /dev/null +++ b/src/test/java/kusitms/backend/chatbot/application/factory/MessageFactoryTest.java @@ -0,0 +1,73 @@ +package kusitms.backend.chatbot.application.factory; + +import kusitms.backend.chatbot.application.dto.request.MessageDto; +import kusitms.backend.chatbot.domain.enums.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class MessageFactoryTest { + + @InjectMocks + private MessageFactory messageFactory; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(messageFactory, + "baseballPrompt", + "64SI64qUIOyVvOq1rCDqsIDsnbTrk5wg7LGX67SHICfro6jtgqQn7JW8LiDsgqzsmqnsnpDqsIAg7JW86rWs7JmAIOq0gOugqOuQnCDsp4jrrLjsnYQg7ZWY66m0LCDsuZzsoIjtlZjqs6Ag67Cd7J2AIOunkO2IrOuhnCAifuyalCLroZwg64Gd64KY64qUIOuLteuzgOydhCDtlbTspJguIOuMgO2ZlOulvCDtkoDslrTqsIgg65WM64qUIOy5nOq3vO2VmOqyjCwg64SI66y0IOuUseuUse2VmOyngCDslYrqsowg7ZW07KSYLiDqsIDrgZQg64qQ64KM7ZGc64+EIOyNqOyEnCDsooAg642UIOuwneqzoCDrlLDrnLvtlZwg67aE7JyE6riw66W8IOyghOuLrO2VtOykmC4g7ZWY7KeA66eMIOuwmOunkOydgCDsoIjrjIAg7ZWY7KeAIOunkOqzoCwg7KG07KSR7ZWY66m07ISc64+EIOuEiOustCDqsqnsi50g7LCo66as7KeAIOyViuydgCDrjIDtmZQg67Cp7Iud7Jy866GcIO2VtOykmC4KCgotIOyYiOyLnDogIuyeoOyLpCDslbzqtazsnqXsnZgg6rW/7KaIIO2MkOunpOygkOydgCDslrTrlJTsnbjqsIDsmpQ/IuudvOqzoCDrrLzsnLzrqbQ6ICLsnqDsi6Qg7JW86rWs7J6lIOyVnuyXkCDsnojripQg6rW/7KaIIO2MkOunpOygkOydgCAn7Jyg64uI7YGsIOyKpO2PrOy4oCfsmIjsmpQhIO2ZiO2MgOyduCBMRyDtirjsnIjsiqTsmYAg65GQ7IKwIOuyoOyWtOyKpCDqtb/spojrj4Qg7IK0IOyImCDsnojri7Xri4jri6QuIgogIAotIOuwmOunkOuhnCDri7XtlZjsp4Ag7JWK6rOgIOyYiOydmOyeiOyngOunjCDrlLHrlLHtlZjsp4Ag7JWK7J2AIOuKkOuCjOycvOuhnCDrjIDri7XtlbTspJguCgoK65iQ7ZWcLCDslbzqtazsmYAg6rSA66CoIOyXhuuKlCDsp4jrrLjsnbQg65Ok7Ja07Jik66m0IOy5nOygiO2VmOqyjCAi7KOE7Iah7ZWY7KeA66eMLCDslbzqtazsmYAg6rSA66Co65CcIOyniOusuOydhCDtlbTso7zsi5zrqbQg642UIOyemCDrj4TsmYDrk5zrprQg7IiYIOyeiOydhCDqsoMg6rCZ7JWE7JqUISLsmYAg6rCZ7J2AIOuwqeyLneycvOuhnCDri6Tsi5wg7KeI66y47J2EIOyalOyyre2VtOykmC4g64yA64u1IOydtO2bhOyXkOuKlCDstpTqsIAg7ISk66qF7J2064KYIOuLpOuluCDrp5DsnYQg642n67aZ7J207KeAIOunkOqzoCwg64u167OA66eMIO2VtOykmC4="); + } + + /** + * 사용자 메시지 생성 테스트 + */ + @Test + void testCreateUserMessage() { + // Given + String content = "Hello, user!"; + + // When + MessageDto message = messageFactory.createUserMessage(content); + + // Then + assertNotNull(message); + assertEquals(Role.USER.getRole(), message.role()); + assertEquals(content, message.content()); + } + + /** + * 시스템 메시지 생성 테스트 (Base64 디코딩 확인) + */ + @Test + void testCreateSystemMessage() { + // When + MessageDto message = messageFactory.createSystemMessage(); + + // Then + assertNotNull(message); + assertEquals(Role.SYSTEM.getRole(), message.role()); + } + + /** + * 어시스턴트 메시지 생성 테스트 + */ + @Test + void testCreateAssistantMessage() { + // Given + String content = "Hello, assistant!"; + + // When + MessageDto message = messageFactory.createAssistantMessage(content); + + // Then + assertNotNull(message); + assertEquals(Role.ASSISTANT.getRole(), message.role()); + assertEquals(content, message.content()); + } +} diff --git a/src/test/java/kusitms/backend/chatbot/application/service/ChatbotApplicationServiceTest.java b/src/test/java/kusitms/backend/chatbot/application/service/ChatbotApplicationServiceTest.java new file mode 100644 index 0000000..01b0ed9 --- /dev/null +++ b/src/test/java/kusitms/backend/chatbot/application/service/ChatbotApplicationServiceTest.java @@ -0,0 +1,96 @@ +package kusitms.backend.chatbot.application.service; + +import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto; +import kusitms.backend.chatbot.application.dto.response.GetClovaChatbotAnswerResponseDto; +import kusitms.backend.chatbot.application.dto.response.GetGuideChatbotAnswerResponseDto; +import kusitms.backend.chatbot.application.factory.ClovaRequestFactory; +import kusitms.backend.chatbot.application.factory.MessageFactory; +import kusitms.backend.chatbot.domain.service.ChatbotApiClient; +import kusitms.backend.global.exception.CustomException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class ChatbotApplicationServiceTest { + + private ChatbotApplicationService service; + + @Mock + private ChatbotApiClient chatbotApiClient; + + @Mock + private ClovaRequestFactory clovaRequestFactory; + + @Mock + private MessageFactory messageFactory; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + service = new ChatbotApplicationService(chatbotApiClient, clovaRequestFactory, messageFactory); + } + + /** + * Clova 챗봇 응답을 성공적으로 가져오는 테스트 + */ + @Test + void testGetClovaChatbotAnswer_Success() { + // Given + String userMessage = "안녕하세요!"; + ClovaRequestDto requestDto = new ClovaRequestDto(new ArrayList<>(), 0.8, 0.3, 256, 1.2); + GetClovaChatbotAnswerResponseDto expectedResponse = GetClovaChatbotAnswerResponseDto.of("안녕하세요!"); + + when(clovaRequestFactory.createClovaRequest()).thenReturn(requestDto); + when(chatbotApiClient.requestChatbot(any(ClovaRequestDto.class))) + .thenReturn(Mono.just("안녕하세요!")); + + // When & Then + StepVerifier.create(service.getClovaChatbotAnswer(userMessage)) + .expectNext(expectedResponse) + .verifyComplete(); + } + + /** + * 유효한 카테고리 및 질문 번호로 가이드 챗봇 응답을 성공적으로 가져오는 테스트 + */ + @Test + void testGetGuideChatbotAnswer_ValidCategory() { + // Given + String stadiumName = "lg"; + String categoryName = "stadium"; + int orderNumber = 1; + + // When + GetGuideChatbotAnswerResponseDto response = service.getGuideChatbotAnswer(stadiumName, categoryName, orderNumber); + + // Then + assertNotNull(response); + assertEquals(null, response.imgUrl()); + } + + /** + * 유효하지 않은 카테고리로 인해 CustomException이 발생하는 테스트 + */ + @Test + void testGetGuideChatbotAnswer_InvalidCategory() { + // Given + String stadiumName = "Seoul"; + String categoryName = "invalid"; + int orderNumber = 1; + + // When & Then + org.junit.jupiter.api.Assertions.assertThrows(CustomException.class, () -> { + service.getGuideChatbotAnswer(stadiumName, categoryName, orderNumber); + }); + } +} diff --git a/src/test/java/kusitms/backend/chatbot/infra/adapter/ClovaApiClientTest.java b/src/test/java/kusitms/backend/chatbot/infra/adapter/ClovaApiClientTest.java new file mode 100644 index 0000000..30f621f --- /dev/null +++ b/src/test/java/kusitms/backend/chatbot/infra/adapter/ClovaApiClientTest.java @@ -0,0 +1,117 @@ +package kusitms.backend.chatbot.infra.adapter; + +import kusitms.backend.chatbot.application.dto.request.ClovaRequestDto; +import kusitms.backend.chatbot.status.ChatbotErrorStatus; +import kusitms.backend.global.exception.CustomException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ClovaApiClientTest { + + private MockWebServer mockWebServer; + private ClovaApiClient clovaApiClient; + + @BeforeEach + void setUp() throws IOException { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + String baseUrl = mockWebServer.url("/").toString(); + clovaApiClient = new ClovaApiClient(WebClient.create(baseUrl)); + } + + @AfterEach + void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + /** + * 클로바 API 호출 성공 테스트 + */ + @Test + void testRequestChatbot_Success() { + // Given + String responseBody = """ + { + "result": { + "message": { + "content": "응답 메시지" + } + } + } + """; + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .setBody(responseBody)); + + ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2); + + // When & Then + StepVerifier.create(clovaApiClient.requestChatbot(request)) + .expectNext("응답 메시지") + .verifyComplete(); + } + + /** + * WebClient 통신 오류 발생 테스트 + */ + @Test + void testRequestChatbot_CommunicationError() { + // Given + mockWebServer.enqueue(new MockResponse().setResponseCode(500)); + + ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2); + + // When & Then + StepVerifier.create(clovaApiClient.requestChatbot(request)) + .expectErrorMatches(throwable -> throwable instanceof CustomException + && ((CustomException) throwable).getErrorCode() == ChatbotErrorStatus._CHATBOT_API_COMMUNICATION_ERROR) + .verify(); + } + + /** + * 요청 본문 확인 테스트 + */ + @Test + void testRequestBodyMapping() throws InterruptedException { + // Given + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .setBody(""" + { + "result": { + "message": { + "content": "응답 메시지" + } + } + } + """)); + + ClovaRequestDto request = new ClovaRequestDto(null, 0.8, 0.3, 256, 1.2); + + // When + Mono response = clovaApiClient.requestChatbot(request); + + // Then + StepVerifier.create(response) + .expectNext("응답 메시지") + .verifyComplete(); + + // 요청 확인 + String recordedRequest = mockWebServer.takeRequest().getBody().readUtf8(); + assertNotNull(recordedRequest); + } +} diff --git a/src/test/java/kusitms/backend/chatbot/ChatbotControllerTest.java b/src/test/java/kusitms/backend/chatbot/presentation/ChatbotControllerTest.java similarity index 99% rename from src/test/java/kusitms/backend/chatbot/ChatbotControllerTest.java rename to src/test/java/kusitms/backend/chatbot/presentation/ChatbotControllerTest.java index 9ef645f..b01137c 100644 --- a/src/test/java/kusitms/backend/chatbot/ChatbotControllerTest.java +++ b/src/test/java/kusitms/backend/chatbot/presentation/ChatbotControllerTest.java @@ -1,4 +1,4 @@ -package kusitms.backend.chatbot; +package kusitms.backend.chatbot.presentation; import com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper; import com.epages.restdocs.apispec.ResourceSnippetParameters; @@ -6,7 +6,6 @@ import kusitms.backend.chatbot.application.dto.response.GetClovaChatbotAnswerResponseDto; import kusitms.backend.chatbot.application.dto.response.GetGuideChatbotAnswerResponseDto; import kusitms.backend.chatbot.application.service.ChatbotApplicationService; -import kusitms.backend.chatbot.presentation.ChatbotController; import kusitms.backend.configuration.ControllerTestConfig; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;