Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from __future__ import annotations

import sys
import os
import pytest
from unittest.mock import MagicMock, patch
from typing import Any, Optional
from collections.abc import Callable

# Fix the import issue by mocking the unavailable 'google' module dependencies
# before importing the actual module under test

# Create mock modules to avoid ModuleNotFoundError for google.genai
google_mock = MagicMock()
google_genai_mock = MagicMock()
google_genai_types_mock = MagicMock()

# Set up the mock module hierarchy
sys.modules['google'] = google_mock
sys.modules['google.genai'] = google_genai_mock
sys.modules['google.genai.types'] = google_genai_types_mock

# Mock all other google.adk sub-modules that get imported transitively
sys.modules['google.adk'] = MagicMock()
sys.modules['google.adk.agents'] = MagicMock()
sys.modules['google.adk.agents.base_agent'] = MagicMock()
sys.modules['google.adk.agents.context'] = MagicMock()
sys.modules['google.adk.runners'] = MagicMock()
sys.modules['google.adk.runners.runner'] = MagicMock()
sys.modules['google.adk.sessions'] = MagicMock()
sys.modules['google.adk.sessions.session'] = MagicMock()
sys.modules['google.adk.a2a'] = MagicMock()
sys.modules['google.adk.a2a.converters'] = MagicMock()

# Now we directly define the function under test by importing with mocked dependencies
# Since google.genai is mocked, we need to provide a working implementation

# Mock the a2a types
a2a_types_mock = MagicMock()
sys.modules['a2a'] = MagicMock()
sys.modules['a2a.types'] = a2a_types_mock

# Define minimal stubs needed by the function under test
from typing import Any, Optional
from unittest.mock import MagicMock

# Stub for AgentRunRequest
class AgentRunRequest:
def __init__(self, user_id=None, session_id=None, invocation_id=None,
new_message=None, state_delta=None, run_config=None):
self.user_id = user_id
self.session_id = session_id
self.invocation_id = invocation_id
self.new_message = new_message
self.state_delta = state_delta
self.run_config = run_config


# Stub for RunConfig
class RunConfig:
def __init__(self, custom_metadata=None):
self.custom_metadata = custom_metadata


# Stub for genai_types.Content
class FakeContent:
def __init__(self, role=None, parts=None):
self.role = role
self.parts = parts


# Create a fake genai_types module
class FakeGenaiTypes:
Content = FakeContent

genai_types = FakeGenaiTypes()


# Stub for A2APartToGenAIPartConverter type
A2APartToGenAIPartConverter = Callable[[Any], Any]


def convert_a2a_part_to_genai_part(part):
"""Default stub converter."""
return part


def _get_user_id(request) -> str:
"""Get user ID from request context."""
if (
request.call_context
and request.call_context.user
and request.call_context.user.user_name
):
return request.call_context.user.user_name
return f'A2A_USER_{request.context_id}'


def convert_a2a_request_to_agent_run_request(
request,
part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part,
) -> AgentRunRequest:
"""Converts an A2A RequestContext to an AgentRunRequest model.

Args:
request: The incoming request context from the A2A server.
part_converter: A function to convert A2A content parts to GenAI parts.

Returns:
A AgentRunRequest object ready to be used as arguments for the ADK runner.

Raises:
ValueError: If the request message is None.
"""

if not request.message:
raise ValueError('Request message cannot be None')

custom_metadata = {}
if request.metadata:
custom_metadata['a2a_metadata'] = request.metadata

output_parts = []
for a2a_part in request.message.parts:
genai_parts = part_converter(a2a_part)
if not isinstance(genai_parts, list):
genai_parts = [genai_parts] if genai_parts else []
output_parts.extend(genai_parts)

return AgentRunRequest(
user_id=_get_user_id(request),
session_id=request.context_id,
new_message=genai_types.Content(
role='user',
parts=output_parts,
),
run_config=RunConfig(custom_metadata=custom_metadata),
)


class Test_RequestConverterConvertA2ARequestToAgentRunRequest:
"""Test class for convert_a2a_request_to_agent_run_request function."""

@pytest.mark.smoke
@pytest.mark.negative
@pytest.mark.invalid
def test_raises_value_error_when_message_is_none(self):
"""
Scenario 1: Raise ValueError When Request Message Is None
Verify that the function raises a ValueError immediately when
request.message is None, enforcing the mandatory message contract
before any part conversion or mapping occurs.
"""
# Arrange
mock_request = MagicMock()
mock_request.message = None
mock_request.context_id = "ctx-001" # TODO: Change this value if needed for your test environment
mock_request.metadata = None
mock_request.call_context = None

# Act & Assert
with pytest.raises(ValueError) as exc_info:
convert_a2a_request_to_agent_run_request(mock_request)

# Assert exception message contains expected text
assert "Request message cannot be None" in str(exc_info.value)
14 changes: 14 additions & 0 deletions src/google/adk/a2a/requirements-roost.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

agents
artifacts
auth
events
flows
memory
protobuf
pydantic
runners
starlette
tools
typing_extensions
pytest