Skip to content
Draft
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
92 changes: 92 additions & 0 deletions tests/test_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,98 @@ async def test_task_id_cleared_on_unknown_state(self, a2a_config):

assert adapter.active_task_id is None

@pytest.mark.asyncio
async def test_rejected_task_result_content(self, a2a_config):
"""TASK_STATE_REJECTED — adapter returns SUBMITTED status with 'rejected'
in metadata and the TextPart message. REJECTED is terminal (task_id is
cleared) but routes through the non-COMPLETED else-branch in
_process_task_response, so data=None."""
adapter = A2AAdapter(a2a_config)

rejected = create_mock_a2a_task(
task_id="task-rejected-content",
context_id="ctx-rej",
state="rejected",
parts=[TextPart(text="policy violation: brand safety")],
)
mock_a2a_client = AsyncMock()
mock_a2a_client.send_message = AsyncMock(
return_value=SendMessageSuccessResponse(result=rejected)
)

with patch.object(adapter, "_get_a2a_client", return_value=mock_a2a_client):
result = await adapter._call_a2a_tool("create_media_buy", {})

assert result.status == TaskStatus.SUBMITTED
assert result.data is None
assert result.message == "policy violation: brand safety"
assert result.metadata is not None
assert result.metadata["status"] == "rejected"

@pytest.mark.asyncio
async def test_rejected_task_adcp_error_datapart_not_extracted(self, a2a_config):
"""TASK_STATE_REJECTED with a DataPart carrying adcp_error — the
DataPart is silently dropped because _process_task_response only
calls _extract_result_from_task for COMPLETED tasks. This test
documents the current gap: callers cannot read structured error
detail from a rejected task's artifact without a separate fix."""
adapter = A2AAdapter(a2a_config)

rejected = create_mock_a2a_task(
task_id="task-rejected-err",
context_id="ctx-rej-err",
state="rejected",
parts=[
DataPart(data={"adcp_error": {"code": "POLICY_VIOLATION", "message": "rejected"}}),
TextPart(text="rejected by server"),
],
)
mock_a2a_client = AsyncMock()
mock_a2a_client.send_message = AsyncMock(
return_value=SendMessageSuccessResponse(result=rejected)
)

with patch.object(adapter, "_get_a2a_client", return_value=mock_a2a_client):
result = await adapter._call_a2a_tool("create_media_buy", {})

assert result.status == TaskStatus.SUBMITTED
# Gap: adcp_error DataPart is not extracted for non-COMPLETED states.
# A future fix should surface structured error detail here.
assert result.data is None
assert result.message == "rejected by server"
assert result.metadata is not None
assert result.metadata["status"] == "rejected"

@pytest.mark.asyncio
async def test_auth_required_task_result_content(self, a2a_config):
"""TASK_STATE_AUTH_REQUIRED — non-terminal state. Adapter returns
SUBMITTED status with 'auth-required' in metadata and the challenge
message from the TextPart. Callers should surface this to trigger
an auth flow before re-submitting."""
adapter = A2AAdapter(a2a_config)

auth_task = create_mock_a2a_task(
task_id="task-auth-content",
context_id="ctx-auth",
state="auth-required",
parts=[TextPart(text="OAuth required: redirect to https://auth.example.com")],
)
mock_a2a_client = AsyncMock()
mock_a2a_client.send_message = AsyncMock(
return_value=SendMessageSuccessResponse(result=auth_task)
)

with patch.object(adapter, "_get_a2a_client", return_value=mock_a2a_client):
result = await adapter._call_a2a_tool("create_media_buy", {})

assert result.status == TaskStatus.SUBMITTED
assert result.data is None
assert result.message == "OAuth required: redirect to https://auth.example.com"
assert result.metadata is not None
assert result.metadata["status"] == "auth-required"
assert result.metadata["task_id"] == "task-auth-content"
assert result.metadata["context_id"] == "ctx-auth"

@pytest.mark.asyncio
async def test_state_not_committed_when_post_processing_raises(self, a2a_config):
"""If _process_task_response raises, the adapter must NOT advance
Expand Down
Loading