Skip to content
Open
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
123 changes: 123 additions & 0 deletions .roost/runreport.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"run_id": "23475726",
"started_at": "2026-04-29T05:28:56.971093Z",
"ended_at": "2026-04-29T05:31:53.965484Z",
"status": "ok",
"config": {
"repo": "/private/var/tmp/Roost/RoostGPT/unit-adk-python/1777440475/source/adk-python",
"out": "/private/var/tmp/Roost/RoostGPT/unit-adk-python/1777440475/source/adk-python",
"workers": 5,
"scenarios_from": "planner",
"provider": "env:AI_TYPE",
"model": "gemini/gemini-3-flash-preview",
"jedi": true
},
"summary": {
"targets_total": 1,
"targets_passed": 1,
"targets_failed": 0,
"targets_partial": 0,
"targets_skipped": 0,
"scenarios_total": 6,
"scenarios_passed": 6,
"scenarios_failed": 0,
"coverage_weighted": null,
"llm_cost_usd": 0.1788,
"llm_input_tokens": 740513,
"llm_output_tokens": 17984,
"llm_cache_read_input_tokens": 545423,
"llm_cache_creation_input_tokens": 0,
"llm_api_calls": 26,
"elapsed_ms": 0
},
"targets": [
{
"target_symbol_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event",
"symbol_kind": "function",
"source_file": "src/google/adk/a2a/converters/event_converter.py",
"source_range": [
201,
265
],
"test_file": "/private/var/tmp/Roost/RoostGPT/unit-adk-python/1777440475/source/adk-python/adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py",
"test_file_hash": "sha256:fccdd611f3cc1af8ad4d5246a61324549a852d102931ad92ab5b903c383a0a12",
"test_status": "passed",
"iterations": 26,
"pivots": 0,
"scenarios": [
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#0",
"scenario_name": "happy_path_extract_from_artifacts",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
},
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#1",
"scenario_name": "happy_path_extract_from_history",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
},
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#2",
"scenario_name": "happy_path_no_message_minimal_event",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
},
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#3",
"scenario_name": "edge_case_with_context",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
},
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#4",
"scenario_name": "error_path_none_input",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
},
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#5",
"scenario_name": "error_path_conversion_exception",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
}
],
"coverage_pct": null,
"writer_cost_usd": 0.17876814999999996,
"writer_input_tokens": 740513,
"writer_output_tokens": 17984,
"writer_cache_read_input_tokens": 545423,
"writer_cache_creation_input_tokens": 0,
"diagnostics": []
}
],
"files_written": [
{
"path": "adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py",
"hash": "sha256:fccdd611f3cc1af8ad4d5246a61324549a852d102931ad92ab5b903c383a0a12",
"status": "created"
}
],
"pr_ready": {
"branch_suggestion": "roost/python-tests-20260429-0531",
"commit_message": "roost-pytest: add 1 test file(s)",
"pr_title": "Auto-generated Python unit tests (1 file(s))",
"pr_body_markdown": "## Summary\n- 1 targets with fully green tests\n\n## Generated test files\n- `adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py`",
"changed_files": [
"adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py"
]
},
"diagnostics": []
}
Empty file added __init__.py
Empty file.
Empty file added adk/__init__.py
Empty file.
Empty file added adk/a2a/__init__.py
Empty file.
Empty file added adk/a2a/converters/__init__.py
Empty file.
Empty file.
123 changes: 123 additions & 0 deletions adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ROOST_METHOD_HASH=convert_a2a_task_to_event_40cb3ce258
# ROOST_METHOD_SIG_HASH=convert_a2a_task_to_event_124ed678ea

"""Tests for `src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event`.

Auto-generated by roost-pytest. Re-generate when the source
changes; see the run report for generation details.
"""
# Source: src/google/adk/a2a/converters/event_converter.py:201-265
# Scenarios:
# - [happy_path] happy_path_extract_from_artifacts: Verify that the function extracts the message from the last artifact when artifacts are present.
# - [happy_path] happy_path_extract_from_history: Verify that the function falls back to history when artifacts and status message are missing.
# - [happy_path] happy_path_no_message_minimal_event: Verify that a minimal Event is returned when no message content can be found in the task.
# - [edge_case] edge_case_with_context: Verify that invocation_id and branch are correctly propagated from the InvocationContext.
# - [error_path] error_path_none_input: Verify that a ValueError is raised if the input task is None.
# - [error_path] error_path_conversion_exception: Verify that a RuntimeError is raised if the underlying message conversion fails.

import pytest
from unittest.mock import MagicMock, patch
from a2a.types import Task, Message, Role, TextPart, Part as A2APart
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
from google.adk.a2a.converters.part_converter import convert_a2a_part_to_genai_part

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_extract_from_artifacts():
"""Verify that the function extracts the message from the last artifact when artifacts are present."""
mock_task = MagicMock(spec=Task)
mock_artifact = MagicMock()
# Use TextPart here; Message will wrap it in A2APart
mock_artifact.parts = [TextPart(text='part1')]
mock_task.artifacts = [mock_artifact]
mock_task.status = None
mock_task.history = []

with patch('google.adk.a2a.converters.event_converter.convert_a2a_message_to_event') as mock_conv:
mock_conv.return_value = Event(invocation_id='123', author='a2a agent')
result = convert_a2a_task_to_event(mock_task)

mock_conv.assert_called_once()
call_args = mock_conv.call_args[0]
assert isinstance(call_args[0], Message)
# Message.parts will contain A2APart objects wrapping the TextPart
assert call_args[0].parts == [A2APart(root=TextPart(text='part1'))]
assert call_args[0].role == Role.agent
assert result == mock_conv.return_value

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_extract_from_history():
"""Verify that the function falls back to history when artifacts and status message are missing."""
mock_task = MagicMock(spec=Task)
mock_task.artifacts = []
mock_task.status = None
hist_message = Message(message_id='m1', role=Role.agent, parts=[TextPart(text='hist_part')])
mock_task.history = [hist_message]

with patch('google.adk.a2a.converters.event_converter.convert_a2a_message_to_event') as mock_conv:
mock_conv.return_value = Event(invocation_id='123', author='a2a agent')
result = convert_a2a_task_to_event(mock_task)

mock_conv.assert_called_once_with(
hist_message, None, None, part_converter=convert_a2a_part_to_genai_part
)
assert result == mock_conv.return_value

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_no_message_minimal_event():
"""Verify that a minimal Event is returned when no message content can be found in the task."""
mock_task = MagicMock(spec=Task)
mock_task.artifacts = []
mock_task.status = None
mock_task.history = []

result = convert_a2a_task_to_event(mock_task, author='custom-author')

assert isinstance(result, Event)
assert result.author == 'custom-author'
assert result.content is None
assert len(result.invocation_id) > 0

@pytest.mark.generated
@pytest.mark.edge_case
def test_edge_case_with_context():
"""Verify that invocation_id and branch are correctly propagated from the InvocationContext."""
mock_task = MagicMock(spec=Task)
mock_task.artifacts = []
mock_task.status = None
mock_task.history = []
ctx = MagicMock(spec=InvocationContext)
ctx.invocation_id = 'inv-456'
ctx.branch = 'main.sub'

result = convert_a2a_task_to_event(mock_task, invocation_context=ctx)

assert result.invocation_id == 'inv-456'
assert result.branch == 'main.sub'

@pytest.mark.generated
@pytest.mark.error_path
def test_error_path_none_input():
"""Verify that a ValueError is raised if the input task is None."""
with pytest.raises(ValueError, match="A2A task cannot be None"):
convert_a2a_task_to_event(None)

@pytest.mark.generated
@pytest.mark.error_path
def test_error_path_conversion_exception():
"""Verify that a RuntimeError is raised if the underlying message conversion fails."""
mock_task = MagicMock(spec=Task)
mock_artifact = MagicMock()
mock_artifact.parts = [TextPart(text='p')]
mock_task.artifacts = [mock_artifact]
mock_task.status = None
mock_task.history = []

with patch('google.adk.a2a.converters.event_converter.convert_a2a_message_to_event',
side_effect=Exception('inner error')):
with pytest.raises(RuntimeError, match="Failed to convert task message: inner error"):
convert_a2a_task_to_event(mock_task)
27 changes: 27 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""roost-pytest test-suite configuration.

This file is auto-generated. It registers the custom markers used by
generated tests so pytest doesn't warn ``PytestUnknownMarkWarning``.
Add your own fixtures/hooks below the marker block as you would in
any pytest project.
"""

from __future__ import annotations

import pytest


_GENERATED_MARKERS = {
'generated': 'test was auto-generated by roost-pytest',
'happy_path': 'exercises the primary success path',
'edge_case': 'exercises an edge / boundary condition',
'error_path': 'exercises an exception or failure path',
'security': 'exercises a security-sensitive behaviour',
'property': 'property / invariant test (often parametrised)',
'regression': 'guards against a previously fixed bug',
}


def pytest_configure(config: pytest.Config) -> None:
for name, description in _GENERATED_MARKERS.items():
config.addinivalue_line("markers", f"{name}: {description}")
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ test = [
"rouge-score>=0.1.2",
"tabulate>=0.9.0",
# go/keep-sorted end
"a2a",
"google"
]

docs = [
Expand Down