use anyhow::Result;
use chrono::Utc;
use serde_json::json;
use std::collections::HashMap;
use std::process::Stdio;
use tcl_mcp_server::mcp_client::{McpClient, McpServerConfig};
use tokio::process::Command;
#[cfg(test)]
mod integration_tests {
use super::*;
async fn start_tcl_mcp_server(port: u16) -> Result<tokio::process::Child> {
let mut cmd = Command::new("cargo");
cmd.arg("run")
.arg("--")
.arg("--port")
.arg(port.to_string())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
Ok(cmd.spawn()?)
}
#[tokio::test]
#[ignore] async fn test_register_tcl_mcp_server() {
let client = McpClient::new();
let config = McpServerConfig {
id: "tcl-local".to_string(),
name: "Local TCL MCP Server".to_string(),
description: Some("Local instance of TCL MCP server".to_string()),
command: "cargo".to_string(),
args: vec!["run".to_string(), "--".to_string()],
env: HashMap::new(),
auto_start: true,
timeout_ms: 10000,
max_retries: 3,
created_at: Utc::now(),
};
let result = client.register_server(config).await;
if result.is_ok() {
let tools = client.get_server_tools("tcl-local").await.unwrap();
let tool_names: Vec<String> = tools.iter().map(|t| t.name.clone()).collect();
assert!(tool_names.contains(&"bin___tcl_execute".to_string()));
assert!(tool_names.contains(&"bin___tcl_tool_list".to_string()));
client.remove_server("tcl-local", true).await.unwrap();
}
}
#[tokio::test]
#[ignore] async fn test_execute_tools_through_mcp() {
let client = McpClient::new();
let config = McpServerConfig {
id: "tcl-test".to_string(),
name: "Test TCL Server".to_string(),
description: None,
command: "target/debug/tcl-mcp".to_string(),
args: vec![],
env: HashMap::new(),
auto_start: true,
timeout_ms: 10000,
max_retries: 3,
created_at: Utc::now(),
};
if client.register_server(config).await.is_ok() {
let params = json!({
"script": "expr 1 + 1"
});
let result = client
.execute_tool("tcl-test", "bin___tcl_execute", params)
.await;
if let Ok(value) = result {
println!("TCL execution result: {:?}", value);
}
client.remove_server("tcl-test", true).await.unwrap();
}
}
#[tokio::test]
async fn test_multiple_mcp_servers() {
let client = McpClient::new();
let servers = vec![
(
"filesystem-mcp",
"npx",
vec!["@modelcontextprotocol/server-filesystem".to_string()],
),
(
"github-mcp",
"npx",
vec!["@modelcontextprotocol/server-github".to_string()],
),
("tcl-mcp", "cargo", vec!["run".to_string()]),
];
for (id, cmd, args) in &servers {
let config = McpServerConfig {
id: id.to_string(),
name: format!("{} Server", id),
description: None,
command: cmd.to_string(),
args: args.clone(),
env: HashMap::new(),
auto_start: false, timeout_ms: 5000,
max_retries: 3,
created_at: Utc::now(),
};
assert!(client.register_server(config).await.is_ok());
}
let server_list = client.list_servers().await;
assert_eq!(server_list.len(), 3);
for (id, _, _) in &servers {
client.remove_server(id, true).await.unwrap();
}
}
#[tokio::test]
async fn test_tool_namespace_collision() {
let client = McpClient::new();
let config1 = McpServerConfig {
id: "server1".to_string(),
name: "Server 1".to_string(),
description: None,
command: "echo".to_string(),
args: vec!["server1".to_string()],
env: HashMap::new(),
auto_start: false,
timeout_ms: 5000,
max_retries: 3,
created_at: Utc::now(),
};
let config2 = McpServerConfig {
id: "server2".to_string(),
name: "Server 2".to_string(),
description: None,
command: "echo".to_string(),
args: vec!["server2".to_string()],
env: HashMap::new(),
auto_start: false,
timeout_ms: 5000,
max_retries: 3,
created_at: Utc::now(),
};
assert!(client.register_server(config1).await.is_ok());
assert!(client.register_server(config2).await.is_ok());
let servers = client.list_servers().await;
assert_eq!(servers.len(), 2);
client.remove_server("server1", true).await.unwrap();
client.remove_server("server2", true).await.unwrap();
}
#[tokio::test]
async fn test_error_propagation() {
let client = McpClient::new();
let config = McpServerConfig {
id: "error-server".to_string(),
name: "Error Test Server".to_string(),
description: None,
command: "/nonexistent/command".to_string(),
args: vec![],
env: HashMap::new(),
auto_start: false,
timeout_ms: 5000,
max_retries: 3,
created_at: Utc::now(),
};
assert!(client.register_server(config).await.is_ok());
let connect_result = client.connect_server("error-server").await;
assert!(connect_result.is_err());
let err_msg = connect_result.unwrap_err().to_string();
assert!(err_msg.contains("Failed to start MCP server"));
client.remove_server("error-server", true).await.unwrap();
}
#[tokio::test]
#[ignore] async fn test_dynamic_tool_discovery() {
let client = McpClient::new();
println!("Test scenario: Dynamic tool discovery");
println!("1. Connect to MCP server with initial tools");
println!("2. Server adds new tools at runtime");
println!("3. Client should be able to discover and use new tools");
println!("4. Server removes tools");
println!("5. Client should handle tool removal gracefully");
}
#[tokio::test]
async fn test_rapid_registration_removal() {
let client = McpClient::new();
for i in 0..50 {
let config = McpServerConfig {
id: format!("stress-server-{}", i),
name: format!("Stress Test Server {}", i),
description: None,
command: "echo".to_string(),
args: vec![i.to_string()],
env: HashMap::new(),
auto_start: false,
timeout_ms: 1000,
max_retries: 1,
created_at: Utc::now(),
};
assert!(client.register_server(config).await.is_ok());
assert!(client
.remove_server(&format!("stress-server-{}", i), true)
.await
.is_ok());
}
assert_eq!(client.list_servers().await.len(), 0);
}
#[tokio::test]
async fn test_jsonrpc_protocol_compliance() {
let test_cases = vec![
(
json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "test-client",
"version": "1.0.0"
}
}
}),
true,
),
(
json!({
"id": 1,
"method": "initialize",
"params": {}
}),
false,
),
(
json!({
"jsonrpc": "1.0",
"id": 1,
"method": "initialize",
"params": {}
}),
false,
),
];
for (request, should_be_valid) in test_cases {
let is_valid = request
.get("jsonrpc")
.and_then(|v| v.as_str())
.map(|v| v == "2.0")
.unwrap_or(false);
assert_eq!(
is_valid, should_be_valid,
"Request validation failed for: {:?}",
request
);
}
}
#[tokio::test]
async fn test_tool_parameter_validation() {
let test_params = vec![
(json!({"script": "puts hello"}), true),
(json!({"script": "expr 1 + 1"}), true),
(json!({"script": ""}), true), (json!({}), false),
(json!({"script": 123}), false),
(json!({"script": "puts hello", "extra": "ignored"}), true),
(json!({"script": null}), false),
(
json!({"script": "puts hello", "options": {"debug": true}}),
true,
),
];
for (params, should_be_valid) in test_params {
let is_valid = params.get("script").and_then(|v| v.as_str()).is_some();
assert_eq!(
is_valid, should_be_valid,
"Parameter validation failed for: {:?}",
params
);
}
}
#[tokio::test]
async fn test_connection_recovery() {
let _client = McpClient::new();
println!("Test scenario: Connection recovery");
println!("1. Establish connection to MCP server");
println!("2. Simulate network interruption");
println!("3. Verify connection status changes");
println!("4. Attempt reconnection");
println!("5. Verify tools are re-discovered");
println!("6. Execute tool to confirm recovery");
}
}