274 lines
10 KiB
Rust
274 lines
10 KiB
Rust
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<TestSuite>,
|
|
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<TestCase>,
|
|
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<TestStep>,
|
|
pub expected_result: String,
|
|
pub actual_result: String,
|
|
pub status: TestStatus,
|
|
pub error_message: Option<String>,
|
|
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<TestStep>) -> 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<String>) -> 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
|
|
}
|
|
} |