diff --git a/client/transport/grpc/pom.xml b/client/transport/grpc/pom.xml index f815ce86a..fbf6648c0 100644 --- a/client/transport/grpc/pom.xml +++ b/client/transport/grpc/pom.xml @@ -51,6 +51,11 @@ junit-jupiter-api test + + io.grpc + grpc-inprocess + test + org.mock-server @@ -59,4 +64,4 @@ - \ No newline at end of file + diff --git a/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransport.java b/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransport.java index 00c86b660..7609bc9d5 100644 --- a/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransport.java +++ b/client/transport/grpc/src/main/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransport.java @@ -256,12 +256,12 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu @Nullable ClientCallContext context) throws A2AClientException { checkNotNullParam("request", request); checkNotNullParam("taskId", request.taskId()); - checkNotNullParam("id", request.id()); + String configId = request.id(); org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest grpcRequest = org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest.newBuilder() .setTaskId(request.taskId()) .setTenant(resolveTenant(request.tenant())) - .setId(request.id()) + .setId(configId == null ? "" : configId) .build(); PayloadAndHeaders payloadAndHeaders = applyInterceptors(GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, grpcRequest, agentCard, context); diff --git a/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransportTest.java b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransportTest.java new file mode 100644 index 000000000..5fd8363c2 --- /dev/null +++ b/client/transport/grpc/src/test/java/org/a2aproject/sdk/client/transport/grpc/GrpcTransportTest.java @@ -0,0 +1,119 @@ +package org.a2aproject.sdk.client.transport.grpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.StreamObserver; +import org.a2aproject.sdk.grpc.A2AServiceGrpc; +import org.a2aproject.sdk.grpc.GetTaskPushNotificationConfigRequest; +import org.a2aproject.sdk.spec.AgentCapabilities; +import org.a2aproject.sdk.spec.AgentCard; +import org.a2aproject.sdk.spec.AgentInterface; +import org.a2aproject.sdk.spec.GetTaskPushNotificationConfigParams; +import org.a2aproject.sdk.spec.TaskPushNotificationConfig; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +@Timeout(5) +class GrpcTransportTest { + + private static final String TASK_ID = "task-1"; + private static final String CALLBACK_URL = "https://example.com/callback"; + private static final AgentCard CARD = AgentCard.builder() + .name("Test Agent") + .description("Agent for gRPC transport tests") + .version("1.0.0") + .supportedInterfaces(List.of(new AgentInterface("GRPC", "http://localhost"))) + .capabilities(AgentCapabilities.builder().pushNotifications(true).build()) + .defaultInputModes(List.of("text")) + .defaultOutputModes(List.of("text")) + .skills(List.of()) + .build(); + + private final AtomicReference receivedRequest = new AtomicReference<>(); + private Server server; + private ManagedChannel channel; + + @BeforeEach + void setUp() throws Exception { + String serverName = InProcessServerBuilder.generateName(); + server = InProcessServerBuilder.forName(serverName) + .directExecutor() + .addService(new A2AServiceGrpc.A2AServiceImplBase() { + @Override + public void getTaskPushNotificationConfig(GetTaskPushNotificationConfigRequest request, + StreamObserver responseObserver) { + receivedRequest.set(request); + responseObserver.onNext(org.a2aproject.sdk.grpc.TaskPushNotificationConfig.newBuilder() + .setTaskId(request.getTaskId()) + .setId(request.getId().isEmpty() ? request.getTaskId() : request.getId()) + .setUrl(CALLBACK_URL) + .build()); + responseObserver.onCompleted(); + } + }) + .build() + .start(); + channel = InProcessChannelBuilder.forName(serverName).directExecutor().build(); + } + + @AfterEach + void tearDown() throws InterruptedException { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + server.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + + @Test + void testGetPushNotificationConfigWithOmittedId() throws Exception { + assertGetPushNotificationConfig(new GetTaskPushNotificationConfigParams(TASK_ID), null, "", ""); + } + + @Test + void testGetPushNotificationConfigWithOmittedIdViaBuilder() throws Exception { + assertGetPushNotificationConfig(GetTaskPushNotificationConfigParams.builder().taskId(TASK_ID).build(), + null, "", ""); + } + + @Test + void testGetPushNotificationConfigWithEmptyId() throws Exception { + assertGetPushNotificationConfig(new GetTaskPushNotificationConfigParams(TASK_ID, ""), null, "", ""); + } + + @Test + void testGetPushNotificationConfigWithExplicitIdAndDefaultTenant() throws Exception { + assertGetPushNotificationConfig(new GetTaskPushNotificationConfigParams(TASK_ID, "config-1"), + "default-tenant", "config-1", "default-tenant"); + } + + @Test + void testGetPushNotificationConfigWithOmittedIdAndRequestTenant() throws Exception { + assertGetPushNotificationConfig(new GetTaskPushNotificationConfigParams(TASK_ID, null, "request-tenant"), + "default-tenant", "", "request-tenant"); + } + + private void assertGetPushNotificationConfig(GetTaskPushNotificationConfigParams params, + @Nullable String defaultTenant, String expectedId, String expectedTenant) throws Exception { + GrpcTransport transport = new GrpcTransport(channel, CARD, defaultTenant, null); + TaskPushNotificationConfig result = transport.getTaskPushNotificationConfiguration(params, null); + + GetTaskPushNotificationConfigRequest request = receivedRequest.get(); + assertNotNull(request); + assertEquals(TASK_ID, request.getTaskId()); + assertEquals(expectedId, request.getId()); + assertEquals(expectedTenant, request.getTenant()); + assertEquals(expectedId.isEmpty() ? TASK_ID : expectedId, result.id()); + assertEquals(TASK_ID, result.taskId()); + assertEquals(CALLBACK_URL, result.url()); + } +} diff --git a/client/transport/rest/src/test/java/org/a2aproject/sdk/client/transport/rest/RestTransportTest.java b/client/transport/rest/src/test/java/org/a2aproject/sdk/client/transport/rest/RestTransportTest.java index d0ba6da72..73e45a330 100644 --- a/client/transport/rest/src/test/java/org/a2aproject/sdk/client/transport/rest/RestTransportTest.java +++ b/client/transport/rest/src/test/java/org/a2aproject/sdk/client/transport/rest/RestTransportTest.java @@ -340,6 +340,33 @@ public void testGetTaskPushNotificationConfiguration() throws Exception { assertEquals("jwt", authenticationInfo.scheme()); } + @Test + public void testGetTaskPushNotificationConfigurationWithOmittedId() throws Exception { + assertGetTaskPushNotificationConfigurationWithDefaultId( + new GetTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64")); + } + + @Test + public void testGetTaskPushNotificationConfigurationWithEmptyId() throws Exception { + assertGetTaskPushNotificationConfigurationWithDefaultId( + new GetTaskPushNotificationConfigParams("de38c76d-d54c-436c-8b9f-4c2703648d64", "")); + } + + private void assertGetTaskPushNotificationConfigurationWithDefaultId( + GetTaskPushNotificationConfigParams params) throws Exception { + this.server.when(request() + .withMethod("GET") + .withPath("/tasks/de38c76d-d54c-436c-8b9f-4c2703648d64/pushNotificationConfigs/")) + .respond(response() + .withStatusCode(200) + .withBody(GET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_RESPONSE)); + + RestTransport client = new RestTransport(CARD); + TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(params, null); + + assertEquals("https://example.com/callback", config.url()); + } + /** * Test of listTaskPushNotificationConfigurations method, of class JSONRestTransport. */