allure-rust-commons 1.3.0

Core Allure runtime model, lifecycle, and result writer for Rust integrations.
Documentation
use super::*;
use crate::test_utils::allure_test;
use crate::{
    http_exchange::HTTP_EXCHANGE_ATTACHMENT_MIME,
    model::{
        Attachment, Categories, Category, GlobalAttachment, GlobalError, Globals, Status,
        TestResult, TestResultContainer,
    },
};
use std::time::{SystemTime, UNIX_EPOCH};

fn make_writer() -> FileSystemResultsWriter {
    let dir = std::env::temp_dir().join(format!(
        "allure-rust-writer-tests-{}",
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("clock should be after epoch")
            .as_nanos()
    ));
    FileSystemResultsWriter::new(dir).expect("writer should initialize")
}

fn read_to_string(path: &Path) -> String {
    fs::read_to_string(path).expect("written file should be readable")
}

#[test]
fn write_result_wrapper_writes_result_json() {
    allure_test(
        module_path!(),
        "write_result_wrapper_writes_result_json",
        "Verifies the writer persists raw result JSON with the expected result-file name.",
        || {
            let writer = make_writer();
            let result = TestResult {
                uuid: "result-1".to_string(),
                name: "line1\n\"quoted\"\\slash".to_string(),
                ..Default::default()
            };

            let path = writer.write_result(&result).expect("result should write");

            assert!(path.ends_with("result-1-result.json"));
            assert!(read_to_string(&path).contains("line1\\n\\\"quoted\\\"\\\\slash"));
        },
    );
}

#[test]
fn write_result_typed_writes_result_json() {
    allure_test(
        module_path!(),
        "write_result_typed_writes_result_json",
        "Verifies the typed result writer serializes test results to Allure result files.",
        || {
            let writer = make_writer();
            let result = TestResult {
                uuid: "result-2".to_string(),
                name: "typed result".to_string(),
                status: Some(Status::Passed),
                ..Default::default()
            };

            let path = writer
                .write_result_typed(&result)
                .expect("typed result should write");

            let json = read_to_string(&path);
            assert!(path.ends_with("result-2-result.json"));
            assert!(json.contains("\"status\":\"passed\""));
        },
    );
}

#[test]
fn write_container_and_typed_write_container_json() {
    allure_test(
        module_path!(),
        "write_container_and_typed_write_container_json",
        "Verifies raw and typed container writes produce Allure container files.",
        || {
            let writer = make_writer();
            let container = TestResultContainer {
                uuid: "container-1".to_string(),
                children: vec!["child".to_string()],
                ..Default::default()
            };

            let wrapper_path = writer
                .write_container(&container)
                .expect("container wrapper should write");
            let typed_path = writer
                .write_container_typed(&TestResultContainer {
                    uuid: "container-2".to_string(),
                    ..Default::default()
                })
                .expect("container typed should write");

            assert!(wrapper_path.ends_with("container-1-container.json"));
            assert!(typed_path.ends_with("container-2-container.json"));
        },
    );
}

#[test]
fn write_globals_and_typed_write_globals_json() {
    allure_test(
        module_path!(),
        "write_globals_and_typed_write_globals_json",
        "Verifies raw and typed global metadata writes produce the globals file.",
        || {
            let writer = make_writer();
            let globals = Globals {
                attachments: vec![GlobalAttachment {
                    name: "global-att".to_string(),
                    source: "source.bin".to_string(),
                    content_type: "application/octet-stream".to_string(),
                }],
                errors: vec![GlobalError {
                    message: "boom".to_string(),
                    trace: None,
                }],
            };

            let path_1 = writer
                .write_globals(&globals)
                .expect("globals wrapper should write");
            let path_2 = writer
                .write_globals_typed(&globals)
                .expect("globals typed should write");

            let file_name_1 = path_1
                .file_name()
                .and_then(|v| v.to_str())
                .expect("globals wrapper file name should be valid utf-8");
            let file_name_2 = path_2
                .file_name()
                .and_then(|v| v.to_str())
                .expect("globals typed file name should be valid utf-8");

            assert!(file_name_1.ends_with("-globals.json"));
            assert!(file_name_2.ends_with("-globals.json"));
            assert!(read_to_string(&path_1).contains("global-att"));
            assert!(read_to_string(&path_2).contains("\"errors\""));
        },
    );
}

#[test]
fn write_environment_properties_writes_sorted_pairs() {
    allure_test(
        module_path!(),
        "write_environment_properties_writes_sorted_pairs",
        "Verifies environment properties are written in deterministic sorted order.",
        || {
            let writer = make_writer();
            let properties = HashMap::from([
                ("z_key".to_string(), "z".to_string()),
                ("a_key".to_string(), "a".to_string()),
            ]);

            let path = writer
                .write_environment_properties(&properties)
                .expect("environment properties should write");

            assert!(path.ends_with("environment.properties"));
            assert_eq!(read_to_string(&path), "a_key=a\nz_key=z");
        },
    );
}

#[test]
fn write_categories_and_typed_write_categories_json() {
    allure_test(
        module_path!(),
        "write_categories_and_typed_write_categories_json",
        "Verifies raw and typed categories writes produce the Allure categories file.",
        || {
            let writer = make_writer();
            let categories = Categories(vec![Category {
                name: "cat".to_string(),
                description: Some("desc".to_string()),
                matched_statuses: Some(vec![Status::Failed]),
                message_regex: Some("error.*".to_string()),
                trace_regex: None,
                flaky: Some(false),
            }]);

            let path_1 = writer
                .write_categories(&categories)
                .expect("categories wrapper should write");
            let path_2 = writer
                .write_categories_typed(&categories)
                .expect("categories typed should write");

            assert!(path_1.ends_with("categories.json"));
            assert!(path_2.ends_with("categories.json"));
            assert!(read_to_string(&path_1).contains("error.*"));
        },
    );
}

#[test]
fn write_attachment_methods_write_expected_files() {
    allure_test(
        module_path!(),
        "write_attachment_methods_write_expected_files",
        "Verifies attachment writer APIs persist byte, string, and path-based attachments.",
        || {
            let writer = make_writer();

            let path_1 = writer
                .write_attachment("manual-1.bin", b"first")
                .expect("attachment wrapper should write");
            let path_2 = writer
                .write_attachment_named("manual-2.bin", b"second")
                .expect("attachment named should write");
            let (source, path_3) = writer
                .write_attachment_auto("att-uuid", Some("report.json"), Some("text/plain"), b"auto")
                .expect("attachment auto should write");

            assert!(path_1.ends_with("manual-1.bin"));
            assert_eq!(
                fs::read(&path_1).expect("first attachment should exist"),
                b"first"
            );
            assert!(path_2.ends_with("manual-2.bin"));
            assert_eq!(
                fs::read(&path_2).expect("second attachment should exist"),
                b"second"
            );
            assert_eq!(source, "att-uuid-attachment.json");
            assert!(path_3.ends_with("att-uuid-attachment.json"));
            assert_eq!(
                fs::read(&path_3).expect("auto attachment should exist"),
                b"auto"
            );
        },
    );
}

#[test]
fn serializes_with_escaped_strings() {
    allure_test(
        module_path!(),
        "serializes_with_escaped_strings",
        "Verifies JSON serialization escapes strings without losing their values.",
        || {
            let result = TestResult {
                uuid: "u-1".to_string(),
                name: "line1\n\"quoted\"\\slash".to_string(),
                status: Some(Status::Passed),
                attachments: vec![Attachment {
                    name: "att\n\"n\"".to_string(),
                    source: "s\\x".to_string(),
                    content_type: "application/json".to_string(),
                }],
                ..Default::default()
            };

            let json = serde_json::to_string(&result).expect("test result should serialize");

            assert!(json.contains("line1\\n\\\"quoted\\\"\\\\slash"));
            assert!(json.contains("att\\n\\\"n\\\""));
            assert!(json.contains("s\\\\x"));
        },
    );
}

#[test]
fn attachment_filename_prefers_name_extension() {
    allure_test(
        module_path!(),
        "attachment_filename_prefers_name_extension",
        "Verifies attachment filenames preserve an extension supplied in the attachment name.",
        || {
            let source =
                attachment_source_name("abc", Some("report.custom"), Some("application/json"));
            assert_eq!(source, "abc-attachment.custom");
        },
    );
}

#[test]
fn attachment_filename_falls_back_to_content_type() {
    allure_test(
        module_path!(),
        "attachment_filename_falls_back_to_content_type",
        "Verifies attachment filenames derive a useful extension from the content type.",
        || {
            let source = attachment_source_name(
                "abc",
                Some("report"),
                Some("application/json; charset=utf-8"),
            );
            assert_eq!(source, "abc-attachment.json");
        },
    );
}

#[test]
fn attachment_filename_falls_back_to_http_exchange_content_type() {
    allure_test(
        module_path!(),
        "attachment_filename_falls_back_to_http_exchange_content_type",
        "Verifies HTTP exchange attachments receive the expected rich-format extension.",
        || {
            let source = attachment_source_name(
                "abc",
                Some("exchange"),
                Some(HTTP_EXCHANGE_ATTACHMENT_MIME),
            );
            assert_eq!(source, "abc-attachment.httpexchange");
        },
    );
}

#[test]
fn attachment_filename_has_no_extension_when_unknown() {
    allure_test(
        module_path!(),
        "attachment_filename_has_no_extension_when_unknown",
        "Verifies attachments avoid inventing an extension when neither name nor content type identifies one.",
        || {
            let source =
                attachment_source_name("abc", Some("report"), Some("application/x-custom"));
            assert_eq!(source, "abc-attachment");
        },
    );
}