use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestReport { pub name: String, pub timestamp: String, pub elapsed_ms: u64, pub suites: Vec, pub summary: TestSummary, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestSummary { pub total_cases: u64, pub passed: u64, pub failed: u64, pub pass_rate: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestSuite { pub name: String, pub description: String, pub cases: Vec, pub total: u64, pub passed: u64, pub failed: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestCase { pub id: String, pub name: String, pub category: String, pub steps: Vec, pub expected_result: String, pub actual_result: String, pub status: TestStatus, pub error_message: Option, pub severity: Severity, pub business_logic_verified: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TestStep { pub step_number: u32, pub action: String, pub expected: String, pub actual: String, pub duration_ms: u64, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum TestStatus { Pass, Fail, Skipped, Blocked, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Severity { Critical, High, Medium, Low, } impl TestReport { pub fn new(name: String) -> Self { Self { name, timestamp: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), elapsed_ms: 0, suites: Vec::new(), summary: TestSummary { total_cases: 0, passed: 0, failed: 0, pass_rate: 0.0, }, } } pub fn add_suite(&mut self, suite: TestSuite) { self.summary.total_cases += suite.total; self.summary.passed += suite.passed; self.summary.failed += suite.failed; self.suites.push(suite); } pub fn finalize(&mut self) { if self.summary.total_cases > 0 { self.summary.pass_rate = (self.summary.passed as f64 / self.summary.total_cases as f64) * 100.0; } } pub fn print_summary(&self) { println!("\n"); println!("╔══════════════════════════════════════════════════════════════════════╗"); println!("║ ERP PLATFORM INTEGRATION TEST REPORT ║"); println!("╠══════════════════════════════════════════════════════════════════════╣"); println!("║ Timestamp: {} ║", self.timestamp); println!("║ Total Duration: {} ms ║", self.elapsed_ms); println!("╠══════════════════════════════════════════════════════════════════════╣"); println!("║ OVERALL SUMMARY ║"); println!("║ ├─ Total Test Cases: {} ║", self.summary.total_cases); println!("║ ├─ Passed: ✓ {} ║", self.summary.passed); println!("║ ├─ Failed: ✗ {} ║", self.summary.failed); println!("║ └─ Pass Rate: {:.2}% ║", self.summary.pass_rate); println!("╠══════════════════════════════════════════════════════════════════════╣"); for suite in &self.suites { let status_icon = if suite.failed == 0 { "✓" } else { "✗" }; println!("║ {} {} ({}/{} passed, {} failed)", status_icon, suite.name, suite.passed, suite.total, suite.failed); } println!("╚══════════════════════════════════════════════════════════════════════╝"); } pub fn generate_markdown_report(&self) -> String { let mut md = String::new(); md.push_str("# ERP Platform Full Integration Test Report\n\n"); md.push_str(&format!("**Generated:** {} \n", self.timestamp)); md.push_str(&format!("**Total Duration:** {} ms \n\n", self.elapsed_ms)); md.push_str("## Executive Summary\n\n"); md.push_str(&format!("| Metric | Value |\n")); md.push_str(&format!("|--------|-------|\n")); md.push_str(&format!("| Total Test Cases | {} |\n", self.summary.total_cases)); md.push_str(&format!("| Passed | {} |\n", self.summary.passed)); md.push_str(&format!("| Failed | {} |\n", self.summary.failed)); md.push_str(&format!("| Pass Rate | {:.2}% |\n\n", self.summary.pass_rate)); md.push_str("## Test Suites\n\n"); for suite in &self.suites { md.push_str(&format!("### {}\n\n", suite.name)); md.push_str(&format!("*{}*\n\n", suite.description)); md.push_str(&format!("**Results:** {} passed, {} failed out of {} total\n\n", suite.passed, suite.failed, suite.total)); md.push_str("| ID | Test Case | Severity | Status | Business Logic |\n"); md.push_str("|----|-----------|----------|--------|----------------|\n"); for case in &suite.cases { let status_icon = match case.status { TestStatus::Pass => "✓ PASS", TestStatus::Fail => "✗ FAIL", TestStatus::Skipped => "⊘ SKIP", TestStatus::Blocked => "⊗ BLOCK", }; let biz_logic = if case.business_logic_verified { "✓" } else { "✗" }; md.push_str(&format!("| {} | {} | {:?} | {} | {} |\n", case.id, case.name, case.severity, status_icon, biz_logic)); } md.push_str("\n"); for case in &suite.cases { if case.status == TestStatus::Fail { md.push_str(&format!("#### {}: {}\n\n", case.id, case.name)); md.push_str(&format!("**Severity:** {:?} \n", case.severity)); md.push_str(&format!("**Expected:** {} \n", case.expected_result)); md.push_str(&format!("**Actual:** {} \n\n", case.actual_result)); if let Some(ref err) = case.error_message { md.push_str(&format!("**Error:** `{}` \n\n", err)); } md.push_str("**Test Steps:**\n\n"); for step in &case.steps { md.push_str(&format!("{}. **{}** \n - Expected: {} \n - Actual: {} \n - Duration: {}ms \n\n", step.step_number, step.action, step.expected, step.actual, step.duration_ms)); } md.push_str("---\n\n"); } } } md } } impl TestSuite { pub fn new(name: &str, description: &str) -> Self { Self { name: name.to_string(), description: description.to_string(), cases: Vec::new(), total: 0, passed: 0, failed: 0, } } pub fn add_case(&mut self, case: TestCase) { self.total += 1; match case.status { TestStatus::Pass => self.passed += 1, TestStatus::Fail => self.failed += 1, _ => {} } self.cases.push(case); } } impl TestCase { pub fn new(id: &str, name: &str, category: &str) -> Self { Self { id: id.to_string(), name: name.to_string(), category: category.to_string(), steps: Vec::new(), expected_result: String::new(), actual_result: String::new(), status: TestStatus::Fail, error_message: None, severity: Severity::Medium, business_logic_verified: false, } } pub fn with_steps(mut self, steps: Vec) -> Self { self.steps = steps; self } pub fn with_expected(mut self, expected: &str) -> Self { self.expected_result = expected.to_string(); self } pub fn with_actual(mut self, actual: &str) -> Self { self.actual_result = actual.to_string(); self } pub fn with_status(mut self, status: TestStatus) -> Self { self.status = status; self } pub fn with_error(mut self, error: Option) -> Self { self.error_message = error; self } pub fn with_severity(mut self, severity: Severity) -> Self { self.severity = severity; self } pub fn verify_business_logic(mut self, verified: bool) -> Self { self.business_logic_verified = verified; self } pub fn add_step(&mut self, step: TestStep) { self.steps.push(step); } } impl TestStep { pub fn new(step_number: u32, action: &str, expected: &str) -> Self { Self { step_number, action: action.to_string(), expected: expected.to_string(), actual: String::new(), duration_ms: 0, } } pub fn with_actual(mut self, actual: &str, duration_ms: u64) -> Self { self.actual = actual.to_string(); self.duration_ms = duration_ms; self } }