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")
);
}