checkmate-cli 0.4.1

Checkmate - API Testing Framework CLI
//! Test result types

use serde::{Deserialize, Serialize};

/// Result of a single assertion
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertionResult {
    pub passed: bool,
    pub message: Option<String>,
    pub expected: Option<String>,
    pub actual: Option<String>,
    pub error: Option<String>,
}

impl AssertionResult {
    pub fn passed() -> Self {
        Self {
            passed: true,
            message: None,
            expected: None,
            actual: None,
            error: None,
        }
    }

    pub fn failed(message: &str, expected: String, actual: String) -> Self {
        Self {
            passed: false,
            message: Some(message.to_string()),
            expected: Some(expected),
            actual: Some(actual),
            error: None,
        }
    }

    pub fn error(error: &str) -> Self {
        Self {
            passed: false,
            message: None,
            expected: None,
            actual: None,
            error: Some(error.to_string()),
        }
    }
}

/// Result of a single test case
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestResult {
    pub name: String,
    pub description: Option<String>,
    pub status: TestStatus,
    pub duration_ms: u64,
    pub request_count: usize,
    pub assertions: AssertionSummary,
    pub failures: Vec<TestFailure>,
}

/// Test execution status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TestStatus {
    Passed,
    Failed,
    Skipped,
    Error,
}

/// Summary of assertion results
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AssertionSummary {
    pub total: usize,
    pub passed: usize,
    pub failed: usize,
}

/// Details of a test failure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestFailure {
    pub request_index: usize,
    pub assertion: String,
    pub expected: Option<String>,
    pub actual: Option<String>,
    pub message: Option<String>,
    pub error: Option<String>,
}

/// Result of an entire test suite
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestSuiteResult {
    pub suite: Option<String>,
    pub timestamp: String,
    pub duration_ms: u64,
    pub summary: SuiteSummary,
    pub tests: Vec<TestResult>,
}

/// Summary of test suite results
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SuiteSummary {
    pub total: usize,
    pub passed: usize,
    pub failed: usize,
    pub skipped: usize,
    pub errors: usize,
}

impl TestSuiteResult {
    pub fn new(suite_name: Option<String>) -> Self {
        Self {
            suite: suite_name,
            timestamp: chrono_lite_timestamp(),
            duration_ms: 0,
            summary: SuiteSummary::default(),
            tests: Vec::new(),
        }
    }

    pub fn add_result(&mut self, result: TestResult) {
        match result.status {
            TestStatus::Passed => self.summary.passed += 1,
            TestStatus::Failed => self.summary.failed += 1,
            TestStatus::Skipped => self.summary.skipped += 1,
            TestStatus::Error => self.summary.errors += 1,
        }
        self.summary.total += 1;
        self.tests.push(result);
    }

    pub fn set_duration(&mut self, duration_ms: u64) {
        self.duration_ms = duration_ms;
    }

    pub fn all_passed(&self) -> bool {
        self.summary.failed == 0 && self.summary.errors == 0
    }
}

/// Simple ISO 8601 timestamp without external dependencies
fn chrono_lite_timestamp() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};

    let duration = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default();

    let secs = duration.as_secs();

    // Calculate date/time components
    let days_since_epoch = secs / 86400;
    let time_of_day = secs % 86400;

    let hours = time_of_day / 3600;
    let minutes = (time_of_day % 3600) / 60;
    let seconds = time_of_day % 60;

    // Simplified year/month/day calculation (approximate, good enough for timestamps)
    let mut year = 1970;
    let mut remaining_days = days_since_epoch as i64;

    loop {
        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
        if remaining_days < days_in_year {
            break;
        }
        remaining_days -= days_in_year;
        year += 1;
    }

    let days_in_months: [i64; 12] = if is_leap_year(year) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };

    let mut month = 1;
    for &days in &days_in_months {
        if remaining_days < days {
            break;
        }
        remaining_days -= days;
        month += 1;
    }

    let day = remaining_days + 1;

    format!(
        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
        year, month, day, hours, minutes, seconds
    )
}

fn is_leap_year(year: i64) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}