Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/google/adk/agents/remote_a2a_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,10 +990,13 @@ async def _ensure_resolved(
if not self._agent_card:

# Resolve agent card if needed
self._agent_card = await self._resolve_agent_card(ctx)
resolved_agent_card = await self._resolve_agent_card(ctx)

# Validate agent card
await self._validate_agent_card(self._agent_card)
# Validate agent card before caching it. If validation fails, the
# card must not be cached, or a subsequent invocation would skip
# resolution and validation entirely and reuse the invalid card.
await self._validate_agent_card(resolved_agent_card)
self._agent_card = resolved_agent_card

# Update description if empty
if not self.description and self._agent_card.description:
Expand Down
41 changes: 41 additions & 0 deletions tests/unittests/agents/test_remote_a2a_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,47 @@ async def test_ensure_resolved_caches_card_without_interceptor(self):

assert mock_resolve.await_count == 1

@pytest.mark.asyncio
async def test_ensure_resolved_does_not_cache_card_on_validation_failure(
self,
):
"""A card that fails validation must not be cached.

Otherwise a later invocation sees `self._agent_card` already set, skips
resolution and validation entirely, and reuses the invalid card.
"""
agent = RemoteA2aAgent(
name="test_agent",
agent_card="https://example.com/agent.json",
)

with patch.object(
agent, "_resolve_agent_card", new_callable=AsyncMock
) as mock_resolve:
mock_resolve.return_value = self.agent_card
with patch.object(
agent, "_validate_agent_card", new_callable=AsyncMock
) as mock_validate:
mock_validate.side_effect = ValueError("invalid rpc url")

with pytest.raises(AgentCardResolutionError):
await agent._ensure_resolved(Mock())

assert agent._agent_card is None

mock_validate.side_effect = None
with patch.object(agent, "_ensure_httpx_client") as mock_ensure:
mock_ensure.return_value = AsyncMock()
mock_factory = Mock()
mock_factory.create.return_value = Mock()
agent._a2a_client_factory = mock_factory

await agent._ensure_resolved(Mock())

assert mock_resolve.await_count == 2
assert mock_validate.await_count == 2
assert agent._agent_card is self.agent_card

@pytest.mark.asyncio
async def test_ensure_resolved_without_ctx_uses_cached_path(self):
"""_ensure_resolved() is callable with no ctx (backward compatible)."""
Expand Down