use matrixcode_core::{
event::{AgentEvent, EventData, EventType},
tools::{
proxy::{ProxyMetadata, ProxyTool, ProxyToolRequest, ProxyToolResponse},
ToolDefinition,
Tool,
},
};
use serde_json::json;
use tokio::sync::mpsc;
use uuid::Uuid;
#[test]
fn test_proxy_tool_creation() {
let definition = ToolDefinition {
name: "image_search".to_string(),
description: "搜索网络图片".to_string(),
parameters: json!({
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"max_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}),
..Default::default()
};
let metadata = ProxyMetadata {
tool_type: "image_search".to_string(),
endpoint: None,
timeout_ms: 30000,
custom: Some(json!({"platforms": ["unsplash", "pexels", "pixabay"]})),
};
let proxy_tool = ProxyTool::new(definition.clone(), metadata.clone());
assert_eq!(proxy_tool.definition().name, "image_search");
assert_eq!(proxy_tool.definition().description, "搜索网络图片");
assert_eq!(proxy_tool.metadata().tool_type, "image_search");
assert_eq!(proxy_tool.metadata().timeout_ms, 30000);
}
#[test]
fn test_proxy_tool_request_response_serialization() {
let request_id = Uuid::new_v4().to_string();
let request = ProxyToolRequest {
request_id: request_id.clone(),
tool_name: "image_search".to_string(),
tool_input: json!({"query": "cats", "max_results": 5}),
metadata: ProxyMetadata {
tool_type: "image_search".to_string(),
endpoint: None,
timeout_ms: 30000,
custom: None,
},
};
let request_json = serde_json::to_string(&request).unwrap();
assert!(request_json.contains("image_search"));
assert!(request_json.contains("cats"));
let deserialized: ProxyToolRequest = serde_json::from_str(&request_json).unwrap();
assert_eq!(deserialized.request_id, request_id);
assert_eq!(deserialized.tool_name, "image_search");
let response = ProxyToolResponse {
request_id: request_id.clone(),
result: json!({"success": true, "images": []}).to_string(),
is_error: false,
};
let response_json = serde_json::to_string(&response).unwrap();
let deserialized_response: ProxyToolResponse = serde_json::from_str(&response_json).unwrap();
assert_eq!(deserialized_response.request_id, request_id);
assert!(!deserialized_response.is_error);
}
#[tokio::test]
async fn test_proxy_tool_channel_chain() {
let (event_tx, mut event_rx) = mpsc::channel::<AgentEvent>(10);
let (response_tx, mut response_rx) = mpsc::channel::<ProxyToolResponse>(10);
let request_id = Uuid::new_v4().to_string();
let event = AgentEvent::proxy_tool_request(
request_id.clone(),
"image_search".to_string(),
json!({"query": "sunset", "max_results": 3}),
ProxyMetadata {
tool_type: "image_search".to_string(),
endpoint: None,
timeout_ms: 30000,
custom: None,
},
);
event_tx.send(event).await.unwrap();
let received_event = event_rx.recv().await.unwrap();
assert_eq!(received_event.event_type, EventType::ProxyToolRequest);
if let Some(EventData::ProxyToolRequest {
request_id: recv_id,
tool_name,
tool_input,
metadata: _,
}) = received_event.data
{
assert_eq!(recv_id, request_id);
assert_eq!(tool_name, "image_search");
assert_eq!(tool_input["query"], "sunset");
let mock_result = json!({
"success": true,
"query": "sunset",
"total": 3,
"images": [
{"url": "https://example.com/sunset1.jpg", "platform": "Unsplash"},
{"url": "https://example.com/sunset2.jpg", "platform": "Pexels"},
{"url": "https://example.com/sunset3.jpg", "platform": "Pixabay"}
]
});
let response = ProxyToolResponse {
request_id: recv_id,
result: mock_result.to_string(),
is_error: false,
};
response_tx.send(response).await.unwrap();
} else {
panic!("Expected ProxyToolRequest event data");
}
let received_response = response_rx.recv().await.unwrap();
assert_eq!(received_response.request_id, request_id);
assert!(!received_response.is_error);
let result: serde_json::Value = serde_json::from_str(&received_response.result).unwrap();
assert_eq!(result["success"], true);
assert_eq!(result["total"], 3);
}
#[tokio::test]
async fn test_proxy_tool_error_chain() {
let (event_tx, mut event_rx) = mpsc::channel::<AgentEvent>(10);
let (response_tx, mut response_rx) = mpsc::channel::<ProxyToolResponse>(10);
let request_id = Uuid::new_v4().to_string();
event_tx.send(AgentEvent::proxy_tool_request(
request_id.clone(),
"image_search".to_string(),
json!({"query": ""}), ProxyMetadata {
tool_type: "image_search".to_string(),
endpoint: None,
timeout_ms: 30000,
custom: None,
},
)).await.unwrap();
let received_event = event_rx.recv().await.unwrap();
if let Some(EventData::ProxyToolRequest { request_id: recv_id, tool_input, .. }) = received_event.data {
let query = tool_input.get("query").and_then(|q| q.as_str()).unwrap_or("");
let response = if query.is_empty() {
ProxyToolResponse {
request_id: recv_id,
result: json!({"error": "query is required"}).to_string(),
is_error: true,
}
} else {
ProxyToolResponse {
request_id: recv_id,
result: json!({"success": true}).to_string(),
is_error: false,
}
};
response_tx.send(response).await.unwrap();
}
let received_response = response_rx.recv().await.unwrap();
assert!(received_response.is_error);
let result: serde_json::Value = serde_json::from_str(&received_response.result).unwrap();
assert!(result.get("error").is_some());
}
#[tokio::test]
async fn test_proxy_tool_timeout() {
use tokio::time::{Duration, timeout};
let (response_tx, mut response_rx) = mpsc::channel::<ProxyToolResponse>(10);
let short_timeout = Duration::from_millis(100);
let result = timeout(short_timeout, response_rx.recv()).await;
assert!(result.is_err(), "Should timeout when no response is sent");
let request_id = Uuid::new_v4().to_string();
let response = ProxyToolResponse {
request_id,
result: json!({"success": true}).to_string(),
is_error: false,
};
response_tx.send(response).await.unwrap();
let longer_timeout = Duration::from_millis(1000);
let result = timeout(longer_timeout, response_rx.recv()).await;
assert!(result.is_ok(), "Should receive response when sent before timeout");
}
#[tokio::test]
async fn test_send_vs_try_send_async() {
let (tx, mut rx) = mpsc::channel::<ProxyToolResponse>(2);
for i in 0..2 {
tx.try_send(ProxyToolResponse {
request_id: format!("req-{}", i),
result: "{}".to_string(),
is_error: false,
}).unwrap();
}
let try_result = tx.try_send(ProxyToolResponse {
request_id: "req-overflow".to_string(),
result: "{}".to_string(),
is_error: false,
});
assert!(try_result.is_err(), "try_send should fail when channel is full");
let tx_clone = tx.clone();
let send_handle = tokio::spawn(async move {
tx_clone.send(ProxyToolResponse {
request_id: "req-async".to_string(),
result: "{}".to_string(),
is_error: false,
}).await
});
rx.recv().await.unwrap();
let send_result = send_handle.await.unwrap();
assert!(send_result.is_ok(), "send().await should succeed after channel has room");
}