Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-admin/tests/the_alert_table.rs

//! ★ **Every operator alert has a code, and the code is in the alert table** (P5, A22 §3.6
//! `observability.alert-table`). The ONE box mails its operator every `ALERT <code> …` line
//! it hears, grouped by the code (`monetize_admin::alerts::classify`: a word with a `.` or a
//! `-`). An alert with no code (`ALERT its guest's …`, `ALERT {product}: …`) was mailed as the
//! catch-all `alert`, or grouped under a product's name.
//!
//! `docs/v2/alerts.md` is the table an operator reads: code, severity, who acts, what it means.
//! This test reads monetize's runtime code (every crate's `src/`, tests and test doubles
//! excepted) for the `ALERT <code>` lines it can write, and holds the table to them both
//! ways: every code written is a row, and every row is a code the code writes (or one the
//! classifier makes of other lines). `ALERT:` with a colon is a NOTE in an order's history,
//! not a log line, and is not read.

use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};

fn repo() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}

/// The kinds the classifier makes of lines that carry no `ALERT <code>` of their own.
const CLASSIFIER_KINDS: [&str; 5] = [
    "critical",
    "grow.room_queued",
    "payment.refused",
    "attach.refused",
    "case.<state>",
];

const SEVERITIES: [&str; 3] = ["critical", "warning", "notice"];

/// A source file's runtime lines: up to its inline test module.
fn runtime_lines(text: &str) -> Vec<&str> {
    let lines: Vec<&str> = text.lines().collect();
    let mut out = Vec::new();
    let mut i = 0;
    while i < lines.len() {
        if lines[i].trim() == "#[cfg(test)]"
            && lines.get(i + 1).is_some_and(|n| {
                let n = n
                    .trim_start()
                    .trim_start_matches("pub(crate) ")
                    .trim_start_matches("pub ");
                n.starts_with("mod ") && n.trim_end().ends_with('{')
            })
        {
            break;
        }
        out.push(lines[i]);
        i += 1;
    }
    out
}

fn runtime_sources() -> Vec<(PathBuf, String)> {
    fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
        let Ok(rd) = std::fs::read_dir(dir) else {
            return;
        };
        for e in rd.flatten() {
            let p = e.path();
            let name = e.file_name().to_string_lossy().into_owned();
            if p.is_dir() {
                if !["tests", "testing", "target", "examples", "benches"].contains(&name.as_str()) {
                    walk(&p, out);
                }
            } else if name.ends_with(".rs")
                && !["tests.rs", "testing.rs", "testkit.rs", "conformance.rs"]
                    .contains(&name.as_str())
            {
                out.push(p);
            }
        }
    }
    let mut files = Vec::new();
    for c in std::fs::read_dir(repo().join("crates")).unwrap().flatten() {
        walk(&c.path().join("src"), &mut files);
    }
    files.sort();
    files
        .into_iter()
        .map(|p| (p.clone(), std::fs::read_to_string(&p).unwrap()))
        .collect()
}

/// `const NAME: &str = "value";` in `lines`.
fn consts<'a>(lines: impl Iterator<Item = &'a str>) -> BTreeMap<String, String> {
    let mut out = BTreeMap::new();
    for l in lines {
        if let Some(rest) = l.split("const ").nth(1) {
            if let Some((name, value)) = rest.split_once(": &str = \"") {
                if name.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
                    if let Some(v) = value.split('"').next() {
                        out.insert(name.to_owned(), v.to_owned());
                    }
                }
            }
        }
    }
    out
}

/// The code an `ALERT ` occurrence names: a literal, `{CONST}`, `{}` with a const as its first
/// argument, or `{kind}` in a file whose `alert("…")` calls name the kinds. `None`: no code.
fn code_at(
    after: &str,
    local: &BTreeMap<String, String>,
    global: &BTreeMap<String, String>,
    kinds: &[String],
) -> Vec<Option<String>> {
    let tok: String = if after.starts_with('{') {
        after.split_inclusive('}').next().unwrap_or("").to_owned()
    } else {
        after
            .chars()
            .take_while(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | '\''))
            .collect()
    };
    let constant = |name: &str| local.get(name).or_else(|| global.get(name)).cloned();
    let resolved = if tok == "{}" {
        // The first argument after the format string (which ends at the next quote).
        let args = after.split_once('"').map(|(_, a)| a).unwrap_or("");
        let first = args
            .trim_start_matches([' ', ','])
            .split(',')
            .next()
            .unwrap_or("")
            .trim()
            .trim_end_matches([')', ';']);
        let last = first.rsplit("::").next().unwrap_or("");
        if !last.is_empty() && last.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
            constant(last)
        } else {
            None
        }
    } else if tok == "{kind}" && !kinds.is_empty() {
        return kinds.iter().map(|k| Some(k.clone())).collect();
    } else if let Some(name) = tok.strip_prefix('{').and_then(|t| t.strip_suffix('}')) {
        if name.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
            constant(name)
        } else {
            None
        }
    } else {
        Some(tok)
    };
    vec![resolved.filter(|c| c.contains(['.', '-']) && !c.contains(' '))]
}

/// Every `ALERT <code>` the runtime code can write: code → where; and the sites with no code.
fn written() -> (BTreeMap<String, Vec<String>>, Vec<String>) {
    let sources = runtime_sources();
    let global = consts(sources.iter().flat_map(|(_, t)| runtime_lines(t)));
    let (mut codes, mut bare) = (BTreeMap::<String, Vec<String>>::new(), Vec::new());
    for (path, text) in &sources {
        let lines = runtime_lines(text);
        let local = consts(lines.iter().copied());
        // Whitespace-blind: rustfmt puts a long call's first argument on its own line
        // (stink-mon 2026-11-03), so `alert(` and its kind are read over line ends.
        let joined = lines.join("\n");
        let kinds: Vec<String> = joined
            .split("alert(")
            .skip(1)
            .filter_map(|r| r.trim_start().strip_prefix('"'))
            .filter_map(|r| r.split('"').next())
            .map(str::to_owned)
            .collect();
        let rel = path
            .strip_prefix(repo())
            .unwrap_or(path)
            .display()
            .to_string();
        for (n, line) in lines.iter().enumerate() {
            if line.trim_start().starts_with("//") {
                continue;
            }
            for (at, _) in line.match_indices("ALERT") {
                // Only inside a string literal, and as a word.
                let before = &line[..at];
                if before.matches('"').count() % 2 == 0
                    || before.ends_with(|c: char| c.is_ascii_alphanumeric() || c == '_')
                {
                    continue;
                }
                let after = &line[at + "ALERT".len()..];
                if after.starts_with(':') || !after.starts_with(' ') {
                    continue; // `ALERT:` is a history note; `ALERT_KIND` a name.
                }
                let after = after.trim_start();
                for code in code_at(after, &local, &global, &kinds) {
                    match code {
                        Some(c) => codes.entry(c).or_default().push(format!("{rel}:{}", n + 1)),
                        None => bare.push(format!("{rel}:{}: {}", n + 1, line.trim())),
                    }
                }
            }
        }
    }
    (codes, bare)
}

/// The table's rows: code → (severity, who).
fn table() -> BTreeMap<String, (String, String)> {
    let doc = std::fs::read_to_string(repo().join("docs/v2/alerts.md")).expect("docs/v2/alerts.md");
    let mut rows = BTreeMap::new();
    let mut header: Option<Vec<String>> = None;
    for line in doc.lines().map(str::trim) {
        if !line.starts_with('|') {
            header = None;
            continue;
        }
        let cells: Vec<String> = line
            .trim_matches('|')
            .split('|')
            .map(|c| c.trim().to_owned())
            .collect();
        match &header {
            None => header = Some(cells.iter().map(|c| c.to_lowercase()).collect()),
            Some(h)
                if cells
                    .iter()
                    .all(|c| c.chars().all(|ch| matches!(ch, '-' | ':'))) =>
            {
                assert!(
                    h.contains(&"code".to_owned()) && h.contains(&"severity".to_owned()),
                    "a table with a code and a severity column: {h:?}"
                );
            }
            Some(h) => {
                let col = |name: &str| {
                    h.iter()
                        .position(|c| c == name)
                        .map(|i| cells[i].clone())
                        .unwrap_or_default()
                };
                let code = col("code").trim_matches('`').to_owned();
                assert!(
                    rows.insert(code.clone(), (col("severity"), col("who")))
                        .is_none(),
                    "`{code}` is a row twice"
                );
            }
        }
    }
    rows
}

#[test]
fn every_alert_the_code_writes_carries_a_code() {
    let (_, bare) = written();
    assert!(
        bare.is_empty(),
        "an ALERT with no code is mailed as the catch-all `alert`; give it one:\n{}",
        bare.join("\n")
    );
}

#[test]
fn the_alert_table_names_every_code_and_only_codes_that_exist() {
    let (codes, _) = written();
    assert!(
        codes.len() >= 40,
        "the reader finds the box's alerts: {codes:?}"
    );
    let rows = table();
    let missing: Vec<String> = codes
        .iter()
        .filter(|(c, _)| !rows.contains_key(*c))
        .map(|(c, at)| format!("{c} ({})", at.join(", ")))
        .collect();
    assert!(
        missing.is_empty(),
        "codes the box writes and docs/v2/alerts.md does not name:\n{}",
        missing.join("\n")
    );
    let classifier =
        std::fs::read_to_string(repo().join("crates/monetize-admin/src/alerts.rs")).unwrap();
    for (code, (severity, who)) in &rows {
        let made = CLASSIFIER_KINDS.contains(&code.as_str());
        assert!(
            codes.contains_key(code) || made,
            "`{code}` is a row, and no code writes it: a stale row"
        );
        if made {
            let probe = code.split('<').next().unwrap_or(code);
            assert!(
                classifier.contains(&format!("\"{probe}")),
                "`{code}` is a kind the classifier makes"
            );
        }
        assert!(
            SEVERITIES.contains(&severity.as_str()),
            "`{code}`: severity {severity:?} is one of {SEVERITIES:?}"
        );
        assert!(!who.is_empty(), "`{code}`: who acts");
    }
    assert_eq!(
        rows.get("code-red").map(|r| r.0.as_str()),
        Some("critical"),
        "CODE RED is critical"
    );
}

#[test]
fn the_reader_reads_codes_constants_and_notes() {
    let local: BTreeMap<String, String> = [("KIND".to_owned(), "a.b".to_owned())].into();
    let none = BTreeMap::new();
    assert_eq!(
        code_at("{} {who}\", other::KIND);", &local, &none, &[]),
        [Some("a.b".to_owned())]
    );
    assert_eq!(
        code_at("{} x\", log_safe(why));", &local, &none, &[]),
        [None]
    );
    assert_eq!(
        code_at("{KIND} y", &local, &none, &[]),
        [Some("a.b".to_owned())]
    );
    assert_eq!(
        code_at("grow.failed x", &none, &none, &[]),
        [Some("grow.failed".to_owned())]
    );
    assert_eq!(code_at("its guest", &none, &none, &[]), [None]);
    assert_eq!(code_at("{product}: x", &none, &none, &[]), [None]);
    assert_eq!(
        code_at("{kind} x", &none, &none, &["n.a".into(), "n.b".into()]),
        [Some("n.a".to_owned()), Some("n.b".to_owned())]
    );
    let test_tail = "fn a() {}\n#[cfg(test)]\nmod tests {\n    \"ALERT x\"\n}\n";
    assert_eq!(runtime_lines(test_tail), ["fn a() {}"]);
    let _ = BTreeSet::<String>::new();
}

/// ★ **A jobs' ledger that does not open is its own alert, `jobs.unrecorded`** (RA4-18): the
/// jobs still run, only unrecorded, so it is not `jobs.unstarted` (a keeper duty that could not
/// start), and the table says what each means.
#[test]
fn a_ledger_that_does_not_open_is_jobs_unrecorded() {
    let mut seen = 0;
    for f in [
        "crates/monetize-server/src/cli.rs",
        "crates/monetize-admin/src/main.rs",
    ] {
        let src = std::fs::read_to_string(repo().join(f)).unwrap();
        for l in runtime_lines(&src)
            .into_iter()
            .filter(|l| l.contains("ALERT jobs.") && l.contains("ledger"))
        {
            assert!(
                l.contains("ALERT jobs.unrecorded "),
                "{f}: the ledger's failure is jobs.unrecorded: {l}"
            );
            seen += 1;
        }
    }
    assert_eq!(seen, 2, "the server's and PID 1's ledger failures");
    let table = std::fs::read_to_string(repo().join("docs/v2/alerts.md")).unwrap();
    let row = |code: &str| {
        table
            .lines()
            .find(|l| l.starts_with(&format!("| `{code}` |")))
            .unwrap_or_else(|| panic!("no row {code}"))
            .to_owned()
    };
    assert!(
        row("jobs.unrecorded").contains("ledger"),
        "{}",
        row("jobs.unrecorded")
    );
    assert!(
        !row("jobs.unstarted").contains("ledger"),
        "{}",
        row("jobs.unstarted")
    );
}