phenotype-mcp-testing 0.1.0

MCP server for game testing and validation operations
//! Testing handler for MCP testing operations

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{info, debug, warn, error};

/// Testing handler for game testing operations
#[derive(Debug, Default)]
pub struct TestingHandler {
    test_results: Vec<TestResult>,
    test_suites: HashMap<String, TestSuite>,
}

impl TestingHandler {
    /// Create a new testing handler
    pub fn new() -> Self {
        Self {
            test_results: Vec::new(),
            test_suites: HashMap::new(),
        }
    }

    /// Register a test suite
    pub fn register_suite(&mut self, name: String, suite: TestSuite) {
        self.test_suites.insert(name, suite);
    }

    /// Run a specific test
    pub fn run_test(&mut self, test_name: &str, args: Option<Value>) -> Result<TestResult, String> {
        info!("Running test: {}", test_name);

        // First, try to find the test by cloning the test data
        let test_opt = self.test_suites.values()
            .flat_map(|suite| suite.tests.iter())
            .find(|t| t.name == test_name)
            .cloned();

        if let Some(test) = test_opt {
            return self.execute_test(&test, args);
        }

        // If not found, create a mock result
        let result = TestResult {
            test_name: test_name.to_string(),
            suite_name: "default".to_string(),
            passed: true,
            duration_ms: 100,
            error_message: None,
            output: format!("Test '{}' executed (mock implementation)", test_name),
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
        };

        self.test_results.push(result.clone());
        Ok(result)
    }

    fn execute_test(&mut self, test: &Test, args: Option<Value>) -> Result<TestResult, String> {
        let start = std::time::Instant::now();

        // In a real implementation, this would execute the actual test
        // For now, we simulate test execution
        let passed = true;

        let result = TestResult {
            test_name: test.name.clone(),
            suite_name: test.suite_name.clone(),
            passed,
            duration_ms: start.elapsed().as_millis() as u64,
            error_message: None,
            output: format!("Test executed with args: {:?}", args),
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
        };

        self.test_results.push(result.clone());
        Ok(result)
    }

    /// Run all tests in a suite
    pub fn run_suite(&mut self, suite_name: &str) -> Result<SuiteResult, String> {
        info!("Running test suite: {}", suite_name);

        let suite = self.test_suites.get(suite_name)
            .ok_or_else(|| format!("Test suite not found: {}", suite_name))?
            .clone();

        let start = std::time::Instant::now();
        let mut results = Vec::new();
        let mut passed = 0;
        let mut failed = 0;

        for test in &suite.tests {
            match self.run_test(&test.name, None) {
                Ok(result) => {
                    if result.passed {
                        passed += 1;
                    } else {
                        failed += 1;
                    }
                    results.push(result);
                }
                Err(e) => {
                    failed += 1;
                    results.push(TestResult {
                        test_name: test.name.clone(),
                        suite_name: suite_name.to_string(),
                        passed: false,
                        duration_ms: 0,
                        error_message: Some(e),
                        output: String::new(),
                        timestamp: std::time::SystemTime::now()
                            .duration_since(std::time::UNIX_EPOCH)
                            .unwrap_or_default()
                            .as_secs(),
                    });
                }
            }
        }

        Ok(SuiteResult {
            suite_name: suite_name.to_string(),
            total_tests: results.len(),
            passed,
            failed,
            duration_ms: start.elapsed().as_millis() as u64,
            results,
        })
    }

    /// Get all test results
    pub fn get_results(&self) -> &[TestResult] {
        &self.test_results
    }

    /// Get test results for a specific suite
    pub fn get_suite_results(&self, suite_name: &str) -> Vec<&TestResult> {
        self.test_results
            .iter()
            .filter(|r| r.suite_name == suite_name)
            .collect()
    }

    /// Generate test report
    pub fn generate_report(&self) -> TestReport {
        let total_tests = self.test_results.len();
        let passed = self.test_results.iter().filter(|r| r.passed).count();
        let failed = total_tests - passed;

        let suite_summaries: Vec<SuiteSummary> = self.test_suites
            .keys()
            .map(|name| {
                let suite_results: Vec<&TestResult> = self.test_results
                    .iter()
                    .filter(|r| &r.suite_name == name)
                    .collect();

                let total = suite_results.len();
                let suite_passed = suite_results.iter().filter(|r| r.passed).count();

                SuiteSummary {
                    name: name.clone(),
                    total,
                    passed: suite_passed,
                    failed: total - suite_passed,
                }
            })
            .collect();

        TestReport {
            total_tests,
            passed,
            failed,
            success_rate: if total_tests > 0 {
                (passed as f64 / total_tests as f64) * 100.0
            } else {
                0.0
            },
            suites: suite_summaries,
        }
    }

    /// Clear all test results
    pub fn clear_results(&mut self) {
        self.test_results.clear();
    }

    /// Validate test configuration
    pub fn validate_config(&self, config: &TestConfig) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();

        if config.test_paths.is_empty() {
            errors.push("No test paths specified".to_string());
        }

        for path in &config.test_paths {
            if !Path::new(path).exists() {
                errors.push(format!("Test path does not exist: {}", path));
            }
        }

        if config.timeout_seconds == 0 {
            errors.push("Timeout must be greater than 0".to_string());
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Test {
    pub name: String,
    pub suite_name: String,
    pub description: Option<String>,
    pub test_type: TestType,
    pub expected_result: Option<Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TestType {
    Unit,
    Integration,
    E2E,
    Performance,
    Visual,
    Accessibility,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestSuite {
    pub name: String,
    pub description: Option<String>,
    pub tests: Vec<Test>,
    pub setup: Option<String>,
    pub teardown: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
    pub test_name: String,
    pub suite_name: String,
    pub passed: bool,
    pub duration_ms: u64,
    pub error_message: Option<String>,
    pub output: String,
    pub timestamp: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuiteResult {
    pub suite_name: String,
    pub total_tests: usize,
    pub passed: usize,
    pub failed: usize,
    pub duration_ms: u64,
    pub results: Vec<TestResult>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestReport {
    pub total_tests: usize,
    pub passed: usize,
    pub failed: usize,
    pub success_rate: f64,
    pub suites: Vec<SuiteSummary>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SuiteSummary {
    pub name: String,
    pub total: usize,
    pub passed: usize,
    pub failed: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestConfig {
    pub test_paths: Vec<String>,
    pub timeout_seconds: u64,
    pub parallel: bool,
    pub retries: u32,
    pub environment: HashMap<String, String>,
}

/// Game state capture
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GameStateCapture {
    pub timestamp: u64,
    pub frame_number: u64,
    pub player_position: Option<(f32, f32, f32)>,
    pub entities: Vec<EntityState>,
    pub screenshot_path: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntityState {
    pub id: String,
    pub entity_type: String,
    pub position: (f32, f32, f32),
    pub health: Option<f32>,
    pub metadata: HashMap<String, Value>,
}

/// Screenshot capture options
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScreenshotOptions {
    pub format: ImageFormat,
    pub quality: u8,
    pub region: Option<Region>,
    pub output_path: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ImageFormat {
    Png,
    Jpeg,
    Webp,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Region {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}