Files
erp/integration-tests/test_report.rs
iven 841766b168
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
fix(用户管理): 修复用户列表页面加载失败问题
修复用户列表页面加载失败导致测试超时的问题,确保页面元素正确渲染
2026-04-19 08:46:28 +08:00

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
}
}