From 0e7ba3bc27c7b66cf2616419f756435a44d01a25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:03:37 +0000 Subject: [PATCH 1/3] Initial plan From 0b4bdaca552e80d832e662f4ebcb2b64272b99a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:11:21 +0000 Subject: [PATCH 2/3] Add error handling tests for HttpClientHelper Added comprehensive error handling test cases including: - Null request body handling - IOException during body buffering - Malformed URLs - Async execution failures Co-authored-by: jpalvarezl <11056031+jpalvarezl@users.noreply.github.com> --- .../http/HttpClientHelperTests.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java index 72c7841dd285..8cb7b24132b8 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java @@ -28,6 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class HttpClientHelperTests { @@ -76,6 +78,113 @@ void executeAsyncCompletesSuccessfully() throws Exception { assertEquals(1, recordingClient.getSendCount()); } + @Test + void executeWithNullRequestBodySucceeds() throws Exception { + RecordingHttpClient recordingClient = new RecordingHttpClient(request -> { + // Verify the request has no body (or empty body) + com.azure.core.util.BinaryData bodyData = request.getBodyAsBinaryData(); + if (bodyData != null) { + assertEquals(0, bodyData.toBytes().length); + } + return createMockResponse(request, 200, new HttpHeaders(), "success"); + }); + com.openai.core.http.HttpClient openAiClient = HttpClientHelper.httpClientMapper(recordingClient); + + com.openai.core.http.HttpRequest openAiRequest = com.openai.core.http.HttpRequest.builder() + .method(com.openai.core.http.HttpMethod.GET) + .baseUrl("https://example.com") + .addPathSegment("test") + .build(); + + try (com.openai.core.http.HttpResponse response = openAiClient.execute(openAiRequest)) { + assertEquals(200, response.statusCode()); + assertEquals("success", new String(readAllBytes(response.body()), StandardCharsets.UTF_8)); + } + } + + @Test + void executeThrowsUncheckedIOExceptionOnBodyBufferingFailure() { + RecordingHttpClient recordingClient + = new RecordingHttpClient(request -> createMockResponse(request, 200, new HttpHeaders(), "")); + com.openai.core.http.HttpClient openAiClient = HttpClientHelper.httpClientMapper(recordingClient); + + com.openai.core.http.HttpRequest openAiRequest = com.openai.core.http.HttpRequest.builder() + .method(com.openai.core.http.HttpMethod.POST) + .baseUrl("https://example.com") + .body(new FailingHttpRequestBody()) + .build(); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + openAiClient.execute(openAiRequest); + }); + // The error should contain information about the buffering failure + assertTrue(exception.getMessage().contains("buffer") || exception.getCause() instanceof IOException, + "Expected error related to buffer failure, got: " + exception.getMessage()); + } + + @Test + void executeThrowsExceptionOnMalformedUrl() { + RecordingHttpClient recordingClient + = new RecordingHttpClient(request -> createMockResponse(request, 200, new HttpHeaders(), "")); + com.openai.core.http.HttpClient openAiClient = HttpClientHelper.httpClientMapper(recordingClient); + + com.openai.core.http.HttpRequest openAiRequest = com.openai.core.http.HttpRequest.builder() + .method(com.openai.core.http.HttpMethod.GET) + .baseUrl("not-a-valid-url") + .build(); + + // The actual exception type may vary (IllegalArgumentException or IllegalStateException) + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + openAiClient.execute(openAiRequest); + }); + // Verify it's a URL-related error + assertTrue( + exception.getMessage().contains("URL") + || exception.getMessage().contains("URI") + || exception.getMessage().contains("absolute"), + "Expected URL-related error, got: " + exception.getMessage()); + } + + @Test + void executeAsyncPropagatesRequestBuildingErrors() { + RecordingHttpClient recordingClient + = new RecordingHttpClient(request -> createMockResponse(request, 200, new HttpHeaders(), "")); + com.openai.core.http.HttpClient openAiClient = HttpClientHelper.httpClientMapper(recordingClient); + + com.openai.core.http.HttpRequest openAiRequest = com.openai.core.http.HttpRequest.builder() + .method(com.openai.core.http.HttpMethod.POST) + .baseUrl("https://example.com") + .body(new FailingHttpRequestBody()) + .build(); + + CompletableFuture future = openAiClient.executeAsync(openAiRequest); + + Exception exception = assertThrows(Exception.class, future::join); + Throwable cause = exception.getCause(); + assertNotNull(cause, "Expected a cause for the exception"); + // The error should be related to request building/buffering failure + assertTrue(cause instanceof RuntimeException, "Expected RuntimeException, got: " + cause.getClass().getName()); + } + + @Test + void executeAsyncPropagatesHttpClientFailures() { + FailingHttpClient failingClient = new FailingHttpClient(new RuntimeException("Network error")); + com.openai.core.http.HttpClient openAiClient = HttpClientHelper.httpClientMapper(failingClient); + + com.openai.core.http.HttpRequest openAiRequest = com.openai.core.http.HttpRequest.builder() + .method(com.openai.core.http.HttpMethod.GET) + .baseUrl("https://example.com") + .build(); + + CompletableFuture future = openAiClient.executeAsync(openAiRequest); + + Exception exception = assertThrows(Exception.class, future::join); + Throwable cause = exception.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof RuntimeException); + assertEquals("Network error", cause.getMessage()); + } + private static com.openai.core.http.HttpRequest createOpenAiRequest() { return com.openai.core.http.HttpRequest.builder() .method(com.openai.core.http.HttpMethod.POST) @@ -173,4 +282,54 @@ public void close() { // no-op } } + + private static final class FailingHttpRequestBody implements HttpRequestBody { + @Override + public void writeTo(OutputStream outputStream) { + try { + // Simulate an I/O failure during body write + throw new IOException("Simulated I/O failure during body write"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public String contentType() { + return "application/octet-stream"; + } + + @Override + public long contentLength() { + return -1; + } + + @Override + public boolean repeatable() { + return false; + } + + @Override + public void close() { + // no-op + } + } + + private static final class FailingHttpClient implements HttpClient { + private final RuntimeException error; + + private FailingHttpClient(RuntimeException error) { + this.error = error; + } + + @Override + public Mono send(HttpRequest request) { + return Mono.error(error); + } + + @Override + public Mono send(HttpRequest request, Context context) { + return send(request); + } + } } From 38ecc26bc7aee4ce08f94c2f04970bbd1ec6ce00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:13:35 +0000 Subject: [PATCH 3/3] Address code review feedback on error handling tests - Simplify FailingHttpRequestBody to directly throw UncheckedIOException - Improve assertion clarity in body buffering test - Remove fragile message checking in malformed URL test Co-authored-by: jpalvarezl <11056031+jpalvarezl@users.noreply.github.com> --- .../http/HttpClientHelperTests.java | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java index 8cb7b24132b8..14e43f692337 100644 --- a/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java +++ b/sdk/ai/azure-ai-agents/src/test/java/com/azure/ai/agents/implementation/http/HttpClientHelperTests.java @@ -117,9 +117,10 @@ void executeThrowsUncheckedIOExceptionOnBodyBufferingFailure() { RuntimeException exception = assertThrows(RuntimeException.class, () -> { openAiClient.execute(openAiRequest); }); - // The error should contain information about the buffering failure - assertTrue(exception.getMessage().contains("buffer") || exception.getCause() instanceof IOException, - "Expected error related to buffer failure, got: " + exception.getMessage()); + // Verify the error is related to body buffering failure + boolean hasBufferMessage = exception.getMessage() != null && exception.getMessage().contains("buffer"); + boolean hasIOCause = exception.getCause() instanceof IOException; + assertTrue(hasBufferMessage || hasIOCause, "Expected error related to buffer failure, got: " + exception); } @Test @@ -133,16 +134,10 @@ void executeThrowsExceptionOnMalformedUrl() { .baseUrl("not-a-valid-url") .build(); - // The actual exception type may vary (IllegalArgumentException or IllegalStateException) - RuntimeException exception = assertThrows(RuntimeException.class, () -> { + // Malformed URLs should throw an exception (typically IllegalArgumentException or IllegalStateException) + assertThrows(RuntimeException.class, () -> { openAiClient.execute(openAiRequest); }); - // Verify it's a URL-related error - assertTrue( - exception.getMessage().contains("URL") - || exception.getMessage().contains("URI") - || exception.getMessage().contains("absolute"), - "Expected URL-related error, got: " + exception.getMessage()); } @Test @@ -286,12 +281,8 @@ public void close() { private static final class FailingHttpRequestBody implements HttpRequestBody { @Override public void writeTo(OutputStream outputStream) { - try { - // Simulate an I/O failure during body write - throw new IOException("Simulated I/O failure during body write"); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + // Simulate an I/O failure during body write + throw new UncheckedIOException(new IOException("Simulated I/O failure during body write")); } @Override