Files
wehub-resource-sync ec2b666284
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:13 +08:00

1019 lines
37 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from unittest.mock import Mock
from unittest.mock import patch
from a2a.types import Artifact
from a2a.types import Message
from a2a.types import Task
from a2a.types import TaskStatus
from a2a.types import TaskStatusUpdateEvent
from google.adk.a2a import _compat
from google.adk.a2a.converters.event_converter import _create_artifact_id
from google.adk.a2a.converters.event_converter import _create_error_status_event
from google.adk.a2a.converters.event_converter import _create_status_update_event
from google.adk.a2a.converters.event_converter import _get_adk_metadata_key
from google.adk.a2a.converters.event_converter import _get_context_metadata
from google.adk.a2a.converters.event_converter import _process_long_running_tool
from google.adk.a2a.converters.event_converter import _serialize_metadata_value
from google.adk.a2a.converters.event_converter import ARTIFACT_ID_SEPARATOR
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
from google.adk.a2a.converters.event_converter import convert_event_to_a2a_events
from google.adk.a2a.converters.part_converter import convert_genai_part_to_a2a_part
from google.adk.a2a.converters.utils import ADK_METADATA_KEY_PREFIX
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event
import pytest
class TestEventConverter:
"""Test suite for event_converter module."""
def setup_method(self):
"""Set up test fixtures."""
self.mock_session = Mock()
self.mock_session.id = "test-session-id"
self.mock_artifact_service = Mock()
self.mock_invocation_context = Mock(spec=InvocationContext)
self.mock_invocation_context.app_name = "test-app"
self.mock_invocation_context.user_id = "test-user"
self.mock_invocation_context.session = self.mock_session
self.mock_invocation_context.artifact_service = self.mock_artifact_service
self.mock_event = Mock(spec=Event)
self.mock_event.id = None
self.mock_event.invocation_id = "test-invocation-id"
self.mock_event.author = "test-author"
self.mock_event.branch = None
self.mock_event.grounding_metadata = None
self.mock_event.custom_metadata = None
self.mock_event.usage_metadata = None
self.mock_event.error_code = None
self.mock_event.error_message = None
self.mock_event.content = None
self.mock_event.long_running_tool_ids = None
self.mock_event.actions = None
def test_get_adk_event_metadata_key_success(self):
"""Test successful metadata key generation."""
key = "test_key"
result = _get_adk_metadata_key(key)
assert result == f"{ADK_METADATA_KEY_PREFIX}{key}"
@pytest.mark.skipif(
_compat.IS_A2A_V1,
reason="TaskStatusUpdateEvent.final field does not exist in a2a-sdk 1.x",
)
def test_create_error_status_event_is_final(self):
"""Error status events must be marked final (0.3.x ``final`` field)."""
result = _create_error_status_event(
self.mock_event,
self.mock_invocation_context,
task_id="test-task-id",
context_id="test-context-id",
)
assert result.final is True
def test_get_adk_event_metadata_key_empty_string(self):
"""Test metadata key generation with empty string."""
with pytest.raises(ValueError) as exc_info:
_get_adk_metadata_key("")
assert "cannot be empty or None" in str(exc_info.value)
def test_get_adk_event_metadata_key_none(self):
"""Test metadata key generation with None."""
with pytest.raises(ValueError) as exc_info:
_get_adk_metadata_key(None)
assert "cannot be empty or None" in str(exc_info.value)
def test_serialize_metadata_value_with_model_dump(self):
"""Test serialization of value with model_dump method."""
mock_value = Mock()
mock_value.model_dump.return_value = {"key": "value"}
result = _serialize_metadata_value(mock_value)
assert result == {"key": "value"}
mock_value.model_dump.assert_called_once_with(
exclude_none=True, by_alias=True
)
def test_serialize_metadata_value_with_model_dump_exception(self):
"""Test serialization when model_dump raises exception."""
mock_value = Mock()
mock_value.model_dump.side_effect = Exception("Serialization failed")
with patch(
"google.adk.a2a.converters.event_converter.logger"
) as mock_logger:
result = _serialize_metadata_value(mock_value)
assert result == str(mock_value)
mock_logger.warning.assert_called_once()
def test_serialize_metadata_value_without_model_dump(self):
"""Test serialization of value without model_dump method."""
value = "simple_string"
result = _serialize_metadata_value(value)
assert result == "simple_string"
def test_get_context_metadata_success(self):
"""Test successful context metadata creation."""
result = _get_context_metadata(
self.mock_event, self.mock_invocation_context
)
assert result is not None
expected_keys = [
f"{ADK_METADATA_KEY_PREFIX}app_name",
f"{ADK_METADATA_KEY_PREFIX}user_id",
f"{ADK_METADATA_KEY_PREFIX}session_id",
f"{ADK_METADATA_KEY_PREFIX}invocation_id",
f"{ADK_METADATA_KEY_PREFIX}author",
f"{ADK_METADATA_KEY_PREFIX}event_id",
]
for key in expected_keys:
assert key in result
def test_get_context_metadata_with_optional_fields(self):
"""Test context metadata creation with optional fields."""
self.mock_event.branch = "test-branch"
self.mock_event.error_code = "ERROR_001"
mock_metadata = Mock()
mock_metadata.model_dump.return_value = {"test": "value"}
self.mock_event.grounding_metadata = mock_metadata
self.mock_event.actions = Mock()
self.mock_event.actions.model_dump.return_value = {"test_actions": "value"}
result = _get_context_metadata(
self.mock_event, self.mock_invocation_context
)
assert result is not None
assert f"{ADK_METADATA_KEY_PREFIX}branch" in result
assert f"{ADK_METADATA_KEY_PREFIX}grounding_metadata" in result
assert f"{ADK_METADATA_KEY_PREFIX}actions" in result
assert result[f"{ADK_METADATA_KEY_PREFIX}branch"] == "test-branch"
assert result[f"{ADK_METADATA_KEY_PREFIX}actions"] == {
"test_actions": "value"
}
# Check if error_code is in the result - it should be there since we set it
if f"{ADK_METADATA_KEY_PREFIX}error_code" in result:
assert result[f"{ADK_METADATA_KEY_PREFIX}error_code"] == "ERROR_001"
def test_get_context_metadata_none_event(self):
"""Test context metadata creation with None event."""
with pytest.raises(ValueError) as exc_info:
_get_context_metadata(None, self.mock_invocation_context)
assert "Event cannot be None" in str(exc_info.value)
def test_get_context_metadata_none_context(self):
"""Test context metadata creation with None context."""
with pytest.raises(ValueError) as exc_info:
_get_context_metadata(self.mock_event, None)
assert "Invocation context cannot be None" in str(exc_info.value)
def test_create_artifact_id(self):
"""Test artifact ID creation."""
app_name = "test-app"
user_id = "user123"
session_id = "session456"
filename = "test.txt"
version = 1
result = _create_artifact_id(
app_name, user_id, session_id, filename, version
)
expected = f"{app_name}{ARTIFACT_ID_SEPARATOR}{user_id}{ARTIFACT_ID_SEPARATOR}{session_id}{ARTIFACT_ID_SEPARATOR}{filename}{ARTIFACT_ID_SEPARATOR}{version}"
assert result == expected
def test_process_long_running_tool_marks_tool(self):
"""Test processing of long-running tool metadata."""
a2a_part = _compat.make_data_part(
data={"id": "tool-123"},
metadata={"adk_type": "function_call", "id": "tool-123"},
)
self.mock_event.long_running_tool_ids = {"tool-123"}
with (
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_KEY",
"type",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL",
"function_call",
),
patch(
"google.adk.a2a.converters.event_converter._get_adk_metadata_key"
) as mock_get_key,
):
mock_get_key.side_effect = lambda key: f"adk_{key}"
_process_long_running_tool(a2a_part, self.mock_event)
expected_key = f"{ADK_METADATA_KEY_PREFIX}is_long_running"
assert _compat.part_metadata(a2a_part)[expected_key] is True
def test_process_long_running_tool_no_marking(self):
"""Test processing when tool should not be marked as long-running."""
a2a_part = _compat.make_data_part(
data={"id": "tool-456"},
metadata={"adk_type": "function_call", "id": "tool-456"},
)
self.mock_event.long_running_tool_ids = {"tool-123"} # Different ID
with (
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_KEY",
"type",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL",
"function_call",
),
patch(
"google.adk.a2a.converters.event_converter._get_adk_metadata_key"
) as mock_get_key,
):
mock_get_key.side_effect = lambda key: f"adk_{key}"
_process_long_running_tool(a2a_part, self.mock_event)
expected_key = f"{ADK_METADATA_KEY_PREFIX}is_long_running"
assert expected_key not in _compat.part_metadata(a2a_part)
@patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
)
@patch("google.adk.a2a.converters.event_converter._create_error_status_event")
@patch(
"google.adk.a2a.converters.event_converter._create_status_update_event"
)
def test_convert_event_to_a2a_events_full_scenario(
self,
mock_create_running,
mock_create_error,
mock_convert_message,
):
"""Test full event to A2A events conversion scenario."""
# Setup error
self.mock_event.error_code = "ERROR_001"
# Setup message
mock_message = Mock(spec=Message)
mock_convert_message.return_value = mock_message
# Setup mock returns
mock_error_event = Mock()
mock_create_error.return_value = mock_error_event
mock_running_event = Mock()
mock_create_running.return_value = mock_running_event
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context
)
# Verify error event - now called with task_id and context_id parameters
mock_create_error.assert_called_once_with(
self.mock_event, self.mock_invocation_context, None, None
)
# Verify running event - now called with task_id and context_id parameters
mock_create_running.assert_called_once_with(
mock_message, self.mock_invocation_context, self.mock_event, None, None
)
# Verify result contains all events
assert len(result) == 2 # 1 error + 1 running
assert mock_error_event in result
assert mock_running_event in result
def test_convert_event_to_a2a_events_empty_scenario(self):
"""Test event to A2A events conversion with empty event."""
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context
)
assert result == []
def test_convert_event_to_a2a_events_none_event(self):
"""Test event to A2A events conversion with None event."""
with pytest.raises(ValueError) as exc_info:
convert_event_to_a2a_events(None, self.mock_invocation_context)
assert "Event cannot be None" in str(exc_info.value)
def test_convert_event_to_a2a_events_none_context(self):
"""Test event to A2A events conversion with None context."""
with pytest.raises(ValueError) as exc_info:
convert_event_to_a2a_events(self.mock_event, None)
assert "Invocation context cannot be None" in str(exc_info.value)
@patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
)
def test_convert_event_to_a2a_events_message_only(self, mock_convert_message):
"""Test event to A2A events conversion with message only."""
mock_message = Mock(spec=Message)
mock_convert_message.return_value = mock_message
with patch(
"google.adk.a2a.converters.event_converter._create_status_update_event"
) as mock_create_running:
mock_running_event = Mock()
mock_create_running.return_value = mock_running_event
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context
)
assert len(result) == 1
assert result[0] == mock_running_event
# Verify the function is called with task_id and context_id parameters
mock_create_running.assert_called_once_with(
mock_message,
self.mock_invocation_context,
self.mock_event,
None,
None,
)
@patch("google.adk.a2a.converters.event_converter.logger")
def test_convert_event_to_a2a_events_exception_handling(self, mock_logger):
"""Test exception handling in convert_event_to_a2a_events."""
# Make convert_event_to_a2a_message raise an exception
with patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
) as mock_convert_message:
mock_convert_message.side_effect = Exception("Test exception")
with pytest.raises(Exception):
convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context
)
mock_logger.error.assert_called_once()
def test_convert_event_to_a2a_events_with_task_id_and_context_id(self):
"""Test event to A2A events conversion with specific task_id and context_id."""
# Setup message
mock_message = Mock(spec=Message)
mock_message.parts = []
with patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
) as mock_convert_message:
mock_convert_message.return_value = mock_message
with patch(
"google.adk.a2a.converters.event_converter._create_status_update_event"
) as mock_create_running:
mock_running_event = Mock()
mock_create_running.return_value = mock_running_event
task_id = "custom-task-id"
context_id = "custom-context-id"
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context, task_id, context_id
)
assert len(result) == 1
assert result[0] == mock_running_event
# Verify the function is called with the specific task_id and context_id
mock_create_running.assert_called_once_with(
mock_message,
self.mock_invocation_context,
self.mock_event,
task_id,
context_id,
)
def test_convert_event_to_a2a_events_with_custom_ids(self):
"""Test event to A2A events conversion with custom IDs."""
# Setup message
mock_message = Mock(spec=Message)
mock_message.parts = []
with patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
) as mock_convert_message:
mock_convert_message.return_value = mock_message
with patch(
"google.adk.a2a.converters.event_converter._create_status_update_event"
) as mock_create_running:
mock_running_event = Mock()
mock_create_running.return_value = mock_running_event
task_id = "custom-task-id"
context_id = "custom-context-id"
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context, task_id, context_id
)
assert len(result) == 1 # 1 status
assert mock_running_event in result
# Verify status update is called with custom IDs
mock_create_running.assert_called_once_with(
mock_message,
self.mock_invocation_context,
self.mock_event,
task_id,
context_id,
)
def test_convert_event_to_a2a_events_user_role(self):
"""Test event to A2A events conversion with events from a user."""
# Setup message
mock_message = Mock(spec=Message)
mock_message.parts = []
with patch(
"google.adk.a2a.converters.event_converter.convert_event_to_a2a_message"
) as mock_convert_message:
mock_convert_message.return_value = mock_message
with patch(
"google.adk.a2a.converters.event_converter._create_status_update_event"
) as mock_create_running:
mock_running_event = Mock()
mock_create_running.return_value = mock_running_event
self.mock_event.author = "user"
task_id = "custom-task-id"
context_id = "custom-context-id"
result = convert_event_to_a2a_events(
self.mock_event, self.mock_invocation_context, task_id, context_id
)
assert len(result) == 1
assert result[0] == mock_running_event
# Verify the function is called with the specific task_id and context_id
mock_convert_message.assert_called_once_with(
self.mock_event,
self.mock_invocation_context,
part_converter=convert_genai_part_to_a2a_part,
role=_compat.ROLE_USER,
)
def test_create_status_update_event_with_auth_required_state(self):
"""Test creation of status update event with auth_required state."""
# A real message whose data part is a long-running EUC function call.
a2a_part = _compat.make_data_part(
data={"name": "request_euc"},
metadata={
"adk_type": "function_call",
"adk_is_long_running": True,
},
)
mock_message = _compat.make_message(
message_id="m1", role=_compat.ROLE_AGENT, parts=[a2a_part]
)
task_id = "test-task-id"
context_id = "test-context-id"
# Timestamps come from ``_compat.make_task_status``, so no datetime patch
# is needed here.
with (
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_KEY",
"type",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL",
"function_call",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY",
"is_long_running",
),
patch(
"google.adk.a2a.converters.event_converter.REQUEST_EUC_FUNCTION_CALL_NAME",
"request_euc",
),
patch(
"google.adk.a2a.converters.event_converter._get_adk_metadata_key"
) as mock_get_key,
):
mock_get_key.side_effect = lambda key: f"adk_{key}"
result = _create_status_update_event(
mock_message,
self.mock_invocation_context,
self.mock_event,
task_id,
context_id,
)
assert isinstance(result, TaskStatusUpdateEvent)
assert result.task_id == task_id
assert result.context_id == context_id
assert result.status.state == _compat.TS_AUTH_REQUIRED
def test_create_status_update_event_with_input_required_state(self):
"""Test creation of status update event with input_required state."""
# A long-running function call that is NOT the EUC call -> input_required.
a2a_part = _compat.make_data_part(
data={"name": "some_other_function"},
metadata={
"adk_type": "function_call",
"adk_is_long_running": True,
},
)
mock_message = _compat.make_message(
message_id="m1", role=_compat.ROLE_AGENT, parts=[a2a_part]
)
task_id = "test-task-id"
context_id = "test-context-id"
# Note: production no longer formats timestamps via ``datetime`` directly
# (they are produced by ``_compat.make_task_status``), so no datetime
# patch is needed here.
with (
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_KEY",
"type",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL",
"function_call",
),
patch(
"google.adk.a2a.converters.event_converter.A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY",
"is_long_running",
),
patch(
"google.adk.a2a.converters.event_converter.REQUEST_EUC_FUNCTION_CALL_NAME",
"request_euc",
),
patch(
"google.adk.a2a.converters.event_converter._get_adk_metadata_key"
) as mock_get_key,
):
mock_get_key.side_effect = lambda key: f"adk_{key}"
result = _create_status_update_event(
mock_message,
self.mock_invocation_context,
self.mock_event,
task_id,
context_id,
)
assert isinstance(result, TaskStatusUpdateEvent)
assert result.task_id == task_id
assert result.context_id == context_id
assert result.status.state == _compat.TS_INPUT_REQUIRED
def test_convert_event_to_a2a_message_with_multiple_parts_returned(self):
"""Test event to message conversion when part_converter returns multiple parts."""
from google.adk.a2a.converters.event_converter import convert_event_to_a2a_message
from google.genai import types as genai_types
# Arrange
mock_genai_part = genai_types.Part(text="source part")
mock_a2a_part1 = _compat.make_text_part("part 1")
mock_a2a_part2 = _compat.make_text_part("part 2")
mock_convert_part = Mock()
mock_convert_part.return_value = [mock_a2a_part1, mock_a2a_part2]
self.mock_event.content = genai_types.Content(
parts=[mock_genai_part], role="model"
)
# Act
result = convert_event_to_a2a_message(
self.mock_event,
self.mock_invocation_context,
part_converter=mock_convert_part,
)
# Assert
assert result is not None
assert len(result.parts) == 2
assert _compat.part_text(result.parts[0]) == "part 1"
assert _compat.part_text(result.parts[1]) == "part 2"
mock_convert_part.assert_called_once_with(mock_genai_part)
class TestA2AToEventConverters:
"""Test suite for A2A to Event conversion functions."""
def setup_method(self):
"""Set up test fixtures."""
self.mock_invocation_context = Mock(spec=InvocationContext)
self.mock_invocation_context.invocation_id = "test-invocation-id"
self.mock_invocation_context.branch = "test-branch"
def test_convert_a2a_task_to_event_with_artifacts_priority(self):
"""Test convert_a2a_task_to_event prioritizes artifacts over status/history."""
# Create mock artifacts
artifact_part = _compat.make_text_part("artifact content")
mock_artifact = Mock(spec=Artifact)
mock_artifact.parts = [artifact_part]
# Create mock status and history
status_part = _compat.make_text_part("status content")
mock_status = Mock(spec=TaskStatus)
mock_status.message = Mock(spec=Message)
mock_status.message.parts = [status_part]
history_part = _compat.make_text_part("history content")
mock_history_message = Mock(spec=Message)
mock_history_message.parts = [history_part]
# Create task with all three sources
mock_task = Mock(spec=Task)
mock_task.artifacts = [mock_artifact]
mock_task.status = mock_status
mock_task.history = [mock_history_message]
with patch(
"google.adk.a2a.converters.event_converter.convert_a2a_message_to_event"
) as mock_convert_message:
mock_event = Mock(spec=Event)
mock_convert_message.return_value = mock_event
result = convert_a2a_task_to_event(
mock_task, "test-author", self.mock_invocation_context
)
assert result == mock_event
# Should call convert_a2a_message_to_event with a message created from artifacts
mock_convert_message.assert_called_once()
called_message = mock_convert_message.call_args[0][0]
assert called_message.role == _compat.ROLE_AGENT
assert called_message.parts == [artifact_part]
def test_convert_a2a_task_to_event_with_status_message(self):
"""Test convert_a2a_task_to_event with status message (no artifacts)."""
# Create mock status
status_part = _compat.make_text_part("status content")
mock_status = Mock(spec=TaskStatus)
mock_status.message = Mock(spec=Message)
mock_status.message.parts = [status_part]
# Create task with no artifacts
mock_task = Mock(spec=Task)
mock_task.artifacts = None
mock_task.status = mock_status
mock_task.history = []
with patch(
"google.adk.a2a.converters.event_converter.convert_a2a_message_to_event"
) as mock_convert_message:
from google.adk.a2a.converters.part_converter import convert_a2a_part_to_genai_part
mock_event = Mock(spec=Event)
mock_convert_message.return_value = mock_event
result = convert_a2a_task_to_event(
mock_task, "test-author", self.mock_invocation_context
)
assert result == mock_event
# Should call convert_a2a_message_to_event with the status message
mock_convert_message.assert_called_once_with(
mock_status.message,
"test-author",
self.mock_invocation_context,
part_converter=convert_a2a_part_to_genai_part,
)
def test_convert_a2a_task_to_event_with_history_message(self):
"""Test converting A2A task with history message when no status message."""
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
# Create mock message and task
mock_message = Mock(spec=Message)
mock_task = Mock(spec=Task)
mock_task.artifacts = None
mock_task.status = None
mock_task.history = [mock_message]
# Mock the convert_a2a_message_to_event function
with patch(
"google.adk.a2a.converters.event_converter.convert_a2a_message_to_event"
) as mock_convert_message:
from google.adk.a2a.converters.part_converter import convert_a2a_part_to_genai_part
mock_event = Mock(spec=Event)
mock_event.invocation_id = "test-invocation-id"
mock_convert_message.return_value = mock_event
result = convert_a2a_task_to_event(mock_task, "test-author")
# Verify the message converter was called with correct parameters
mock_convert_message.assert_called_once_with(
mock_message,
"test-author",
None,
part_converter=convert_a2a_part_to_genai_part,
)
assert result == mock_event
def test_convert_a2a_task_to_event_no_message(self):
"""Test converting A2A task with no message."""
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
# Create mock task with no message
mock_task = Mock(spec=Task)
mock_task.artifacts = None
mock_task.status = None
mock_task.history = []
result = convert_a2a_task_to_event(
mock_task, "test-author", self.mock_invocation_context
)
# Verify minimal event was created with correct invocation_id
assert result.author == "test-author"
assert result.branch == "test-branch"
assert result.invocation_id == "test-invocation-id"
@patch("google.adk.a2a.converters.event_converter.platform_uuid.new_uuid")
def test_convert_a2a_task_to_event_default_author(self, mock_uuid):
"""Test converting A2A task with default author and no invocation context."""
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
# Create mock task with no message
mock_task = Mock(spec=Task)
mock_task.artifacts = None
mock_task.status = None
mock_task.history = []
# Mock UUID generation
mock_uuid.return_value = "generated-uuid"
result = convert_a2a_task_to_event(mock_task)
# Verify default author was used and UUID was generated for invocation_id
assert result.author == "a2a agent"
assert result.branch is None
assert result.invocation_id == "generated-uuid"
def test_convert_a2a_task_to_event_none_task(self):
"""Test converting None task raises ValueError."""
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
with pytest.raises(ValueError, match="A2A task cannot be None"):
convert_a2a_task_to_event(None)
def test_convert_a2a_task_to_event_message_conversion_error(self):
"""Test error handling when message conversion fails."""
from google.adk.a2a.converters.event_converter import convert_a2a_task_to_event
# Create mock message and task
mock_message = Mock(spec=Message, parts=[Mock()])
mock_status = Mock(message=mock_message)
mock_task = Mock(spec=Task, artifacts=None, status=mock_status, history=[])
# Mock the convert_a2a_message_to_event function to raise an exception
with patch(
"google.adk.a2a.converters.event_converter.convert_a2a_message_to_event"
) as mock_convert_message:
mock_convert_message.side_effect = Exception("Conversion failed")
with pytest.raises(RuntimeError, match="Failed to convert task message"):
convert_a2a_task_to_event(mock_task, "test-author")
def test_convert_a2a_message_to_event_success(self):
"""Test successful conversion of A2A message to event."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
from google.genai import types as genai_types
# Use a real A2A part (production reads its metadata); the part_converter
# callback is still mocked to return a canned genai Part.
mock_a2a_part = _compat.make_text_part("test content")
mock_genai_part = genai_types.Part(text="test content")
mock_convert_part = Mock(return_value=mock_genai_part)
mock_message = Mock(spec=Message, parts=[mock_a2a_part])
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Verify conversion was successful
assert result.author == "test-author"
assert result.branch == "test-branch"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
assert len(result.content.parts) == 1
assert result.content.parts[0].text == "test content"
mock_convert_part.assert_called_once_with(mock_a2a_part)
def test_convert_a2a_message_to_event_with_multiple_parts_returned(self):
"""Test message to event conversion when part_converter returns multiple parts."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
from google.genai import types as genai_types
# Arrange
mock_a2a_part = _compat.make_text_part("part 1")
mock_genai_part1 = genai_types.Part(text="part 1")
mock_genai_part2 = genai_types.Part(text="part 2")
mock_convert_part = Mock(return_value=[mock_genai_part1, mock_genai_part2])
mock_message = Mock(spec=Message, parts=[mock_a2a_part])
# Act
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Assert
assert result.content.role == "model"
assert len(result.content.parts) == 2
assert result.content.parts[0].text == "part 1"
assert result.content.parts[1].text == "part 2"
mock_convert_part.assert_called_once_with(mock_a2a_part)
def test_convert_a2a_message_to_event_with_long_running_tools(self):
"""Test conversion with long-running tools by mocking the entire flow."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
# Create mock parts and message
mock_message = Mock(spec=Message, parts=[Mock()])
# Mock the part conversion to return None to simulate long-running tool detection logic
mock_convert_part = Mock(return_value=None)
# Patch the long-running tool detection since the main logic is in the actual conversion
with patch(
"google.adk.a2a.converters.event_converter.logger"
) as mock_logger:
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Verify basic conversion worked
assert result.author == "test-author"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
# Parts will be empty since conversion returned None, but that's expected for this test
def test_convert_a2a_message_to_event_empty_parts(self):
"""Test conversion with empty parts list."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
mock_message = Mock(spec=Message, parts=[])
result = convert_a2a_message_to_event(
mock_message, "test-author", self.mock_invocation_context
)
# Verify event was created with empty parts
assert result.author == "test-author"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
assert len(result.content.parts) == 0
def test_convert_a2a_message_to_event_none_message(self):
"""Test converting None message raises ValueError."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
with pytest.raises(ValueError, match="A2A message cannot be None"):
convert_a2a_message_to_event(None)
def test_convert_a2a_message_to_event_part_conversion_fails(self):
"""Test handling when part conversion returns None."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
# Setup mock to return None (conversion failure)
mock_a2a_part = Mock()
mock_convert_part = Mock(return_value=None)
mock_message = Mock(spec=Message, parts=[mock_a2a_part])
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Verify event was created but with no parts
assert result.author == "test-author"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
assert len(result.content.parts) == 0
def test_convert_a2a_message_to_event_part_conversion_exception(self):
"""Test handling when part conversion raises exception."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
from google.genai import types as genai_types
# Setup mock to raise exception. The A2A parts are real (production
# reads their metadata); the converter callback drives the behavior.
mock_a2a_part1 = _compat.make_text_part("first")
mock_a2a_part2 = _compat.make_text_part("second")
mock_genai_part = genai_types.Part(text="successful conversion")
mock_convert_part = Mock(
side_effect=[
Exception("Conversion failed"), # First part fails
mock_genai_part, # Second part succeeds
]
)
mock_message = Mock(spec=Message, parts=[mock_a2a_part1, mock_a2a_part2])
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Verify event was created with only the successfully converted part
assert result.author == "test-author"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
assert len(result.content.parts) == 1
assert result.content.parts[0].text == "successful conversion"
def test_convert_a2a_message_to_event_missing_tool_id(self):
"""Test handling of message conversion when part conversion fails."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
# Create mock parts and message
mock_message = Mock(spec=Message, parts=[Mock()])
# Mock the part conversion to return None
mock_convert_part = Mock(return_value=None)
result = convert_a2a_message_to_event(
mock_message,
"test-author",
self.mock_invocation_context,
mock_convert_part,
)
# Verify basic conversion worked
assert result.author == "test-author"
assert result.invocation_id == "test-invocation-id"
assert result.content.role == "model"
# Parts will be empty since conversion returned None
assert len(result.content.parts) == 0
@patch("google.adk.a2a.converters.event_converter.platform_uuid.new_uuid")
def test_convert_a2a_message_to_event_default_author(self, mock_uuid):
"""Test conversion with default author and no invocation context."""
from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event
mock_message = Mock(spec=Message, parts=[])
# Mock UUID generation
mock_uuid.return_value = "generated-uuid"
result = convert_a2a_message_to_event(mock_message)
# Verify default author was used and UUID was generated for invocation_id
assert result.author == "a2a agent"
assert result.branch is None
assert result.invocation_id == "generated-uuid"