2222 AgentCapabilities ,
2323 AgentCard ,
2424 AgentInterface ,
25+ CancelTaskRequest ,
26+ DeleteTaskPushNotificationConfigRequest ,
27+ GetTaskPushNotificationConfigRequest ,
2528 GetTaskRequest ,
29+ ListTaskPushNotificationConfigsRequest ,
2630 ListTasksRequest ,
2731 Message ,
2832 Part ,
2933 Role ,
3034 SendMessageConfiguration ,
3135 SendMessageRequest ,
36+ SubscribeToTaskRequest ,
3237 TaskState ,
3338 a2a_pb2_grpc ,
3439)
@@ -273,6 +278,22 @@ def transport_setups(request) -> ClientSetup:
273278 return request .getfixturevalue (request .param )
274279
275280
281+ @pytest .fixture (
282+ params = [
283+ pytest .param ('jsonrpc_setup' , id = 'JSON-RPC' ),
284+ pytest .param ('grpc_setup' , id = 'gRPC' ),
285+ ]
286+ )
287+ def rpc_transport_setups (request ) -> ClientSetup :
288+ """Parametrized fixture for RPC transports only (excludes REST).
289+
290+ REST encodes some required fields in URL paths, so empty-field validation
291+ tests hit routing errors before reaching the handler. JSON-RPC and gRPC
292+ send the full request message, allowing server-side validation to work.
293+ """
294+ return request .getfixturevalue (request .param )
295+
296+
276297@pytest .mark .asyncio
277298async def test_end_to_end_send_message_blocking (transport_setups ):
278299 client = transport_setups .client
@@ -569,9 +590,15 @@ async def test_end_to_end_input_required(transport_setups):
569590 SendMessageRequest (message = Message ()),
570591 {'message.message_id' , 'message.role' , 'message.parts' },
571592 ),
593+ (
594+ SendMessageRequest (
595+ message = Message (message_id = 'm1' , role = Role .ROLE_USER )
596+ ),
597+ {'message.parts' },
598+ ),
572599 ],
573600)
574- async def test_end_to_end_validation_errors (
601+ async def test_end_to_end_send_message_validation_errors (
575602 transport_setups ,
576603 empty_request : SendMessageRequest ,
577604 expected_fields : set [str ],
@@ -586,3 +613,67 @@ async def test_end_to_end_validation_errors(
586613 assert {e ['field' ] for e in errors } == expected_fields
587614
588615 await client .close ()
616+
617+
618+ @pytest .mark .asyncio
619+ @pytest .mark .parametrize (
620+ 'method, invalid_request, expected_fields' ,
621+ [
622+ (
623+ 'get_task' ,
624+ GetTaskRequest (),
625+ {'id' },
626+ ),
627+ (
628+ 'cancel_task' ,
629+ CancelTaskRequest (),
630+ {'id' },
631+ ),
632+ (
633+ 'get_task_push_notification_config' ,
634+ GetTaskPushNotificationConfigRequest (),
635+ {'task_id' , 'id' },
636+ ),
637+ (
638+ 'list_task_push_notification_configs' ,
639+ ListTaskPushNotificationConfigsRequest (),
640+ {'task_id' },
641+ ),
642+ (
643+ 'delete_task_push_notification_config' ,
644+ DeleteTaskPushNotificationConfigRequest (),
645+ {'task_id' , 'id' },
646+ ),
647+ ],
648+ )
649+ async def test_end_to_end_unary_validation_errors (
650+ rpc_transport_setups ,
651+ method : str ,
652+ invalid_request ,
653+ expected_fields : set [str ],
654+ ) -> None :
655+ client = rpc_transport_setups .client
656+
657+ with pytest .raises (InvalidParamsError ) as exc_info :
658+ await getattr (client , method )(request = invalid_request )
659+
660+ errors = exc_info .value .data .get ('errors' , [])
661+ assert {e ['field' ] for e in errors } == expected_fields
662+
663+ await client .close ()
664+
665+
666+ @pytest .mark .asyncio
667+ async def test_end_to_end_subscribe_validation_error (
668+ rpc_transport_setups ,
669+ ) -> None :
670+ client = rpc_transport_setups .client
671+
672+ with pytest .raises (InvalidParamsError ) as exc_info :
673+ async for _ in client .subscribe (request = SubscribeToTaskRequest ()):
674+ pass
675+
676+ errors = exc_info .value .data .get ('errors' , [])
677+ assert {e ['field' ] for e in errors } == {'id' }
678+
679+ await client .close ()
0 commit comments