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
131 changes: 131 additions & 0 deletions .roost/runreport.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
{
"run_id": "9133e0e9",
"started_at": "2026-04-29T05:46:20.901176Z",
"ended_at": "2026-04-29T05:48:25.658533Z",
"status": "ok",
"config": {
"repo": "/private/var/tmp/Roost/RoostGPT/unit-adk-python/1777441516/source/adk-python",
"out": "/private/var/tmp/Roost/RoostGPT/unit-adk-python/1777441516/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": 7,
"scenarios_passed": 7,
"scenarios_failed": 0,
"coverage_weighted": null,
"llm_cost_usd": 0.1452,
"llm_input_tokens": 541206,
"llm_output_tokens": 12324,
"llm_cache_read_input_tokens": 360814,
"llm_cache_creation_input_tokens": 0,
"llm_api_calls": 19,
"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/1777441516/source/adk-python/adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py",
"test_file_hash": "sha256:46cc961cb6ad23a88ebbd58e126a82963a00fb97267044dc6507ed8377a93c60",
"test_status": "passed",
"iterations": 19,
"pivots": 0,
"scenarios": [
{
"scenario_id": "src.google.adk.a2a.converters.event_converter.convert_a2a_task_to_event#0",
"scenario_name": "happy_path_with_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_with_status_message",
"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_with_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#3",
"scenario_name": "happy_path_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#4",
"scenario_name": "edge_case_with_invocation_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#5",
"scenario_name": "error_path_none_task",
"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#6",
"scenario_name": "error_path_conversion_failure",
"passed": true,
"duration_ms": null,
"failure_reason": null,
"stderr_excerpt": null
}
],
"coverage_pct": null,
"writer_cost_usd": 0.14520869999999997,
"writer_input_tokens": 541206,
"writer_output_tokens": 12324,
"writer_cache_read_input_tokens": 360814,
"writer_cache_creation_input_tokens": 0,
"diagnostics": []
}
],
"files_written": [
{
"path": "adk/a2a/converters/event_converter/test_convert_a2a_task_to_event.py",
"hash": "sha256:46cc961cb6ad23a88ebbd58e126a82963a00fb97267044dc6507ed8377a93c60",
"status": "created"
}
],
"pr_ready": {
"branch_suggestion": "roost/python-tests-20260429-0548",
"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.
145 changes: 145 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,145 @@
# 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_with_artifacts: Verify that the function correctly extracts the message from task artifacts when they are present.
# - [happy_path] happy_path_with_status_message: Verify that the function correctly extracts the message from task status when artifacts are missing.
# - [happy_path] happy_path_with_history: Verify that the function correctly extracts the message from task history when artifacts and status message are missing.
# - [happy_path] happy_path_minimal_event: Verify that the function returns a minimal Event when no message source is available in the task.
# - [edge_case] edge_case_with_invocation_context: Verify that the function correctly uses the provided invocation context for the resulting Event.
# - [error_path] error_path_none_task: Verify that the function raises ValueError when the input task is None.
# - [error_path] error_path_conversion_failure: Verify that the function raises RuntimeError when the underlying message conversion fails.

import pytest
from unittest.mock import MagicMock, patch, ANY
import uuid

from a2a.types import Message, Role, Task, Part as A2APart, TextPart
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

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_with_artifacts():
"""Verify that the function correctly extracts the message from task artifacts when they are present."""
a2a_task = MagicMock(spec=Task)
# Use real Part and TextPart to avoid Pydantic validation errors in Message constructor
mock_part = A2APart(root=TextPart(text="test artifact"))
a2a_task.artifacts = [MagicMock(parts=[mock_part])]
a2a_task.status = None
a2a_task.history = []

mock_event = MagicMock(spec=Event)

with patch("google.adk.a2a.converters.event_converter.convert_a2a_message_to_event", return_value=mock_event) as mock_convert:
result = convert_a2a_task_to_event(a2a_task)

assert result == mock_event
mock_convert.assert_called_once()
call_args = mock_convert.call_args[0]
passed_message = call_args[0]
assert isinstance(passed_message, Message)
assert passed_message.message_id == ""
assert passed_message.role == Role.agent
assert passed_message.parts == [mock_part]

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_with_status_message():
"""Verify that the function correctly extracts the message from task status when artifacts are missing."""
a2a_task = MagicMock(spec=Task)
a2a_task.artifacts = []
mock_message = MagicMock(spec=Message)
mock_message.parts = [MagicMock()]
a2a_task.status = MagicMock(message=mock_message)
a2a_task.history = []

mock_event = MagicMock(spec=Event)

with patch("google.adk.a2a.converters.event_converter.convert_a2a_message_to_event", return_value=mock_event) as mock_convert:
result = convert_a2a_task_to_event(a2a_task)

assert result == mock_event
mock_convert.assert_called_once_with(mock_message, None, None, part_converter=ANY)

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_with_history():
"""Verify that the function correctly extracts the message from task history when artifacts and status message are missing."""
a2a_task = MagicMock(spec=Task)
a2a_task.artifacts = []
a2a_task.status = None
mock_message = MagicMock(spec=Message)
a2a_task.history = [mock_message]

mock_event = MagicMock(spec=Event)

with patch("google.adk.a2a.converters.event_converter.convert_a2a_message_to_event", return_value=mock_event) as mock_convert:
result = convert_a2a_task_to_event(a2a_task)

assert result == mock_event
mock_convert.assert_called_once_with(mock_message, None, None, part_converter=ANY)

@pytest.mark.generated
@pytest.mark.happy_path
def test_happy_path_minimal_event():
"""Verify that the function returns a minimal Event when no message source is available in the task."""
a2a_task = MagicMock(spec=Task)
a2a_task.artifacts = []
a2a_task.status = None
a2a_task.history = []

fixed_uuid = "12345678-1234-5678-1234-567812345678"
with patch("uuid.uuid4", return_value=uuid.UUID(fixed_uuid)):
result = convert_a2a_task_to_event(a2a_task)

assert isinstance(result, Event)
assert result.author == "a2a agent"
assert result.invocation_id == fixed_uuid
assert result.branch is None

@pytest.mark.generated
@pytest.mark.edge_case
def test_edge_case_with_invocation_context():
"""Verify that the function correctly uses the provided invocation context for the resulting Event."""
a2a_task = MagicMock(spec=Task)
a2a_task.artifacts = []
a2a_task.status = None
a2a_task.history = []

invocation_context = MagicMock(spec=InvocationContext)
invocation_context.invocation_id = 'test-id'
invocation_context.branch = 'test-branch'

result = convert_a2a_task_to_event(a2a_task, invocation_context=invocation_context)

assert isinstance(result, Event)
assert result.invocation_id == 'test-id'
assert result.branch == 'test-branch'

@pytest.mark.generated
@pytest.mark.error_path
def test_error_path_none_task():
"""Verify that the function raises ValueError when 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_failure():
"""Verify that the function raises RuntimeError when the underlying message conversion fails."""
a2a_task = MagicMock(spec=Task)
a2a_task.artifacts = []
a2a_task.status = None
a2a_task.history = [MagicMock(spec=Message)]

with patch("google.adk.a2a.converters.event_converter.convert_a2a_message_to_event", side_effect=Exception("fail")):
with pytest.raises(RuntimeError, match="Failed to convert task message: fail"):
convert_a2a_task_to_event(a2a_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