From e191ea95d10b6ed6bbf58ee1e4282b8ff101be75 Mon Sep 17 00:00:00 2001 From: secprog Date: Tue, 21 Oct 2025 17:32:22 +0100 Subject: [PATCH 01/14] Enhance agent_to_a2a with optional services Added optional services for session, artifact, memory, and credential management to the agent_to_a2a function. --- src/google/adk/a2a/utils/agent_to_a2a.py | 26 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index e550dc7643..9cc2c4f5b4 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -36,6 +36,10 @@ from starlette.applications import Starlette from ...agents.base_agent import BaseAgent +from ...auth.credential_service.base_credential_service import BaseCredentialService +from ...artifacts.base_artifact_service import BaseArtifactService +from ...memory.base_memory_service import BaseMemoryService +from ...sessions.base_session_service import BaseSessionService from ...artifacts.in_memory_artifact_service import InMemoryArtifactService from ...auth.credential_service.in_memory_credential_service import InMemoryCredentialService from ...cli.utils.logs import setup_adk_logger @@ -90,6 +94,10 @@ def to_a2a( port: int = 8000, protocol: str = "http", agent_card: Optional[Union[AgentCard, str]] = None, + session_service: Optional[BaseSessionService] = InMemorySessionService(), + artifact_service: Optional[BaseArtifactService] = InMemoryArtifactService(), + memory_service: Optional[BaseMemoryService] = InMemoryMemoryService(), + credential_service: Optional[BaseCredentialService] = InMemoryCredentialService() ) -> Starlette: """Convert an ADK agent to a A2A Starlette application. @@ -101,7 +109,14 @@ def to_a2a( agent_card: Optional pre-built AgentCard object or path to agent card JSON. If not provided, will be built automatically from the agent. - + artifact_service: Service for artifact management (file storage, logs, etc.). + Defaults to in-memory artifact service. + session_service: Service for session management. Defaults to in-memory service. + memory_service: Service for conversation or workspace memory. + Defaults to in-memory memory service. + credential_service: Service for authentication/credential management. + Defaults to in-memory credential service. + Returns: A Starlette application that can be run with uvicorn @@ -121,11 +136,10 @@ async def create_runner() -> Runner: return Runner( app_name=agent.name or "adk_agent", agent=agent, - # Use minimal services - in a real implementation these could be configured - artifact_service=InMemoryArtifactService(), - session_service=InMemorySessionService(), - memory_service=InMemoryMemoryService(), - credential_service=InMemoryCredentialService(), + artifact_service=artifact_service, + session_service=session_service, + memory_service=memory_service, + credential_service=credential_service, ) # Create A2A components From 6dd83a75f09bbd780b4b4f2dae728bd083c5a91a Mon Sep 17 00:00:00 2001 From: secprog Date: Tue, 21 Oct 2025 17:37:42 +0100 Subject: [PATCH 02/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 9cc2c4f5b4..3f557e7b52 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -36,8 +36,8 @@ from starlette.applications import Starlette from ...agents.base_agent import BaseAgent -from ...auth.credential_service.base_credential_service import BaseCredentialService from ...artifacts.base_artifact_service import BaseArtifactService +from ...auth.credential_service.base_credential_service import BaseCredentialService from ...memory.base_memory_service import BaseMemoryService from ...sessions.base_session_service import BaseSessionService from ...artifacts.in_memory_artifact_service import InMemoryArtifactService From 1e4dd51aaa37c530dd64117ee79961d4f23e33ac Mon Sep 17 00:00:00 2001 From: secprog Date: Tue, 21 Oct 2025 17:38:02 +0100 Subject: [PATCH 03/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 3f557e7b52..053f2fa621 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -94,10 +94,10 @@ def to_a2a( port: int = 8000, protocol: str = "http", agent_card: Optional[Union[AgentCard, str]] = None, - session_service: Optional[BaseSessionService] = InMemorySessionService(), artifact_service: Optional[BaseArtifactService] = InMemoryArtifactService(), + credential_service: Optional[BaseCredentialService] = InMemoryCredentialService(), memory_service: Optional[BaseMemoryService] = InMemoryMemoryService(), - credential_service: Optional[BaseCredentialService] = InMemoryCredentialService() + session_service: Optional[BaseSessionService] = InMemorySessionService(), ) -> Starlette: """Convert an ADK agent to a A2A Starlette application. From 82ef1c6905c260d029b687f367067addf8cffe79 Mon Sep 17 00:00:00 2001 From: secprog Date: Tue, 21 Oct 2025 17:38:13 +0100 Subject: [PATCH 04/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 053f2fa621..710d7ce2ca 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -111,11 +111,11 @@ def to_a2a( agent. artifact_service: Service for artifact management (file storage, logs, etc.). Defaults to in-memory artifact service. - session_service: Service for session management. Defaults to in-memory service. - memory_service: Service for conversation or workspace memory. - Defaults to in-memory memory service. credential_service: Service for authentication/credential management. Defaults to in-memory credential service. + memory_service: Service for conversation or workspace memory. + Defaults to in-memory memory service. + session_service: Service for session management. Defaults to in-memory service. Returns: A Starlette application that can be run with uvicorn From 1a51c89fb0ddb51fdd2205e1e3185873d0c4a936 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 20:49:55 +0100 Subject: [PATCH 05/14] test: add tests for Enhance agent_to_a2a with optional services --- .../unittests/a2a/utils/test_agent_to_a2a.py | 337 ++++++++++++++++++ 1 file changed, 337 insertions(+) diff --git a/tests/unittests/a2a/utils/test_agent_to_a2a.py b/tests/unittests/a2a/utils/test_agent_to_a2a.py index 1106bf9673..262a398dcc 100644 --- a/tests/unittests/a2a/utils/test_agent_to_a2a.py +++ b/tests/unittests/a2a/utils/test_agent_to_a2a.py @@ -34,10 +34,14 @@ from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder from google.adk.a2a.utils.agent_to_a2a import to_a2a from google.adk.agents.base_agent import BaseAgent + from google.adk.artifacts.base_artifact_service import BaseArtifactService from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService + from google.adk.auth.credential_service.base_credential_service import BaseCredentialService from google.adk.auth.credential_service.in_memory_credential_service import InMemoryCredentialService + from google.adk.memory.base_memory_service import BaseMemoryService from google.adk.memory.in_memory_memory_service import InMemoryMemoryService from google.adk.runners import Runner + from google.adk.sessions.base_session_service import BaseSessionService from google.adk.sessions.in_memory_session_service import InMemorySessionService from starlette.applications import Starlette except ImportError as e: @@ -869,3 +873,336 @@ def test_to_a2a_with_invalid_agent_card_file_path( # Act & Assert with pytest.raises(ValueError, match="Failed to load agent card from"): to_a2a(self.mock_agent, agent_card="/invalid/path.json") + + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") + @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") + @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") + @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") + @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") + @patch("google.adk.a2a.utils.agent_to_a2a.Runner") + async def test_to_a2a_with_custom_services( + self, + mock_runner_class, + mock_starlette_class, + mock_card_builder_class, + mock_task_store_class, + mock_request_handler_class, + mock_agent_executor_class, + ): + """Test to_a2a with custom service implementations.""" + # Arrange + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + # Create custom service mocks + custom_artifact_service = Mock(spec=BaseArtifactService) + custom_session_service = Mock(spec=BaseSessionService) + custom_memory_service = Mock(spec=BaseMemoryService) + custom_credential_service = Mock(spec=BaseCredentialService) + + # Act + result = to_a2a( + self.mock_agent, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + memory_service=custom_memory_service, + credential_service=custom_credential_service, + ) + + # Assert + assert result == mock_app + # Get the runner function that was passed to A2aAgentExecutor + call_args = mock_agent_executor_class.call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner with custom services + await runner_func() + + # Verify Runner was created with custom services + mock_runner_class.assert_called_once_with( + app_name="test_agent", + agent=self.mock_agent, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + memory_service=custom_memory_service, + credential_service=custom_credential_service, + ) + + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") + @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") + @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") + @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") + @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") + @patch("google.adk.a2a.utils.agent_to_a2a.Runner") + async def test_to_a2a_with_none_services_uses_defaults( + self, + mock_runner_class, + mock_starlette_class, + mock_card_builder_class, + mock_task_store_class, + mock_request_handler_class, + mock_agent_executor_class, + ): + """Test to_a2a with None services uses default in-memory services.""" + # Arrange + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + # Act + result = to_a2a( + self.mock_agent, + artifact_service=None, + session_service=None, + memory_service=None, + credential_service=None, + ) + + # Assert + assert result == mock_app + # Get the runner function that was passed to A2aAgentExecutor + call_args = mock_agent_executor_class.call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner with default services + await runner_func() + + # Verify Runner was created with default services + mock_runner_class.assert_called_once() + call_args = mock_runner_class.call_args[1] + assert call_args["app_name"] == "test_agent" + assert call_args["agent"] == self.mock_agent + # Verify the services are of the correct default types + assert isinstance(call_args["artifact_service"], InMemoryArtifactService) + assert isinstance(call_args["session_service"], InMemorySessionService) + assert isinstance(call_args["memory_service"], InMemoryMemoryService) + assert isinstance(call_args["credential_service"], InMemoryCredentialService) + + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") + @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") + @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") + @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") + @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") + @patch("google.adk.a2a.utils.agent_to_a2a.Runner") + async def test_to_a2a_with_mixed_services( + self, + mock_runner_class, + mock_starlette_class, + mock_card_builder_class, + mock_task_store_class, + mock_request_handler_class, + mock_agent_executor_class, + ): + """Test to_a2a with mix of custom and default services.""" + # Arrange + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + # Create custom service mocks for some services + custom_artifact_service = Mock(spec=BaseArtifactService) + custom_memory_service = Mock(spec=BaseMemoryService) + + # Act - only provide custom services for artifact and memory + result = to_a2a( + self.mock_agent, + artifact_service=custom_artifact_service, + memory_service=custom_memory_service, + ) + + # Assert + assert result == mock_app + # Get the runner function that was passed to A2aAgentExecutor + call_args = mock_agent_executor_class.call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner with mixed services + await runner_func() + + # Verify Runner was created with mixed services + mock_runner_class.assert_called_once() + call_args = mock_runner_class.call_args[1] + assert call_args["app_name"] == "test_agent" + assert call_args["agent"] == self.mock_agent + # Verify custom services are used + assert call_args["artifact_service"] == custom_artifact_service + assert call_args["memory_service"] == custom_memory_service + # Verify default services are used for the others + assert isinstance(call_args["session_service"], InMemorySessionService) + assert isinstance(call_args["credential_service"], InMemoryCredentialService) + + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") + @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") + @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") + @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") + @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") + @patch("google.adk.a2a.utils.agent_to_a2a.Runner") + async def test_to_a2a_services_parameter_order_independence( + self, + mock_runner_class, + mock_starlette_class, + mock_card_builder_class, + mock_task_store_class, + mock_request_handler_class, + mock_agent_executor_class, + ): + """Test that services can be provided in any order and still work correctly.""" + # Arrange + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + # Create custom service mocks + custom_artifact_service = Mock(spec=BaseArtifactService) + custom_session_service = Mock(spec=BaseSessionService) + custom_memory_service = Mock(spec=BaseMemoryService) + custom_credential_service = Mock(spec=BaseCredentialService) + + # Act - provide services in different order + result = to_a2a( + self.mock_agent, + credential_service=custom_credential_service, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + memory_service=custom_memory_service, + ) + + # Assert + assert result == mock_app + # Get the runner function that was passed to A2aAgentExecutor + call_args = mock_agent_executor_class.call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner with correct services + await runner_func() + + # Verify Runner was created with all custom services regardless of order + mock_runner_class.assert_called_once() + call_args = mock_runner_class.call_args[1] + assert call_args["artifact_service"] == custom_artifact_service + assert call_args["session_service"] == custom_session_service + assert call_args["memory_service"] == custom_memory_service + assert call_args["credential_service"] == custom_credential_service + + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") + @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") + @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") + @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") + @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") + @patch("google.adk.a2a.utils.agent_to_a2a.Runner") + async def test_to_a2a_with_agent_without_name_and_custom_services( + self, + mock_runner_class, + mock_starlette_class, + mock_card_builder_class, + mock_task_store_class, + mock_request_handler_class, + mock_agent_executor_class, + ): + """Test to_a2a with agent that has no name and custom services.""" + # Arrange + self.mock_agent.name = None + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + # Create custom service mocks + custom_artifact_service = Mock(spec=BaseArtifactService) + custom_session_service = Mock(spec=BaseSessionService) + + # Act + result = to_a2a( + self.mock_agent, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + ) + + # Assert + assert result == mock_app + # Get the runner function that was passed to A2aAgentExecutor + call_args = mock_agent_executor_class.call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner correctly + await runner_func() + + # Verify Runner was created with default app_name and custom services + mock_runner_class.assert_called_once() + call_args = mock_runner_class.call_args[1] + assert call_args["app_name"] == "adk_agent" # Default name when agent has no name + assert call_args["agent"] == self.mock_agent + assert call_args["artifact_service"] == custom_artifact_service + assert call_args["session_service"] == custom_session_service + # Verify default services are used for the others + assert isinstance(call_args["memory_service"], InMemoryMemoryService) + assert isinstance(call_args["credential_service"], InMemoryCredentialService) + + def test_to_a2a_service_parameter_validation(self): + """Test that to_a2a accepts valid service types without raising errors.""" + # Arrange + custom_artifact_service = Mock(spec=BaseArtifactService) + custom_session_service = Mock(spec=BaseSessionService) + custom_memory_service = Mock(spec=BaseMemoryService) + custom_credential_service = Mock(spec=BaseCredentialService) + + # Act & Assert - should not raise any errors + with patch("google.adk.a2a.utils.agent_to_a2a.Starlette") as mock_starlette: + mock_app = Mock(spec=Starlette) + mock_starlette.return_value = mock_app + + # This should not raise any validation errors + result = to_a2a( + self.mock_agent, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + memory_service=custom_memory_service, + credential_service=custom_credential_service, + ) + + assert result == mock_app \ No newline at end of file From e393c042042651fccaf12fea8b0427e1dd9393a6 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:05:26 +0100 Subject: [PATCH 06/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 710d7ce2ca..ea67cca32d 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -94,10 +94,10 @@ def to_a2a( port: int = 8000, protocol: str = "http", agent_card: Optional[Union[AgentCard, str]] = None, - artifact_service: Optional[BaseArtifactService] = InMemoryArtifactService(), - credential_service: Optional[BaseCredentialService] = InMemoryCredentialService(), - memory_service: Optional[BaseMemoryService] = InMemoryMemoryService(), - session_service: Optional[BaseSessionService] = InMemorySessionService(), + artifact_service: Optional[BaseArtifactService] = None, + credential_service: Optional[BaseCredentialService] = None, + memory_service: Optional[BaseMemoryService] = None, + session_service: Optional[BaseSessionService] = None, ) -> Starlette: """Convert an ADK agent to a A2A Starlette application. From 5dfba4ad77560c345040912523e11def4cea6183 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:05:39 +0100 Subject: [PATCH 07/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index ea67cca32d..9ba9edeef4 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -136,10 +136,10 @@ async def create_runner() -> Runner: return Runner( app_name=agent.name or "adk_agent", agent=agent, - artifact_service=artifact_service, - session_service=session_service, - memory_service=memory_service, - credential_service=credential_service, + artifact_service=artifact_service or InMemoryArtifactService(), + session_service=session_service or InMemorySessionService(), + memory_service=memory_service or InMemoryMemoryService(), + credential_service=credential_service or InMemoryCredentialService(), ) # Create A2A components From 8daf19319103bf69f8e0f2abc85b75d60a348daf Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:05:52 +0100 Subject: [PATCH 08/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 9ba9edeef4..690b3b0df9 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -111,11 +111,11 @@ def to_a2a( agent. artifact_service: Service for artifact management (file storage, logs, etc.). Defaults to in-memory artifact service. - credential_service: Service for authentication/credential management. + credential_service: Service for authentication/credential management. Defaults to in-memory credential service. - memory_service: Service for conversation or workspace memory. + memory_service: Service for conversation or workspace memory. Defaults to in-memory memory service. - session_service: Service for session management. Defaults to in-memory service. + session_service: Service for session management. Defaults to in-memory session service. Returns: A Starlette application that can be run with uvicorn From f6134c5da479256e56d4a6334e8071885e730a1a Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:11:03 +0100 Subject: [PATCH 09/14] test: Enhance unit tests for A2A with new mock fixture and service parameter validation Added a new fixture `mock_a2a_components` to streamline the mocking of A2A components in tests. Updated existing tests to utilize this fixture, improving readability and maintainability. Enhanced service parameter validation tests to ensure correct handling of custom services. --- .../unittests/a2a/utils/test_agent_to_a2a.py | 286 +++++++----------- 1 file changed, 107 insertions(+), 179 deletions(-) diff --git a/tests/unittests/a2a/utils/test_agent_to_a2a.py b/tests/unittests/a2a/utils/test_agent_to_a2a.py index 262a398dcc..2d20a022a2 100644 --- a/tests/unittests/a2a/utils/test_agent_to_a2a.py +++ b/tests/unittests/a2a/utils/test_agent_to_a2a.py @@ -63,6 +63,53 @@ def setup_method(self): self.mock_agent.name = "test_agent" self.mock_agent.description = "Test agent description" + @pytest.fixture + def mock_a2a_components(self): + """Fixture that provides all the mocked A2A components for testing.""" + with ( + patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") as mock_agent_executor_class, + patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") as mock_request_handler_class, + patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") as mock_task_store_class, + patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") as mock_card_builder_class, + patch("google.adk.a2a.utils.agent_to_a2a.Starlette") as mock_starlette_class, + patch("google.adk.a2a.utils.agent_to_a2a.Runner") as mock_runner_class, + ): + # Create mock instances + mock_app = Mock(spec=Starlette) + mock_starlette_class.return_value = mock_app + + mock_task_store = Mock(spec=InMemoryTaskStore) + mock_task_store_class.return_value = mock_task_store + + mock_agent_executor = Mock(spec=A2aAgentExecutor) + mock_agent_executor_class.return_value = mock_agent_executor + + mock_request_handler = Mock(spec=DefaultRequestHandler) + mock_request_handler_class.return_value = mock_request_handler + + mock_card_builder = Mock(spec=AgentCardBuilder) + mock_card_builder_class.return_value = mock_card_builder + + mock_runner = Mock(spec=Runner) + mock_runner_class.return_value = mock_runner + + yield { + "app": mock_app, + "task_store": mock_task_store, + "agent_executor": mock_agent_executor, + "request_handler": mock_request_handler, + "card_builder": mock_card_builder, + "runner": mock_runner, + "classes": { + "starlette": mock_starlette_class, + "task_store": mock_task_store_class, + "agent_executor": mock_agent_executor_class, + "request_handler": mock_request_handler_class, + "card_builder": mock_card_builder_class, + "runner": mock_runner_class, + } + } + @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") @@ -874,37 +921,10 @@ def test_to_a2a_with_invalid_agent_card_file_path( with pytest.raises(ValueError, match="Failed to load agent card from"): to_a2a(self.mock_agent, agent_card="/invalid/path.json") - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - @patch("google.adk.a2a.utils.agent_to_a2a.Runner") - async def test_to_a2a_with_custom_services( - self, - mock_runner_class, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + async def test_to_a2a_with_custom_services(self, mock_a2a_components): """Test to_a2a with custom service implementations.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder - mock_runner = Mock(spec=Runner) - mock_runner_class.return_value = mock_runner - - # Create custom service mocks + mocks = mock_a2a_components custom_artifact_service = Mock(spec=BaseArtifactService) custom_session_service = Mock(spec=BaseSessionService) custom_memory_service = Mock(spec=BaseMemoryService) @@ -920,16 +940,16 @@ async def test_to_a2a_with_custom_services( ) # Assert - assert result == mock_app + assert result == mocks["app"] # Get the runner function that was passed to A2aAgentExecutor - call_args = mock_agent_executor_class.call_args + call_args = mocks["classes"]["agent_executor"].call_args runner_func = call_args[1]["runner"] # Call the runner function to verify it creates Runner with custom services await runner_func() # Verify Runner was created with custom services - mock_runner_class.assert_called_once_with( + mocks["classes"]["runner"].assert_called_once_with( app_name="test_agent", agent=self.mock_agent, artifact_service=custom_artifact_service, @@ -938,35 +958,10 @@ async def test_to_a2a_with_custom_services( credential_service=custom_credential_service, ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - @patch("google.adk.a2a.utils.agent_to_a2a.Runner") - async def test_to_a2a_with_none_services_uses_defaults( - self, - mock_runner_class, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + async def test_to_a2a_with_none_services_uses_defaults(self, mock_a2a_components): """Test to_a2a with None services uses default in-memory services.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder - mock_runner = Mock(spec=Runner) - mock_runner_class.return_value = mock_runner + mocks = mock_a2a_components # Act result = to_a2a( @@ -978,17 +973,17 @@ async def test_to_a2a_with_none_services_uses_defaults( ) # Assert - assert result == mock_app + assert result == mocks["app"] # Get the runner function that was passed to A2aAgentExecutor - call_args = mock_agent_executor_class.call_args + call_args = mocks["classes"]["agent_executor"].call_args runner_func = call_args[1]["runner"] # Call the runner function to verify it creates Runner with default services await runner_func() # Verify Runner was created with default services - mock_runner_class.assert_called_once() - call_args = mock_runner_class.call_args[1] + mocks["classes"]["runner"].assert_called_once() + call_args = mocks["classes"]["runner"].call_args[1] assert call_args["app_name"] == "test_agent" assert call_args["agent"] == self.mock_agent # Verify the services are of the correct default types @@ -997,37 +992,10 @@ async def test_to_a2a_with_none_services_uses_defaults( assert isinstance(call_args["memory_service"], InMemoryMemoryService) assert isinstance(call_args["credential_service"], InMemoryCredentialService) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - @patch("google.adk.a2a.utils.agent_to_a2a.Runner") - async def test_to_a2a_with_mixed_services( - self, - mock_runner_class, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + async def test_to_a2a_with_mixed_services(self, mock_a2a_components): """Test to_a2a with mix of custom and default services.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder - mock_runner = Mock(spec=Runner) - mock_runner_class.return_value = mock_runner - - # Create custom service mocks for some services + mocks = mock_a2a_components custom_artifact_service = Mock(spec=BaseArtifactService) custom_memory_service = Mock(spec=BaseMemoryService) @@ -1039,17 +1007,17 @@ async def test_to_a2a_with_mixed_services( ) # Assert - assert result == mock_app + assert result == mocks["app"] # Get the runner function that was passed to A2aAgentExecutor - call_args = mock_agent_executor_class.call_args + call_args = mocks["classes"]["agent_executor"].call_args runner_func = call_args[1]["runner"] # Call the runner function to verify it creates Runner with mixed services await runner_func() # Verify Runner was created with mixed services - mock_runner_class.assert_called_once() - call_args = mock_runner_class.call_args[1] + mocks["classes"]["runner"].assert_called_once() + call_args = mocks["classes"]["runner"].call_args[1] assert call_args["app_name"] == "test_agent" assert call_args["agent"] == self.mock_agent # Verify custom services are used @@ -1059,37 +1027,10 @@ async def test_to_a2a_with_mixed_services( assert isinstance(call_args["session_service"], InMemorySessionService) assert isinstance(call_args["credential_service"], InMemoryCredentialService) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - @patch("google.adk.a2a.utils.agent_to_a2a.Runner") - async def test_to_a2a_services_parameter_order_independence( - self, - mock_runner_class, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + async def test_to_a2a_services_parameter_order_independence(self, mock_a2a_components): """Test that services can be provided in any order and still work correctly.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder - mock_runner = Mock(spec=Runner) - mock_runner_class.return_value = mock_runner - - # Create custom service mocks + mocks = mock_a2a_components custom_artifact_service = Mock(spec=BaseArtifactService) custom_session_service = Mock(spec=BaseSessionService) custom_memory_service = Mock(spec=BaseMemoryService) @@ -1105,54 +1046,27 @@ async def test_to_a2a_services_parameter_order_independence( ) # Assert - assert result == mock_app + assert result == mocks["app"] # Get the runner function that was passed to A2aAgentExecutor - call_args = mock_agent_executor_class.call_args + call_args = mocks["classes"]["agent_executor"].call_args runner_func = call_args[1]["runner"] # Call the runner function to verify it creates Runner with correct services await runner_func() # Verify Runner was created with all custom services regardless of order - mock_runner_class.assert_called_once() - call_args = mock_runner_class.call_args[1] + mocks["classes"]["runner"].assert_called_once() + call_args = mocks["classes"]["runner"].call_args[1] assert call_args["artifact_service"] == custom_artifact_service assert call_args["session_service"] == custom_session_service assert call_args["memory_service"] == custom_memory_service assert call_args["credential_service"] == custom_credential_service - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - @patch("google.adk.a2a.utils.agent_to_a2a.Runner") - async def test_to_a2a_with_agent_without_name_and_custom_services( - self, - mock_runner_class, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + async def test_to_a2a_with_agent_without_name_and_custom_services(self, mock_a2a_components): """Test to_a2a with agent that has no name and custom services.""" # Arrange + mocks = mock_a2a_components self.mock_agent.name = None - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder - mock_runner = Mock(spec=Runner) - mock_runner_class.return_value = mock_runner - - # Create custom service mocks custom_artifact_service = Mock(spec=BaseArtifactService) custom_session_service = Mock(spec=BaseSessionService) @@ -1164,17 +1078,17 @@ async def test_to_a2a_with_agent_without_name_and_custom_services( ) # Assert - assert result == mock_app + assert result == mocks["app"] # Get the runner function that was passed to A2aAgentExecutor - call_args = mock_agent_executor_class.call_args + call_args = mocks["classes"]["agent_executor"].call_args runner_func = call_args[1]["runner"] # Call the runner function to verify it creates Runner correctly await runner_func() # Verify Runner was created with default app_name and custom services - mock_runner_class.assert_called_once() - call_args = mock_runner_class.call_args[1] + mocks["classes"]["runner"].assert_called_once() + call_args = mocks["classes"]["runner"].call_args[1] assert call_args["app_name"] == "adk_agent" # Default name when agent has no name assert call_args["agent"] == self.mock_agent assert call_args["artifact_service"] == custom_artifact_service @@ -1183,26 +1097,40 @@ async def test_to_a2a_with_agent_without_name_and_custom_services( assert isinstance(call_args["memory_service"], InMemoryMemoryService) assert isinstance(call_args["credential_service"], InMemoryCredentialService) - def test_to_a2a_service_parameter_validation(self): - """Test that to_a2a accepts valid service types without raising errors.""" + async def test_to_a2a_service_parameter_validation(self, mock_a2a_components): + """Test that to_a2a accepts valid service types and passes them through correctly.""" # Arrange + mocks = mock_a2a_components custom_artifact_service = Mock(spec=BaseArtifactService) custom_session_service = Mock(spec=BaseSessionService) custom_memory_service = Mock(spec=BaseMemoryService) custom_credential_service = Mock(spec=BaseCredentialService) - # Act & Assert - should not raise any errors - with patch("google.adk.a2a.utils.agent_to_a2a.Starlette") as mock_starlette: - mock_app = Mock(spec=Starlette) - mock_starlette.return_value = mock_app - - # This should not raise any validation errors - result = to_a2a( - self.mock_agent, - artifact_service=custom_artifact_service, - session_service=custom_session_service, - memory_service=custom_memory_service, - credential_service=custom_credential_service, - ) - - assert result == mock_app \ No newline at end of file + # Act + result = to_a2a( + self.mock_agent, + artifact_service=custom_artifact_service, + session_service=custom_session_service, + memory_service=custom_memory_service, + credential_service=custom_credential_service, + ) + + # Assert - verify the app is returned + assert result == mocks["app"] + + # Get the runner function that was passed to A2aAgentExecutor + call_args = mocks["classes"]["agent_executor"].call_args + runner_func = call_args[1]["runner"] + + # Call the runner function to verify it creates Runner with correct services + await runner_func() + + # Verify Runner was created with the custom services + mocks["classes"]["runner"].assert_called_once() + call_args = mocks["classes"]["runner"].call_args[1] + assert call_args["app_name"] == "test_agent" + assert call_args["agent"] == self.mock_agent + assert call_args["artifact_service"] == custom_artifact_service + assert call_args["session_service"] == custom_session_service + assert call_args["memory_service"] == custom_memory_service + assert call_args["credential_service"] == custom_credential_service \ No newline at end of file From d6a280ea22a6e94b2429dc3420dba2bba00a1c4c Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:15:16 +0100 Subject: [PATCH 10/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 690b3b0df9..29d0e617e9 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -112,9 +112,9 @@ def to_a2a( artifact_service: Service for artifact management (file storage, logs, etc.). Defaults to in-memory artifact service. credential_service: Service for authentication/credential management. - Defaults to in-memory credential service. + Defaults to in-memory credential service. memory_service: Service for conversation or workspace memory. - Defaults to in-memory memory service. + Defaults to in-memory memory service. session_service: Service for session management. Defaults to in-memory session service. Returns: From eae242b4d1ef28d9717f401720ddf324cecdf333 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:18:12 +0100 Subject: [PATCH 11/14] refactor: Improve docstring formatting for session_service parameter in to_a2a function Updated the docstring for the session_service parameter to enhance readability by breaking it into multiple lines. This change aims to maintain consistency in documentation style across the codebase. --- src/google/adk/a2a/utils/agent_to_a2a.py | 3 +- .../unittests/a2a/utils/test_agent_to_a2a.py | 40 +------------------ 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 29d0e617e9..b5b2e4d8c6 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -115,7 +115,8 @@ def to_a2a( Defaults to in-memory credential service. memory_service: Service for conversation or workspace memory. Defaults to in-memory memory service. - session_service: Service for session management. Defaults to in-memory session service. + session_service: Service for session management. + Defaults to in-memory session service. Returns: A Starlette application that can be run with uvicorn diff --git a/tests/unittests/a2a/utils/test_agent_to_a2a.py b/tests/unittests/a2a/utils/test_agent_to_a2a.py index 2d20a022a2..a2b8c8cead 100644 --- a/tests/unittests/a2a/utils/test_agent_to_a2a.py +++ b/tests/unittests/a2a/utils/test_agent_to_a2a.py @@ -1095,42 +1095,4 @@ async def test_to_a2a_with_agent_without_name_and_custom_services(self, mock_a2a assert call_args["session_service"] == custom_session_service # Verify default services are used for the others assert isinstance(call_args["memory_service"], InMemoryMemoryService) - assert isinstance(call_args["credential_service"], InMemoryCredentialService) - - async def test_to_a2a_service_parameter_validation(self, mock_a2a_components): - """Test that to_a2a accepts valid service types and passes them through correctly.""" - # Arrange - mocks = mock_a2a_components - custom_artifact_service = Mock(spec=BaseArtifactService) - custom_session_service = Mock(spec=BaseSessionService) - custom_memory_service = Mock(spec=BaseMemoryService) - custom_credential_service = Mock(spec=BaseCredentialService) - - # Act - result = to_a2a( - self.mock_agent, - artifact_service=custom_artifact_service, - session_service=custom_session_service, - memory_service=custom_memory_service, - credential_service=custom_credential_service, - ) - - # Assert - verify the app is returned - assert result == mocks["app"] - - # Get the runner function that was passed to A2aAgentExecutor - call_args = mocks["classes"]["agent_executor"].call_args - runner_func = call_args[1]["runner"] - - # Call the runner function to verify it creates Runner with correct services - await runner_func() - - # Verify Runner was created with the custom services - mocks["classes"]["runner"].assert_called_once() - call_args = mocks["classes"]["runner"].call_args[1] - assert call_args["app_name"] == "test_agent" - assert call_args["agent"] == self.mock_agent - assert call_args["artifact_service"] == custom_artifact_service - assert call_args["session_service"] == custom_session_service - assert call_args["memory_service"] == custom_memory_service - assert call_args["credential_service"] == custom_credential_service \ No newline at end of file + assert isinstance(call_args["credential_service"], InMemoryCredentialService) \ No newline at end of file From 5fcafa95b3391ad886c8b6a3a8c7eb3ee4893792 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:25:37 +0100 Subject: [PATCH 12/14] refactor: Simplify unit tests for A2A by utilizing mock fixture Refactored unit tests in `test_agent_to_a2a.py` to use the new `mock_a2a_components` fixture for improved readability and maintainability. This change consolidates mock setup, reducing redundancy across tests while ensuring consistent behavior in assertions. --- .../unittests/a2a/utils/test_agent_to_a2a.py | 331 +++--------------- 1 file changed, 50 insertions(+), 281 deletions(-) diff --git a/tests/unittests/a2a/utils/test_agent_to_a2a.py b/tests/unittests/a2a/utils/test_agent_to_a2a.py index a2b8c8cead..8f006fdff2 100644 --- a/tests/unittests/a2a/utils/test_agent_to_a2a.py +++ b/tests/unittests/a2a/utils/test_agent_to_a2a.py @@ -110,155 +110,71 @@ def mock_a2a_components(self): } } - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_default_parameters( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_default_parameters(self, mock_a2a_components): """Test to_a2a with default parameters.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent) # Assert - assert result == mock_app - mock_starlette_class.assert_called_once() - mock_task_store_class.assert_called_once() - mock_agent_executor_class.assert_called_once() - mock_request_handler_class.assert_called_once_with( - agent_executor=mock_agent_executor, task_store=mock_task_store + assert result == mocks["app"] + mocks["classes"]["starlette"].assert_called_once() + mocks["classes"]["task_store"].assert_called_once() + mocks["classes"]["agent_executor"].assert_called_once() + mocks["classes"]["request_handler"].assert_called_once_with( + agent_executor=mocks["agent_executor"], task_store=mocks["task_store"] ) - mock_card_builder_class.assert_called_once_with( + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://localhost:8000/" ) - mock_app.add_event_handler.assert_called_once_with( - "startup", mock_app.add_event_handler.call_args[0][1] + mocks["app"].add_event_handler.assert_called_once_with( + "startup", mocks["app"].add_event_handler.call_args[0][1] ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_custom_host_port( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_custom_host_port(self, mock_a2a_components): """Test to_a2a with custom host and port.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, host="example.com", port=9000) # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://example.com:9000/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_agent_without_name( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_agent_without_name(self, mock_a2a_components): """Test to_a2a with agent that has no name.""" # Arrange + mocks = mock_a2a_components self.mock_agent.name = None - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder # Act result = to_a2a(self.mock_agent) # Assert - assert result == mock_app + assert result == mocks["app"] # The create_runner function should use "adk_agent" as default name # We can't directly test the create_runner function, but we can verify # the agent executor was created with the runner function - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_creates_runner_with_correct_services( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_creates_runner_with_correct_services(self, mock_a2a_components): """Test that the create_runner function creates Runner with correct services.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent) # Assert - assert result == mock_app + assert result == mocks["app"] # Verify that the agent executor was created with a runner function - mock_agent_executor_class.assert_called_once() - call_args = mock_agent_executor_class.call_args + mocks["classes"]["agent_executor"].assert_called_once() + call_args = mocks["classes"]["agent_executor"].call_args assert "runner" in call_args[1] runner_func = call_args[1]["runner"] assert callable(runner_func) @@ -477,38 +393,17 @@ async def test_setup_a2a_function_handles_agent_card_build_failure( with pytest.raises(Exception, match="Build failed"): await startup_handler() - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_returns_starlette_app( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_returns_starlette_app(self, mock_a2a_components): """Test that to_a2a returns a Starlette application.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent) # Assert assert isinstance(result, Mock) # Mock of Starlette - assert result == mock_app + assert result == mocks["app"] def test_to_a2a_with_none_agent(self): """Test that to_a2a raises error when agent is None.""" @@ -531,213 +426,87 @@ def test_to_a2a_with_invalid_agent_type(self): asyncio.run(app.router.on_startup[0]()) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_custom_port_zero( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_custom_port_zero(self, mock_a2a_components): """Test to_a2a with port 0 (dynamic port assignment).""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, port=0) # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://localhost:0/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_empty_string_host( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_empty_string_host(self, mock_a2a_components): """Test to_a2a with empty string host.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, host="") # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://:8000/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_negative_port( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_negative_port(self, mock_a2a_components): """Test to_a2a with negative port number.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, port=-1) # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://localhost:-1/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_very_large_port( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_very_large_port(self, mock_a2a_components): """Test to_a2a with very large port number.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, port=65535) # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://localhost:65535/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_special_characters_in_host( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_special_characters_in_host(self, mock_a2a_components): """Test to_a2a with special characters in host name.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, host="test-host.example.com") # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://test-host.example.com:8000/" ) - @patch("google.adk.a2a.utils.agent_to_a2a.A2aAgentExecutor") - @patch("google.adk.a2a.utils.agent_to_a2a.DefaultRequestHandler") - @patch("google.adk.a2a.utils.agent_to_a2a.InMemoryTaskStore") - @patch("google.adk.a2a.utils.agent_to_a2a.AgentCardBuilder") - @patch("google.adk.a2a.utils.agent_to_a2a.Starlette") - def test_to_a2a_with_ip_address_host( - self, - mock_starlette_class, - mock_card_builder_class, - mock_task_store_class, - mock_request_handler_class, - mock_agent_executor_class, - ): + def test_to_a2a_with_ip_address_host(self, mock_a2a_components): """Test to_a2a with IP address as host.""" # Arrange - mock_app = Mock(spec=Starlette) - mock_starlette_class.return_value = mock_app - mock_task_store = Mock(spec=InMemoryTaskStore) - mock_task_store_class.return_value = mock_task_store - mock_agent_executor = Mock(spec=A2aAgentExecutor) - mock_agent_executor_class.return_value = mock_agent_executor - mock_request_handler = Mock(spec=DefaultRequestHandler) - mock_request_handler_class.return_value = mock_request_handler - mock_card_builder = Mock(spec=AgentCardBuilder) - mock_card_builder_class.return_value = mock_card_builder + mocks = mock_a2a_components # Act result = to_a2a(self.mock_agent, host="192.168.1.1") # Assert - assert result == mock_app - mock_card_builder_class.assert_called_once_with( + assert result == mocks["app"] + mocks["classes"]["card_builder"].assert_called_once_with( agent=self.mock_agent, rpc_url="http://192.168.1.1:8000/" ) From 61593aaf6c8943aff1b725be6f79e7ff08e63d07 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:28:29 +0100 Subject: [PATCH 13/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index b5b2e4d8c6..265af62f8c 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -37,12 +37,12 @@ from ...agents.base_agent import BaseAgent from ...artifacts.base_artifact_service import BaseArtifactService -from ...auth.credential_service.base_credential_service import BaseCredentialService -from ...memory.base_memory_service import BaseMemoryService -from ...sessions.base_session_service import BaseSessionService from ...artifacts.in_memory_artifact_service import InMemoryArtifactService +from ...auth.credential_service.base_credential_service import BaseCredentialService from ...auth.credential_service.in_memory_credential_service import InMemoryCredentialService from ...cli.utils.logs import setup_adk_logger +from ...memory.base_memory_service import BaseMemoryService +from ...sessions.base_session_service import BaseSessionService from ...memory.in_memory_memory_service import InMemoryMemoryService from ...runners import Runner from ...sessions.in_memory_session_service import InMemorySessionService From 3a5ca7005233fe5a6142b61c3c02ab118dd16738 Mon Sep 17 00:00:00 2001 From: secprog Date: Wed, 22 Oct 2025 21:28:45 +0100 Subject: [PATCH 14/14] Update src/google/adk/a2a/utils/agent_to_a2a.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/a2a/utils/agent_to_a2a.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index 265af62f8c..4fa449f655 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -115,7 +115,7 @@ def to_a2a( Defaults to in-memory credential service. memory_service: Service for conversation or workspace memory. Defaults to in-memory memory service. - session_service: Service for session management. + session_service: Service for session management. Defaults to in-memory session service. Returns: