33import grpc
44import pytest
55
6+ from google .protobuf import any_pb2
7+ from google .rpc import error_details_pb2 , status_pb2
8+
69from a2a .client .middleware import ClientCallContext
710from a2a .client .transports .grpc import GrpcTransport
811from a2a .extensions .common import HTTP_EXTENSION_HEADER
912from a2a .utils .constants import VERSION_HEADER , PROTOCOL_VERSION_CURRENT
13+ from a2a .utils .errors import A2A_ERROR_REASONS
1014from a2a .types import a2a_pb2
1115from a2a .types .a2a_pb2 import (
1216 AgentCapabilities ,
@@ -257,16 +261,15 @@ async def test_send_message_with_timeout_context(
257261
258262@pytest .mark .parametrize ('error_cls' , list (JSON_RPC_ERROR_CODE_MAP .keys ()))
259263@pytest .mark .asyncio
260- async def test_grpc_mapped_errors (
264+ async def test_grpc_mapped_errors_legacy (
261265 grpc_transport : GrpcTransport ,
262266 mock_grpc_stub : AsyncMock ,
263267 sample_message_send_params : SendMessageRequest ,
264268 error_cls ,
265269) -> None :
266- """Test handling of mapped gRPC error responses."""
270+ """Test handling of legacy gRPC error responses."""
267271 error_details = f'{ error_cls .__name__ } : Mapped Error'
268272
269- # We must trigger it from a standard transport method call, for example `send_message`.
270273 mock_grpc_stub .SendMessage .side_effect = grpc .aio .AioRpcError (
271274 code = grpc .StatusCode .INTERNAL ,
272275 initial_metadata = grpc .aio .Metadata (),
@@ -278,6 +281,46 @@ async def test_grpc_mapped_errors(
278281 await grpc_transport .send_message (sample_message_send_params )
279282
280283
284+ @pytest .mark .parametrize ('error_cls' , list (JSON_RPC_ERROR_CODE_MAP .keys ()))
285+ @pytest .mark .asyncio
286+ async def test_grpc_mapped_errors_rich (
287+ grpc_transport : GrpcTransport ,
288+ mock_grpc_stub : AsyncMock ,
289+ sample_message_send_params : SendMessageRequest ,
290+ error_cls ,
291+ ) -> None :
292+ """Test handling of rich gRPC error responses with Status metadata."""
293+
294+ reason = A2A_ERROR_REASONS .get (error_cls , 'UNKNOWN_ERROR' )
295+
296+ error_info = error_details_pb2 .ErrorInfo (
297+ reason = reason ,
298+ domain = 'a2a-protocol.org' ,
299+ )
300+
301+ error_details = f'{ error_cls .__name__ } : Mapped Error'
302+ status = status_pb2 .Status (
303+ code = grpc .StatusCode .INTERNAL .value [0 ], message = error_details
304+ )
305+ detail = any_pb2 .Any ()
306+ detail .Pack (error_info )
307+ status .details .append (detail )
308+
309+ mock_grpc_stub .SendMessage .side_effect = grpc .aio .AioRpcError (
310+ code = grpc .StatusCode .INTERNAL ,
311+ initial_metadata = grpc .aio .Metadata (),
312+ trailing_metadata = grpc .aio .Metadata (
313+ ('grpc-status-details-bin' , status .SerializeToString ()),
314+ ),
315+ details = 'A generic error message' ,
316+ )
317+
318+ with pytest .raises (error_cls ) as excinfo :
319+ await grpc_transport .send_message (sample_message_send_params )
320+
321+ assert str (excinfo .value ) == error_details
322+
323+
281324@pytest .mark .asyncio
282325async def test_send_message_message_response (
283326 grpc_transport : GrpcTransport ,
0 commit comments