1use a2a::jsonrpc::methods;
5use a2a::*;
6use async_trait::async_trait;
7use futures::stream::BoxStream;
8use std::borrow::Cow;
9use std::sync::Arc;
10
11use crate::middleware::CallInterceptor;
12use crate::transport::{ServiceParams, Transport};
13
14pub struct A2AClient<T: Transport> {
16 transport: T,
17 interceptors: Vec<Arc<dyn CallInterceptor>>,
18 default_params: ServiceParams,
19 tenant: Option<String>,
20}
21
22impl<T: Transport> A2AClient<T> {
23 pub fn new(transport: T) -> Self {
24 let mut default_params = ServiceParams::new();
25 default_params.insert(SVC_PARAM_VERSION.to_string(), vec![VERSION.to_string()]);
26 A2AClient {
27 transport,
28 interceptors: Vec::new(),
29 default_params,
30 tenant: None,
31 }
32 }
33
34 pub fn with_interceptors(mut self, interceptors: Vec<Arc<dyn CallInterceptor>>) -> Self {
35 self.interceptors = interceptors;
36 self
37 }
38
39 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
47 self.tenant = Some(tenant.into());
48 self
49 }
50
51 pub fn tenant(&self) -> Option<&str> {
54 self.tenant.as_deref()
55 }
56
57 fn apply_tenant<'a, R: Clone>(
64 &self,
65 req: &'a R,
66 tenant_field: impl FnOnce(&mut R) -> &mut Option<String>,
67 ) -> Cow<'a, R> {
68 let Some(tenant) = &self.tenant else {
69 return Cow::Borrowed(req);
70 };
71 let mut filled = req.clone();
72 *tenant_field(&mut filled) = Some(tenant.clone());
73 Cow::Owned(filled)
74 }
75
76 fn params(&self) -> ServiceParams {
77 self.default_params.clone()
78 }
79
80 async fn apply_before(&self, method: &str) -> Result<ServiceParams, A2AError> {
81 let mut params = self.params();
82 for interceptor in &self.interceptors {
83 interceptor.before(method, &mut params).await?;
84 }
85 Ok(params)
86 }
87
88 async fn apply_after(
89 &self,
90 method: &str,
91 result: &Result<(), A2AError>,
92 ) -> Result<(), A2AError> {
93 for interceptor in self.interceptors.iter().rev() {
94 interceptor.after(method, result).await?;
95 }
96 Ok(())
97 }
98
99 async fn finish_call<R>(
100 &self,
101 method: &str,
102 result: Result<R, A2AError>,
103 ) -> Result<R, A2AError> {
104 let status = result.as_ref().map(|_| ()).map_err(Clone::clone);
105 let after_result = self.apply_after(method, &status).await;
106
107 match (result, after_result) {
108 (Ok(value), Ok(())) => Ok(value),
109 (Err(error), _) => Err(error),
110 (Ok(_), Err(error)) => Err(error),
111 }
112 }
113
114 pub async fn send_message(
115 &self,
116 req: &SendMessageRequest,
117 ) -> Result<SendMessageResponse, A2AError> {
118 let params = self.apply_before(methods::SEND_MESSAGE).await?;
119 let req = self.apply_tenant(req, |r| &mut r.tenant);
120 let result = self.transport.send_message(¶ms, &req).await;
121 self.finish_call(methods::SEND_MESSAGE, result).await
122 }
123
124 pub async fn send_streaming_message(
125 &self,
126 req: &SendMessageRequest,
127 ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
128 let params = self.apply_before(methods::SEND_STREAMING_MESSAGE).await?;
129 let req = self.apply_tenant(req, |r| &mut r.tenant);
130 let result = self.transport.send_streaming_message(¶ms, &req).await;
131 self.finish_call(methods::SEND_STREAMING_MESSAGE, result)
132 .await
133 }
134
135 pub async fn get_task(&self, req: &GetTaskRequest) -> Result<Task, A2AError> {
136 let params = self.apply_before(methods::GET_TASK).await?;
137 let req = self.apply_tenant(req, |r| &mut r.tenant);
138 let result = self.transport.get_task(¶ms, &req).await;
139 self.finish_call(methods::GET_TASK, result).await
140 }
141
142 pub async fn list_tasks(&self, req: &ListTasksRequest) -> Result<ListTasksResponse, A2AError> {
143 let params = self.apply_before(methods::LIST_TASKS).await?;
144 let req = self.apply_tenant(req, |r| &mut r.tenant);
145 let result = self.transport.list_tasks(¶ms, &req).await;
146 self.finish_call(methods::LIST_TASKS, result).await
147 }
148
149 pub async fn cancel_task(&self, req: &CancelTaskRequest) -> Result<Task, A2AError> {
150 let params = self.apply_before(methods::CANCEL_TASK).await?;
151 let req = self.apply_tenant(req, |r| &mut r.tenant);
152 let result = self.transport.cancel_task(¶ms, &req).await;
153 self.finish_call(methods::CANCEL_TASK, result).await
154 }
155
156 pub async fn subscribe_to_task(
157 &self,
158 req: &SubscribeToTaskRequest,
159 ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
160 let params = self.apply_before(methods::SUBSCRIBE_TO_TASK).await?;
161 let req = self.apply_tenant(req, |r| &mut r.tenant);
162 let result = self.transport.subscribe_to_task(¶ms, &req).await;
163 self.finish_call(methods::SUBSCRIBE_TO_TASK, result).await
164 }
165
166 pub async fn create_push_config(
167 &self,
168 req: &TaskPushNotificationConfig,
169 ) -> Result<TaskPushNotificationConfig, A2AError> {
170 let params = self.apply_before(methods::CREATE_PUSH_CONFIG).await?;
171 let req = self.apply_tenant(req, |r| &mut r.tenant);
172 let result = self.transport.create_push_config(¶ms, &req).await;
173 self.finish_call(methods::CREATE_PUSH_CONFIG, result).await
174 }
175
176 pub async fn get_push_config(
177 &self,
178 req: &GetTaskPushNotificationConfigRequest,
179 ) -> Result<TaskPushNotificationConfig, A2AError> {
180 let params = self.apply_before(methods::GET_PUSH_CONFIG).await?;
181 let req = self.apply_tenant(req, |r| &mut r.tenant);
182 let result = self.transport.get_push_config(¶ms, &req).await;
183 self.finish_call(methods::GET_PUSH_CONFIG, result).await
184 }
185
186 pub async fn list_push_configs(
187 &self,
188 req: &ListTaskPushNotificationConfigsRequest,
189 ) -> Result<ListTaskPushNotificationConfigsResponse, A2AError> {
190 let params = self.apply_before(methods::LIST_PUSH_CONFIGS).await?;
191 let req = self.apply_tenant(req, |r| &mut r.tenant);
192 let result = self.transport.list_push_configs(¶ms, &req).await;
193 self.finish_call(methods::LIST_PUSH_CONFIGS, result).await
194 }
195
196 pub async fn delete_push_config(
197 &self,
198 req: &DeleteTaskPushNotificationConfigRequest,
199 ) -> Result<(), A2AError> {
200 let params = self.apply_before(methods::DELETE_PUSH_CONFIG).await?;
201 let req = self.apply_tenant(req, |r| &mut r.tenant);
202 let result = self.transport.delete_push_config(¶ms, &req).await;
203 self.finish_call(methods::DELETE_PUSH_CONFIG, result).await
204 }
205
206 pub async fn get_extended_agent_card(
207 &self,
208 req: &GetExtendedAgentCardRequest,
209 ) -> Result<AgentCard, A2AError> {
210 let params = self.apply_before(methods::GET_EXTENDED_AGENT_CARD).await?;
211 let req = self.apply_tenant(req, |r| &mut r.tenant);
212 let result = self.transport.get_extended_agent_card(¶ms, &req).await;
213 self.finish_call(methods::GET_EXTENDED_AGENT_CARD, result)
214 .await
215 }
216
217 pub async fn destroy(&self) -> Result<(), A2AError> {
218 self.transport.destroy().await
219 }
220}
221
222#[async_trait]
224pub trait SendMessageExt {
225 async fn send_text(
226 &self,
227 text: impl Into<String> + Send,
228 ) -> Result<SendMessageResponse, A2AError>;
229}
230
231#[async_trait]
232impl<T: Transport> SendMessageExt for A2AClient<T> {
233 async fn send_text(
234 &self,
235 text: impl Into<String> + Send,
236 ) -> Result<SendMessageResponse, A2AError> {
237 let msg = Message::new(Role::User, vec![Part::text(text)]);
238 let req = SendMessageRequest {
239 message: msg,
240 configuration: None,
241 metadata: None,
242 tenant: None,
243 };
244 self.send_message(&req).await
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use a2a::event::StreamResponse;
252 use futures::stream;
253 use std::sync::Mutex;
254
255 #[derive(Default)]
256 struct MockTransportState {
257 calls: Mutex<Vec<(String, ServiceParams)>>,
258 send_message_error: Mutex<Option<A2AError>>,
259 request_tenants: Mutex<Vec<Option<String>>>,
262 }
263
264 struct MockTransport {
266 state: Arc<MockTransportState>,
267 }
268
269 impl MockTransport {
270 fn new() -> (Self, Arc<MockTransportState>) {
271 let state = Arc::new(MockTransportState::default());
272 (
273 MockTransport {
274 state: state.clone(),
275 },
276 state,
277 )
278 }
279
280 fn record(&self, method: &str, params: &ServiceParams) {
281 self.state
282 .calls
283 .lock()
284 .unwrap()
285 .push((method.to_string(), params.clone()));
286 }
287 }
288
289 #[async_trait]
290 impl Transport for MockTransport {
291 async fn send_message(
292 &self,
293 params: &ServiceParams,
294 req: &SendMessageRequest,
295 ) -> Result<SendMessageResponse, A2AError> {
296 self.record(methods::SEND_MESSAGE, params);
297 self.state
298 .request_tenants
299 .lock()
300 .unwrap()
301 .push(req.tenant.clone());
302 if let Some(error) = self.state.send_message_error.lock().unwrap().clone() {
303 return Err(error);
304 }
305 Ok(SendMessageResponse::Task(Task {
306 id: "t1".into(),
307 context_id: "c1".into(),
308 status: TaskStatus {
309 state: TaskState::Completed,
310 message: None,
311 timestamp: None,
312 },
313 artifacts: None,
314 history: None,
315 metadata: None,
316 }))
317 }
318
319 async fn send_streaming_message(
320 &self,
321 params: &ServiceParams,
322 _req: &SendMessageRequest,
323 ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
324 self.record(methods::SEND_STREAMING_MESSAGE, params);
325 Ok(Box::pin(stream::once(async {
326 Ok(StreamResponse::StatusUpdate(
327 a2a::event::TaskStatusUpdateEvent {
328 task_id: "t1".into(),
329 context_id: "c1".into(),
330 status: TaskStatus {
331 state: TaskState::Working,
332 message: None,
333 timestamp: None,
334 },
335 metadata: None,
336 },
337 ))
338 })))
339 }
340
341 async fn get_task(
342 &self,
343 params: &ServiceParams,
344 req: &GetTaskRequest,
345 ) -> Result<Task, A2AError> {
346 self.record(methods::GET_TASK, params);
347 self.state
348 .request_tenants
349 .lock()
350 .unwrap()
351 .push(req.tenant.clone());
352 Ok(Task {
353 id: req.id.clone(),
354 context_id: "c1".into(),
355 status: TaskStatus {
356 state: TaskState::Completed,
357 message: None,
358 timestamp: None,
359 },
360 artifacts: None,
361 history: None,
362 metadata: None,
363 })
364 }
365
366 async fn list_tasks(
367 &self,
368 params: &ServiceParams,
369 _req: &ListTasksRequest,
370 ) -> Result<ListTasksResponse, A2AError> {
371 self.record(methods::LIST_TASKS, params);
372 Ok(ListTasksResponse {
373 tasks: vec![],
374 next_page_token: String::new(),
375 page_size: 0,
376 total_size: 0,
377 })
378 }
379
380 async fn cancel_task(
381 &self,
382 params: &ServiceParams,
383 req: &CancelTaskRequest,
384 ) -> Result<Task, A2AError> {
385 self.record(methods::CANCEL_TASK, params);
386 Ok(Task {
387 id: req.id.clone(),
388 context_id: "c1".into(),
389 status: TaskStatus {
390 state: TaskState::Canceled,
391 message: None,
392 timestamp: None,
393 },
394 artifacts: None,
395 history: None,
396 metadata: None,
397 })
398 }
399
400 async fn subscribe_to_task(
401 &self,
402 params: &ServiceParams,
403 _req: &SubscribeToTaskRequest,
404 ) -> Result<BoxStream<'static, Result<StreamResponse, A2AError>>, A2AError> {
405 self.record(methods::SUBSCRIBE_TO_TASK, params);
406 Ok(Box::pin(stream::empty()))
407 }
408
409 async fn create_push_config(
410 &self,
411 params: &ServiceParams,
412 req: &TaskPushNotificationConfig,
413 ) -> Result<TaskPushNotificationConfig, A2AError> {
414 self.record(methods::CREATE_PUSH_CONFIG, params);
415 Ok(req.clone())
416 }
417
418 async fn get_push_config(
419 &self,
420 params: &ServiceParams,
421 req: &GetTaskPushNotificationConfigRequest,
422 ) -> Result<TaskPushNotificationConfig, A2AError> {
423 self.record(methods::GET_PUSH_CONFIG, params);
424 Ok(TaskPushNotificationConfig {
425 task_id: req.task_id.clone(),
426 url: "http://example.com".into(),
427 id: Some(req.id.clone()),
428 token: None,
429 authentication: None,
430 tenant: None,
431 })
432 }
433
434 async fn list_push_configs(
435 &self,
436 params: &ServiceParams,
437 _req: &ListTaskPushNotificationConfigsRequest,
438 ) -> Result<ListTaskPushNotificationConfigsResponse, A2AError> {
439 self.record(methods::LIST_PUSH_CONFIGS, params);
440 Ok(ListTaskPushNotificationConfigsResponse {
441 configs: vec![],
442 next_page_token: None,
443 })
444 }
445
446 async fn delete_push_config(
447 &self,
448 params: &ServiceParams,
449 _req: &DeleteTaskPushNotificationConfigRequest,
450 ) -> Result<(), A2AError> {
451 self.record(methods::DELETE_PUSH_CONFIG, params);
452 Ok(())
453 }
454
455 async fn get_extended_agent_card(
456 &self,
457 params: &ServiceParams,
458 _req: &GetExtendedAgentCardRequest,
459 ) -> Result<AgentCard, A2AError> {
460 self.record(methods::GET_EXTENDED_AGENT_CARD, params);
461 Ok(AgentCard {
462 name: "Test".into(),
463 description: "Test agent".into(),
464 version: "1.0".into(),
465 supported_interfaces: vec![],
466 capabilities: AgentCapabilities::default(),
467 default_input_modes: vec!["text/plain".into()],
468 default_output_modes: vec!["text/plain".into()],
469 skills: vec![],
470 provider: None,
471 documentation_url: None,
472 icon_url: None,
473 security_schemes: None,
474 security_requirements: None,
475 signatures: None,
476 })
477 }
478
479 async fn destroy(&self) -> Result<(), A2AError> {
480 Ok(())
481 }
482 }
483
484 fn make_client() -> A2AClient<MockTransport> {
485 let (transport, _) = MockTransport::new();
486 A2AClient::new(transport)
487 }
488
489 struct RecordingInterceptor {
490 name: &'static str,
491 events: Arc<Mutex<Vec<String>>>,
492 }
493
494 #[async_trait]
495 impl CallInterceptor for RecordingInterceptor {
496 async fn before(&self, _method: &str, params: &mut ServiceParams) -> Result<(), A2AError> {
497 self.events
498 .lock()
499 .unwrap()
500 .push(format!("before:{}", self.name));
501 params
502 .entry("X-Interceptor".to_string())
503 .or_default()
504 .push(self.name.to_string());
505 Ok(())
506 }
507
508 async fn after(
509 &self,
510 _method: &str,
511 result: &Result<(), A2AError>,
512 ) -> Result<(), A2AError> {
513 let status = if result.is_ok() { "ok" } else { "err" };
514 self.events
515 .lock()
516 .unwrap()
517 .push(format!("after:{}:{status}", self.name));
518 Ok(())
519 }
520 }
521
522 #[test]
523 fn test_new_sets_default_params() {
524 let client = make_client();
525 let params = client.params();
526 assert!(params.contains_key(SVC_PARAM_VERSION));
527 }
528
529 #[test]
530 fn test_with_interceptors() {
531 let client = make_client().with_interceptors(vec![]);
532 assert!(client.interceptors.is_empty());
533 }
534
535 #[tokio::test]
536 async fn test_send_message() {
537 let client = make_client();
538 let req = SendMessageRequest {
539 message: Message::new(Role::User, vec![Part::text("hi")]),
540 configuration: None,
541 metadata: None,
542 tenant: None,
543 };
544 let resp = client.send_message(&req).await.unwrap();
545 assert!(matches!(resp, SendMessageResponse::Task(_)));
546 }
547
548 #[tokio::test]
549 async fn test_send_message_applies_interceptors_and_reverses_after_order() {
550 let (transport, state) = MockTransport::new();
551 let events = Arc::new(Mutex::new(Vec::new()));
552 let client = A2AClient::new(transport).with_interceptors(vec![
553 Arc::new(RecordingInterceptor {
554 name: "first",
555 events: events.clone(),
556 }),
557 Arc::new(RecordingInterceptor {
558 name: "second",
559 events: events.clone(),
560 }),
561 ]);
562
563 let req = SendMessageRequest {
564 message: Message::new(Role::User, vec![Part::text("hi")]),
565 configuration: None,
566 metadata: None,
567 tenant: None,
568 };
569
570 client.send_message(&req).await.unwrap();
571
572 let calls = state.calls.lock().unwrap();
573 let params = &calls[0].1;
574 assert_eq!(
575 params.get("X-Interceptor").unwrap(),
576 &vec!["first".to_string(), "second".to_string()]
577 );
578
579 let events = events.lock().unwrap().clone();
580 assert_eq!(
581 events,
582 vec![
583 "before:first".to_string(),
584 "before:second".to_string(),
585 "after:second:ok".to_string(),
586 "after:first:ok".to_string(),
587 ]
588 );
589 }
590
591 #[tokio::test]
592 async fn test_send_message_preserves_transport_error_after_after_hooks() {
593 let (transport, state) = MockTransport::new();
594 *state.send_message_error.lock().unwrap() = Some(A2AError::internal("boom"));
595 let events = Arc::new(Mutex::new(Vec::new()));
596 let client =
597 A2AClient::new(transport).with_interceptors(vec![Arc::new(RecordingInterceptor {
598 name: "only",
599 events: events.clone(),
600 })]);
601
602 let req = SendMessageRequest {
603 message: Message::new(Role::User, vec![Part::text("hi")]),
604 configuration: None,
605 metadata: None,
606 tenant: None,
607 };
608
609 let err = client.send_message(&req).await.unwrap_err();
610 assert_eq!(err.message, "boom");
611
612 let events = events.lock().unwrap().clone();
613 assert_eq!(
614 events,
615 vec!["before:only".to_string(), "after:only:err".to_string(),]
616 );
617 }
618
619 #[tokio::test]
620 async fn test_send_streaming_message() {
621 use futures::StreamExt;
622 let client = make_client();
623 let req = SendMessageRequest {
624 message: Message::new(Role::User, vec![Part::text("hi")]),
625 configuration: None,
626 metadata: None,
627 tenant: None,
628 };
629 let mut stream = client.send_streaming_message(&req).await.unwrap();
630 let item = stream.next().await.unwrap().unwrap();
631 assert!(matches!(item, StreamResponse::StatusUpdate(_)));
632 }
633
634 #[tokio::test]
635 async fn test_get_task() {
636 let client = make_client();
637 let req = GetTaskRequest {
638 id: "t1".into(),
639 history_length: None,
640 tenant: None,
641 };
642 let task = client.get_task(&req).await.unwrap();
643 assert_eq!(task.id, "t1");
644 }
645
646 #[tokio::test]
647 async fn test_list_tasks() {
648 let client = make_client();
649 let req = ListTasksRequest {
650 context_id: None,
651 status: None,
652 page_size: None,
653 page_token: None,
654 history_length: None,
655 status_timestamp_after: None,
656 include_artifacts: None,
657 tenant: None,
658 };
659 let resp = client.list_tasks(&req).await.unwrap();
660 assert!(resp.tasks.is_empty());
661 }
662
663 #[tokio::test]
664 async fn test_cancel_task() {
665 let client = make_client();
666 let req = CancelTaskRequest {
667 id: "t1".into(),
668 metadata: None,
669 tenant: None,
670 };
671 let task = client.cancel_task(&req).await.unwrap();
672 assert_eq!(task.status.state, TaskState::Canceled);
673 }
674
675 #[tokio::test]
676 async fn test_subscribe_to_task() {
677 let client = make_client();
678 let req = SubscribeToTaskRequest {
679 id: "t1".into(),
680 tenant: None,
681 };
682 let _stream = client.subscribe_to_task(&req).await.unwrap();
683 }
684
685 #[tokio::test]
686 async fn test_create_push_config() {
687 let client = make_client();
688 let req = TaskPushNotificationConfig {
689 task_id: "t1".into(),
690 url: "http://example.com".into(),
691 id: None,
692 token: None,
693 authentication: None,
694 tenant: None,
695 };
696 let resp = client.create_push_config(&req).await.unwrap();
697 assert_eq!(resp.task_id, "t1");
698 }
699
700 #[tokio::test]
701 async fn test_get_push_config() {
702 let client = make_client();
703 let req = GetTaskPushNotificationConfigRequest {
704 task_id: "t1".into(),
705 id: "cfg1".into(),
706 tenant: None,
707 };
708 let resp = client.get_push_config(&req).await.unwrap();
709 assert_eq!(resp.id, Some("cfg1".into()));
710 }
711
712 #[tokio::test]
713 async fn test_list_push_configs() {
714 let client = make_client();
715 let req = ListTaskPushNotificationConfigsRequest {
716 task_id: "t1".into(),
717 page_size: None,
718 page_token: None,
719 tenant: None,
720 };
721 let resp = client.list_push_configs(&req).await.unwrap();
722 assert!(resp.configs.is_empty());
723 }
724
725 #[tokio::test]
726 async fn test_delete_push_config() {
727 let client = make_client();
728 let req = DeleteTaskPushNotificationConfigRequest {
729 task_id: "t1".into(),
730 id: "cfg1".into(),
731 tenant: None,
732 };
733 client.delete_push_config(&req).await.unwrap();
734 }
735
736 #[tokio::test]
737 async fn test_get_extended_agent_card() {
738 let client = make_client();
739 let req = GetExtendedAgentCardRequest { tenant: None };
740 let card = client.get_extended_agent_card(&req).await.unwrap();
741 assert_eq!(card.name, "Test");
742 }
743
744 #[tokio::test]
745 async fn test_destroy() {
746 let client = make_client();
747 client.destroy().await.unwrap();
748 }
749
750 fn send_request() -> SendMessageRequest {
751 SendMessageRequest {
752 message: Message::new(Role::User, vec![Part::text("hi")]),
753 configuration: None,
754 metadata: None,
755 tenant: None,
756 }
757 }
758
759 #[tokio::test]
761 async fn test_declared_tenant_is_echoed_on_requests() {
762 let (transport, state) = MockTransport::new();
763 let client = A2AClient::new(transport).with_tenant("acme");
764
765 client.send_message(&send_request()).await.unwrap();
766 client
767 .get_task(&GetTaskRequest {
768 id: "t1".into(),
769 history_length: None,
770 tenant: None,
771 })
772 .await
773 .unwrap();
774
775 let tenants = state.request_tenants.lock().unwrap().clone();
776 assert_eq!(
777 tenants,
778 vec![Some("acme".to_string()), Some("acme".to_string())]
779 );
780 }
781
782 #[tokio::test]
786 async fn test_declared_tenant_replaces_a_caller_supplied_one() {
787 let (transport, state) = MockTransport::new();
788 let client = A2AClient::new(transport).with_tenant("declared");
789
790 let mut req = send_request();
791 req.tenant = Some("caller-chosen".to_string());
792 client.send_message(&req).await.unwrap();
793
794 assert_eq!(
795 state.request_tenants.lock().unwrap().clone(),
796 vec![Some("declared".to_string())]
797 );
798 assert_eq!(req.tenant.as_deref(), Some("caller-chosen"));
800 }
801
802 #[tokio::test]
806 async fn test_without_a_declared_tenant_the_request_is_untouched() {
807 let (transport, state) = MockTransport::new();
808 let client = A2AClient::new(transport);
809 assert_eq!(client.tenant(), None);
810
811 let mut req = send_request();
812 req.tenant = Some("caller-chosen".to_string());
813 client.send_message(&req).await.unwrap();
814 client.send_message(&send_request()).await.unwrap();
815
816 assert_eq!(
817 state.request_tenants.lock().unwrap().clone(),
818 vec![Some("caller-chosen".to_string()), None]
819 );
820 }
821}