diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index 384fd5e85..883e8558d 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -547,6 +547,12 @@ async def on_create_task_push_notification_config( await self._reject_unsafe_push_url(params.url) + # Stores default an empty id to the task id, but only the in-memory + # store does so on the caller's object. Normalize here so the returned + # config carries the id that was persisted, on every store. + if not params.id: + params.id = task_id + await self._push_config_store.set_info( task_id, params, diff --git a/src/a2a/server/request_handlers/default_request_handler_v2.py b/src/a2a/server/request_handlers/default_request_handler_v2.py index 59996236e..5c067b067 100644 --- a/src/a2a/server/request_handlers/default_request_handler_v2.py +++ b/src/a2a/server/request_handlers/default_request_handler_v2.py @@ -385,6 +385,12 @@ async def on_create_task_push_notification_config( # noqa: D102 await self._reject_unsafe_push_url(params.url) + # Stores default an empty id to the task id, but only the in-memory + # store does so on the caller's object. Normalize here so the returned + # config carries the id that was persisted, on every store. + if not params.id: + params.id = task_id + await self._push_config_store.set_info( task_id, params, diff --git a/tests/server/request_handlers/test_default_request_handler.py b/tests/server/request_handlers/test_default_request_handler.py index f9a2a0fb3..b13a6a1e2 100644 --- a/tests/server/request_handlers/test_default_request_handler.py +++ b/tests/server/request_handlers/test_default_request_handler.py @@ -1935,6 +1935,50 @@ async def test_set_task_push_notification_config_task_not_found(agent_card): mock_push_store.set_info.assert_not_awaited() +@pytest.mark.asyncio +@pytest.mark.parametrize('store_kind', ['inmemory', 'database']) +async def test_create_task_push_notification_config_returns_stored_id( + agent_card, store_kind +): + """Test on_create_task_push_notification_config returns the id that was stored.""" + if store_kind == 'database': + from a2a.server.tasks.database_push_notification_config_store import ( + DatabasePushNotificationConfigStore, + ) + from sqlalchemy.ext.asyncio import create_async_engine + + engine = create_async_engine( + 'sqlite+aiosqlite:///file:pushid?mode=memory&cache=shared&uri=true' + ) + push_config_store = DatabasePushNotificationConfigStore(engine=engine) + else: + push_config_store = InMemoryPushNotificationConfigStore() + + task = create_sample_task() + task_store = InMemoryTaskStore() + context = create_server_call_context() + await task_store.save(task, context) + + request_handler = DefaultRequestHandler( + agent_executor=MockAgentExecutor(), + task_store=task_store, + push_config_store=push_config_store, + agent_card=agent_card, + ) + params = TaskPushNotificationConfig( + task_id=task.id, + url='http://example.com', + ) + + response = await request_handler.on_create_task_push_notification_config( + params, context + ) + + stored = await push_config_store.get_info(task.id, context) + assert response.id == task.id + assert [config.id for config in stored] == [response.id] + + @pytest.mark.asyncio async def test_get_task_push_notification_config_no_store(agent_card): """Test on_get_task_push_notification_config when _push_config_store is None.""" diff --git a/tests/server/request_handlers/test_default_request_handler_v2.py b/tests/server/request_handlers/test_default_request_handler_v2.py index 2eb7e4725..08c91d424 100644 --- a/tests/server/request_handlers/test_default_request_handler_v2.py +++ b/tests/server/request_handlers/test_default_request_handler_v2.py @@ -511,6 +511,49 @@ async def test_set_task_push_notification_config_task_not_found(): mock_push_store.set_info.assert_not_awaited() +@pytest.mark.asyncio +@pytest.mark.parametrize('store_kind', ['inmemory', 'database']) +async def test_create_task_push_notification_config_returns_stored_id( + store_kind, +): + """Test on_create_task_push_notification_config returns the id that was stored.""" + if store_kind == 'database': + from a2a.server.tasks.database_push_notification_config_store import ( + DatabasePushNotificationConfigStore, + ) + from sqlalchemy.ext.asyncio import create_async_engine + + engine = create_async_engine( + 'sqlite+aiosqlite:///file:pushidv2?mode=memory&cache=shared&uri=true' + ) + push_config_store = DatabasePushNotificationConfigStore(engine=engine) + else: + push_config_store = InMemoryPushNotificationConfigStore() + + task = create_sample_task() + task_store = InMemoryTaskStore() + context = create_server_call_context() + await task_store.save(task, context) + + request_handler = DefaultRequestHandlerV2( + agent_executor=MockAgentExecutor(), + task_store=task_store, + push_config_store=push_config_store, + agent_card=create_default_agent_card(), + ) + params = TaskPushNotificationConfig( + task_id=task.id, url='http://example.com' + ) + + response = await request_handler.on_create_task_push_notification_config( + params, context + ) + + stored = await push_config_store.get_info(task.id, context) + assert response.id == task.id + assert [config.id for config in stored] == [response.id] + + @pytest.mark.asyncio async def test_get_task_push_notification_config_no_store(): """Test on_get_task_push_notification_config when _push_config_store is None."""