diff --git a/spring-ai/src/main/java/com/baeldung/spring/ai/dto/PoetryDto.java b/spring-ai/src/main/java/com/baeldung/spring/ai/dto/PoetryDto.java index b51c511659..a636189f2e 100644 --- a/spring-ai/src/main/java/com/baeldung/spring/ai/dto/PoetryDto.java +++ b/spring-ai/src/main/java/com/baeldung/spring/ai/dto/PoetryDto.java @@ -1,3 +1,3 @@ package com.baeldung.spring.ai.dto; -public record PoetryDto (String title, String poetry){} +public record PoetryDto (String title, String poetry, String genre, String theme){} diff --git a/spring-ai/src/main/java/com/baeldung/spring/ai/service/impl/PoetryServiceImpl.java b/spring-ai/src/main/java/com/baeldung/spring/ai/service/impl/PoetryServiceImpl.java index 26ae407a6e..bceddcd67e 100644 --- a/spring-ai/src/main/java/com/baeldung/spring/ai/service/impl/PoetryServiceImpl.java +++ b/spring-ai/src/main/java/com/baeldung/spring/ai/service/impl/PoetryServiceImpl.java @@ -14,7 +14,9 @@ import org.springframework.web.bind.annotation.GetMapping; @Service public class PoetryServiceImpl implements PoetryService { - public static final String WRITE_ME_HAIKU_ABOUT_CAT = "Write me Haiku about cat, haiku should contain word cat"; + public static final String WRITE_ME_HAIKU_ABOUT_CAT = """ + Write me Haiku about cat, + haiku should start with the word cat obligatory"""; private final AiClient aiClient; @Autowired diff --git a/spring-ai/src/main/java/com/baeldung/spring/ai/web/ExceptionTranslator.java b/spring-ai/src/main/java/com/baeldung/spring/ai/web/ExceptionTranslator.java index 98c6a35e94..55ba383fa6 100644 --- a/spring-ai/src/main/java/com/baeldung/spring/ai/web/ExceptionTranslator.java +++ b/spring-ai/src/main/java/com/baeldung/spring/ai/web/ExceptionTranslator.java @@ -7,13 +7,21 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import java.util.Optional; + @RestControllerAdvice public class ExceptionTranslator extends ResponseEntityExceptionHandler { + public static final String OPEN_AI_CLIENT_RAISED_EXCEPTION = "Open AI client raised exception"; + @ExceptionHandler(OpenAiHttpException.class) - ProblemDetail handleOpenAiHttpException(OpenAiHttpException ex){ - ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getMessage()); - problemDetail.setTitle("Open AI client raised exception"); + ProblemDetail handleOpenAiHttpException(OpenAiHttpException ex) { + HttpStatus status = Optional + .ofNullable(HttpStatus.resolve(ex.statusCode)) + .orElse(HttpStatus.BAD_REQUEST); + + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, ex.getMessage()); + problemDetail.setTitle(OPEN_AI_CLIENT_RAISED_EXCEPTION); return problemDetail; } } diff --git a/spring-ai/src/test/java/com/baeldung/spring/ai/web/ExceptionTranslatorIntegrationTest.java b/spring-ai/src/test/java/com/baeldung/spring/ai/web/ExceptionTranslatorIntegrationTest.java new file mode 100644 index 0000000000..4795dce1f8 --- /dev/null +++ b/spring-ai/src/test/java/com/baeldung/spring/ai/web/ExceptionTranslatorIntegrationTest.java @@ -0,0 +1,45 @@ +package com.baeldung.spring.ai.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.ai.client.AiClient; +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.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import static java.lang.String.format; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@AutoConfigureMockMvc +@RunWith(SpringRunner.class) +@SpringBootTest +public class ExceptionTranslatorIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private AiClient aiClient; + + @DynamicPropertySource + static void setupTestcontainerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.ai.openai.api-key", () -> "incorrect_token"); + } + + @Test + public void givenGetCatHaiku_whenCallingAiClientWithIncorrectToken_thenUnauthorized() throws Exception { + mockMvc.perform(get("/ai/cathaiku")) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.title").value(ExceptionTranslator.OPEN_AI_CLIENT_RAISED_EXCEPTION)); + } +} diff --git a/spring-ai/src/test/java/com/baeldung/spring/ai/web/PoetryControllerIntegrationTest.java b/spring-ai/src/test/java/com/baeldung/spring/ai/web/PoetryControllerIntegrationTest.java index 782eb6aa86..7a454f09fa 100644 --- a/spring-ai/src/test/java/com/baeldung/spring/ai/web/PoetryControllerIntegrationTest.java +++ b/spring-ai/src/test/java/com/baeldung/spring/ai/web/PoetryControllerIntegrationTest.java @@ -11,9 +11,8 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @AutoConfigureMockMvc @RunWith(SpringRunner.class) @@ -30,9 +29,21 @@ public class PoetryControllerIntegrationTest { private AiClient aiClient; @Test - public void getCatHaikuTest() throws Exception { + public void givenGetCatHaiku_whenCallingAiClient_thenCorrect() throws Exception { mockMvc.perform(get("/ai/cathaiku")) .andExpect(status().isOk()) .andExpect(content().string(containsStringIgnoringCase("cat"))); } + + @Test + public void givenGetPoetryWithGenreAndTheme_whenCallingAiClient_thenCorrect() throws Exception { + String genre = "lyric"; + String theme = "coffee"; + mockMvc.perform(get("/ai/poetry?genre={genre}&theme={theme}", genre, theme)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.genre").value(containsStringIgnoringCase(genre))) + .andExpect(jsonPath("$.theme").value(containsStringIgnoringCase(theme))) + .andExpect(jsonPath("$.poetry").isNotEmpty()) + .andExpect(jsonPath("$.title").exists()); + } }