Skip to content

Commit 1f75ef7

Browse files
committed
test(client): add unit tests for A2ACardResolver
1 parent 5b54ce6 commit 1f75ef7

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

tests/client/test_card_resolver.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""Tests for the A2ACardResolver."""
2+
3+
import json
4+
from unittest.mock import AsyncMock, MagicMock, patch
5+
6+
import httpx
7+
import pytest
8+
9+
from a2a.client.card_resolver import A2ACardResolver
10+
from a2a.client.errors import A2AClientHTTPError, A2AClientJSONError
11+
12+
from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
13+
14+
15+
@pytest.fixture
16+
def mock_httpx_client() -> AsyncMock:
17+
"""Provides a mock httpx.AsyncClient."""
18+
return AsyncMock(spec=httpx.AsyncClient)
19+
20+
21+
@pytest.fixture
22+
def base_agent_card_data() -> dict:
23+
"""Provides base valid agent card data."""
24+
return {
25+
'name': 'Test Agent',
26+
'description': 'An agent for testing.',
27+
'url': 'http://example.com',
28+
'version': '1.0.0',
29+
'capabilities': {},
30+
'skills': [],
31+
'default_input_modes': [],
32+
'default_output_modes': [],
33+
'preferred_transport': 'jsonrpc',
34+
}
35+
36+
37+
@pytest.mark.asyncio
38+
@pytest.mark.parametrize(
39+
'relative_path, expected_path_segment',
40+
[
41+
(None, AGENT_CARD_WELL_KNOWN_PATH),
42+
('/custom/card', '/custom/card'),
43+
('', AGENT_CARD_WELL_KNOWN_PATH),
44+
],
45+
)
46+
async def test_get_agent_card_success(
47+
mock_httpx_client: AsyncMock,
48+
base_agent_card_data: dict,
49+
relative_path: str | None,
50+
expected_path_segment: str,
51+
):
52+
"""Test successful agent card retrieval using default and relative paths."""
53+
base_url = 'http://example.com'
54+
resolver = A2ACardResolver(mock_httpx_client, base_url)
55+
56+
mock_response = MagicMock(spec=httpx.Response)
57+
mock_response.json.return_value = base_agent_card_data
58+
mock_httpx_client.get.return_value = mock_response
59+
60+
agent_card = await resolver.get_agent_card(relative_card_path=relative_path)
61+
62+
expected_url = f'{base_url}{expected_path_segment}'
63+
mock_httpx_client.get.assert_awaited_once_with(expected_url)
64+
mock_response.raise_for_status.assert_called_once()
65+
assert agent_card.name == base_agent_card_data['name']
66+
assert agent_card.url == base_agent_card_data['url']
67+
68+
69+
@pytest.mark.asyncio
70+
async def test_get_agent_card_http_error(mock_httpx_client: AsyncMock):
71+
"""Test handling of HTTP errors during agent card retrieval."""
72+
base_url = 'http://example.com'
73+
resolver = A2ACardResolver(mock_httpx_client, base_url)
74+
75+
mock_response = MagicMock(spec=httpx.Response)
76+
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
77+
'Not Found', request=MagicMock(), response=mock_response
78+
)
79+
mock_response.status_code = 404
80+
mock_httpx_client.get.return_value = mock_response
81+
82+
with pytest.raises(A2AClientHTTPError) as excinfo:
83+
await resolver.get_agent_card()
84+
assert excinfo.value.status_code == 404
85+
86+
87+
@pytest.mark.asyncio
88+
async def test_get_agent_card_json_decode_error(mock_httpx_client: AsyncMock):
89+
"""Test handling of JSON decoding errors."""
90+
base_url = 'http://example.com'
91+
resolver = A2ACardResolver(mock_httpx_client, base_url)
92+
93+
mock_response = MagicMock(spec=httpx.Response)
94+
mock_response.json.side_effect = json.JSONDecodeError('msg', 'doc', 0)
95+
mock_httpx_client.get.return_value = mock_response
96+
97+
with pytest.raises(A2AClientJSONError, match='Failed to parse JSON'):
98+
await resolver.get_agent_card()
99+
100+
101+
@pytest.mark.asyncio
102+
async def test_get_agent_card_network_error(mock_httpx_client: AsyncMock):
103+
"""Test handling of network communication errors."""
104+
base_url = 'http://example.com'
105+
resolver = A2ACardResolver(mock_httpx_client, base_url)
106+
107+
mock_httpx_client.get.side_effect = httpx.RequestError('Network error')
108+
109+
with pytest.raises(A2AClientHTTPError, match='Network communication error'):
110+
await resolver.get_agent_card()
111+
112+
113+
@pytest.mark.asyncio
114+
async def test_get_agent_card_validation_error(
115+
mock_httpx_client: AsyncMock, base_agent_card_data: dict
116+
):
117+
"""Test handling of Pydantic validation errors."""
118+
base_url = 'http://example.com'
119+
resolver = A2ACardResolver(mock_httpx_client, base_url)
120+
121+
invalid_card_data = base_agent_card_data.copy()
122+
del invalid_card_data['name'] # Make it invalid
123+
124+
mock_response = MagicMock(spec=httpx.Response)
125+
mock_response.json.return_value = invalid_card_data
126+
mock_httpx_client.get.return_value = mock_response
127+
128+
with pytest.raises(
129+
A2AClientJSONError, match='Failed to validate agent card structure'
130+
):
131+
await resolver.get_agent_card()
132+
133+
134+
@pytest.mark.asyncio
135+
async def test_get_agent_card_with_http_kwargs(
136+
mock_httpx_client: AsyncMock, base_agent_card_data: dict
137+
):
138+
"""Test that http_kwargs are passed to the httpx client."""
139+
base_url = 'http://example.com'
140+
resolver = A2ACardResolver(mock_httpx_client, base_url)
141+
http_kwargs = {'headers': {'X-Test': 'true'}}
142+
143+
mock_response = MagicMock(spec=httpx.Response)
144+
mock_response.json.return_value = base_agent_card_data
145+
mock_httpx_client.get.return_value = mock_response
146+
147+
await resolver.get_agent_card(http_kwargs=http_kwargs)
148+
149+
expected_url = f'{base_url}{AGENT_CARD_WELL_KNOWN_PATH}'
150+
mock_httpx_client.get.assert_awaited_once_with(
151+
expected_url, headers={'X-Test': 'true'}
152+
)
153+
154+
155+
@pytest.mark.asyncio
156+
async def test_get_agent_card_with_signature_verifier(
157+
mock_httpx_client: AsyncMock, base_agent_card_data: dict
158+
):
159+
"""Test that the signature verifier is called if provided."""
160+
base_url = 'http://example.com'
161+
resolver = A2ACardResolver(mock_httpx_client, base_url)
162+
mock_verifier = MagicMock()
163+
164+
mock_response = MagicMock(spec=httpx.Response)
165+
mock_response.json.return_value = base_agent_card_data
166+
mock_httpx_client.get.return_value = mock_response
167+
168+
agent_card = await resolver.get_agent_card(signature_verifier=mock_verifier)
169+
170+
mock_verifier.assert_called_once_with(agent_card)

0 commit comments

Comments
 (0)