#[cfg(test)]
use crate::A2AError;
use crate::A2AResult;
use protocol_transport_core::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse};
use serde_json::Value;
use std::collections::HashMap;
#[allow(async_fn_in_trait)]
pub trait A2ATransport: Send + Sync {
async fn send_request(&self, request: JsonRpcRequest) -> A2AResult<JsonRpcResponse>;
async fn send_notification(&self, notification: JsonRpcNotification) -> A2AResult<()>;
async fn health_check(&self) -> A2AResult<()>;
fn get_metadata(&self) -> HashMap<String, Value> {
HashMap::new()
}
fn transport_type(&self) -> &'static str {
"unknown"
}
}
#[allow(async_fn_in_trait)]
pub trait A2ATransportFactory: Send + Sync {
type Transport: A2ATransport;
async fn create_transport(
&self,
agent_id: &str,
endpoint: &str,
config: Option<HashMap<String, Value>>,
) -> A2AResult<Self::Transport>;
async fn get_transport(&self, agent_id: &str, endpoint: &str) -> A2AResult<Self::Transport> {
self.create_transport(agent_id, endpoint, None).await
}
async fn remove_transport(&self, agent_id: &str) -> A2AResult<()>;
fn get_config(&self) -> HashMap<String, Value> {
HashMap::new()
}
}
#[cfg(test)]
pub struct MockTransport {
responses: HashMap<String, JsonRpcResponse>,
health_status: bool,
}
#[cfg(test)]
impl MockTransport {
pub fn new() -> Self {
Self {
responses: HashMap::new(),
health_status: true,
}
}
pub fn with_response(mut self, method: String, response: JsonRpcResponse) -> Self {
self.responses.insert(method, response);
self
}
pub fn with_health_status(mut self, healthy: bool) -> Self {
self.health_status = healthy;
self
}
}
#[cfg(test)]
impl A2ATransport for MockTransport {
async fn send_request(&self, request: JsonRpcRequest) -> A2AResult<JsonRpcResponse> {
if let Some(response) = self.responses.get(&request.method) {
let mut response = response.clone();
response.id = request.id; Ok(response)
} else {
Ok(JsonRpcResponse::success(
request.id,
serde_json::json!({"method": request.method, "received": true}),
))
}
}
async fn send_notification(&self, _notification: JsonRpcNotification) -> A2AResult<()> {
Ok(())
}
async fn health_check(&self) -> A2AResult<()> {
if self.health_status {
Ok(())
} else {
Err(A2AError::agent_unavailable(
"mock-transport",
"Health check failed",
))
}
}
fn transport_type(&self) -> &'static str {
"mock"
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_mock_transport() {
let transport = MockTransport::new().with_response(
"ping".to_string(),
JsonRpcResponse::success(json!("test-id"), json!({"pong": true})),
);
let request = JsonRpcRequest::new(json!("req-123"), "ping".to_string(), json!({}));
let response = transport.send_request(request).await.unwrap();
assert!(response.is_success());
assert_eq!(response.id, json!("req-123"));
assert_eq!(response.result.unwrap()["pong"], true);
}
#[tokio::test]
async fn test_mock_transport_notification() {
let transport = MockTransport::new();
let notification = JsonRpcNotification::new("log.info".to_string(), json!({"msg": "test"}));
let result = transport.send_notification(notification).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mock_transport_health_check() {
let healthy_transport = MockTransport::new().with_health_status(true);
assert!(healthy_transport.health_check().await.is_ok());
let unhealthy_transport = MockTransport::new().with_health_status(false);
assert!(unhealthy_transport.health_check().await.is_err());
}
#[tokio::test]
async fn test_default_response() {
let transport = MockTransport::new();
let request =
JsonRpcRequest::new(json!("req-456"), "unknown_method".to_string(), json!({}));
let response = transport.send_request(request).await.unwrap();
assert!(response.is_success());
assert_eq!(response.result.unwrap()["method"], "unknown_method");
}
#[test]
fn test_mock_transport_type() {
let transport = MockTransport::new();
assert_eq!(transport.transport_type(), "mock");
}
#[test]
fn test_mock_transport_metadata() {
let transport = MockTransport::new();
let metadata = transport.get_metadata();
assert!(metadata.is_empty()); }
#[tokio::test]
async fn test_mock_transport_complex_scenarios() {
let transport = MockTransport::new()
.with_response(
"complex_method".to_string(),
JsonRpcResponse::success(
json!("test-id"),
json!({
"status": "success",
"data": {
"items": [1, 2, 3],
"total": 3
}
}),
),
)
.with_health_status(true);
let request = JsonRpcRequest::new(
json!("complex-req-123"),
"complex_method".to_string(),
json!({
"filters": {"type": "active"},
"pagination": {"limit": 10, "offset": 0}
}),
);
let response = transport.send_request(request).await.unwrap();
assert!(response.is_success());
assert_eq!(response.id, json!("complex-req-123"));
assert_eq!(response.result.unwrap()["data"]["total"], 3);
assert!(transport.health_check().await.is_ok());
let notification = JsonRpcNotification::new(
"system.alert".to_string(),
json!({
"level": "warning",
"message": "System resource usage high",
"details": {
"cpu": 85.5,
"memory": 92.1,
"timestamp": "2025-01-01T12:00:00Z"
}
}),
);
assert!(transport.send_notification(notification).await.is_ok());
}
struct MockTransportFactory;
impl A2ATransportFactory for MockTransportFactory {
type Transport = MockTransport;
async fn create_transport(
&self,
_agent_id: &str,
_endpoint: &str,
_config: Option<HashMap<String, Value>>,
) -> A2AResult<Self::Transport> {
Ok(MockTransport::new())
}
async fn remove_transport(&self, _agent_id: &str) -> A2AResult<()> {
Ok(())
}
}
#[tokio::test]
async fn test_transport_factory_default_implementations() {
let factory = MockTransportFactory;
let transport = factory
.get_transport("test-agent", "http://test-endpoint")
.await
.unwrap();
assert_eq!(transport.transport_type(), "mock");
let config = factory.get_config();
assert!(config.is_empty());
assert!(factory.remove_transport("test-agent").await.is_ok());
}
#[tokio::test]
async fn test_transport_factory_comprehensive_workflow() {
let factory = MockTransportFactory;
let transport1 = factory
.create_transport("agent-1", "http://agent1.local", None)
.await
.unwrap();
let transport2 = factory
.create_transport(
"agent-2",
"http://agent2.local",
Some({
let mut config = HashMap::new();
config.insert("timeout".to_string(), Value::from(5000));
config
}),
)
.await
.unwrap();
let request1 = JsonRpcRequest::new(json!("req-1"), "ping".to_string(), json!({}));
let request2 = JsonRpcRequest::new(json!("req-2"), "status".to_string(), json!({}));
let response1 = transport1.send_request(request1).await.unwrap();
let response2 = transport2.send_request(request2).await.unwrap();
assert!(response1.is_success());
assert!(response2.is_success());
assert_eq!(response1.id, json!("req-1"));
assert_eq!(response2.id, json!("req-2"));
assert!(transport1.health_check().await.is_ok());
assert!(transport2.health_check().await.is_ok());
let cached_transport = factory
.get_transport("agent-1", "http://agent1.local")
.await
.unwrap();
assert_eq!(cached_transport.transport_type(), "mock");
assert!(factory.remove_transport("agent-1").await.is_ok());
assert!(factory.remove_transport("agent-2").await.is_ok());
}
#[tokio::test]
async fn test_mock_transport_error_scenarios() {
let unhealthy_transport = MockTransport::new().with_health_status(false);
let health_result = unhealthy_transport.health_check().await;
assert!(health_result.is_err());
let error = health_result.unwrap_err();
let error_message = format!("{}", error);
assert!(error_message.contains("Health check failed"));
let multi_response_transport = MockTransport::new()
.with_response(
"method1".to_string(),
JsonRpcResponse::success(json!("id1"), json!({"result": "first"})),
)
.with_response(
"method2".to_string(),
JsonRpcResponse::success(json!("id2"), json!({"result": "second"})),
);
let req1 = JsonRpcRequest::new(json!("test1"), "method1".to_string(), json!({}));
let req2 = JsonRpcRequest::new(json!("test2"), "method2".to_string(), json!({}));
let resp1 = multi_response_transport.send_request(req1).await.unwrap();
let resp2 = multi_response_transport.send_request(req2).await.unwrap();
assert_eq!(resp1.result.unwrap()["result"], "first");
assert_eq!(resp2.result.unwrap()["result"], "second");
assert_eq!(resp1.id, json!("test1"));
assert_eq!(resp2.id, json!("test2"));
}
}