1
1
import json
2
+
2
3
from collections .abc import AsyncGenerator
3
4
from typing import Any
4
5
from unittest .mock import AsyncMock , MagicMock , patch
5
6
6
7
import httpx
7
8
import pytest
9
+
8
10
from httpx_sse import EventSource , ServerSentEvent
9
- from pydantic import ValidationError as PydanticValidationError
10
-
11
- from a2a .client import (A2ACardResolver , A2AClient , A2AClientHTTPError ,
12
- A2AClientJSONError , create_text_message_object )
13
- from a2a .types import (A2ARequest , AgentCapabilities , AgentCard , AgentSkill ,
14
- CancelTaskRequest , CancelTaskResponse ,
15
- CancelTaskSuccessResponse , GetTaskRequest ,
16
- GetTaskResponse , InvalidParamsError ,
17
- JSONRPCErrorResponse , MessageSendParams , Role ,
18
- SendMessageRequest , SendMessageResponse ,
19
- SendMessageSuccessResponse , SendStreamingMessageRequest ,
20
- SendStreamingMessageResponse , TaskIdParams ,
21
- TaskNotCancelableError , TaskQueryParams )
11
+
12
+ from a2a .client import (
13
+ A2ACardResolver ,
14
+ A2AClient ,
15
+ A2AClientHTTPError ,
16
+ A2AClientJSONError ,
17
+ create_text_message_object ,
18
+ )
19
+ from a2a .types import (
20
+ A2ARequest ,
21
+ AgentCapabilities ,
22
+ AgentCard ,
23
+ AgentSkill ,
24
+ CancelTaskRequest ,
25
+ CancelTaskResponse ,
26
+ CancelTaskSuccessResponse ,
27
+ GetTaskRequest ,
28
+ GetTaskResponse ,
29
+ InvalidParamsError ,
30
+ JSONRPCErrorResponse ,
31
+ MessageSendParams ,
32
+ Role ,
33
+ SendMessageRequest ,
34
+ SendMessageResponse ,
35
+ SendMessageSuccessResponse ,
36
+ SendStreamingMessageRequest ,
37
+ SendStreamingMessageResponse ,
38
+ TaskIdParams ,
39
+ TaskNotCancelableError ,
40
+ TaskQueryParams ,
41
+ )
42
+
22
43
23
44
AGENT_CARD = AgentCard (
24
45
name = 'Hello World Agent' ,
@@ -100,7 +121,9 @@ class TestA2ACardResolver:
100
121
BASE_URL = 'http://example.com'
101
122
AGENT_CARD_PATH = '/.well-known/agent.json'
102
123
FULL_AGENT_CARD_URL = f'{ BASE_URL } { AGENT_CARD_PATH } '
103
- EXTENDED_AGENT_CARD_PATH = '/agent/authenticatedExtendedCard' # Default path
124
+ EXTENDED_AGENT_CARD_PATH = (
125
+ '/agent/authenticatedExtendedCard' # Default path
126
+ )
104
127
105
128
@pytest .mark .asyncio
106
129
async def test_init_strips_slashes (self , mock_httpx_client : AsyncMock ):
@@ -141,11 +164,12 @@ async def test_get_agent_card_success_public_only(
141
164
142
165
@pytest .mark .asyncio
143
166
async def test_get_agent_card_success_with_specified_path_for_extended_card (
144
- self , mock_httpx_client : AsyncMock ):
167
+ self , mock_httpx_client : AsyncMock
168
+ ):
145
169
extended_card_response = AsyncMock (spec = httpx .Response )
146
170
extended_card_response .status_code = 200
147
- extended_card_response .json .return_value = AGENT_CARD_EXTENDED . model_dump (
148
- mode = 'json'
171
+ extended_card_response .json .return_value = (
172
+ AGENT_CARD_EXTENDED . model_dump ( mode = 'json' )
149
173
)
150
174
151
175
# Mock the single call for the extended card
@@ -156,20 +180,26 @@ async def test_get_agent_card_success_with_specified_path_for_extended_card(
156
180
base_url = self .BASE_URL ,
157
181
agent_card_path = self .AGENT_CARD_PATH ,
158
182
)
159
-
183
+
160
184
# Fetch the extended card by providing its relative path and example auth
161
- auth_kwargs = {" headers" : {" Authorization" : " Bearer test token" }}
185
+ auth_kwargs = {' headers' : {' Authorization' : ' Bearer test token' }}
162
186
agent_card_result = await resolver .get_agent_card (
163
187
relative_card_path = self .EXTENDED_AGENT_CARD_PATH ,
164
- http_kwargs = auth_kwargs
188
+ http_kwargs = auth_kwargs ,
165
189
)
166
190
167
- expected_extended_url = f'{ self .BASE_URL } /{ self .EXTENDED_AGENT_CARD_PATH .lstrip ("/" )} '
168
- mock_httpx_client .get .assert_called_once_with (expected_extended_url , ** auth_kwargs )
191
+ expected_extended_url = (
192
+ f'{ self .BASE_URL } /{ self .EXTENDED_AGENT_CARD_PATH .lstrip ("/" )} '
193
+ )
194
+ mock_httpx_client .get .assert_called_once_with (
195
+ expected_extended_url , ** auth_kwargs
196
+ )
169
197
extended_card_response .raise_for_status .assert_called_once ()
170
198
171
199
assert isinstance (agent_card_result , AgentCard )
172
- assert agent_card_result == AGENT_CARD_EXTENDED # Should return the extended card
200
+ assert (
201
+ agent_card_result == AGENT_CARD_EXTENDED
202
+ ) # Should return the extended card
173
203
174
204
@pytest .mark .asyncio
175
205
async def test_get_agent_card_validation_error (
@@ -178,19 +208,29 @@ async def test_get_agent_card_validation_error(
178
208
mock_response = AsyncMock (spec = httpx .Response )
179
209
mock_response .status_code = 200
180
210
# Data that will cause a Pydantic ValidationError
181
- mock_response .json .return_value = {"invalid_field" : "value" , "name" : "Test Agent" }
211
+ mock_response .json .return_value = {
212
+ 'invalid_field' : 'value' ,
213
+ 'name' : 'Test Agent' ,
214
+ }
182
215
mock_httpx_client .get .return_value = mock_response
183
216
184
217
resolver = A2ACardResolver (
185
218
httpx_client = mock_httpx_client , base_url = self .BASE_URL
186
219
)
187
220
# The call that is expected to raise an error should be within pytest.raises
188
221
with pytest .raises (A2AClientJSONError ) as exc_info :
189
- await resolver .get_agent_card () # Fetches from default path
190
-
191
- assert f'Failed to validate agent card structure from { self .FULL_AGENT_CARD_URL } ' in str (exc_info .value )
192
- assert 'invalid_field' in str (exc_info .value ) # Check if Pydantic error details are present
193
- assert mock_httpx_client .get .call_count == 1 # Should only be called once
222
+ await resolver .get_agent_card () # Fetches from default path
223
+
224
+ assert (
225
+ f'Failed to validate agent card structure from { self .FULL_AGENT_CARD_URL } '
226
+ in str (exc_info .value )
227
+ )
228
+ assert 'invalid_field' in str (
229
+ exc_info .value
230
+ ) # Check if Pydantic error details are present
231
+ assert (
232
+ mock_httpx_client .get .call_count == 1
233
+ ) # Should only be called once
194
234
195
235
@pytest .mark .asyncio
196
236
async def test_get_agent_card_http_status_error (
@@ -217,7 +257,10 @@ async def test_get_agent_card_http_status_error(
217
257
await resolver .get_agent_card ()
218
258
219
259
assert exc_info .value .status_code == 404
220
- assert f'Failed to fetch agent card from { self .FULL_AGENT_CARD_URL } ' in str (exc_info .value )
260
+ assert (
261
+ f'Failed to fetch agent card from { self .FULL_AGENT_CARD_URL } '
262
+ in str (exc_info .value )
263
+ )
221
264
assert 'Not Found' in str (exc_info .value )
222
265
mock_httpx_client .get .assert_called_once_with (self .FULL_AGENT_CARD_URL )
223
266
@@ -242,7 +285,10 @@ async def test_get_agent_card_json_decode_error(
242
285
await resolver .get_agent_card ()
243
286
244
287
# Assertions using exc_info must be after the with block
245
- assert f'Failed to parse JSON for agent card from { self .FULL_AGENT_CARD_URL } ' in str (exc_info .value )
288
+ assert (
289
+ f'Failed to parse JSON for agent card from { self .FULL_AGENT_CARD_URL } '
290
+ in str (exc_info .value )
291
+ )
246
292
assert 'Expecting value' in str (exc_info .value )
247
293
mock_httpx_client .get .assert_called_once_with (self .FULL_AGENT_CARD_URL )
248
294
@@ -263,7 +309,10 @@ async def test_get_agent_card_request_error(
263
309
await resolver .get_agent_card ()
264
310
265
311
assert exc_info .value .status_code == 503
266
- assert f'Network communication error fetching agent card from { self .FULL_AGENT_CARD_URL } ' in str (exc_info .value )
312
+ assert (
313
+ f'Network communication error fetching agent card from { self .FULL_AGENT_CARD_URL } '
314
+ in str (exc_info .value )
315
+ )
267
316
assert 'Network issue' in str (exc_info .value )
268
317
mock_httpx_client .get .assert_called_once_with (self .FULL_AGENT_CARD_URL )
269
318
0 commit comments