Skip to content

Comments

feat(llm): enhance error logging and add comprehensive unit tests#3033

Merged
marevol merged 1 commit intomasterfrom
feat/llm-client-error-logging-and-tests
Feb 1, 2026
Merged

feat(llm): enhance error logging and add comprehensive unit tests#3033
marevol merged 1 commit intomasterfrom
feat/llm-client-error-logging-and-tests

Conversation

@marevol
Copy link
Contributor

@marevol marevol commented Feb 1, 2026

Summary

  • Improve LLM client error handling by including response body in error log messages for better debugging of API failures
  • Add extensive unit tests using MockWebServer to verify chat and streaming functionality for both OpenAI and Gemini clients
  • Add mockwebserver test dependency to pom.xml

Changes Made

  • GeminiLlmClient.java: Added response body extraction and logging for error responses in both chat() and streamChat() methods
  • OpenAiLlmClient.java: Added response body extraction and logging for error responses in both chat() and streamChat() methods
  • pom.xml: Added mockwebserver test dependency for HTTP mocking in unit tests
  • GeminiLlmClientTest.java: Added comprehensive tests for:
    • chat() method: success cases, error responses (401, 429, 500, 503), empty candidates, null finish reason, partial usage metadata
    • streamChat() method: success cases, multiple chunks, error responses, finish reasons, malformed JSON handling
  • OpenAiLlmClientTest.java: Added comprehensive tests for:
    • chat() method: success cases, error responses (401, 429, 500, 503), empty choices, null finish reason, partial usage
    • streamChat() method: success cases, multiple chunks, error responses, SSE format handling, malformed JSON handling

Testing

  • All new tests use MockWebServer to simulate API responses
  • Tests cover both happy path and error scenarios
  • Existing tests remain passing

🤖 Generated with Claude Code

Improve LLM client error handling by including response body in error logs for better debugging of API failures. Add extensive unit tests using MockWebServer to verify chat and streaming functionality for both OpenAI and Gemini clients.

Changes:
- Add response body to error log messages in GeminiLlmClient and OpenAiLlmClient
- Add mockwebserver test dependency to pom.xml
- Add comprehensive tests for chat() and streamChat() methods
- Test coverage includes success cases, error responses, edge cases, and streaming

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@marevol marevol requested a review from Copilot February 1, 2026 03:04
@marevol marevol self-assigned this Feb 1, 2026
@marevol marevol added this to the 15.5.0 milestone Feb 1, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances error handling and testing infrastructure for LLM client implementations by adding error response body logging and comprehensive unit tests.

Changes:

  • Enhanced error logging in OpenAiLlmClient and GeminiLlmClient to include response bodies for better debugging
  • Added extensive unit tests for both clients covering success cases, error responses, and edge cases
  • Added mockwebserver test dependency to support HTTP mocking in tests

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/main/java/org/codelibs/fess/llm/openai/OpenAiLlmClient.java Added response body extraction and logging in error handling for chat() and streamChat() methods
src/main/java/org/codelibs/fess/llm/gemini/GeminiLlmClient.java Added response body extraction and logging in error handling for chat() and streamChat() methods
src/test/java/org/codelibs/fess/llm/openai/OpenAiLlmClientTest.java Added comprehensive test suite using MockWebServer for chat and streaming functionality
src/test/java/org/codelibs/fess/llm/gemini/GeminiLlmClientTest.java Added comprehensive test suite using MockWebServer for chat and streaming functionality
pom.xml Added mockwebserver test dependency

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +235 to +244
String errorBody = "";
if (response.body() != null) {
try {
errorBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("OpenAI API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
errorBody);
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name errorBody should follow Java naming conventions with camelCase starting with lowercase. While it already does, consider a more descriptive name like responseBody since this represents the full response body, not just error content.

Suggested change
String errorBody = "";
if (response.body() != null) {
try {
errorBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("OpenAI API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
errorBody);
String responseBody = "";
if (response.body() != null) {
try {
responseBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("OpenAI API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
responseBody);

Copilot uses AI. Check for mistakes.
Comment on lines +237 to +246
String errorBody = "";
if (response.body() != null) {
try {
errorBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("Gemini API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
errorBody);
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name errorBody should be more descriptive. Consider renaming to responseBody since it represents the full response body, not just error content.

Suggested change
String errorBody = "";
if (response.body() != null) {
try {
errorBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("Gemini API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
errorBody);
String responseBody = "";
if (response.body() != null) {
try {
responseBody = response.body().string();
} catch (final IOException e) {
// ignore
}
}
logger.warn("Gemini API error. url={}, statusCode={}, message={}, body={}", url, response.code(), response.message(),
responseBody);

Copilot uses AI. Check for mistakes.
Comment on lines +863 to +866
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
// Remove trailing slash
final String apiUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trailing slash removal logic is duplicated in both test files. Consider extracting this to a shared utility method to reduce duplication and improve maintainability.

Suggested change
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
// Remove trailing slash
final String apiUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
private String removeTrailingSlash(final String url) {
if (url == null) {
return null;
}
return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
}
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
// Remove trailing slash
final String apiUrl = removeTrailingSlash(baseUrl);

Copilot uses AI. Check for mistakes.
Comment on lines +1038 to +1041
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
// Remove trailing slash
final String apiUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trailing slash removal logic is duplicated from OpenAiLlmClientTest. Consider extracting this to a shared utility method to reduce duplication and improve maintainability.

Suggested change
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
// Remove trailing slash
final String apiUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
private String normalizeBaseUrl(final String url) {
if (url == null || url.isEmpty()) {
return url;
}
return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
}
private void setupClientForMockServer() {
final String baseUrl = mockServer.url("").toString();
final String apiUrl = normalizeBaseUrl(baseUrl);

Copilot uses AI. Check for mistakes.
@marevol marevol merged commit 0c65b1b into master Feb 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant