incode 0.30.26038

InCode - MCP server for LLDB debugging automation
Documentation
// InCode MCP Integration Test Suite
//
// GRANULAR FEATURES TESTED:
// - MCP Server initialization and configuration
// - Tool registry with all 65+ debugging tools across 13 categories  
// - MCP protocol compliance (tools/list, tools/call, initialize)
// - Process Control MCP tools integration (F0001-F0006)
// - Error handling in MCP context
// - Tool parameter validation and response formatting
// - Async tool execution via MCP protocol
//
// Each MCP integration aspect is tested individually with comprehensive scenarios:
// - MCP protocol message handling
// - Tool discovery and listing
// - Tool execution with various parameter combinations
// - Error response formatting
// - JSON-RPC compliance

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() {
    // Test MCP server can be created and initialized with tool registry
    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);
            // This is acceptable in test environment without full LLDB setup
        }
    }
}

#[tokio::test]  
async fn test_tool_registry_initialization() {
    // Test tool registry creates all expected tool categories
    let registry = ToolRegistry::new();
    let tool_count = registry.tool_count();
    
    // Should have tools from all 13 categories (some may be placeholders)
    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);
    
    // Test tool list generation for MCP
    let tool_list = registry.get_tool_list();
    assert!(!tool_list.is_empty(), "Tool list should not be empty");
    
    // Verify tool list structure and debug the 16th tool
    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 { // Tools 15-18 for debugging
            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() {
    // Test Process Control tools are properly registered and callable via MCP
    let registry = ToolRegistry::new();
    let tool_list = registry.get_tool_list();
    
    // Check for Process Control tools (F0001-F0006)
    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() {
    // Test F0001: launch_process tool execution via MCP protocol
    let registry = ToolRegistry::new();
    
    // Create test manager (may fail in test environment)
    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() {
    // Test F0002: attach_to_process tool execution via MCP protocol  
    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() {
    // Test F0005: get_process_info tool execution via MCP protocol
    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() {
    // Test MCP tool parameter validation and error handling
    let registry = ToolRegistry::new();
    
    if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
        // Test launch_process with missing executable parameter
        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);
            }
        }
        
        // Test attach_to_process with invalid PID type
        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() {
    // Test MCP error handling for unknown tool requests
    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() {
    // Test that all expected tool categories are represented in MCP registry
    let registry = ToolRegistry::new();
    let tool_list = registry.get_tool_list();
    
    // Expected categories based on our 13-category architecture
    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"
    ];
    
    // Check that we have reasonable tool coverage
    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");
    
    // Verify some key tools are present
    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() {
    // Test MCP tool responses follow proper JSON-RPC format
    let registry = ToolRegistry::new();
    
    if let Ok(mut lldb_manager) = incode::lldb_manager::LldbManager::new(None) {
        // Test error response format
        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() {
    // Test MCP handles concurrent tool requests properly
    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));
        
        // Create multiple concurrent tool requests
        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
                })
            }
        ];
        
        // Wait for all tasks to complete
        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");
    }
}