# 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"