diff --git a/src/google/adk/a2a/utils/agent_to_a2a.py b/src/google/adk/a2a/utils/agent_to_a2a.py index e550dc7643..4fa449f655 100644 --- a/src/google/adk/a2a/utils/agent_to_a2a.py +++ b/src/google/adk/a2a/utils/agent_to_a2a.py @@ -36,9 +36,13 @@ from starlette.applications import Starlette from ...agents.base_agent import BaseAgent +from ...artifacts.base_artifact_service import BaseArtifactService 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 @@ -90,6 +94,10 @@ def to_a2a( port: int = 8000, protocol: str = "http", agent_card: Optional[Union[AgentCard, str]] = None, + 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. @@ -101,7 +109,15 @@ 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. + 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 session service. + Returns: A Starlette application that can be run with uvicorn @@ -121,11 +137,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 or InMemoryArtifactService(), + session_service=session_service or InMemorySessionService(), + memory_service=memory_service or InMemoryMemoryService(), + credential_service=credential_service or InMemoryCredentialService(), ) # Create A2A components diff --git a/tests/unittests/a2a/utils/test_agent_to_a2a.py b/tests/unittests/a2a/utils/test_agent_to_a2a.py index 1106bf9673..8f006fdff2 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: @@ -59,155 +63,118 @@ def setup_method(self): self.mock_agent.name = "test_agent" self.mock_agent.description = "Test agent description" - @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, - ): + @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, + } + } + + 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) @@ -426,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.""" @@ -480,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/" ) @@ -869,3 +689,179 @@ 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") + + async def test_to_a2a_with_custom_services(self, mock_a2a_components): + """Test to_a2a with custom service implementations.""" + # 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 + 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 custom services + await runner_func() + + # Verify Runner was created with custom services + mocks["classes"]["runner"].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, + ) + + 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 + mocks = mock_a2a_components + + # Act + result = to_a2a( + self.mock_agent, + artifact_service=None, + session_service=None, + memory_service=None, + credential_service=None, + ) + + # Assert + 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 default services + await runner_func() + + # Verify Runner was created with default 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 + # 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) + + async def test_to_a2a_with_mixed_services(self, mock_a2a_components): + """Test to_a2a with mix of custom and default services.""" + # Arrange + mocks = mock_a2a_components + 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 == 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 mixed services + await runner_func() + + # Verify Runner was created with mixed 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 + # 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) + + 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 + 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 - 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 == 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 all custom services regardless of order + 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 + + 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 + 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 == 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 correctly + await runner_func() + + # Verify Runner was created with default app_name and custom services + 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 + 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) \ No newline at end of file