diff --git a/pom.xml b/pom.xml index a0ebc391f..5045215b9 100644 --- a/pom.xml +++ b/pom.xml @@ -632,6 +632,9 @@ tests/multitenancy + + tests/multitenancy/jsonrpc + tests/multiversion/jsonrpc tests/multiversion/rest diff --git a/reference/jsonrpc/src/main/java/org/a2aproject/sdk/server/apps/quarkus/A2AServerRoutes.java b/reference/jsonrpc/src/main/java/org/a2aproject/sdk/server/apps/quarkus/A2AServerRoutes.java index 4ec4905ad..c18e8dc4d 100644 --- a/reference/jsonrpc/src/main/java/org/a2aproject/sdk/server/apps/quarkus/A2AServerRoutes.java +++ b/reference/jsonrpc/src/main/java/org/a2aproject/sdk/server/apps/quarkus/A2AServerRoutes.java @@ -440,7 +440,7 @@ public String getAgentCard(RoutingContext rc) throws JsonProcessingException { * @throws JsonProcessingException if serialization fails */ public String getTenantAgentCard(RoutingContext rc) throws JsonProcessingException { - String tenant = extractTenant(rc); + String tenant = rc.pathParam("tenant"); cacheMetadata.getHttpHeadersMap().forEach((k, v) -> rc.response().putHeader(k, v)); return JsonUtil.toJson(jsonRpcHandler.getAgentCard(tenant)); } diff --git a/tests/multitenancy/jsonrpc/pom.xml b/tests/multitenancy/jsonrpc/pom.xml new file mode 100644 index 000000000..86acbf835 --- /dev/null +++ b/tests/multitenancy/jsonrpc/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + org.a2aproject.sdk + a2a-java-sdk-parent + 1.3.1.Final-SNAPSHOT + ../../../pom.xml + + + a2a-java-sdk-tests-multitenancy-jsonrpc + + jar + + Java A2A SDK Tests Multitenancy JSON-RPC + Integration tests for multi-tenant public agent card on a JSON-RPC-only server (no REST transport on classpath) + + + true + + + + + ${project.groupId} + a2a-java-sdk-reference-jsonrpc + test + + + ${project.groupId} + a2a-java-extras-multitenancy + test + + + io.quarkus + quarkus-junit5 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + io.rest-assured + rest-assured + test + + + + io.quarkus + quarkus-security + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + diff --git a/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentCardProducer.java b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentCardProducer.java new file mode 100644 index 000000000..172458dcd --- /dev/null +++ b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentCardProducer.java @@ -0,0 +1,75 @@ +package org.a2aproject.sdk.tests.multitenancy.jsonrpc; + +import java.util.List; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import org.a2aproject.sdk.extras.multitenancy.Tenant; +import org.a2aproject.sdk.server.ExtendedAgentCard; +import org.a2aproject.sdk.server.PublicAgentCard; +import org.a2aproject.sdk.spec.AgentCapabilities; +import org.a2aproject.sdk.spec.AgentCard; +import org.a2aproject.sdk.spec.AgentInterface; +import org.a2aproject.sdk.spec.TransportProtocol; + +@Singleton +public class AgentCardProducer { + + private static final String BASE_URL = "http://localhost:8081"; + + @Produces + @PublicAgentCard + public AgentCard publicCard() { + return card("Default Agent"); + } + + @Produces + @ExtendedAgentCard + public AgentCard defaultExtendedCard() { + return card("Default Agent (extended)"); + } + + @Produces + @Singleton + @Tenant("acme") + public AgentCard acmePublicCard() { + return card("Acme Agent"); + } + + @Produces + @Singleton + @Tenant("acme") + @ExtendedAgentCard + public AgentCard acmeExtendedCard() { + return card("Acme Agent (extended)"); + } + + @Produces + @Singleton + @Tenant("beta") + public AgentCard betaPublicCard() { + return card("Beta Agent"); + } + + @Produces + @Singleton + @Tenant("beta") + @ExtendedAgentCard + public AgentCard betaExtendedCard() { + return card("Beta Agent (extended)"); + } + + private static AgentCard card(String name) { + return AgentCard.builder() + .name(name) + .description(name) + .version("1.0.0") + .defaultInputModes(List.of("text")) + .defaultOutputModes(List.of("text")) + .capabilities(AgentCapabilities.builder().streaming(true).extendedAgentCard(true).build()) + .skills(List.of()) + .supportedInterfaces(List.of(new AgentInterface(TransportProtocol.JSONRPC.asString(), BASE_URL))) + .build(); + } +} diff --git a/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentExecutorProducer.java b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentExecutorProducer.java new file mode 100644 index 000000000..566b5a865 --- /dev/null +++ b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/AgentExecutorProducer.java @@ -0,0 +1,22 @@ +package org.a2aproject.sdk.tests.multitenancy.jsonrpc; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import org.a2aproject.sdk.server.agentexecution.AgentExecutor; +import org.a2aproject.sdk.spec.TextPart; + +@ApplicationScoped +public class AgentExecutorProducer { + + @Produces + public AgentExecutor defaultExecutor() { + return (context, emitter) -> { + emitter.startWork(); + emitter.addArtifact(List.of(new TextPart("default"))); + emitter.complete(); + }; + } +} diff --git a/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/MultiTenantJSONRPCPublicCardTest.java b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/MultiTenantJSONRPCPublicCardTest.java new file mode 100644 index 000000000..c61009b70 --- /dev/null +++ b/tests/multitenancy/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multitenancy/jsonrpc/MultiTenantJSONRPCPublicCardTest.java @@ -0,0 +1,53 @@ +package org.a2aproject.sdk.tests.multitenancy.jsonrpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.path.json.JsonPath; +import org.junit.jupiter.api.Test; + +/** + * Verifies that the JSON-RPC transport serves the public agent card endpoints + * (/.well-known/agent-card.json and /.well-known/{tenant}/agent-card.json) + * when the REST transport is NOT on the classpath. + */ +@QuarkusTest +public class MultiTenantJSONRPCPublicCardTest { + + @Test + public void defaultPublicCardIsServed() { + String response = RestAssured.given() + .when().get("/.well-known/agent-card.json") + .then().statusCode(200) + .extract().asString(); + assertEquals("Default Agent", JsonPath.from(response).getString("name")); + } + + @Test + public void acmeTenantPublicCardIsServed() { + String response = RestAssured.given() + .when().get("/.well-known/acme/agent-card.json") + .then().statusCode(200) + .extract().asString(); + assertEquals("Acme Agent", JsonPath.from(response).getString("name")); + } + + @Test + public void betaTenantPublicCardIsServed() { + String response = RestAssured.given() + .when().get("/.well-known/beta/agent-card.json") + .then().statusCode(200) + .extract().asString(); + assertEquals("Beta Agent", JsonPath.from(response).getString("name")); + } + + @Test + public void unknownTenantPublicCardFallsBackToDefault() { + String response = RestAssured.given() + .when().get("/.well-known/unknown-corp/agent-card.json") + .then().statusCode(200) + .extract().asString(); + assertEquals("Default Agent", JsonPath.from(response).getString("name")); + } +} diff --git a/tests/multitenancy/jsonrpc/src/test/resources/META-INF/beans.xml b/tests/multitenancy/jsonrpc/src/test/resources/META-INF/beans.xml new file mode 100644 index 000000000..b0e6ff3e2 --- /dev/null +++ b/tests/multitenancy/jsonrpc/src/test/resources/META-INF/beans.xml @@ -0,0 +1,7 @@ + + + + diff --git a/tests/multitenancy/jsonrpc/src/test/resources/application.properties b/tests/multitenancy/jsonrpc/src/test/resources/application.properties new file mode 100644 index 000000000..44cbfd7a4 --- /dev/null +++ b/tests/multitenancy/jsonrpc/src/test/resources/application.properties @@ -0,0 +1 @@ +a2a.authorization.required=false