88 BasePushNotificationSender ,
99)
1010from a2a .types import (
11+ PushNotificationAuthenticationInfo ,
1112 PushNotificationConfig ,
1213 Task ,
1314 TaskState ,
@@ -29,8 +30,11 @@ def create_sample_push_config(
2930 url : str = 'http://example.com/callback' ,
3031 config_id : str = 'cfg1' ,
3132 token : str | None = None ,
33+ authentication : PushNotificationAuthenticationInfo | None = None ,
3234) -> PushNotificationConfig :
33- return PushNotificationConfig (id = config_id , url = url , token = token )
35+ return PushNotificationConfig (
36+ id = config_id , url = url , token = token , authentication = authentication
37+ )
3438
3539
3640class TestBasePushNotificationSender (unittest .IsolatedAsyncioTestCase ):
@@ -92,6 +96,90 @@ async def test_send_notification_with_token_success(self) -> None:
9296 )
9397 mock_response .raise_for_status .assert_called_once ()
9498
99+ async def test_send_notification_with_bearer_authentication (self ) -> None :
100+ task_id = 'task_send_bearer_auth'
101+ task_data = create_sample_task (task_id = task_id )
102+ auth_info = PushNotificationAuthenticationInfo (
103+ schemes = ['Bearer' ], credentials = 'test-jwt-token'
104+ )
105+ config = create_sample_push_config (
106+ url = 'http://notify.me/here' ,
107+ token = 'unique_token' ,
108+ authentication = auth_info ,
109+ )
110+ self .mock_config_store .get_info .return_value = [config ]
111+
112+ mock_response = AsyncMock (spec = httpx .Response )
113+ mock_response .status_code = 200
114+ self .mock_httpx_client .post .return_value = mock_response
115+
116+ await self .sender .send_notification (task_data )
117+
118+ self .mock_config_store .get_info .assert_awaited_once_with (task_id )
119+
120+ # assert httpx_client post method got invoked with right parameters
121+ self .mock_httpx_client .post .assert_awaited_once_with (
122+ config .url ,
123+ json = task_data .model_dump (mode = 'json' , exclude_none = True ),
124+ headers = {
125+ 'X-A2A-Notification-Token' : 'unique_token' ,
126+ 'Authorization' : 'Bearer test-jwt-token' ,
127+ },
128+ )
129+ mock_response .raise_for_status .assert_called_once ()
130+
131+ async def test_send_notification_with_bearer_authentication_no_credentials (
132+ self ,
133+ ) -> None :
134+ task_id = 'task_send_bearer_no_creds'
135+ task_data = create_sample_task (task_id = task_id )
136+ auth_info = PushNotificationAuthenticationInfo (
137+ schemes = ['Bearer' ], credentials = None
138+ )
139+ config = create_sample_push_config (
140+ url = 'http://notify.me/here' , authentication = auth_info
141+ )
142+ self .mock_config_store .get_info .return_value = [config ]
143+
144+ mock_response = AsyncMock (spec = httpx .Response )
145+ mock_response .status_code = 200
146+ self .mock_httpx_client .post .return_value = mock_response
147+
148+ await self .sender .send_notification (task_data )
149+
150+ # Should not add Authorization header when credentials are missing
151+ self .mock_httpx_client .post .assert_awaited_once_with (
152+ config .url ,
153+ json = task_data .model_dump (mode = 'json' , exclude_none = True ),
154+ headers = None ,
155+ )
156+
157+ async def test_send_notification_with_non_bearer_authentication (
158+ self ,
159+ ) -> None :
160+ task_id = 'task_send_non_bearer'
161+ task_data = create_sample_task (task_id = task_id )
162+ auth_info = PushNotificationAuthenticationInfo (
163+ schemes = ['Basic' ], credentials = 'user:pass'
164+ )
165+ config = create_sample_push_config (
166+ url = 'http://notify.me/here' , authentication = auth_info
167+ )
168+ self .mock_config_store .get_info .return_value = [config ]
169+
170+ mock_response = AsyncMock (spec = httpx .Response )
171+ mock_response .status_code = 200
172+ self .mock_httpx_client .post .return_value = mock_response
173+
174+ await self .sender .send_notification (task_data )
175+
176+ # Should not add Authorization header for non-Bearer schemes
177+ self .mock_httpx_client .post .assert_awaited_once_with (
178+ config .url ,
179+ json = task_data .model_dump (mode = 'json' , exclude_none = True ),
180+ headers = None ,
181+ )
182+
95183 async def test_send_notification_no_config (self ) -> None :
96184 task_id = 'task_send_no_config'
97185 task_data = create_sample_task (task_id = task_id )
0 commit comments