@@ -1922,6 +1922,203 @@ async def test_handle_a2a_response_impl_handles_client_error(self):
19221922 assert result .branch == self .mock_context .branch
19231923
19241924
1925+ class TestRemoteA2aAgentNoneConverterResults :
1926+ """Regression tests for None converter results in both legacy and v2 handlers.
1927+
1928+ Converters can legitimately return None for messages/tasks with no convertible
1929+ parts, metadata-only events, or empty status updates. The handlers must not
1930+ crash with AttributeError when this happens.
1931+ """
1932+
1933+ def setup_method (self ):
1934+ """Setup test fixtures."""
1935+ from google .adk .a2a .agent .config import A2aRemoteAgentConfig
1936+
1937+ self .agent_card = create_test_agent_card ()
1938+
1939+ # Legacy handler agent
1940+ self .mock_a2a_part_converter = Mock ()
1941+ self .legacy_agent = RemoteA2aAgent (
1942+ name = "test_agent" ,
1943+ agent_card = self .agent_card ,
1944+ a2a_part_converter = self .mock_a2a_part_converter ,
1945+ )
1946+
1947+ # V2 handler agent
1948+ self .mock_config = Mock (spec = A2aRemoteAgentConfig )
1949+ self .mock_config .a2a_part_converter = Mock ()
1950+ self .mock_config .a2a_task_converter = Mock ()
1951+ self .mock_config .a2a_status_update_converter = Mock ()
1952+ self .mock_config .a2a_artifact_update_converter = Mock ()
1953+ self .mock_config .a2a_message_converter = Mock ()
1954+ self .mock_config .request_interceptors = None
1955+ self .v2_agent = RemoteA2aAgent (
1956+ name = "test_agent" ,
1957+ agent_card = self .agent_card ,
1958+ config = self .mock_config ,
1959+ )
1960+
1961+ # Shared mock context
1962+ self .mock_session = Mock (spec = Session )
1963+ self .mock_session .id = "session-123"
1964+ self .mock_session .events = []
1965+
1966+ self .mock_context = Mock (spec = InvocationContext )
1967+ self .mock_context .session = self .mock_session
1968+ self .mock_context .invocation_id = "invocation-123"
1969+ self .mock_context .branch = "main"
1970+
1971+ # --- V2 handler regression tests ---
1972+
1973+ @pytest .mark .asyncio
1974+ async def test_v2_message_converter_returns_none (self ):
1975+ """V2 handler must not crash when message converter returns None."""
1976+ mock_msg = Mock (spec = A2AMessage )
1977+ mock_msg .metadata = {}
1978+ mock_msg .context_id = None
1979+
1980+ self .mock_config .a2a_message_converter .return_value = None
1981+
1982+ result = await self .v2_agent ._handle_a2a_response_v2 (
1983+ mock_msg , self .mock_context
1984+ )
1985+
1986+ assert result is None
1987+ self .mock_config .a2a_message_converter .assert_called_once ()
1988+
1989+ @pytest .mark .asyncio
1990+ async def test_v2_message_converter_returns_none_with_context_id (self ):
1991+ """V2 handler returns None even when message has a context_id."""
1992+ mock_msg = Mock (spec = A2AMessage )
1993+ mock_msg .metadata = {}
1994+ mock_msg .context_id = "ctx-should-not-be-accessed"
1995+
1996+ self .mock_config .a2a_message_converter .return_value = None
1997+
1998+ result = await self .v2_agent ._handle_a2a_response_v2 (
1999+ mock_msg , self .mock_context
2000+ )
2001+
2002+ assert result is None
2003+
2004+ @pytest .mark .asyncio
2005+ async def test_v2_task_converter_returns_none (self ):
2006+ """V2 handler must not crash when task converter returns None."""
2007+ mock_task = Mock (spec = A2ATask )
2008+ mock_task .id = "task-123"
2009+ mock_task .context_id = "ctx-123"
2010+
2011+ self .mock_config .a2a_task_converter .return_value = None
2012+
2013+ result = await self .v2_agent ._handle_a2a_response_v2 (
2014+ (mock_task , None ), self .mock_context
2015+ )
2016+
2017+ assert result is None
2018+
2019+ @pytest .mark .asyncio
2020+ async def test_v2_status_update_converter_returns_none (self ):
2021+ """V2 handler must not crash when status update converter returns None."""
2022+ mock_task = Mock (spec = A2ATask )
2023+ mock_task .id = "task-123"
2024+ mock_task .context_id = None
2025+
2026+ mock_update = Mock (spec = TaskStatusUpdateEvent )
2027+
2028+ self .mock_config .a2a_status_update_converter .return_value = None
2029+
2030+ result = await self .v2_agent ._handle_a2a_response_v2 (
2031+ (mock_task , mock_update ), self .mock_context
2032+ )
2033+
2034+ assert result is None
2035+
2036+ # --- Legacy handler regression tests ---
2037+
2038+ @pytest .mark .asyncio
2039+ async def test_legacy_message_converter_returns_none (self ):
2040+ """Legacy handler must not crash when message converter returns None."""
2041+ mock_msg = Mock (spec = A2AMessage )
2042+ mock_msg .context_id = "context-123"
2043+
2044+ with patch (
2045+ "google.adk.agents.remote_a2a_agent.convert_a2a_message_to_event"
2046+ ) as mock_convert :
2047+ mock_convert .return_value = None
2048+
2049+ result = await self .legacy_agent ._handle_a2a_response (
2050+ mock_msg , self .mock_context
2051+ )
2052+
2053+ assert result is None
2054+ mock_convert .assert_called_once ()
2055+
2056+ @pytest .mark .asyncio
2057+ async def test_legacy_task_converter_returns_none_no_update (self ):
2058+ """Legacy handler must not crash when task converter returns None (no update)."""
2059+ mock_task = Mock (spec = A2ATask )
2060+ mock_task .id = "task-123"
2061+ mock_task .context_id = None
2062+ mock_task .status = Mock ()
2063+ mock_task .status .state = TaskState .completed
2064+
2065+ with patch (
2066+ "google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
2067+ ) as mock_convert :
2068+ mock_convert .return_value = None
2069+
2070+ result = await self .legacy_agent ._handle_a2a_response (
2071+ (mock_task , None ), self .mock_context
2072+ )
2073+
2074+ assert result is None
2075+
2076+ @pytest .mark .asyncio
2077+ async def test_legacy_message_converter_returns_none_status_update (self ):
2078+ """Legacy handler must not crash when message converter returns None for status update."""
2079+ mock_task = Mock (spec = A2ATask )
2080+ mock_task .id = "task-123"
2081+ mock_task .context_id = "ctx-123"
2082+
2083+ mock_update = Mock (spec = TaskStatusUpdateEvent )
2084+ mock_update .status = Mock ()
2085+ mock_update .status .message = Mock ()
2086+ mock_update .status .state = TaskState .working
2087+
2088+ with patch (
2089+ "google.adk.agents.remote_a2a_agent.convert_a2a_message_to_event"
2090+ ) as mock_convert :
2091+ mock_convert .return_value = None
2092+
2093+ result = await self .legacy_agent ._handle_a2a_response (
2094+ (mock_task , mock_update ), self .mock_context
2095+ )
2096+
2097+ assert result is None
2098+
2099+ @pytest .mark .asyncio
2100+ async def test_legacy_task_converter_returns_none_artifact_update (self ):
2101+ """Legacy handler must not crash when task converter returns None for artifact update."""
2102+ mock_task = Mock (spec = A2ATask )
2103+ mock_task .id = "task-123"
2104+ mock_task .context_id = None
2105+
2106+ mock_update = Mock (spec = TaskArtifactUpdateEvent )
2107+ mock_update .append = False
2108+ mock_update .last_chunk = True
2109+
2110+ with patch (
2111+ "google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
2112+ ) as mock_convert :
2113+ mock_convert .return_value = None
2114+
2115+ result = await self .legacy_agent ._handle_a2a_response (
2116+ (mock_task , mock_update ), self .mock_context
2117+ )
2118+
2119+ assert result is None
2120+
2121+
19252122class TestRemoteA2aAgentExecution :
19262123 """Test agent execution functionality."""
19272124
0 commit comments