ctrf-rs 0.2.0

A reporter for Common Test Report Format (CTRF) in Rust
Documentation
// crate import(s)
use crate::{
    impl_extra,
    insights::test_insights::TestInsights,
    test::{attachment::Attachment, step::Step},
};

// std import(s)
use std::{collections::HashMap, path::PathBuf, time::Duration, vec};

// other import(s)
use serde::{Deserialize, Serialize};
use serde_json::Value;

pub mod attachment;
pub mod step;

/// Status indicator for a CTRF [`Test`] element.
/// Corresponds to the spec's ["Status"](https://www.ctrf.io/docs/specification/status) enumeration.
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Status {
    /// The test has not yet executed or is awaiting fulfillment of some condition.
    Pending,
    /// The test was intentionally not executed.
    Skipped,
    /// The test executed but did not meet all success criteria.
    Failed,
    /// The test executed and met all success criteria.
    Passed,
    /// This is a catch-all for outcomes that do not align with any other pre-defined status.
    /// Typically indicative of a unique or library-specific status.
    Other,
}

/// Test element for a CTRF report.
/// Corresponds to the spec's ["Test"](https://www.ctrf.io/docs/specification/test) object.
#[derive(Deserialize, Serialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Test {
    name: String,
    status: Status,
    duration: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop: Option<u64>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub suite: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trace: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub snippet: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ai: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub raw_status: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub tags: Vec<String>,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub test_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub file_path: Option<PathBuf>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub line: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retries: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub flaky: Option<bool>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub stdout: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub stderr: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thread_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub browser: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub device: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub screenshot: Option<String>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub parameters: HashMap<String, Value>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub steps: Vec<Step>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub attachments: Vec<Attachment>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub insights: Option<TestInsights>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    extra: HashMap<String, Value>,
}

impl Test {
    /// Creates an instance of a `Test` with the provided arguments.
    /// For other fields, applies a vacant default (`None` or empty collection).
    pub fn new(name: String, status: Status, duration: Duration) -> Self {
        Self {
            name,
            status,
            duration: duration.as_millis() as u64,
            start: None,
            stop: None,
            suite: vec![],
            message: None,
            trace: None,
            snippet: None,
            ai: None,
            raw_status: None,
            tags: vec![],
            test_type: None,
            file_path: None,
            line: None,
            retries: None,
            flaky: None,
            stdout: vec![],
            stderr: vec![],
            thread_id: None,
            browser: None,
            device: None,
            screenshot: None,
            parameters: HashMap::new(),
            steps: vec![],
            attachments: vec![],
            insights: None,
            extra: HashMap::new(),
        }
    }

    /// Returns the `Test`'s contained `status` value
    pub fn status(&self) -> Status {
        self.status
    }
}

impl_extra!(Test);