diff --git a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/AbstractA2AServerServerTest_v0_3.java b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/AbstractA2AServerServerTest_v0_3.java index d1f622402..6cf582458 100644 --- a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/AbstractA2AServerServerTest_v0_3.java +++ b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/AbstractA2AServerServerTest_v0_3.java @@ -1740,6 +1740,7 @@ protected void savePushNotificationConfigInStore(String taskId, PushNotification .uri(URI.create("http://localhost:" + serverPort + "/test/task/" + taskId)) .POST(HttpRequest.BodyPublishers.ofString(org.a2aproject.sdk.jsonrpc.common.json.JsonUtil.toJson(v10Config))) .header("Content-Type", APPLICATION_JSON) + .header("A2A-Version", A2AProtocol_v0_3.PROTOCOL_VERSION) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); diff --git a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/TestUtilsBean_v0_3.java b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/TestUtilsBean_v0_3.java index 6a43e444c..3800880d4 100644 --- a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/TestUtilsBean_v0_3.java +++ b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/TestUtilsBean_v0_3.java @@ -64,7 +64,8 @@ public void deleteTaskPushNotificationConfig(String taskId, String configId) { } public void saveTaskPushNotificationConfig(String taskId, TaskPushNotificationConfig notificationConfig) { - pushNotificationConfigStore.setInfo(TaskPushNotificationConfig.builder(notificationConfig).taskId(taskId).build()); + pushNotificationConfigStore.setInfo( + TaskPushNotificationConfig.builder(notificationConfig).taskId(taskId).build(), "0.3"); } /** diff --git a/extras/push-notification-config-store-database-jpa/src/main/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaDatabasePushNotificationConfigStore.java b/extras/push-notification-config-store-database-jpa/src/main/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaDatabasePushNotificationConfigStore.java index dee737d4b..424291974 100644 --- a/extras/push-notification-config-store-database-jpa/src/main/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaDatabasePushNotificationConfigStore.java +++ b/extras/push-notification-config-store-database-jpa/src/main/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaDatabasePushNotificationConfigStore.java @@ -50,10 +50,9 @@ public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig notificatio @Override public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig notificationConfig, @Nullable String protocolVersion) { String taskId = Assert.checkNotNullParam("taskId", notificationConfig.taskId()); - // Ensure config has an ID - default to taskId if not provided (mirroring InMemoryPushNotificationConfigStore behavior) - if (notificationConfig.id().isEmpty()) { - // This means the taskId and configId are same. This will not allow having multiple configs for a single Task. - // The configId is a required field in the spec and should not be empty + // Default missing config IDs to the task ID, matching the in-memory store. + boolean configIdIsMissing = notificationConfig.id() == null || notificationConfig.id().isEmpty(); + if (configIdIsMissing) { notificationConfig = TaskPushNotificationConfig.builder(notificationConfig).id(taskId).build(); } @@ -65,6 +64,11 @@ public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig notificatio // Check if entity already exists JpaPushNotificationConfig existingJpaConfig = em.find(JpaPushNotificationConfig.class, configId); + if (configIdIsMissing && existingJpaConfig != null && !"0.3".equals(resolvedVersion)) { + throw new InvalidParamsError("A push notification config with the default ID already exists for task " + + taskId + "; specify the config ID explicitly to update it"); + } + if (existingJpaConfig != null) { // Update existing entity existingJpaConfig.setConfig(notificationConfig); diff --git a/extras/push-notification-config-store-database-jpa/src/test/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaPushNotificationConfigStoreTest.java b/extras/push-notification-config-store-database-jpa/src/test/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaPushNotificationConfigStoreTest.java index 089a418b7..4c96f50f7 100644 --- a/extras/push-notification-config-store-database-jpa/src/test/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaPushNotificationConfigStoreTest.java +++ b/extras/push-notification-config-store-database-jpa/src/test/java/org/a2aproject/sdk/extras/pushnotificationconfigstore/database/jpa/JpaPushNotificationConfigStoreTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @@ -21,6 +22,7 @@ import org.a2aproject.sdk.server.tasks.BasePushNotificationSender; import org.a2aproject.sdk.server.tasks.PushNotificationConfigStore; import org.a2aproject.sdk.server.tasks.PushNotificationUrlValidator; +import org.a2aproject.sdk.spec.InvalidParamsError; import org.a2aproject.sdk.spec.ListTaskPushNotificationConfigsParams; import org.a2aproject.sdk.spec.ListTaskPushNotificationConfigsResult; import org.a2aproject.sdk.spec.Task; @@ -149,18 +151,65 @@ public void testSetInfoWithoutConfigId() { assertEquals(1, configResult.configs().size()); assertEquals(taskId, configResult.configs().get(0).id()); - TaskPushNotificationConfig updatedConfig = TaskPushNotificationConfig.builder() + TaskPushNotificationConfig duplicateConfig = TaskPushNotificationConfig.builder() .id("") .url("http://initial.url/callback_new") .taskId(taskId) .build(); - TaskPushNotificationConfig updatedResult = configStore.setInfo(updatedConfig); - assertEquals(taskId, updatedResult.id()); + assertThrows(InvalidParamsError.class, () -> configStore.setInfo(duplicateConfig)); configResult = configStore.getInfo(new ListTaskPushNotificationConfigsParams(taskId)); - assertEquals(1, configResult.configs().size(), "Should replace existing config with same ID rather than adding new one"); - assertEquals(updatedConfig.url(), configResult.configs().get(0).url()); + assertEquals(1, configResult.configs().size()); + assertEquals(initialConfig.url(), configResult.configs().get(0).url()); + } + + @Test + @Transactional + public void testSetInfoWithNullConfigId() { + String taskId = "task_null_config_id"; + TaskPushNotificationConfig config = TaskPushNotificationConfig.builder() + .url("http://null-id.url/callback") + .taskId(taskId) + .build(); + + TaskPushNotificationConfig result = configStore.setInfo(config); + + assertEquals(taskId, result.id(), "A missing config ID should default to the task ID"); + ListTaskPushNotificationConfigsResult configResult = configStore.getInfo( + new ListTaskPushNotificationConfigsParams(taskId)); + assertEquals(1, configResult.configs().size()); + assertEquals(taskId, configResult.configs().get(0).id()); + + TaskPushNotificationConfig duplicateConfig = TaskPushNotificationConfig.builder() + .url("http://updated.url/callback") + .taskId(taskId) + .build(); + + assertThrows(InvalidParamsError.class, () -> configStore.setInfo(duplicateConfig)); + } + + @Test + @Transactional + public void testSetInfoAllowsV03DefaultConfigUpdate() { + String taskId = "task_v03_default_update"; + TaskPushNotificationConfig initialConfig = TaskPushNotificationConfig.builder() + .taskId(taskId) + .url("http://initial.url/callback") + .build(); + TaskPushNotificationConfig updatedConfig = TaskPushNotificationConfig.builder() + .taskId(taskId) + .url("http://updated.url/callback") + .build(); + + configStore.setInfo(initialConfig, "0.3"); + TaskPushNotificationConfig result = configStore.setInfo(updatedConfig, "0.3"); + + assertEquals(taskId, result.id()); + ListTaskPushNotificationConfigsResult stored = + configStore.getInfo(new ListTaskPushNotificationConfigsParams(taskId)); + assertEquals(1, stored.configs().size()); + assertEquals(updatedConfig.url(), stored.configs().get(0).url()); } @Test diff --git a/server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java b/server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java index d234e7764..bdb540000 100644 --- a/server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java +++ b/server-common/src/main/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandler.java @@ -1126,7 +1126,8 @@ public TaskPushNotificationConfig onGetTaskPushNotificationConfig( throw new InternalError("No push notification config found"); } - String configId = params.id(); + String requestedConfigId = params.id(); + String configId = requestedConfigId == null || requestedConfigId.isEmpty() ? params.taskId() : requestedConfigId; return getTaskPushNotificationConfig(listTaskPushNotificationConfigsResult, configId); } diff --git a/server-common/src/main/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStore.java b/server-common/src/main/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStore.java index 854bd7fbd..acbe29b1c 100644 --- a/server-common/src/main/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStore.java +++ b/server-common/src/main/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStore.java @@ -46,17 +46,31 @@ public InMemoryPushNotificationConfigStore() { @Override public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig notificationConfig) { + return setInfo(notificationConfig, false); + } + + private TaskPushNotificationConfig setInfo( + TaskPushNotificationConfig notificationConfig, + boolean allowDefaultConfigUpdate) { String taskId = Assert.checkNotNullParam("taskId", notificationConfig.taskId()); TaskPushNotificationConfig.Builder builder = TaskPushNotificationConfig.builder(notificationConfig); - if (notificationConfig.id().isEmpty()) { + String requestedConfigId = notificationConfig.id(); + boolean configIdIsMissing = requestedConfigId == null || requestedConfigId.isEmpty(); + String configId = configIdIsMissing ? taskId : requestedConfigId; + if (configIdIsMissing) { builder.id(taskId); } TaskPushNotificationConfig config = builder.build(); - String configId = config.id(); int maxPerTask = PushNotificationConfigStore.maxPushConfigsPerTask(configProvider); pushNotificationInfos.compute(taskId, (key, list) -> { List mutable = list == null ? new ArrayList<>() : new ArrayList<>(list); + boolean defaultConfigAlreadyExists = configIdIsMissing + && mutable.stream().anyMatch(existing -> existing.id() != null && existing.id().equals(configId)); + if (defaultConfigAlreadyExists && !allowDefaultConfigUpdate) { + throw new InvalidParamsError("A push notification config with the default ID already exists for task " + + taskId + "; specify the config ID explicitly to update it"); + } boolean isExistingConfig = mutable.removeIf( existing -> existing.id() != null && existing.id().equals(configId)); if (!isExistingConfig && mutable.size() >= maxPerTask) { @@ -71,8 +85,10 @@ public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig notificatio @Override public TaskPushNotificationConfig setInfo(TaskPushNotificationConfig config, @Nullable String protocolVersion) { - TaskPushNotificationConfig result = setInfo(config); - protocolVersions.put(result.taskId() + ":" + result.id(), PushNotificationConfigStore.resolveProtocolVersion(protocolVersion)); + TaskPushNotificationConfig result = setInfo(config, "0.3".equals(protocolVersion)); + protocolVersions.put( + result.taskId() + ":" + result.id(), + PushNotificationConfigStore.resolveProtocolVersion(protocolVersion)); return result; } diff --git a/server-common/src/main/java/org/a2aproject/sdk/server/tasks/PushNotificationConfigStore.java b/server-common/src/main/java/org/a2aproject/sdk/server/tasks/PushNotificationConfigStore.java index 64138f3aa..e70e246e6 100644 --- a/server-common/src/main/java/org/a2aproject/sdk/server/tasks/PushNotificationConfigStore.java +++ b/server-common/src/main/java/org/a2aproject/sdk/server/tasks/PushNotificationConfigStore.java @@ -88,8 +88,12 @@ public interface PushNotificationConfigStore { /** * Sets or updates the push notification configuration for a task. *

- * If {@code notificationConfig.id()} is null or empty, it's set to the task ID. - * If a config with the same ID already exists for this task, it's replaced. + * If {@code notificationConfig.id()} is null or empty, the store creates the + * default config with the task ID. Omitting the ID is a create-only shorthand: + * if the default config already exists, the store rejects the request instead + * of silently replacing it. To update the default config, provide the task ID + * explicitly. Configurations beyond the default one must always provide an ID. + * The v0.3 compatibility path keeps its historical single-config update behavior. *

* * @param notificationConfig the task push notification configuration diff --git a/server-common/src/test/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandlerTest.java b/server-common/src/test/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandlerTest.java index da0603961..fa9bf6814 100644 --- a/server-common/src/test/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandlerTest.java +++ b/server-common/src/test/java/org/a2aproject/sdk/server/requesthandlers/DefaultRequestHandlerTest.java @@ -45,6 +45,7 @@ import org.a2aproject.sdk.spec.CancelTaskParams; import org.a2aproject.sdk.spec.Event; import org.a2aproject.sdk.spec.EventKind; +import org.a2aproject.sdk.spec.GetTaskPushNotificationConfigParams; import org.a2aproject.sdk.spec.InvalidParamsError; import org.a2aproject.sdk.spec.ListTasksParams; import org.a2aproject.sdk.spec.ListTaskPushNotificationConfigsParams; @@ -989,6 +990,26 @@ void testVersionStored_OnCreateTaskPushNotificationConfig() throws Exception { "Protocol version should be stored for the push notification config"); } + @Test + void testGetTaskPushNotificationConfigDefaultsMissingIdToTaskId() throws Exception { + String taskId = "get-default-config-id"; + taskStore.save(Task.builder() + .id(taskId) + .contextId("ctx-get-default-config-id") + .status(new TaskStatus(TaskState.TASK_STATE_WORKING)) + .build(), false); + requestHandler.onCreateTaskPushNotificationConfig(TaskPushNotificationConfig.builder() + .taskId(taskId) + .url("http://example.com/get-default-config-id") + .build(), NULL_CONTEXT); + + TaskPushNotificationConfig result = requestHandler.onGetTaskPushNotificationConfig( + new GetTaskPushNotificationConfigParams(taskId), NULL_CONTEXT); + + assertEquals(taskId, result.id()); + assertEquals("http://example.com/get-default-config-id", result.url()); + } + /** * Verify that onMessageSend stores the protocol version when the request * includes a push notification config (new task path). diff --git a/server-common/src/test/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStoreTest.java b/server-common/src/test/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStoreTest.java index 9a1a0f706..5217fa889 100644 --- a/server-common/src/test/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStoreTest.java +++ b/server-common/src/test/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStoreTest.java @@ -151,10 +151,10 @@ public void testSetInfoAppendsToExistingConfig() { } @Test - public void testSetInfoWithoutConfigId() { + public void testSetInfoWithEmptyConfigId() { String taskId = "task1"; TaskPushNotificationConfig initialConfig = TaskPushNotificationConfig.builder() - .id("") // No ID set + .id("") .url("http://initial.url/callback") .taskId(taskId) .build(); @@ -166,18 +166,59 @@ public void testSetInfoWithoutConfigId() { assertEquals(1, configResult.configs().size()); assertEquals(taskId, configResult.configs().get(0).id()); - TaskPushNotificationConfig updatedConfig = TaskPushNotificationConfig.builder() - .id("") // No ID set + TaskPushNotificationConfig duplicateConfig = TaskPushNotificationConfig.builder() + .id("") .url("http://initial.url/callback_new") .taskId(taskId) .build(); - TaskPushNotificationConfig updatedResult = configStore.setInfo(updatedConfig); - assertEquals(taskId, updatedResult.id()); + assertThrows(InvalidParamsError.class, () -> configStore.setInfo(duplicateConfig)); configResult = configStore.getInfo(new ListTaskPushNotificationConfigsParams(taskId)); - assertEquals(1, configResult.configs().size(), "Should replace existing config with same ID rather than adding new one"); - assertEquals(updatedConfig.url(), configResult.configs().get(0).url()); + assertEquals(1, configResult.configs().size()); + assertEquals(initialConfig.url(), configResult.configs().get(0).url()); + } + + @Test + public void testSetInfoWithNullConfigId() { + String taskId = "task_with_null_config_id"; + TaskPushNotificationConfig config = TaskPushNotificationConfig.builder() + .url("http://initial.url/callback") + .taskId(taskId) + .build(); + + TaskPushNotificationConfig result = configStore.setInfo(config); + + assertEquals(taskId, result.id(), "Config ID should default to taskId when null"); + + TaskPushNotificationConfig duplicateConfig = TaskPushNotificationConfig.builder() + .url("http://updated.url/callback") + .taskId(taskId) + .build(); + + assertThrows(InvalidParamsError.class, () -> configStore.setInfo(duplicateConfig)); + } + + @Test + public void testSetInfoAllowsV03DefaultConfigUpdate() { + String taskId = "task_v03_default_update"; + TaskPushNotificationConfig initialConfig = TaskPushNotificationConfig.builder() + .taskId(taskId) + .url("http://initial.url/callback") + .build(); + TaskPushNotificationConfig updatedConfig = TaskPushNotificationConfig.builder() + .taskId(taskId) + .url("http://updated.url/callback") + .build(); + + configStore.setInfo(initialConfig, "0.3"); + TaskPushNotificationConfig result = configStore.setInfo(updatedConfig, "0.3"); + + assertEquals(taskId, result.id()); + ListTaskPushNotificationConfigsResult stored = + configStore.getInfo(new ListTaskPushNotificationConfigsParams(taskId)); + assertEquals(1, stored.configs().size()); + assertEquals(updatedConfig.url(), stored.configs().get(0).url()); } @Test diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java b/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java index eeb950cd8..584b7de23 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParams.java @@ -18,7 +18,7 @@ * @see TaskPushNotificationConfig for the returned configuration structure * @see A2A Protocol Specification */ -public record GetTaskPushNotificationConfigParams(String taskId, String id, @Nullable String tenant) { +public record GetTaskPushNotificationConfigParams(String taskId, @Nullable String id, @Nullable String tenant) { /** * Compact constructor that validates required fields. @@ -26,21 +26,29 @@ public record GetTaskPushNotificationConfigParams(String taskId, String id, @Nul * @param taskId the taskId parameter (see class-level JavaDoc) * @param id the id parameter (see class-level JavaDoc) * @param tenant the tenant parameter (see class-level JavaDoc) - * @throws IllegalArgumentException if taskId or tenant is null + * @throws IllegalArgumentException if taskId is null */ public GetTaskPushNotificationConfigParams { Assert.checkNotNullParam("taskId", taskId); - Assert.checkNotNullParam("id", id); Utils.validateTenant(tenant); } + /** + * Convenience constructor for retrieving the configuration that uses the task ID as its default ID. + * + * @param taskId the task identifier (required) + */ + public GetTaskPushNotificationConfigParams(String taskId) { + this(taskId, null, null); + } + /** * Convenience constructor for creating parameters without tenant. * * @param taskId the task identifier (required) * @param id optional configuration ID to retrieve */ - public GetTaskPushNotificationConfigParams(String taskId, String id) { + public GetTaskPushNotificationConfigParams(String taskId, @Nullable String id) { this(taskId, id, null); } @@ -84,7 +92,7 @@ public Builder taskId(String taskId) { * @param id the configuration ID * @return this builder for method chaining */ - public Builder id(String id) { + public Builder id(@Nullable String id) { this.id = id; return this; } @@ -108,7 +116,7 @@ public Builder tenant(@Nullable String tenant) { public GetTaskPushNotificationConfigParams build() { return new GetTaskPushNotificationConfigParams( Assert.checkNotNullParam("taskId", taskId), - Assert.checkNotNullParam("id", id), + id, tenant); } } diff --git a/spec/src/main/java/org/a2aproject/sdk/spec/TaskPushNotificationConfig.java b/spec/src/main/java/org/a2aproject/sdk/spec/TaskPushNotificationConfig.java index dc3fa7a0f..e98924bee 100644 --- a/spec/src/main/java/org/a2aproject/sdk/spec/TaskPushNotificationConfig.java +++ b/spec/src/main/java/org/a2aproject/sdk/spec/TaskPushNotificationConfig.java @@ -21,7 +21,8 @@ * Used for managing task-specific push notification settings via the push notification * management methods ({@code tasks/pushNotificationConfig/set}, {@code tasks/pushNotificationConfig/get}, etc.). * - * @param id unique identifier (e.g. UUID) for this push notification configuration + * @param id optional unique identifier (e.g. UUID) for this push notification configuration. + * When omitted while creating a configuration, the server assigns one. * @param taskId the unique identifier of the task to receive push notifications for * @param url the HTTP/HTTPS endpoint URL to receive push notifications (required) * @param token optional bearer token for simple authentication @@ -31,14 +32,14 @@ * @see MessageSendConfiguration for configuring push notifications on message send * @see A2A Protocol Specification */ -public record TaskPushNotificationConfig(String id, @Nullable String taskId, String url, @Nullable String token, +public record TaskPushNotificationConfig(@Nullable String id, @Nullable String taskId, String url, @Nullable String token, @Nullable AuthenticationInfo authentication, @Nullable String tenant) { /** * Compact constructor for validation. * Validates that required parameters are not null. * - * @param id the configuration identifier + * @param id the optional configuration identifier * @param taskId the task identifier * @param url the notification endpoint URL * @param token optional bearer token @@ -46,7 +47,6 @@ public record TaskPushNotificationConfig(String id, @Nullable String taskId, Str * @param tenant the tenant identifier */ public TaskPushNotificationConfig { - Assert.checkNotNullParam("id", id); Assert.checkNotNullParam("url", url); Utils.validateTenant(tenant); } @@ -105,10 +105,10 @@ private Builder(TaskPushNotificationConfig config) { /** * Sets the configuration identifier. * - * @param id the configuration ID + * @param id the optional configuration ID * @return this builder */ - public Builder id(String id) { + public Builder id(@Nullable String id) { this.id = id; return this; } @@ -172,11 +172,11 @@ public Builder tenant(String tenant) { * Builds the {@link TaskPushNotificationConfig}. * * @return a new push notification configuration - * @throws IllegalArgumentException if id or url is null + * @throws IllegalArgumentException if url is null */ public TaskPushNotificationConfig build() { return new TaskPushNotificationConfig( - Assert.checkNotNullParam("id", id), + id, taskId, Assert.checkNotNullParam("url", url), token, diff --git a/spec/src/test/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParamsTest.java b/spec/src/test/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParamsTest.java new file mode 100644 index 000000000..030bf209e --- /dev/null +++ b/spec/src/test/java/org/a2aproject/sdk/spec/GetTaskPushNotificationConfigParamsTest.java @@ -0,0 +1,26 @@ +package org.a2aproject.sdk.spec; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class GetTaskPushNotificationConfigParamsTest { + + @Test + void testConstructionAllowsOmittedConfigurationId() { + GetTaskPushNotificationConfigParams params = new GetTaskPushNotificationConfigParams("task-1"); + + assertEquals("task-1", params.taskId()); + assertNull(params.id()); + } + + @Test + void testBuilderAllowsOmittedConfigurationId() { + GetTaskPushNotificationConfigParams params = GetTaskPushNotificationConfigParams.builder() + .taskId("task-1") + .build(); + + assertNull(params.id()); + } +} diff --git a/spec/src/test/java/org/a2aproject/sdk/spec/TaskPushNotificationConfigTest.java b/spec/src/test/java/org/a2aproject/sdk/spec/TaskPushNotificationConfigTest.java new file mode 100644 index 000000000..bf5216182 --- /dev/null +++ b/spec/src/test/java/org/a2aproject/sdk/spec/TaskPushNotificationConfigTest.java @@ -0,0 +1,20 @@ +package org.a2aproject.sdk.spec; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class TaskPushNotificationConfigTest { + + @Test + void builderAllowsAnOmittedConfigurationId() { + TaskPushNotificationConfig config = TaskPushNotificationConfig.builder() + .taskId("task-123") + .url("https://example.com/callback") + .build(); + + assertNull(config.id()); + assertEquals("task-123", config.taskId()); + } +} diff --git a/tests/multiversion/grpc/src/test/java/org/a2aproject/sdk/tests/multiversion/grpc/A2ATestResource.java b/tests/multiversion/grpc/src/test/java/org/a2aproject/sdk/tests/multiversion/grpc/A2ATestResource.java index b5ac86735..d30fa6e9f 100644 --- a/tests/multiversion/grpc/src/test/java/org/a2aproject/sdk/tests/multiversion/grpc/A2ATestResource.java +++ b/tests/multiversion/grpc/src/test/java/org/a2aproject/sdk/tests/multiversion/grpc/A2ATestResource.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; @@ -131,12 +132,15 @@ public Response deleteTaskPushNotificationConfig(@PathParam("taskId") String tas @POST @Path("/task/{taskId}") @Consumes(MediaType.APPLICATION_JSON) - public Response savePushNotificationConfigInStore(@PathParam("taskId") String taskId, String body) throws Exception { + public Response savePushNotificationConfigInStore( + @PathParam("taskId") String taskId, + @HeaderParam("A2A-Version") String protocolVersion, + String body) throws Exception { TaskPushNotificationConfig notificationConfig = JsonUtil.fromJson(body, TaskPushNotificationConfig.class); if (notificationConfig == null) { return Response.status(404).build(); } - testUtilsBean.saveTaskPushNotificationConfig(taskId, notificationConfig); + testUtilsBean.saveTaskPushNotificationConfig(taskId, notificationConfig, protocolVersion); return Response.ok().build(); } diff --git a/tests/multiversion/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multiversion/jsonrpc/A2ATestRoutes.java b/tests/multiversion/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multiversion/jsonrpc/A2ATestRoutes.java index 82870a693..1c06793c6 100644 --- a/tests/multiversion/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multiversion/jsonrpc/A2ATestRoutes.java +++ b/tests/multiversion/jsonrpc/src/test/java/org/a2aproject/sdk/tests/multiversion/jsonrpc/A2ATestRoutes.java @@ -221,7 +221,8 @@ public void saveTaskPushNotificationConfig(String taskId, String body, RoutingCo rc.response().setStatusCode(404).end(); return; } - testUtilsBean.saveTaskPushNotificationConfig(taskId, notificationConfig); + testUtilsBean.saveTaskPushNotificationConfig( + taskId, notificationConfig, rc.request().getHeader("A2A-Version")); rc.response().setStatusCode(200).end(); } catch (Throwable t) { errorResponse(t, rc); diff --git a/tests/multiversion/rest/src/test/java/org/a2aproject/sdk/tests/multiversion/rest/A2ATestRoutes.java b/tests/multiversion/rest/src/test/java/org/a2aproject/sdk/tests/multiversion/rest/A2ATestRoutes.java index 950699473..038ac6e96 100644 --- a/tests/multiversion/rest/src/test/java/org/a2aproject/sdk/tests/multiversion/rest/A2ATestRoutes.java +++ b/tests/multiversion/rest/src/test/java/org/a2aproject/sdk/tests/multiversion/rest/A2ATestRoutes.java @@ -289,7 +289,8 @@ public void saveTaskPushNotificationConfig(String taskId, String body, RoutingCo .end(); return; } - testUtilsBean.saveTaskPushNotificationConfig(taskId, notificationConfig); + testUtilsBean.saveTaskPushNotificationConfig( + taskId, notificationConfig, rc.request().getHeader("A2A-Version")); rc.response() .setStatusCode(200) .end(); diff --git a/tests/server-common/src/test/java/org/a2aproject/sdk/server/apps/common/TestUtilsBean.java b/tests/server-common/src/test/java/org/a2aproject/sdk/server/apps/common/TestUtilsBean.java index 7e771a32f..a7360bbd3 100644 --- a/tests/server-common/src/test/java/org/a2aproject/sdk/server/apps/common/TestUtilsBean.java +++ b/tests/server-common/src/test/java/org/a2aproject/sdk/server/apps/common/TestUtilsBean.java @@ -59,7 +59,16 @@ public void deleteTaskPushNotificationConfig(String taskId, String configId) { } public void saveTaskPushNotificationConfig(String taskId, TaskPushNotificationConfig notificationConfig) { - pushNotificationConfigStore.setInfo(TaskPushNotificationConfig.builder(notificationConfig).taskId(taskId).build()); + saveTaskPushNotificationConfig(taskId, notificationConfig, null); + } + + public void saveTaskPushNotificationConfig( + String taskId, + TaskPushNotificationConfig notificationConfig, + String protocolVersion) { + pushNotificationConfigStore.setInfo( + TaskPushNotificationConfig.builder(notificationConfig).taskId(taskId).build(), + protocolVersion); } /**