use std::collections::HashMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{info, debug, warn, error};
#[derive(Debug, Default)]
pub struct TestingHandler {
test_results: Vec<TestResult>,
test_suites: HashMap<String, TestSuite>,
}
impl TestingHandler {
pub fn new() -> Self {
Self {
test_results: Vec::new(),
test_suites: HashMap::new(),
}
}
pub fn register_suite(&mut self, name: String, suite: TestSuite) {
self.test_suites.insert(name, suite);
}
pub fn run_test(&mut self, test_name: &str, args: Option<Value>) -> Result<TestResult, String> {
info!("Running test: {}", test_name);
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);
}
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();
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)
}
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,
})
}
pub fn get_results(&self) -> &[TestResult] {
&self.test_results
}
pub fn get_suite_results(&self, suite_name: &str) -> Vec<&TestResult> {
self.test_results
.iter()
.filter(|r| r.suite_name == suite_name)
.collect()
}
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,
}
}
pub fn clear_results(&mut self) {
self.test_results.clear();
}
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>,
}
#[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>,
}
#[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,
}