chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,13 @@
# 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.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,208 @@
# 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.
"""Round trip tests for ADK and A2A event converters."""
from __future__ import annotations
from typing import Dict
from unittest.mock import Mock
from a2a.types import TaskArtifactUpdateEvent
from a2a.types import TaskStatusUpdateEvent
from google.adk.a2a.converters.from_adk_event import convert_event_to_a2a_events
from google.adk.a2a.converters.from_adk_event import create_error_status_event
from google.adk.a2a.converters.to_adk_event import convert_a2a_artifact_update_to_event
from google.adk.a2a.converters.to_adk_event import convert_a2a_status_update_to_event
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event
from google.genai import types as genai_types
def test_round_trip_text_event():
original_event = Event(
invocation_id="test_invocation",
author="test_agent",
branch="main",
content=genai_types.Content(
role="model",
parts=[genai_types.Part.from_text(text="Hello world!")],
),
partial=False,
)
agents_artifacts: Dict[str, str] = {}
a2a_events = convert_event_to_a2a_events(
event=original_event,
agents_artifacts=agents_artifacts,
task_id="task1",
context_id="context1",
)
assert len(a2a_events) == 1
a2a_event = a2a_events[0]
assert isinstance(a2a_event, TaskArtifactUpdateEvent)
mock_context = Mock(
spec=InvocationContext, invocation_id="test_invocation", branch="main"
)
restored_event = convert_a2a_artifact_update_to_event(
a2a_artifact_update=a2a_event,
author="test_agent",
invocation_context=mock_context,
)
assert restored_event is not None
assert restored_event.author == original_event.author
assert restored_event.invocation_id == original_event.invocation_id
assert restored_event.branch == original_event.branch
assert restored_event.partial == original_event.partial
assert len(restored_event.content.parts) == len(original_event.content.parts)
assert (
restored_event.content.parts[0].text
== original_event.content.parts[0].text
)
def test_round_trip_error_status_event():
original_event = Event(
invocation_id="error_inv",
author="error_agent",
branch="main",
error_message="Test Error",
)
a2a_event = create_error_status_event(
event=original_event,
task_id="task2",
context_id="ctx2",
)
assert isinstance(a2a_event, TaskStatusUpdateEvent)
mock_context = Mock(
spec=InvocationContext, invocation_id="error_inv", branch="main"
)
restored_event = convert_a2a_status_update_to_event(
a2a_status_update=a2a_event,
author="error_agent",
invocation_context=mock_context,
)
assert restored_event is not None
assert restored_event.author == original_event.author
assert restored_event.invocation_id == original_event.invocation_id
assert restored_event.branch == original_event.branch
assert len(restored_event.content.parts) == 1
assert restored_event.content.parts[0].text == "Test Error"
def test_round_trip_function_call_event():
original_event = Event(
invocation_id="test_invocation",
author="test_agent",
branch="main",
content=genai_types.Content(
role="model",
parts=[
genai_types.Part.from_function_call(
name="my_function",
args={"arg1": "value1"},
)
],
),
partial=False,
)
agents_artifacts: Dict[str, str] = {}
a2a_events = convert_event_to_a2a_events(
event=original_event,
agents_artifacts=agents_artifacts,
task_id="task1",
context_id="context1",
)
assert len(a2a_events) == 1
a2a_event = a2a_events[0]
mock_context = Mock(
spec=InvocationContext, invocation_id="test_invocation", branch="main"
)
restored_event = convert_a2a_artifact_update_to_event(
a2a_artifact_update=a2a_event,
author="test_agent",
invocation_context=mock_context,
)
assert restored_event is not None
assert restored_event.author == original_event.author
assert restored_event.invocation_id == original_event.invocation_id
assert restored_event.branch == original_event.branch
assert len(restored_event.content.parts) == 1
assert restored_event.content.parts[0].function_call.name == "my_function"
assert restored_event.content.parts[0].function_call.args == {
"arg1": "value1"
}
def test_round_trip_function_response_event():
original_event = Event(
invocation_id="test_invocation",
author="test_agent",
branch="main",
content=genai_types.Content(
role="user",
parts=[
genai_types.Part.from_function_response(
name="my_function",
response={"result": "success"},
)
],
),
partial=False,
)
agents_artifacts: Dict[str, str] = {}
a2a_events = convert_event_to_a2a_events(
event=original_event,
agents_artifacts=agents_artifacts,
task_id="task1",
context_id="context1",
)
assert len(a2a_events) == 1
a2a_event = a2a_events[0]
mock_context = Mock(
spec=InvocationContext, invocation_id="test_invocation", branch="main"
)
restored_event = convert_a2a_artifact_update_to_event(
a2a_artifact_update=a2a_event,
author="test_agent",
invocation_context=mock_context,
)
assert restored_event is not None
assert restored_event.author == original_event.author
assert restored_event.invocation_id == original_event.invocation_id
assert restored_event.branch == original_event.branch
assert len(restored_event.content.parts) == 1
assert restored_event.content.parts[0].function_response.name == "my_function"
assert restored_event.content.parts[0].function_response.response == {
"result": "success"
}
@@ -0,0 +1,210 @@
# 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)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,459 @@
# 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 a2a.server.agent_execution import RequestContext
from google.adk.a2a import _compat
from google.adk.a2a.converters.request_converter import _get_user_id
from google.adk.a2a.converters.request_converter import convert_a2a_request_to_agent_run_request
from google.adk.runners import RunConfig
from google.genai import types as genai_types
import pytest
class TestGetUserId:
"""Test cases for _get_user_id function."""
def test_get_user_id_from_call_context(self):
"""Test getting user ID from call context when auth is enabled."""
# Arrange
mock_user = Mock()
mock_user.user_name = "authenticated_user"
mock_call_context = Mock()
mock_call_context.user = mock_user
request = Mock(spec=RequestContext)
request.call_context = mock_call_context
request.context_id = "test_context"
# Act
result = _get_user_id(request)
# Assert
assert result == "authenticated_user"
def test_get_user_id_from_context_when_no_call_context(self):
"""Test getting user ID from context when call context is not available."""
# Arrange
request = Mock(spec=RequestContext)
request.call_context = None
request.context_id = "test_context"
# Act
result = _get_user_id(request)
# Assert
assert result == "A2A_USER_test_context"
def test_get_user_id_from_context_when_call_context_has_no_user(self):
"""Test getting user ID from context when call context has no user."""
# Arrange
mock_call_context = Mock()
mock_call_context.user = None
request = Mock(spec=RequestContext)
request.call_context = mock_call_context
request.context_id = "test_context"
# Act
result = _get_user_id(request)
# Assert
assert result == "A2A_USER_test_context"
def test_get_user_id_with_empty_user_name(self):
"""Test getting user ID when user exists but user_name is empty."""
# Arrange
mock_user = Mock()
mock_user.user_name = ""
mock_call_context = Mock()
mock_call_context.user = mock_user
request = Mock(spec=RequestContext)
request.call_context = mock_call_context
request.context_id = "test_context"
# Act
result = _get_user_id(request)
# Assert
assert result == "A2A_USER_test_context"
def test_get_user_id_with_none_user_name(self):
"""Test getting user ID when user exists but user_name is None."""
# Arrange
mock_user = Mock()
mock_user.user_name = None
mock_call_context = Mock()
mock_call_context.user = mock_user
request = Mock(spec=RequestContext)
request.call_context = mock_call_context
request.context_id = "test_context"
# Act
result = _get_user_id(request)
# Assert
assert result == "A2A_USER_test_context"
def test_get_user_id_with_none_context_id(self):
"""Test getting user ID when context_id is None."""
# Arrange
request = Mock(spec=RequestContext)
request.call_context = None
request.context_id = None
# Act
result = _get_user_id(request)
# Assert
assert result == "A2A_USER_None"
class TestConvertA2aRequestToAgentRunRequest:
"""Test cases for convert_a2a_request_to_agent_run_request function."""
def test_convert_a2a_request_basic(self):
"""Test basic conversion of A2A request to ADK AgentRunRequest."""
# Arrange
mock_part1 = Mock()
mock_part2 = Mock()
mock_message = Mock()
mock_message.parts = [mock_part1, mock_part2]
mock_user = Mock()
mock_user.user_name = "test_user"
mock_call_context = Mock()
mock_call_context.user = mock_user
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "test_context_123"
request.call_context = mock_call_context
request.metadata = {"test_key": "test_value"}
# Create proper genai_types.Part objects instead of mocks
mock_genai_part1 = genai_types.Part(text="test part 1")
mock_genai_part2 = genai_types.Part(text="test part 2")
mock_convert_part = Mock()
mock_convert_part.side_effect = [mock_genai_part1, mock_genai_part2]
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "test_user"
assert result.session_id == "test_context_123"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [mock_genai_part1, mock_genai_part2]
assert isinstance(result.run_config, RunConfig)
assert result.run_config.custom_metadata == {
"a2a_metadata": {"test_key": "test_value"}
}
# Verify calls
assert mock_convert_part.call_count == 2
mock_convert_part.assert_any_call(mock_part1)
mock_convert_part.assert_any_call(mock_part2)
def test_convert_a2a_request_multiple_parts(self):
"""Test basic conversion of A2A request to ADK AgentRunRequest."""
# Arrange
mock_part1 = Mock()
mock_part2 = Mock()
mock_message = Mock()
mock_message.parts = [mock_part1, mock_part2]
mock_user = Mock()
mock_user.user_name = "test_user"
mock_call_context = Mock()
mock_call_context.user = mock_user
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "test_context_123"
request.call_context = mock_call_context
request.metadata = {"test_key": "test_value"}
# Create proper genai_types.Part objects instead of mocks
mock_genai_part1 = genai_types.Part(text="test part 1")
mock_genai_part2 = genai_types.Part(text="test part 2")
mock_convert_part = Mock()
mock_convert_part.side_effect = [mock_genai_part1, mock_genai_part2]
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "test_user"
assert result.session_id == "test_context_123"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [
mock_genai_part1,
mock_genai_part2,
]
assert isinstance(result.run_config, RunConfig)
assert result.run_config.custom_metadata == {
"a2a_metadata": {"test_key": "test_value"}
}
# Verify calls
assert mock_convert_part.call_count == 2
mock_convert_part.assert_any_call(mock_part1)
mock_convert_part.assert_any_call(mock_part2)
def test_convert_a2a_request_normalizes_proto_struct_metadata(self):
"""Request metadata is normalized to a plain dict across SDK versions."""
# Build metadata in the native shape of the active SDK (proto Struct on 1.x,
# plain dict on 0.3.x) by writing onto a real a2a Message.
message_with_meta = _compat.make_message(
message_id="m1", role=_compat.ROLE_USER, parts=[]
)
_compat.set_struct_metadata(
message_with_meta, {"test_key": "test_value", "n": 1}
)
mock_message = Mock()
mock_message.parts = []
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "ctx"
request.call_context = None
request.metadata = message_with_meta.metadata
result = convert_a2a_request_to_agent_run_request(request, Mock())
stored = result.run_config.custom_metadata["a2a_metadata"]
# Must be a plain dict (not a proto Struct) regardless of SDK version.
assert isinstance(stored, dict)
assert stored["test_key"] == "test_value"
# Numbers may come back as float on 1.x (proto Struct) -> tolerate both.
assert float(stored["n"]) == 1.0
def test_convert_a2a_request_empty_metadata_omitted(self):
"""Empty request metadata must not add an ``a2a_metadata`` entry."""
empty_meta_msg = _compat.make_message(
message_id="m1", role=_compat.ROLE_USER, parts=[]
)
mock_message = Mock()
mock_message.parts = []
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "ctx"
request.call_context = None
request.metadata = empty_meta_msg.metadata
result = convert_a2a_request_to_agent_run_request(request, Mock())
assert "a2a_metadata" not in result.run_config.custom_metadata
def test_convert_a2a_request_no_message_raises_error(self):
"""Test that conversion raises ValueError when message is None."""
# Arrange
request = Mock(spec=RequestContext)
request.message = None
# Act & Assert
with pytest.raises(ValueError, match="Request message cannot be None"):
convert_a2a_request_to_agent_run_request(request)
def test_convert_a2a_request_empty_parts(self):
"""Test conversion with empty parts list."""
# Arrange
mock_message = Mock()
mock_message.parts = []
mock_convert_part = Mock()
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "test_context_123"
request.call_context = None
request.metadata = {}
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "A2A_USER_test_context_123"
assert result.session_id == "test_context_123"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == []
assert isinstance(result.run_config, RunConfig)
# Verify convert_part wasn't called
mock_convert_part.assert_not_called()
def test_convert_a2a_request_none_context_id(self):
"""Test conversion when context_id is None."""
# Arrange
mock_part = Mock()
mock_message = Mock()
mock_message.parts = [mock_part]
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = None
request.call_context = None
request.metadata = {}
# Create proper genai_types.Part object instead of mock
mock_genai_part = genai_types.Part(text="test part")
mock_convert_part = Mock()
mock_convert_part.return_value = mock_genai_part
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "A2A_USER_None"
assert result.session_id is None
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [mock_genai_part]
assert isinstance(result.run_config, RunConfig)
def test_convert_a2a_request_no_auth(self):
"""Test conversion when no authentication is available."""
# Arrange
mock_part = Mock()
mock_message = Mock()
mock_message.parts = [mock_part]
request = Mock(spec=RequestContext)
request.message = mock_message
request.context_id = "session_123"
request.call_context = None
request.metadata = {}
# Create proper genai_types.Part object instead of mock
mock_genai_part = genai_types.Part(text="test part")
mock_convert_part = Mock()
mock_convert_part.return_value = mock_genai_part
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "A2A_USER_session_123"
assert result.session_id == "session_123"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [mock_genai_part]
assert isinstance(result.run_config, RunConfig)
class TestIntegration:
"""Integration test cases combining both functions."""
def test_end_to_end_conversion_with_auth_user(self):
"""Test end-to-end conversion with authenticated user."""
# Arrange
mock_user = Mock()
mock_user.user_name = "auth_user"
mock_call_context = Mock()
mock_call_context.user = mock_user
mock_part = Mock()
mock_message = Mock()
mock_message.parts = [mock_part]
request = Mock(spec=RequestContext)
request.call_context = mock_call_context
request.message = mock_message
request.context_id = "mysession"
request.metadata = {}
# Create proper genai_types.Part object instead of mock
mock_genai_part = genai_types.Part(text="test part")
mock_convert_part = Mock()
mock_convert_part.return_value = mock_genai_part
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert result.user_id == "auth_user" # Should use authenticated user
assert result.session_id == "mysession"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [mock_genai_part]
assert isinstance(result.run_config, RunConfig)
def test_end_to_end_conversion_with_fallback_user(self):
"""Test end-to-end conversion with fallback user ID."""
# Arrange
mock_part = Mock()
mock_message = Mock()
mock_message.parts = [mock_part]
request = Mock(spec=RequestContext)
request.call_context = None
request.message = mock_message
request.context_id = "test_session_456"
request.metadata = {}
# Create proper genai_types.Part object instead of mock
mock_genai_part = genai_types.Part(text="test part")
mock_convert_part = Mock()
mock_convert_part.return_value = mock_genai_part
# Act
result = convert_a2a_request_to_agent_run_request(
request, mock_convert_part
)
# Assert
assert result is not None
assert (
result.user_id == "A2A_USER_test_session_456"
) # Should fall back to context ID
assert result.session_id == "test_session_456"
assert isinstance(result.new_message, genai_types.Content)
assert result.new_message.role == "user"
assert result.new_message.parts == [mock_genai_part]
assert isinstance(result.run_config, RunConfig)
@@ -0,0 +1,682 @@
# 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
import json
from unittest.mock import Mock
from a2a.types import Message
from a2a.types import Part as A2APart
from a2a.types import Task
from a2a.types import TaskArtifactUpdateEvent
from google.adk.a2a import _compat
from google.adk.a2a.converters.part_converter import A2A_DATA_PART_END_TAG
from google.adk.a2a.converters.part_converter import A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY
from google.adk.a2a.converters.part_converter import A2A_DATA_PART_START_TAG
from google.adk.a2a.converters.part_converter import A2A_DATA_PART_TEXT_MIME_TYPE
from google.adk.a2a.converters.to_adk_event import convert_a2a_artifact_update_to_event
from google.adk.a2a.converters.to_adk_event import convert_a2a_message_to_event
from google.adk.a2a.converters.to_adk_event import convert_a2a_status_update_to_event
from google.adk.a2a.converters.to_adk_event import convert_a2a_task_to_event
from google.adk.a2a.converters.to_adk_event import MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_AUTH
from google.adk.a2a.converters.to_adk_event import MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_INPUT
from google.adk.a2a.converters.utils import _get_adk_metadata_key
from google.adk.agents.invocation_context import InvocationContext
from google.genai import types as genai_types
import pytest
def _make_a2a_part_for_test(metadata=None):
"""Returns a real proto Part on 1.x, or Mock(spec=A2APart) on 0.3."""
if _compat.IS_A2A_V1:
p = _compat.make_text_part("test")
if metadata:
_compat.set_part_metadata(p, metadata)
return p
else:
from unittest.mock import Mock
from a2a.types import TextPart
m = Mock(spec=A2APart)
m.root = Mock(spec=TextPart)
m.root.metadata = metadata or {}
return m
class TestToAdk:
"""Test suite for to_adk functions."""
def setup_method(self):
"""Set up test fixtures."""
self.mock_context = Mock(spec=InvocationContext)
self.mock_context.invocation_id = "test-invocation"
self.mock_context.branch = "test-branch"
def test_convert_a2a_message_to_event_success(self):
"""Test successful conversion of A2A message to Event."""
a2a_part = _make_a2a_part_for_test({})
message = Message(
message_id="msg-1", role=_compat.ROLE_USER, parts=[a2a_part]
)
mock_genai_part = genai_types.Part.from_text(text="hello")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_message_to_event(
message,
author="test-author",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.author == "test-author"
assert event.invocation_id == "test-invocation"
assert event.branch == "test-branch"
assert len(event.content.parts) == 1
assert event.content.parts[0] == mock_genai_part
def test_convert_a2a_message_to_event_none(self):
"""Test convert_a2a_message_to_event with None."""
with pytest.raises(ValueError, match="A2A message cannot be None"):
convert_a2a_message_to_event(None)
def test_convert_a2a_message_to_event_restores_actions_from_metadata(self):
"""Test A2A message conversion restores ADK actions metadata."""
a2a_part = _make_a2a_part_for_test({})
message = Message(
message_id="msg-1",
role=_compat.ROLE_USER,
parts=[a2a_part],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"saved_key": "saved-value"}
}
},
)
mock_genai_part = genai_types.Part.from_text(text="hello")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_message_to_event(
message,
author="test-author",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.actions.state_delta == {"saved_key": "saved-value"}
assert event.content is not None
assert event.content.parts[0] == mock_genai_part
def test_convert_a2a_message_to_event_returns_action_only_event(self):
"""Test A2A message conversion returns action-only events."""
message = Message(
message_id="msg-1",
role=_compat.ROLE_USER,
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"saved_key": "saved-value"}
}
},
)
event = convert_a2a_message_to_event(
message,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(),
)
assert event is not None
assert event.actions.state_delta == {"saved_key": "saved-value"}
assert event.content is None
def test_convert_a2a_task_to_event_success(self):
"""Test successful conversion of A2A task to Event."""
a2a_part = _make_a2a_part_for_test({})
task = Task(
id="task-1",
status=_compat.make_task_status(
_compat.TS_SUBMITTED, timestamp="2024-01-01T00:00:00Z"
),
context_id="context-1",
history=[
Message(
message_id="msg-1", role=_compat.ROLE_AGENT, parts=[a2a_part]
)
],
artifacts=[
_compat.make_artifact(
artifact_id="art-1", artifact_type="message", parts=[a2a_part]
)
],
)
mock_genai_part = genai_types.Part.from_text(text="task artifact text")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.author == "test-author"
assert event.invocation_id == "test-invocation"
assert len(event.content.parts) == 1
assert event.content.parts[0] == mock_genai_part
def test_convert_a2a_task_to_event_returns_action_only_event(self):
"""Test A2A task conversion returns action-only events."""
task = Task(
id="task-1",
status=_compat.make_task_status(
_compat.TS_SUBMITTED, timestamp="2024-01-01T00:00:00Z"
),
context_id="context-1",
artifacts=[
_compat.make_artifact(
artifact_id="art-1",
artifact_type="message",
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"saved_key": "saved-value"}
}
},
)
],
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(),
)
assert event is not None
assert event.actions.state_delta == {"saved_key": "saved-value"}
assert event.content is None
def test_convert_a2a_task_to_event_merges_actions_across_artifacts(self):
"""Test task conversion merges actions across artifact metadata."""
task = Task(
id="task-1",
status=_compat.make_task_status(
_compat.TS_SUBMITTED, timestamp="2024-01-01T00:00:00Z"
),
context_id="context-1",
artifacts=[
_compat.make_artifact(
artifact_id="art-1",
artifact_type="message",
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"first_key": "first-value"}
}
},
),
_compat.make_artifact(
artifact_id="art-2",
artifact_type="message",
parts=[],
metadata={},
),
],
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(),
)
assert event is not None
assert event.actions.state_delta == {"first_key": "first-value"}
assert event.content is None
def test_convert_a2a_task_to_event_overwrites_nested_state_delta_values(self):
"""Test task conversion preserves top-level state overwrite semantics."""
task = Task(
id="task-1",
status=_compat.make_task_status(
_compat.TS_SUBMITTED, timestamp="2024-01-01T00:00:00Z"
),
context_id="context-1",
artifacts=[
_compat.make_artifact(
artifact_id="art-1",
artifact_type="message",
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {
"settings": {
"theme": "light",
"language": "en",
}
}
}
},
),
_compat.make_artifact(
artifact_id="art-2",
artifact_type="message",
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"settings": {"theme": "dark"}}
}
},
),
],
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(),
)
assert event is not None
assert event.actions.state_delta == {"settings": {"theme": "dark"}}
assert event.content is None
def test_convert_a2a_task_to_event_merges_status_and_artifact_actions(self):
"""Test task conversion merges status and artifact actions."""
a2a_part = _make_a2a_part_for_test({})
task = Task(
id="task-1",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="2024-01-01T00:00:00Z",
message=Message(
message_id="msg-1",
role=_compat.ROLE_AGENT,
parts=[a2a_part],
metadata={
_get_adk_metadata_key("actions"): {
"transferToAgent": "agent-2"
}
},
),
),
context_id="context-1",
artifacts=[
_compat.make_artifact(
artifact_id="art-1",
artifact_type="message",
parts=[],
metadata={
_get_adk_metadata_key("actions"): {
"stateDelta": {"saved_key": "saved-value"}
}
},
)
],
)
mock_genai_part = genai_types.Part.from_text(text="need input")
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(return_value=[mock_genai_part]),
)
assert event is not None
assert event.actions.state_delta == {"saved_key": "saved-value"}
assert event.actions.transfer_to_agent == "agent-2"
assert event.content is not None
assert (
event.content.parts[0].function_call.name
== MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_INPUT
)
assert (
event.content.parts[0].function_call.args["input_required"]
== "need input"
)
def test_convert_a2a_task_to_event_auth_required_uses_auth_args_key(self):
"""Test auth-required state populates the function call with auth args."""
a2a_part = _make_a2a_part_for_test({})
task = _compat.make_task(
id="task-1",
context_id="context-1",
kind="task",
status=_compat.make_task_status(
_compat.TS_AUTH_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[a2a_part],
),
),
)
mock_genai_part = genai_types.Part.from_text(text="need auth")
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(return_value=[mock_genai_part]),
)
assert event is not None
assert event.content is not None
assert (
event.content.parts[0].function_call.name
== MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_AUTH
)
# auth_required state should populate the auth_required arg key, not
# input_required.
assert (
event.content.parts[0].function_call.args["auth_required"]
== "need auth"
)
assert "input_required" not in event.content.parts[0].function_call.args
def test_convert_a2a_task_to_event_multiple_parts_replaces_last_text(self):
"""Test converting A2A task with multiple text parts, only replacing the last text."""
part1 = _make_a2a_part_for_test({})
part2 = _make_a2a_part_for_test({})
task = _compat.make_task(
id="task-1",
context_id="context-1",
kind="task",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[part1, part2],
),
),
)
mock_genai_part_1 = genai_types.Part.from_text(text="Part 1")
mock_genai_part_2 = genai_types.Part.from_text(text="Part 2")
part_converter_mock = Mock()
part_converter_mock.side_effect = [[mock_genai_part_1], [mock_genai_part_2]]
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=part_converter_mock,
)
assert event is not None
assert event.content is not None
assert len(event.content.parts) == 2
assert event.content.parts[0].text == "Part 1"
assert (
event.content.parts[1].function_call.name
== MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_INPUT
)
def test_convert_a2a_task_to_event_no_text_parts(self):
"""Test converting A2A task with no text parts should not inject function call."""
# A real non-text (data) part; converter output is mocked below.
part1 = _compat.make_data_part(data={"placeholder": True})
task = _compat.make_task(
id="task-1",
context_id="context-1",
kind="task",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[part1],
),
),
)
mock_image_part = genai_types.Part(
inline_data=genai_types.Blob(mime_type="image/jpeg", data=b"fake")
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(return_value=[mock_image_part]),
)
assert event is not None
assert event.content is not None
assert event.content.parts == [mock_image_part]
def test_convert_a2a_task_to_event_data_part_input_required(self):
"""Input-required prompt carried in a data part becomes a function call."""
# A real non-text (data) part; converter output is mocked below.
part1 = _compat.make_data_part(data={"placeholder": True})
task = _compat.make_task(
id="task-1",
context_id="context-1",
kind="task",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[part1],
),
),
)
prompt = {
"id": "abc123",
"text": "Please confirm this action. Do you want to continue?",
}
data_part_json = json.dumps({"data": prompt, "kind": "data"}).encode(
"utf-8"
)
mock_data_blob_part = genai_types.Part(
inline_data=genai_types.Blob(
mime_type=A2A_DATA_PART_TEXT_MIME_TYPE,
data=A2A_DATA_PART_START_TAG
+ data_part_json
+ A2A_DATA_PART_END_TAG,
)
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(return_value=[mock_data_blob_part]),
)
assert event is not None
assert event.content is not None
assert (
event.content.parts[0].function_call.name
== MOCK_FUNCTION_CALL_FOR_REQUIRED_USER_INPUT
)
assert event.content.parts[0].function_call.args["input_required"] == prompt
assert event.long_running_tool_ids
def test_convert_a2a_task_to_event_data_part_malformed_json(self):
"""A malformed data-part blob is left untouched (no crash, no fc)."""
# A real non-text (data) part; converter output is mocked below.
part1 = _compat.make_data_part(data={"placeholder": True})
task = _compat.make_task(
id="task-1",
context_id="context-1",
kind="task",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[part1],
),
),
)
mock_bad_blob_part = genai_types.Part(
inline_data=genai_types.Blob(
mime_type=A2A_DATA_PART_TEXT_MIME_TYPE,
data=A2A_DATA_PART_START_TAG + b"not-json" + A2A_DATA_PART_END_TAG,
)
)
event = convert_a2a_task_to_event(
task,
author="test-author",
invocation_context=self.mock_context,
part_converter=Mock(return_value=[mock_bad_blob_part]),
)
assert event is not None
assert event.content is not None
assert event.content.parts == [mock_bad_blob_part]
assert not event.long_running_tool_ids
def test_convert_a2a_status_update_to_event_success(self):
"""Test successful conversion of A2A status update to Event."""
a2a_part = _make_a2a_part_for_test({
_get_adk_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY): True
})
update = _compat.make_task_status_update_event(
task_id="task-1",
status=_compat.make_task_status(
_compat.TS_INPUT_REQUIRED,
timestamp="now",
message=Message(
message_id="m1",
role=_compat.ROLE_AGENT,
parts=[a2a_part],
),
),
context_id="context-1",
final=False,
)
mock_genai_part = genai_types.Part(
function_call=genai_types.FunctionCall(
name="status update text", args={"arg": "value"}, id="call-1"
)
)
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_status_update_to_event(
update,
author="test-author",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.author == "test-author"
assert event.invocation_id == "test-invocation"
assert len(event.content.parts) == 1
assert event.content.parts[0] == mock_genai_part
def test_convert_a2a_status_update_to_event_none(self):
"""Test convert_a2a_status_update_to_event with None."""
with pytest.raises(ValueError, match="A2A status update cannot be None"):
convert_a2a_status_update_to_event(None)
def test_convert_a2a_artifact_update_to_event_success(self):
"""Test successful conversion of A2A artifact update to Event."""
a2a_part = _make_a2a_part_for_test({})
update = TaskArtifactUpdateEvent(
task_id="task-1",
artifact=_compat.make_artifact(
artifact_id="art-1", artifact_type="message", parts=[a2a_part]
),
append=True,
context_id="context-1",
last_chunk=False,
)
mock_genai_part = genai_types.Part.from_text(text="artifact chunk text")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_artifact_update_to_event(
update,
author="test-author",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.author == "test-author"
assert event.invocation_id == "test-invocation"
assert event.partial is True
assert len(event.content.parts) == 1
assert event.content.parts[0] == mock_genai_part
def test_convert_a2a_artifact_update_to_event_none(self):
"""Test convert_a2a_artifact_update_to_event with None."""
with pytest.raises(ValueError, match="A2A artifact update cannot be None"):
convert_a2a_artifact_update_to_event(None)
def test_convert_a2a_message_to_event_user_role(self) -> None:
"""Test that A2A user role maps to GenAI content role 'user'."""
a2a_part = _make_a2a_part_for_test({})
message = Message(
message_id="msg-1", role=_compat.ROLE_USER, parts=[a2a_part]
)
mock_genai_part = genai_types.Part.from_text(text="hello from user")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_message_to_event(
message,
author="user",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.content.role == "user"
def test_convert_a2a_message_to_event_agent_role(self) -> None:
"""Test that A2A agent role maps to GenAI content role 'model'."""
a2a_part = _make_a2a_part_for_test({})
message = Message(
message_id="msg-1", role=_compat.ROLE_AGENT, parts=[a2a_part]
)
mock_genai_part = genai_types.Part.from_text(text="hello from agent")
mock_part_converter = Mock(return_value=[mock_genai_part])
event = convert_a2a_message_to_event(
message,
author="test-agent",
invocation_context=self.mock_context,
part_converter=mock_part_converter,
)
assert event.content.role == "model"
@@ -0,0 +1,204 @@
# 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 google.adk.a2a.converters.utils import _from_a2a_context_id
from google.adk.a2a.converters.utils import _get_adk_metadata_key
from google.adk.a2a.converters.utils import _to_a2a_context_id
from google.adk.a2a.converters.utils import ADK_CONTEXT_ID_PREFIX
from google.adk.a2a.converters.utils import ADK_METADATA_KEY_PREFIX
import pytest
class TestUtilsFunctions:
"""Test suite for utils module functions."""
def test_get_adk_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}"
def test_get_adk_metadata_key_empty_string(self):
"""Test metadata key generation with empty string."""
with pytest.raises(
ValueError, match="Metadata key cannot be empty or None"
):
_get_adk_metadata_key("")
def test_get_adk_metadata_key_none(self):
"""Test metadata key generation with None."""
with pytest.raises(
ValueError, match="Metadata key cannot be empty or None"
):
_get_adk_metadata_key(None)
def test_get_adk_metadata_key_whitespace(self):
"""Test metadata key generation with whitespace string."""
key = " "
result = _get_adk_metadata_key(key)
assert result == f"{ADK_METADATA_KEY_PREFIX}{key}"
def test_to_a2a_context_id_success(self):
"""Test successful context ID generation."""
app_name = "test-app"
user_id = "test-user"
session_id = "test-session"
result = _to_a2a_context_id(app_name, user_id, session_id)
expected = f"{ADK_CONTEXT_ID_PREFIX}/test-app/test-user/test-session"
assert result == expected
def test_to_a2a_context_id_empty_app_name(self):
"""Test context ID generation with empty app name."""
with pytest.raises(
ValueError,
match=(
"All parameters \\(app_name, user_id, session_id\\) must be"
" non-empty"
),
):
_to_a2a_context_id("", "user", "session")
def test_to_a2a_context_id_empty_user_id(self):
"""Test context ID generation with empty user ID."""
with pytest.raises(
ValueError,
match=(
"All parameters \\(app_name, user_id, session_id\\) must be"
" non-empty"
),
):
_to_a2a_context_id("app", "", "session")
def test_to_a2a_context_id_empty_session_id(self):
"""Test context ID generation with empty session ID."""
with pytest.raises(
ValueError,
match=(
"All parameters \\(app_name, user_id, session_id\\) must be"
" non-empty"
),
):
_to_a2a_context_id("app", "user", "")
def test_to_a2a_context_id_none_values(self):
"""Test context ID generation with None values."""
with pytest.raises(
ValueError,
match=(
"All parameters \\(app_name, user_id, session_id\\) must be"
" non-empty"
),
):
_to_a2a_context_id(None, "user", "session")
def test_to_a2a_context_id_special_characters(self):
"""Test context ID generation with special characters."""
app_name = "test-app@2024"
user_id = "user_123"
session_id = "session-456"
result = _to_a2a_context_id(app_name, user_id, session_id)
expected = f"{ADK_CONTEXT_ID_PREFIX}/test-app@2024/user_123/session-456"
assert result == expected
def test_from_a2a_context_id_success(self):
"""Test successful context ID parsing."""
context_id = f"{ADK_CONTEXT_ID_PREFIX}/test-app/test-user/test-session"
app_name, user_id, session_id = _from_a2a_context_id(context_id)
assert app_name == "test-app"
assert user_id == "test-user"
assert session_id == "test-session"
def test_from_a2a_context_id_none_input(self):
"""Test context ID parsing with None input."""
result = _from_a2a_context_id(None)
assert result == (None, None, None)
def test_from_a2a_context_id_empty_string(self):
"""Test context ID parsing with empty string."""
result = _from_a2a_context_id("")
assert result == (None, None, None)
def test_from_a2a_context_id_invalid_prefix(self):
"""Test context ID parsing with invalid prefix."""
context_id = "INVALID/test-app/test-user/test-session"
result = _from_a2a_context_id(context_id)
assert result == (None, None, None)
def test_from_a2a_context_id_too_few_parts(self):
"""Test context ID parsing with too few parts."""
context_id = f"{ADK_CONTEXT_ID_PREFIX}/test-app/test-user"
result = _from_a2a_context_id(context_id)
assert result == (None, None, None)
def test_from_a2a_context_id_too_many_parts(self):
"""Test context ID parsing with too many parts."""
context_id = (
f"{ADK_CONTEXT_ID_PREFIX}/test-app/test-user/test-session/extra"
)
result = _from_a2a_context_id(context_id)
assert result == (None, None, None)
def test_from_a2a_context_id_empty_components(self):
"""Test context ID parsing with empty components."""
context_id = f"{ADK_CONTEXT_ID_PREFIX}//test-user/test-session"
result = _from_a2a_context_id(context_id)
assert result == (None, None, None)
def test_from_a2a_context_id_no_dollar_separator(self):
"""Test context ID parsing without dollar separators."""
context_id = f"{ADK_CONTEXT_ID_PREFIX}-test-app-test-user-test-session"
result = _from_a2a_context_id(context_id)
assert result == (None, None, None)
def test_roundtrip_context_id(self):
"""Test roundtrip conversion: to -> from."""
app_name = "test-app"
user_id = "test-user"
session_id = "test-session"
# Convert to context ID
context_id = _to_a2a_context_id(app_name, user_id, session_id)
# Convert back
parsed_app, parsed_user, parsed_session = _from_a2a_context_id(context_id)
assert parsed_app == app_name
assert parsed_user == user_id
assert parsed_session == session_id
def test_from_a2a_context_id_special_characters(self):
"""Test context ID parsing with special characters."""
context_id = f"{ADK_CONTEXT_ID_PREFIX}/test-app@2024/user_123/session-456"
app_name, user_id, session_id = _from_a2a_context_id(context_id)
assert app_name == "test-app@2024"
assert user_id == "user_123"
assert session_id == "session-456"