From aa9948e288a5116ced73fe1137a074bb6d1c715f Mon Sep 17 00:00:00 2001 From: Emmanuel Hugonnet Date: Fri, 25 Sep 2026 11:18:38 +0200 Subject: [PATCH] fix(http-client): always provide a fallback URL in A2ACardResolver When the user-provided base URL matched the computed card URL, no fallback was attempted on 404. Now falls back to cleanBase in that case, and to the original URL otherwise. This fixes #1178 Signed-off-by: Emmanuel Hugonnet --- .../sdk/client/http/A2ACardResolver.java | 2 +- .../sdk/client/http/A2ACardResolverTest.java | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/http-client/src/main/java/org/a2aproject/sdk/client/http/A2ACardResolver.java b/http-client/src/main/java/org/a2aproject/sdk/client/http/A2ACardResolver.java index 2c47f7cdb..90b87c992 100644 --- a/http-client/src/main/java/org/a2aproject/sdk/client/http/A2ACardResolver.java +++ b/http-client/src/main/java/org/a2aproject/sdk/client/http/A2ACardResolver.java @@ -269,7 +269,7 @@ private static ResolvedUrls resolveUrls(String cleanBase, String originalBase, @ Utils.validateAbsoluteUrl(cleanBase); cardUrl = Utils.buildCardUrl(cleanBase, Utils.DEFAULT_AGENT_CARD_PATH); } - String fallbackUrl = isSameUrl(cardUrl, originalBase) ? null : cleanBase; + String fallbackUrl = isSameUrl(cardUrl, originalBase) ? cleanBase : originalBase; return new ResolvedUrls(cardUrl, fallbackUrl); } diff --git a/http-client/src/test/java/org/a2aproject/sdk/client/http/A2ACardResolverTest.java b/http-client/src/test/java/org/a2aproject/sdk/client/http/A2ACardResolverTest.java index 344648e60..a4d4a3da4 100644 --- a/http-client/src/test/java/org/a2aproject/sdk/client/http/A2ACardResolverTest.java +++ b/http-client/src/test/java/org/a2aproject/sdk/client/http/A2ACardResolverTest.java @@ -337,14 +337,16 @@ public void testTenantUrl_fullTenantCardUrlProvidedAsBase_withTrailingSlash_notD } @Test - public void testTenantUrl_fullTenantCardUrlProvidedAsBase_noFallback() throws Exception { - // No fallback when the provided URL already IS the computed card URL. + public void testTenantUrl_fullTenantCardUrlProvidedAsBase_fallsBackToCleanBase() throws Exception { + // When the provided URL already IS the computed card URL, fall back to the clean base. TestHttpClient client = createTestClient(); - client.status = 404; + client.statusSequence.add(404); + client.statusSequence.add(200); String tenantCardUrl = "http://example.com/.well-known/acme/agent-card.json"; - A2ACardResolver resolver = A2ACardResolver.builder().httpClient(client).baseUrl(tenantCardUrl).tenant("acme").build(); - assertThrows(A2AClientHTTPError.class, resolver::getAgentCard); - assertEquals(1, client.urlsCalled.size()); + A2ACardResolver.builder().httpClient(client).baseUrl(tenantCardUrl).tenant("acme").build().getAgentCard(); + assertEquals(2, client.urlsCalled.size()); + assertEquals("http://example.com/.well-known/acme/agent-card.json", client.urlsCalled.get(0)); + assertEquals("http://example.com", client.urlsCalled.get(1)); } @Test @@ -371,16 +373,15 @@ public void testGetAgentCard_httpError_fallsBackToProvidedUrl() throws Exception } @Test - public void testGetAgentCard_doubleSlashBaseUrl_fallbackUrlNormalized() throws Exception { - // A baseUrl with a double trailing slash must not produce a double-slash fallback URL. + public void testGetAgentCard_doubleSlashBaseUrl_fallbackToOriginalUrl() throws Exception { + // A baseUrl with a double trailing slash falls back to the originally provided URL. TestHttpClient client = createTestClient(); client.statusSequence.add(404); client.statusSequence.add(200); A2ACardResolver.builder().httpClient(client).baseUrl("http://example.com//").build().getAgentCard(); assertEquals(2, client.urlsCalled.size()); assertEquals("http://example.com" + AGENT_CARD_PATH, client.urlsCalled.get(0)); - // cleanBase strips one trailing slash from "http://example.com//", yielding "http://example.com/" - assertEquals("http://example.com/", client.urlsCalled.get(1)); + assertEquals("http://example.com//", client.urlsCalled.get(1)); } @Test @@ -448,14 +449,16 @@ public void testGetAgentCard_nonNotFound_noFallback() throws Exception { } @Test - public void testGetAgentCard_noFallback_whenUrlAlreadyIsCardUrl() throws Exception { - // When the provided URL already equals the computed card URL, no fallback. + public void testGetAgentCard_fallbackToCleanBase_whenUrlAlreadyIsCardUrl() throws Exception { + // When the provided URL already equals the computed card URL, fall back to the clean base. TestHttpClient client = createTestClient(); - client.status = 404; + client.statusSequence.add(404); + client.statusSequence.add(200); String fullCardUrl = "http://example.com" + AGENT_CARD_PATH; - A2ACardResolver resolver = A2ACardResolver.builder().httpClient(client).baseUrl(fullCardUrl).build(); - assertThrows(A2AClientHTTPError.class, resolver::getAgentCard); - assertEquals(1, client.urlsCalled.size()); + A2ACardResolver.builder().httpClient(client).baseUrl(fullCardUrl).build().getAgentCard(); + assertEquals(2, client.urlsCalled.size()); + assertEquals(fullCardUrl, client.urlsCalled.get(0)); + assertEquals("http://example.com", client.urlsCalled.get(1)); } @Test