use std::fs;
use std::path::PathBuf;
fn rules_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("deploy")
.join("prometheus")
.join("captchaforge.rules.yml")
}
#[test]
fn rules_file_exists_and_is_non_empty() {
let body = fs::read_to_string(rules_path()).expect("read rules file");
assert!(!body.is_empty());
assert!(body.starts_with('#') || body.starts_with("groups:"));
}
#[test]
fn ships_required_alerts() {
let body = fs::read_to_string(rules_path()).expect("read rules file");
const REQUIRED_ALERTS: &[&str] = &[
"CaptchaforgeVendorSuccessRateLow",
"CaptchaforgeVendorSuccessRateCritical",
"CaptchaforgeSolveP95Slow",
"CaptchaforgeHardBlockRateHigh",
"CaptchaforgeSilentFailRateHigh",
"CaptchaforgeServeDown",
];
for name in REQUIRED_ALERTS {
assert!(
body.contains(&format!("alert: {name}")),
"alert {name} missing from rules.yml"
);
}
}
#[test]
fn references_the_metric_names_captchaforge_actually_emits() {
let body = fs::read_to_string(rules_path()).expect("read rules file");
assert!(
body.contains("captchaforge_solve_total"),
"alerts must reference captchaforge_solve_total"
);
assert!(
body.contains("captchaforge_solve_duration_ms_bucket"),
"alerts must reference captchaforge_solve_duration_ms_bucket"
);
assert!(body.contains("outcome=\"success\""));
assert!(body.contains("outcome=\"hard_block\""));
assert!(body.contains("outcome=\"silent_fail\""));
}
#[test]
fn every_alert_has_severity_label() {
let body = fs::read_to_string(rules_path()).expect("read rules file");
let alert_lines: Vec<&str> = body
.lines()
.filter(|l| l.trim().starts_with("- alert:"))
.collect();
let severity_lines: Vec<&str> = body
.lines()
.filter(|l| l.trim().starts_with("severity:"))
.collect();
assert!(!alert_lines.is_empty(), "rules file has zero alerts");
assert!(
severity_lines.len() >= alert_lines.len(),
"every alert must declare a severity label: got {} alerts, {} severities",
alert_lines.len(),
severity_lines.len()
);
}