Skip to content

Commit aa63b98

Browse files
authored
fix: Resolve APIKeySecurityScheme parsing failed (#226)
Fixes #220 - Adds `by_alias=True` parameter when using `model_dump()` for `AgentCard` and `ExtendedAgentCard`
1 parent 2d851ad commit aa63b98

File tree

3 files changed

+103
-3
lines changed

3 files changed

+103
-3
lines changed

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ testpaths = ["tests"]
5050
python_files = "test_*.py"
5151
python_functions = "test_*"
5252
addopts = "-ra --strict-markers"
53-
asyncio_mode = "strict"
5453
markers = [
5554
"asyncio: mark a test as a coroutine that should be run by pytest-asyncio",
5655
]
5756

57+
[tool.pytest-asyncio]
58+
mode = "strict"
59+
5860
[build-system]
5961
requires = ["hatchling", "uv-dynamic-versioning"]
6062
build-backend = "hatchling.build"

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,10 @@ async def _handle_get_agent_card(self, request: Request) -> JSONResponse:
375375
# The public agent card is a direct serialization of the agent_card
376376
# provided at initialization.
377377
return JSONResponse(
378-
self.agent_card.model_dump(mode='json', exclude_none=True)
378+
self.agent_card.model_dump(
379+
exclude_none=True,
380+
by_alias=True,
381+
)
379382
)
380383

381384
async def _handle_get_authenticated_extended_agent_card(
@@ -392,7 +395,8 @@ async def _handle_get_authenticated_extended_agent_card(
392395
if self.extended_agent_card:
393396
return JSONResponse(
394397
self.extended_agent_card.model_dump(
395-
mode='json', exclude_none=True
398+
exclude_none=True,
399+
by_alias=True,
396400
)
397401
)
398402
# If supportsAuthenticatedExtendedCard is true, but no specific
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from unittest import mock
2+
3+
import pytest
4+
from starlette.testclient import TestClient
5+
6+
from a2a.server.apps import A2AFastAPIApplication, A2AStarletteApplication
7+
from a2a.types import (
8+
APIKeySecurityScheme,
9+
AgentCapabilities,
10+
AgentCard,
11+
In,
12+
SecurityScheme,
13+
)
14+
from pydantic import ValidationError
15+
16+
17+
@pytest.fixture
18+
def agent_card_with_api_key():
19+
"""Provides an AgentCard with an APIKeySecurityScheme for testing serialization."""
20+
# This data uses the alias 'in', which is correct for creating the model.
21+
api_key_scheme_data = {
22+
'type': 'apiKey',
23+
'name': 'X-API-KEY',
24+
'in': 'header',
25+
}
26+
api_key_scheme = APIKeySecurityScheme.model_validate(api_key_scheme_data)
27+
28+
agent_card = AgentCard(
29+
name='APIKeyAgent',
30+
description='An agent that uses API Key auth.',
31+
url='http://example.com/apikey-agent',
32+
version='1.0.0',
33+
capabilities=AgentCapabilities(),
34+
defaultInputModes=['text/plain'],
35+
defaultOutputModes=['text/plain'],
36+
skills=[],
37+
securitySchemes={'api_key_auth': SecurityScheme(root=api_key_scheme)},
38+
security=[{'api_key_auth': []}],
39+
)
40+
return agent_card
41+
42+
43+
def test_starlette_agent_card_with_api_key_scheme_alias(
44+
agent_card_with_api_key: AgentCard,
45+
):
46+
"""
47+
Tests that the A2AStarletteApplication endpoint correctly serializes aliased fields.
48+
49+
This verifies the fix for `APIKeySecurityScheme.in_` being serialized as `in_` instead of `in`.
50+
"""
51+
handler = mock.AsyncMock()
52+
app_instance = A2AStarletteApplication(agent_card_with_api_key, handler)
53+
client = TestClient(app_instance.build())
54+
55+
response = client.get('/.well-known/agent.json')
56+
assert response.status_code == 200
57+
response_data = response.json()
58+
59+
security_scheme_json = response_data['securitySchemes']['api_key_auth']
60+
assert 'in' in security_scheme_json
61+
assert security_scheme_json['in'] == 'header'
62+
assert 'in_' not in security_scheme_json
63+
64+
try:
65+
parsed_card = AgentCard.model_validate(response_data)
66+
parsed_scheme_wrapper = parsed_card.securitySchemes['api_key_auth']
67+
assert isinstance(parsed_scheme_wrapper.root, APIKeySecurityScheme)
68+
assert parsed_scheme_wrapper.root.in_ == In.header
69+
except ValidationError as e:
70+
pytest.fail(
71+
f"AgentCard.model_validate failed on the server's response: {e}"
72+
)
73+
74+
75+
def test_fastapi_agent_card_with_api_key_scheme_alias(
76+
agent_card_with_api_key: AgentCard,
77+
):
78+
"""
79+
Tests that the A2AFastAPIApplication endpoint correctly serializes aliased fields.
80+
81+
This verifies the fix for `APIKeySecurityScheme.in_` being serialized as `in_` instead of `in`.
82+
"""
83+
handler = mock.AsyncMock()
84+
app_instance = A2AFastAPIApplication(agent_card_with_api_key, handler)
85+
client = TestClient(app_instance.build())
86+
87+
response = client.get('/.well-known/agent.json')
88+
assert response.status_code == 200
89+
response_data = response.json()
90+
91+
security_scheme_json = response_data['securitySchemes']['api_key_auth']
92+
assert 'in' in security_scheme_json
93+
assert 'in_' not in security_scheme_json
94+
assert security_scheme_json['in'] == 'header'

0 commit comments

Comments
 (0)