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
211 lines
7.1 KiB
Python
211 lines
7.1 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 __future__ import annotations
|
|
|
|
from unittest.mock import Mock
|
|
|
|
from a2a.types import TaskArtifactUpdateEvent
|
|
from a2a.types import TaskStatusUpdateEvent
|
|
from google.adk.a2a import _compat
|
|
from google.adk.a2a.converters.from_adk_event import convert_event_to_a2a_events
|
|
from google.adk.events import event_actions
|
|
from google.adk.events.event import Event
|
|
from google.genai import types as genai_types
|
|
import pytest
|
|
|
|
|
|
class TestFromAdk:
|
|
"""Test suite for from_adk functions."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.mock_event = Mock(spec=Event)
|
|
self.mock_event.id = "test-event-id"
|
|
self.mock_event.invocation_id = "test-invocation-id"
|
|
self.mock_event.author = "test-author"
|
|
self.mock_event.branch = None
|
|
self.mock_event.content = None
|
|
self.mock_event.error_code = None
|
|
self.mock_event.error_message = None
|
|
self.mock_event.grounding_metadata = None
|
|
self.mock_event.citation_metadata = None
|
|
self.mock_event.custom_metadata = None
|
|
self.mock_event.usage_metadata = None
|
|
self.mock_event.actions = None
|
|
self.mock_event.partial = True
|
|
self.mock_event.long_running_tool_ids = None
|
|
|
|
def test_convert_event_to_a2a_events_artifact_update(self):
|
|
"""Test conversion of event to TaskArtifactUpdateEvent."""
|
|
# Setup event with content
|
|
self.mock_event.content = genai_types.Content(
|
|
parts=[genai_types.Part(text="hello")], role="model"
|
|
)
|
|
self.mock_event.author = "agent-1"
|
|
|
|
agents_artifacts = {}
|
|
|
|
# Mock part converter to return a standard text part
|
|
mock_a2a_part = _compat.make_text_part("hello")
|
|
mock_convert_part = Mock(return_value=[mock_a2a_part])
|
|
|
|
result = convert_event_to_a2a_events(
|
|
self.mock_event,
|
|
agents_artifacts,
|
|
task_id="task-123",
|
|
context_id="context-456",
|
|
part_converter=mock_convert_part,
|
|
)
|
|
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], TaskArtifactUpdateEvent)
|
|
assert result[0].task_id == "task-123"
|
|
assert result[0].context_id == "context-456"
|
|
assert result[0].artifact.parts == [mock_a2a_part]
|
|
assert "agent-1" in agents_artifacts # Artifact ID should be stored
|
|
|
|
def test_convert_event_to_a2a_events_error(self):
|
|
"""Test conversion of event with error to TaskStatusUpdateEvent."""
|
|
self.mock_event.error_code = "ERR001"
|
|
self.mock_event.error_message = "Something went wrong"
|
|
|
|
agents_artifacts = {}
|
|
|
|
result = convert_event_to_a2a_events(
|
|
self.mock_event,
|
|
agents_artifacts,
|
|
task_id="task-123",
|
|
context_id="context-456",
|
|
)
|
|
|
|
# Should not return any artifact events
|
|
assert len(result) == 0
|
|
|
|
def test_convert_event_to_a2a_events_none_event(self):
|
|
"""Test convert_event_to_a2a_events with None event."""
|
|
with pytest.raises(ValueError, match="Event cannot be None"):
|
|
convert_event_to_a2a_events(None, {})
|
|
|
|
def test_convert_event_to_a2a_events_none_artifacts(self):
|
|
"""Test convert_event_to_a2a_events with None agents_artifacts."""
|
|
with pytest.raises(ValueError, match="Agents artifacts cannot be None"):
|
|
convert_event_to_a2a_events(self.mock_event, None)
|
|
|
|
def test_convert_event_to_a2a_events_with_actions(self):
|
|
"""Test conversion of event with actions to TaskStatusUpdateEvent."""
|
|
self.mock_event.actions = event_actions.EventActions()
|
|
self.mock_event.actions.artifact_delta["image"] = 0
|
|
|
|
agents_artifacts = {}
|
|
|
|
result = convert_event_to_a2a_events(
|
|
self.mock_event,
|
|
agents_artifacts,
|
|
task_id="task-123",
|
|
context_id="context-456",
|
|
)
|
|
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], TaskStatusUpdateEvent)
|
|
assert result[0].task_id == "task-123"
|
|
assert result[0].context_id == "context-456"
|
|
|
|
metadata = result[0].status.message.metadata
|
|
assert "adk_actions" in metadata
|
|
assert metadata["adk_actions"]["artifactDelta"] == {"image": 0}
|
|
|
|
|
|
class TestSerializeValue:
|
|
"""Tests for _serialize_value preserving JSON-native types."""
|
|
|
|
def setup_method(self) -> None:
|
|
from google.adk.a2a.converters.from_adk_event import _serialize_value
|
|
|
|
self.serialize = _serialize_value
|
|
|
|
def test_dict_preserved(self) -> None:
|
|
value = {"key": "val", "nested": {"a": 1}}
|
|
result = self.serialize(value)
|
|
assert result == value
|
|
assert isinstance(result, dict)
|
|
|
|
def test_list_preserved(self) -> None:
|
|
value = [1, "two", {"three": 3}]
|
|
result = self.serialize(value)
|
|
assert result == value
|
|
assert isinstance(result, list)
|
|
|
|
def test_int_preserved(self) -> None:
|
|
result = self.serialize(42)
|
|
assert result == 42
|
|
assert isinstance(result, int)
|
|
|
|
def test_float_preserved(self) -> None:
|
|
result = self.serialize(3.14)
|
|
assert result == 3.14
|
|
assert isinstance(result, float)
|
|
|
|
def test_bool_preserved(self) -> None:
|
|
assert self.serialize(True) is True
|
|
assert self.serialize(False) is False
|
|
|
|
def test_string_preserved(self) -> None:
|
|
assert self.serialize("hello") == "hello"
|
|
|
|
def test_none_returns_none(self) -> None:
|
|
assert self.serialize(None) is None
|
|
|
|
def test_non_json_type_stringified(self) -> None:
|
|
"""Non-JSON-native types should still be converted to str."""
|
|
from datetime import datetime
|
|
|
|
dt = datetime(2025, 1, 1)
|
|
result = self.serialize(dt)
|
|
assert isinstance(result, str)
|
|
|
|
def test_nested_non_json_value_in_dict_stringified(self) -> None:
|
|
"""A non-JSON-native value nested in a dict is stringified."""
|
|
from datetime import datetime
|
|
|
|
dt = datetime(2025, 1, 1)
|
|
value = {"when": dt, "count": 1, "label": "x"}
|
|
result = self.serialize(value)
|
|
assert result == {"when": str(dt), "count": 1, "label": "x"}
|
|
assert isinstance(result["when"], str)
|
|
assert isinstance(result["count"], int)
|
|
assert isinstance(result["label"], str)
|
|
|
|
def test_nested_non_json_value_in_list_stringified(self) -> None:
|
|
"""A non-JSON-native value nested in a list is stringified."""
|
|
from datetime import datetime
|
|
|
|
dt = datetime(2025, 1, 1)
|
|
value = [dt, 1, "x"]
|
|
result = self.serialize(value)
|
|
assert result == [str(dt), 1, "x"]
|
|
assert isinstance(result[0], str)
|
|
assert isinstance(result[1], int)
|
|
assert isinstance(result[2], str)
|
|
|
|
def test_non_string_dict_key_stringified(self) -> None:
|
|
"""Non-string dict keys are stringified so the result is JSON-encodable."""
|
|
from datetime import datetime
|
|
|
|
dt = datetime(2025, 1, 1)
|
|
value = {dt: "when", 1: "one", "label": "x"}
|
|
result = self.serialize(value)
|
|
assert result == {str(dt): "when", "1": "one", "label": "x"}
|
|
assert all(isinstance(k, str) for k in result)
|