mxr-test-support 0.4.15

Shared test fixtures and helpers for mxr
Documentation
use ratatui::backend::TestBackend;
use ratatui::Terminal;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

pub const STANDARDS_FIXTURES: &[&str] = &[
    "alternative-html-first.eml",
    "duplicate-singletons.eml",
    "encoded-words.eml",
    "folded-flowed.eml",
    "malformed-minimal.eml",
    "missing-content-type.eml",
    "missing-message-id.eml",
    "multipart-calendar.eml",
    "nested-multipart.eml",
    "quoted-local-group.eml",
    "rfc2231-attachment.eml",
    "unsubscribe-oneclick.eml",
];

pub fn standards_fixture_names() -> &'static [&'static str] {
    STANDARDS_FIXTURES
}

pub fn fixture_stem(name: &str) -> &str {
    name.strip_suffix(".eml").unwrap_or(name)
}

pub fn standards_fixture_path(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("fixtures")
        .join("standards")
        .join(name)
}

pub fn standards_fixture_bytes(name: &str) -> Vec<u8> {
    std::fs::read(standards_fixture_path(name)).unwrap()
}

pub fn standards_fixture_string(name: &str) -> String {
    std::fs::read_to_string(standards_fixture_path(name)).unwrap()
}

pub fn redact_rfc822(raw: &str) -> String {
    static MESSAGE_ID_RE: OnceLock<Regex> = OnceLock::new();
    static DATE_RE: OnceLock<Regex> = OnceLock::new();
    let message_id_re =
        MESSAGE_ID_RE.get_or_init(|| Regex::new(r"(?m)^Message-ID:\s*<[^>\r\n]+>\r?$").unwrap());
    let date_re = DATE_RE.get_or_init(|| Regex::new(r"(?m)^Date:\s*[^\r\n]+\r?$").unwrap());

    let mut redacted = message_id_re
        .replace_all(raw, "Message-ID: <redacted@example.com>")
        .to_string();
    redacted = date_re
        .replace_all(&redacted, "Date: Fri, 20 Mar 2026 00:00:00 +0000")
        .to_string();

    let boundary_re = Regex::new(r#"boundary="([^"]+)""#).unwrap();
    let boundaries = boundary_re
        .captures_iter(raw)
        .enumerate()
        .map(|(index, caps)| {
            (
                caps[1].to_string(),
                format!("boundary=\"BOUNDARY_{index}\""),
            )
        })
        .collect::<Vec<_>>();

    for (index, (boundary, replacement)) in boundaries.iter().enumerate() {
        redacted = redacted.replace(&format!("boundary=\"{boundary}\""), replacement);
        redacted = redacted.replace(boundary, &format!("BOUNDARY_{index}"));
    }

    redacted
}

pub fn render_to_string<F>(width: u16, height: u16, draw: F) -> String
where
    F: FnOnce(&mut ratatui::Frame<'_>),
{
    let backend = TestBackend::new(width, height);
    let mut terminal = Terminal::new(backend).unwrap();
    terminal.draw(draw).unwrap();
    format!("{}", terminal.backend())
}