use std::collections::HashMap;
use serde_json::{json, Value};
use incode::mcp_server::McpServer;
use incode::tools::{ToolRegistry, ToolResponse};
use incode::error::{IncodeError, IncodeResult};
#[tokio::test]
async fn test_mcp_server_initialization() {
let server_result = McpServer::new(None);
match server_result {
Ok(_server) => {
println!("✅ MCP Server initialized successfully with LLDB manager");
}
Err(e) => {
println!("⚠️ MCP Server initialization failed (may be expected in test env): {}", e);
}
}
}
#[tokio::test]
async fn test_tool_registry_initialization() {
let registry = ToolRegistry::new();
let tool_count = registry.tool_count();
assert!(tool_count >= 13, "Should have at least one tool per category, got {}", tool_count);
println!("✅ Tool registry initialized with {} tools across categories", tool_count);
let tool_list = registry.get_tool_list();
assert!(!tool_list.is_empty(), "Tool list should not be empty");
for (index, tool) in tool_list.iter().enumerate() {
assert!(tool.get("name").is_some(), "Each tool should have a name");
assert!(tool.get("description").is_some(), "Each tool should have a description");
assert!(tool.get("inputSchema").is_some(), "Each tool should have input schema");
if index >= 14 && index <= 17 { let name = tool.get("name").and_then(|n| n.as_str()).unwrap_or("unknown");
println!("=== TOOL {} DEBUG ===", index + 1);
println!("Tool {}: {}", index + 1, name);
println!("Schema: {}", serde_json::to_string_pretty(&tool).unwrap());
println!("====================");
}
}
println!("✅ Tool list structure valid with {} tools", tool_list.len());
}
#[tokio::test]
async fn test_process_control_tools_mcp_integration() {
let registry = ToolRegistry::new();
let tool_list = registry.get_tool_list();
let process_control_tools = ["launch_process", "attach_to_process", "detach_process",
"kill_process", "get_process_info", "list_processes"];
for tool_name in &process_control_tools {
let found = tool_list.iter().any(|tool| {
tool.get("name").and_then(|n| n.as_str()) == Some(*tool_name)
});
if found {
println!("✅ Found Process Control tool in MCP registry: {}", tool_name);
} else {
println!("⚠️ Process Control tool not found in registry: {}", tool_name);
}
}
}
#[tokio::test]
async fn test_mcp_tool_execution_launch_process() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let arguments = HashMap::from([
("executable".to_string(), Value::String("/bin/echo".to_string())),
("args".to_string(), Value::Array(vec![
Value::String("test".to_string()),
Value::String("message".to_string())
])),
("env".to_string(), Value::Object(serde_json::Map::new()))
]);
let result = registry.execute_tool("launch_process", arguments, &mut lldb_manager).await;
match result {
Ok(ToolResponse::Json(response)) => {
println!("✅ F0001: launch_process MCP execution successful: {:?}", response);
assert!(response.get("success").is_some() || response.get("pid").is_some(),
"Response should indicate success or contain PID");
}
Ok(ToolResponse::Error(error)) => {
println!("⚠️ F0001: launch_process failed (expected in test env): {}", error);
assert!(error.contains("LLDB") || error.contains("not found") || error.contains("Failed"),
"Error should be related to LLDB or file system");
}
Ok(ToolResponse::Success(msg)) => {
println!("✅ F0001: launch_process success: {}", msg);
}
Err(e) => {
println!("⚠️ F0001: launch_process error (may be expected): {}", e);
}
}
} else {
println!("⚠️ Skipping launch_process test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_tool_execution_attach_process() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let arguments = HashMap::from([
("pid".to_string(), Value::Number(serde_json::Number::from(99999u32))),
]);
let result = registry.execute_tool("attach_to_process", arguments, &mut lldb_manager).await;
match result {
Ok(ToolResponse::Json(response)) => {
println!("✅ F0002: attach_to_process MCP execution: {:?}", response);
}
Ok(ToolResponse::Error(error)) => {
println!("✅ F0002: attach_to_process correctly failed for invalid PID: {}", error);
assert!(error.contains("Failed to attach") || error.contains("not found"),
"Error should mention attachment failure");
}
Ok(ToolResponse::Success(msg)) => {
println!("⚠️ F0002: attach_to_process unexpectedly succeeded: {}", msg);
}
Err(e) => {
println!("✅ F0002: attach_to_process error handling: {}", e);
}
}
} else {
println!("⚠️ Skipping attach_to_process test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_tool_execution_get_process_info() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let arguments = HashMap::new();
let result = registry.execute_tool("get_process_info", arguments, &mut lldb_manager).await;
match result {
Ok(ToolResponse::Json(response)) => {
println!("⚠️ F0005: get_process_info unexpectedly succeeded: {:?}", response);
}
Ok(ToolResponse::Error(error)) => {
println!("✅ F0005: get_process_info correctly failed with no process: {}", error);
assert!(error.contains("No active process") || error.contains("No process"),
"Error should mention no active process");
}
Ok(ToolResponse::Success(msg)) => {
println!("⚠️ F0005: get_process_info unexpectedly succeeded: {}", msg);
}
Err(e) => {
println!("✅ F0005: get_process_info error handling: {}", e);
}
}
} else {
println!("⚠️ Skipping get_process_info test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_parameter_validation() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let invalid_arguments = HashMap::from([
("args".to_string(), Value::Array(vec![Value::String("test".to_string())]))
]);
let result = registry.execute_tool("launch_process", invalid_arguments, &mut lldb_manager).await;
match result {
Ok(ToolResponse::Error(error)) => {
println!("✅ Parameter validation: launch_process correctly rejected missing executable: {}", error);
assert!(error.contains("Missing executable") || error.contains("executable parameter"),
"Error should mention missing executable parameter");
}
Err(IncodeError::McpProtocol(error)) => {
println!("✅ Parameter validation: MCP protocol error for missing parameter: {}", error);
assert!(error.contains("Missing") || error.contains("executable"),
"Error should mention missing parameter");
}
Ok(response) => {
panic!("Should not succeed with missing executable parameter, got: {:?}", response);
}
Err(e) => {
println!("✅ Parameter validation error handling: {}", e);
}
}
let invalid_pid_args = HashMap::from([
("pid".to_string(), Value::String("not_a_number".to_string()))
]);
let result = registry.execute_tool("attach_to_process", invalid_pid_args, &mut lldb_manager).await;
match result {
Ok(ToolResponse::Error(error)) => {
println!("✅ Parameter validation: attach_to_process correctly rejected invalid PID: {}", error);
}
Err(e) => {
println!("✅ Parameter validation error for invalid PID type: {}", e);
}
Ok(response) => {
println!("⚠️ attach_to_process accepted invalid PID (may have fallback handling): {:?}", response);
}
}
} else {
println!("⚠️ Skipping parameter validation test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_unknown_tool_handling() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let result = registry.execute_tool("nonexistent_tool", HashMap::new(), &mut lldb_manager).await;
match result {
Err(IncodeError::McpProtocol(error)) => {
println!("✅ Unknown tool handling: Correctly rejected unknown tool: {}", error);
assert!(error.contains("Unknown tool") || error.contains("nonexistent_tool"),
"Error should mention unknown tool");
}
Ok(response) => {
panic!("Should not succeed with unknown tool, got: {:?}", response);
}
Err(e) => {
println!("✅ Unknown tool error handling: {}", e);
}
}
} else {
println!("⚠️ Skipping unknown tool test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_tool_categories_coverage() {
let registry = ToolRegistry::new();
let tool_list = registry.get_tool_list();
let expected_categories = [
"Process Control", "Execution Control", "Breakpoint Management",
"Stack Analysis", "Memory Inspection", "Variable Inspection",
"Thread Management", "Register Inspection", "Debug Information",
"Target Information", "LLDB Control", "Session Management", "Advanced Analysis"
];
let tool_count = tool_list.len();
println!("📊 MCP Tool Coverage Analysis:");
println!(" Total tools registered: {}", tool_count);
println!(" Expected categories: {}", expected_categories.len());
println!(" Tools per category (avg): {:.1}", tool_count as f64 / expected_categories.len() as f64);
assert!(tool_count >= expected_categories.len(),
"Should have at least one tool per category");
let key_tools = ["launch_process", "attach_to_process", "get_process_info"];
for tool_name in &key_tools {
let found = tool_list.iter().any(|tool| {
tool.get("name").and_then(|n| n.as_str()) == Some(*tool_name)
});
assert!(found, "Key tool should be registered: {}", tool_name);
}
println!("✅ MCP tool registry coverage validation passed");
}
#[tokio::test]
async fn test_mcp_response_format_compliance() {
let registry = ToolRegistry::new();
if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let result = registry.execute_tool("get_process_info", HashMap::new(), &mut lldb_manager).await;
match result {
Ok(ToolResponse::Error(error_msg)) => {
println!("✅ MCP Error response format valid: {}", error_msg);
assert!(!error_msg.is_empty(), "Error message should not be empty");
}
Ok(ToolResponse::Json(json_response)) => {
println!("✅ MCP JSON response format: {:?}", json_response);
assert!(json_response.is_object(), "JSON response should be an object");
}
Ok(ToolResponse::Success(success_msg)) => {
println!("✅ MCP Success response format: {}", success_msg);
assert!(!success_msg.is_empty(), "Success message should not be empty");
}
Err(e) => {
println!("✅ MCP Error response: {}", e);
}
}
} else {
println!("⚠️ Skipping response format test - LLDB manager initialization failed");
}
}
#[tokio::test]
async fn test_mcp_concurrent_tool_execution() {
let registry = std::sync::Arc::new(ToolRegistry::new());
if let Ok(lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
let manager = std::sync::Arc::new(tokio::sync::Mutex::new(lldb_manager));
let tasks = vec![
{
let reg = registry.clone();
let mgr = manager.clone();
tokio::spawn(async move {
let mut locked_manager = mgr.lock().await;
reg.execute_tool("get_process_info", HashMap::new(), &mut *locked_manager).await
})
},
{
let reg = registry.clone();
let mgr = manager.clone();
tokio::spawn(async move {
let mut locked_manager = mgr.lock().await;
reg.execute_tool("list_processes", HashMap::new(), &mut *locked_manager).await
})
}
];
let results = futures::future::join_all(tasks).await;
println!("✅ MCP concurrent execution test completed with {} tasks", results.len());
for (i, result) in results.into_iter().enumerate() {
match result {
Ok(tool_result) => {
match tool_result {
Ok(_) => println!(" Task {}: Completed successfully", i),
Err(e) => println!(" Task {}: Failed with error (expected): {}", i, e),
}
}
Err(e) => println!(" Task {}: Join error: {}", i, e),
}
}
} else {
println!("⚠️ Skipping concurrent execution test - LLDB manager initialization failed");
}
}