diff --git a/src/google/adk/cli/fast_api.py b/src/google/adk/cli/fast_api.py index 3f702a6ccf..74e94c3b30 100644 --- a/src/google/adk/cli/fast_api.py +++ b/src/google/adk/cli/fast_api.py @@ -756,7 +756,6 @@ async def _get_a2a_runner_async() -> Runner: ) import inspect - import json from google.adk.agents import Agent import google.auth diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index d3cdd1ea79..a1e03d2773 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -2041,6 +2041,82 @@ def test_openapi_json_schema_accessible(test_app): logger.info("OpenAPI /openapi.json endpoint is accessible") +def test_a2a_setup_reaches_application_build( + mock_session_service, + mock_artifact_service, + mock_memory_service, + mock_agent_loader, + mock_eval_sets_manager, + mock_eval_set_results_manager, + temp_agents_dir_with_a2a, + monkeypatch, +): + """Regression test for the A2A setup silently failing. + + A function-local ``import json`` made ``json`` a local for the whole + ``get_fast_api_app`` function, so the earlier ``json.load(agent.json)`` in + the A2A loop raised ``UnboundLocalError``. The surrounding ``except`` swallowed + it, so no A2A routes were ever mounted. This asserts the loop gets past the + ``json.load`` call and actually builds the A2A application. + """ + with ( + patch("signal.signal", return_value=None), + patch( + "google.adk.cli.fast_api.create_session_service_from_options", + return_value=mock_session_service, + ), + patch( + "google.adk.cli.fast_api.create_artifact_service_from_options", + return_value=mock_artifact_service, + ), + patch( + "google.adk.cli.fast_api.create_memory_service_from_options", + return_value=mock_memory_service, + ), + patch( + "google.adk.cli.fast_api.AgentLoader", + return_value=mock_agent_loader, + ), + patch( + "google.adk.cli.fast_api.LocalEvalSetsManager", + return_value=mock_eval_sets_manager, + ), + patch( + "google.adk.cli.fast_api.LocalEvalSetResultsManager", + return_value=mock_eval_set_results_manager, + ), + patch( + "google.adk.cli.fast_api._create_task_store_from_options", + return_value=MagicMock(), + ), + patch("google.adk.a2a.executor.a2a_agent_executor.A2aAgentExecutor"), + patch("a2a.server.request_handlers.DefaultRequestHandler"), + patch("a2a.types.AgentCard", return_value=MagicMock()), + patch("a2a.server.apps.A2AStarletteApplication") as mock_a2a_app, + ): + mock_app_instance = MagicMock() + mock_app_instance.routes.return_value = [] + mock_a2a_app.return_value = mock_app_instance + + monkeypatch.chdir(temp_agents_dir_with_a2a) + + get_fast_api_app( + agents_dir=".", + web=True, + session_service_uri="", + artifact_service_uri="", + memory_service_uri="", + allow_origins=["*"], + a2a=True, + host="127.0.0.1", + port=8000, + ) + + # If json.load raised UnboundLocalError, execution never reaches the + # A2AStarletteApplication construction and this mock is never called. + assert mock_a2a_app.called + + def test_a2a_agent_discovery(test_app_with_a2a): """Test that A2A agents are properly discovered and configured.""" # This test mainly verifies that the A2A setup doesn't break the app