Skip to content

Commit 4cbc3dc

Browse files
google-genai-botcopybara-github
authored andcommitted
test: add integration tests for ADK's A2A client-server interaction
PiperOrigin-RevId: 888114589
1 parent 4b677e7 commit 4cbc3dc

4 files changed

Lines changed: 764 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A2A integration tests package."""
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A2A Client for integration tests."""
16+
17+
from a2a.client.client import ClientConfig as A2AClientConfig
18+
from a2a.client.client_factory import ClientFactory as A2AClientFactory
19+
from a2a.extensions.common import HTTP_EXTENSION_HEADER
20+
from a2a.types import TransportProtocol as A2ATransport
21+
from google.adk.a2a.agent.interceptors.new_integration_extension import _NEW_A2A_ADK_INTEGRATION_EXTENSION
22+
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
23+
import httpx
24+
25+
from .server import agent_card
26+
27+
28+
def create_client(app, streaming: bool = False) -> RemoteA2aAgent:
29+
"""Creates a RemoteA2aAgent connected to the provided FastAPI app.
30+
31+
Args:
32+
app: The FastAPI application (server) to connect to.
33+
streaming: Whether to enable streaming mode in the client.
34+
35+
Returns:
36+
A RemoteA2aAgent instance.
37+
"""
38+
39+
client = httpx.AsyncClient(
40+
transport=httpx.ASGITransport(app=app), base_url="http://test"
41+
)
42+
43+
client_config = A2AClientConfig(
44+
httpx_client=client,
45+
streaming=streaming,
46+
polling=False,
47+
supported_transports=[A2ATransport.jsonrpc],
48+
)
49+
factory = A2AClientFactory(config=client_config)
50+
51+
# use_legacy=False forces the new implementation
52+
agent = RemoteA2aAgent(
53+
name="remote_agent",
54+
agent_card=agent_card,
55+
a2a_client_factory=factory,
56+
use_legacy=False,
57+
)
58+
59+
return agent
60+
61+
62+
def create_a2a_client(app, streaming: bool = False):
63+
"""Creates a bare A2A Client connected to the provided FastAPI app.
64+
65+
This is in contrast to create_client, which wraps the a2a_client into a
66+
RemoteA2aAgent for the standard runner framework ecosystem execution.
67+
68+
Args:
69+
app: The FastAPI application (server) to connect to.
70+
streaming: Whether to enable streaming mode in the client.
71+
72+
Returns:
73+
An A2A Client instance.
74+
"""
75+
client = httpx.AsyncClient(
76+
transport=httpx.ASGITransport(app=app),
77+
base_url="http://test",
78+
headers={HTTP_EXTENSION_HEADER: _NEW_A2A_ADK_INTEGRATION_EXTENSION},
79+
)
80+
81+
client_config = A2AClientConfig(
82+
httpx_client=client,
83+
streaming=streaming,
84+
polling=False,
85+
supported_transports=[A2ATransport.jsonrpc],
86+
)
87+
factory = A2AClientFactory(config=client_config)
88+
return factory.create(agent_card)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A2A Server for integration tests."""
16+
17+
from unittest.mock import Mock
18+
19+
from a2a.server.apps.jsonrpc.fastapi_app import A2AFastAPIApplication
20+
from a2a.server.request_handlers.default_request_handler import DefaultRequestHandler
21+
from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore
22+
from a2a.types import AgentCapabilities
23+
from a2a.types import AgentCard
24+
from a2a.types import AgentSkill
25+
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
26+
from google.adk.agents.base_agent import BaseAgent
27+
from google.adk.runners import Runner
28+
from google.adk.sessions.in_memory_session_service import InMemorySessionService
29+
30+
31+
class FakeRunner(Runner):
32+
"""A Fake Runner that delegates run_async to a provided function."""
33+
34+
def __init__(self, run_async_fn):
35+
agent = Mock(spec=BaseAgent)
36+
agent.name = "FakeAgent"
37+
38+
session_service = InMemorySessionService()
39+
super().__init__(
40+
app_name="FakeApp",
41+
agent=agent,
42+
session_service=session_service,
43+
)
44+
self.run_async_fn = run_async_fn
45+
46+
async def run_async(self, **kwargs):
47+
async for event in self.run_async_fn(**kwargs):
48+
yield event
49+
50+
51+
agent_card = AgentCard(
52+
name="remote_agent",
53+
url="http://test",
54+
description="A fun fact generator agent",
55+
capabilities=AgentCapabilities(
56+
streaming=True,
57+
extensions=[{"uri": "https://a2a-adk/a2a-extension/new-integration"}],
58+
),
59+
version="0.0.1",
60+
default_input_modes=["text/plain"],
61+
default_output_modes=["text/plain"],
62+
skills=[],
63+
)
64+
65+
66+
def create_server_app(run_async_fn):
67+
"""Creates an A2A FastAPI application with a mocked runner.
68+
69+
Args:
70+
run_async_fn: A generator function that takes **kwargs and yields Event
71+
objects.
72+
73+
Returns:
74+
A FastAPI application instance.
75+
"""
76+
runner = FakeRunner(run_async_fn)
77+
executor = A2aAgentExecutor(runner=runner)
78+
task_store = InMemoryTaskStore()
79+
handler = DefaultRequestHandler(
80+
agent_executor=executor, task_store=task_store
81+
)
82+
83+
app = A2AFastAPIApplication(agent_card=agent_card, http_handler=handler)
84+
return app.build()

0 commit comments

Comments
 (0)