//! PoM's projection onto the shared operator status payload. //! //! Spec + rationale: maintainer wiki. //! //! //! `GET /status.json` serves this. Sando and Bento report on the thing they //! *are*; PoM reports on the things it *watches*, so every target it monitors //! becomes one node and the viewer needs no knowledge of health checks, TLS, or //! incidents to draw PoM. //! //! [`payload`] is a pure function of `(&[TargetView], now)`. Every clock is an //! argument, so a fixture renders identically forever and the mapping is //! testable without a database. //! //! # What maps to what //! //! | PoM | payload | //! |---|---| //! | monitored target | node, `kind = "target"` | //! | latest health check | node status + `health` condition | //! | open incident | `incident` condition carrying the why | //! | TLS certificate | `tls` condition (expiry) | //! | domain registration | `whois` condition (expiry) | //! | uptime / latency / version | fields | //! //! # Every signal colors the target //! //! The load-bearing difference from Sando. A Sando gate guards *promotion* and //! must not color a tier, burn-in blocks for 48 hours as routine. PoM has no //! such notion: a cert 3 days from expiry, an open incident, a domain about to //! lapse are each a real problem with the target regardless of what the health //! endpoint says. So a target's status is the worst of every condition on it, //! and the why is never lost because it is in the conditions either way. //! //! PoM declares no actions. It observes; it does not act, and there is no route //! to trigger a recheck. An empty actions map is the honest shape. use chrono::{DateTime, Utc}; use ops_status::{Condition, Field, Node, Payload, Status, Value}; use crate::types::HealthStatus; /// The `source` name PoM answers to in a viewer's config. pub const SOURCE: &str = "pom"; /// Days before a certificate expires at which the target goes `degraded`. const TLS_EXPIRY_WARN_DAYS: i64 = 14; /// Days before a domain registration lapses at which the target goes `degraded`. const WHOIS_EXPIRY_WARN_DAYS: i64 = 30; /// Everything the payload needs about one monitored target, already read out of /// the database. /// /// A dedicated view rather than the API's `TargetStatus`: that type is shaped /// for the existing `/api/status` JSON and carries pre-formatted strings, which /// is exactly what the shared contract forbids a producer from owning. Building /// this from typed rows keeps [`payload`] pure and the DB out of the mapping. pub(crate) struct TargetView { /// Config key, e.g. "mnw". pub name: String, /// Human-readable label, e.g. "MakeNotWork". pub label: String, /// The most recent health snapshot. `None` before the first check. pub health: Option, /// Uptime percentage over the last 24 hours, if any checks fell in the window. pub uptime_24h: Option, /// Mean response time over the last 24 hours of operational checks, in ms. pub latency_avg_ms: Option, /// Latest TLS check. `None` if TLS is not monitored for this target. pub tls: Option, /// Currently open incident, if the target is in one. pub incident: Option, /// Latest WHOIS check. `None` if domain expiry is not monitored. pub whois: Option, /// Latest backup freshness, one per monitored database. Empty if the target /// has no backup monitoring configured. pub backups: Vec, /// Latest scan-pipeline check. `None` if not monitored for this target. pub scan_pipeline: Option, /// Latest test run and PoM's staleness verdict. `None` if the target has no /// test config. pub tests: Option, /// Latest DNS record checks. `None` if DNS is not monitored for this target. pub dns: Option, /// Latest CORS preflight checks. `None` if CORS is not monitored. pub cors: Option, } pub(crate) struct HealthView { pub status: HealthStatus, pub checked_at: String, pub version: Option, /// The failure reason when the check did not pass. pub error: Option, } pub(crate) struct TlsView { pub valid: bool, pub days_remaining: i64, pub checked_at: String, pub error: Option, } pub(crate) struct IncidentView { pub from_status: String, pub to_status: String, pub started_at: String, } pub(crate) struct WhoisView { pub days_remaining: Option, pub checked_at: String, pub error: Option, } pub(crate) struct BackupView { pub database: String, /// One of "ok", "stale", "missing", "error". pub status: String, pub age_hours: Option, pub checked_at: String, pub error: Option, } pub(crate) struct ScanView { /// One of "operational", "degraded", "unreachable". pub status: String, /// Fired-threshold issue lines, the why behind a non-operational status. pub issues: Vec, pub checked_at: String, pub error: Option, } pub(crate) struct TestsView { /// Whether any run has ever been recorded. Separates "no evidence" /// (pending) from a run that failed. pub ran: bool, /// Whether the most recent run passed. pub passed: bool, pub total_passed: Option, pub total_failed: Option, /// When the most recent run started, RFC 3339. pub started_at: Option, /// PoM's staleness verdict for the run: true when it is older than the /// configured threshold or a version was deployed after it ran. pub stale: bool, pub stale_reason: Option, } pub(crate) struct DnsView { /// One entry per monitored record; non-empty by construction. pub records: Vec, /// Latest checked_at across the records, for the condition's `since`. pub checked_at: Option, } pub(crate) struct DnsRecordView { pub name: String, pub record_type: String, pub matches: bool, pub error: Option, } pub(crate) struct CorsView { /// One entry per monitored URL; non-empty by construction. pub checks: Vec, pub checked_at: Option, } pub(crate) struct CorsCheckView { pub url: String, pub origin: String, pub passes: bool, pub error: Option, } /// Restate every monitored target as the shared payload. /// /// `now` is an argument rather than read from the clock so the mapping stays /// pure and snapshot-testable. pub(crate) fn payload(targets: &[TargetView], now: DateTime) -> Payload { let mut payload = Payload::new(SOURCE, now); for target in targets { payload.nodes.push(target_node(target)); } payload } fn target_node(target: &TargetView) -> Node { let mut conditions = vec![health_condition(target.health.as_ref())]; if let Some(incident) = &target.incident { conditions.push(incident_condition(incident)); } if let Some(tls) = &target.tls { conditions.push(tls_condition(tls)); } if let Some(whois) = &target.whois && let Some(condition) = whois_condition(whois) { conditions.push(condition); } for backup in &target.backups { conditions.push(backup_condition(backup)); } if let Some(scan) = &target.scan_pipeline { conditions.push(scan_condition(scan)); } if let Some(tests) = &target.tests { conditions.push(tests_condition(tests)); } if let Some(dns) = &target.dns { conditions.push(dns_condition(dns)); } if let Some(cors) = &target.cors { conditions.push(cors_condition(cors)); } // The target's status is the worst thing said about it. There is always at // least the health condition, so the max is well-defined. let status = conditions .iter() .map(|c| c.status) .max() .unwrap_or(Status::Unknown); Node { id: format!("target:{}", target.name), kind: "target".into(), label: target.label.clone(), status, fields: target_fields(target), conditions, children: Vec::new(), actions: Vec::new(), } } /// A health snapshot's status, or `pending` when the target has never been /// checked, PoM has evidence of nothing rather than evidence of health. fn health_condition(health: Option<&HealthView>) -> Condition { let Some(health) = health else { return Condition { condition_type: "health".into(), status: Status::Pending, since: None, detail: Some("no health check recorded yet".into()), }; }; Condition { condition_type: "health".into(), status: health_status(health.status), since: parse_instant(&health.checked_at), // The error is the why on a failing check; a passing one needs no words. detail: health.error.clone(), } } /// PoM's four-value health vocabulary onto the shared five. /// /// `unreachable` and `error` both map to `failed`: from an operator's chair a /// target that 5xxs and one that will not answer are the same event, it is /// down. The distinction is preserved in the health snapshot's own error text. fn health_status(status: HealthStatus) -> Status { match status { HealthStatus::Operational => Status::Ok, HealthStatus::Degraded => Status::Degraded, HealthStatus::Error | HealthStatus::Unreachable => Status::Failed, } } /// An open incident is reported at the severity it escalated *to*, carrying when /// it started so the viewer can age it. fn incident_condition(incident: &IncidentView) -> Condition { Condition { condition_type: "incident".into(), status: health_status_from_str(&incident.to_status), since: parse_instant(&incident.started_at), detail: Some(format!( "{} to {}", incident.from_status, incident.to_status )), } } /// Incident rows store the health status as a string. An unrecognized value is /// `unknown` rather than a parse failure, keeping the node legible against a /// future status PoM learns before the viewer does. fn health_status_from_str(raw: &str) -> Status { raw.parse::() .map_or(Status::Unknown, health_status) } /// TLS state as a condition. A failed probe or an invalid chain is `failed`; a /// certificate inside the warning window is `degraded`; anything further out is /// `ok` and still carries its days-remaining, since the number is the point. fn tls_condition(tls: &TlsView) -> Condition { let (status, detail) = if let Some(error) = &tls.error { (Status::Failed, format!("tls check failed: {error}")) } else if !tls.valid { (Status::Failed, "certificate chain is not valid".into()) } else if tls.days_remaining < 0 { ( Status::Failed, format!("certificate expired {} days ago", -tls.days_remaining), ) } else if tls.days_remaining <= TLS_EXPIRY_WARN_DAYS { ( Status::Degraded, format!("certificate expires in {} days", tls.days_remaining), ) } else { ( Status::Ok, format!("certificate valid, {} days remaining", tls.days_remaining), ) }; Condition { condition_type: "tls".into(), status, since: parse_instant(&tls.checked_at), detail: Some(detail), } } /// Domain-expiry state as a condition, or `None` when there is nothing to say. /// /// A WHOIS lookup that failed is `degraded`, not `failed`: registrar WHOIS is /// flaky and a lookup error is not evidence the domain lapsed. Absent both an /// error and a days count there is genuinely no signal, so no condition is /// emitted rather than a permanently `unknown` one that trains the eye to skip /// the target. fn whois_condition(whois: &WhoisView) -> Option { let (status, detail) = if let Some(error) = &whois.error { (Status::Degraded, format!("whois lookup failed: {error}")) } else { match whois.days_remaining { Some(days) if days < 0 => ( Status::Failed, format!("domain registration expired {} days ago", -days), ), Some(days) if days <= WHOIS_EXPIRY_WARN_DAYS => ( Status::Degraded, format!("domain registration expires in {days} days"), ), Some(days) => ( Status::Ok, format!("domain registration valid, {days} days remaining"), ), None => return None, } }; Some(Condition { condition_type: "whois".into(), status, since: parse_instant(&whois.checked_at), detail: Some(detail), }) } /// Backup freshness as a condition, one per database. A stale backup or a check /// error is `degraded`; a missing backup is `failed`. The 40-day-stale backup /// that stayed green by every check that existed is exactly the signal this /// surfaces, the `type` names the database so several read as distinct rows. fn backup_condition(backup: &BackupView) -> Condition { let db = &backup.database; let (status, detail) = match backup.status.as_str() { "ok" => ( Status::Ok, match backup.age_hours { Some(hours) => format!("{db} backup is {hours}h old"), None => format!("{db} backup present"), }, ), "stale" => ( Status::Degraded, match backup.age_hours { Some(hours) => format!("{db} backup is stale, {hours}h old"), None => format!("{db} backup is stale"), }, ), "missing" => (Status::Failed, format!("no backup found for {db}")), "error" => ( Status::Degraded, match &backup.error { Some(error) => format!("{db} backup check failed: {error}"), None => format!("{db} backup check failed"), }, ), other => (Status::Unknown, format!("{db} backup status {other:?}")), }; Condition { condition_type: format!("backup:{db}"), status, since: parse_instant(&backup.checked_at), detail: Some(detail), } } /// Scan-pipeline state as a condition. `operational` is ok, `degraded` is /// degraded, `unreachable` is failed; the fired-threshold issues, or the probe /// error, are the why. fn scan_condition(scan: &ScanView) -> Condition { let status = match scan.status.as_str() { "operational" => Status::Ok, "degraded" => Status::Degraded, "unreachable" => Status::Failed, _ => Status::Unknown, }; let detail = if let Some(error) = &scan.error { format!("unreachable: {error}") } else if !scan.issues.is_empty() { scan.issues.join("; ") } else { "pipeline operational".into() }; Condition { condition_type: "scan_pipeline".into(), status, since: parse_instant(&scan.checked_at), detail: Some(detail), } } /// Test state as a condition. A never-run target is `pending`: evidence of /// nothing, like one never health-checked. A failing or stale run is `degraded`, /// not `failed`: a red suite is a real regression signal, but the running /// service is covered by `health`, so a test result colors the target yellow /// (look at this) rather than red (it is down). Staleness reuses PoM's own /// verdict, which fires on age past the configured threshold or a version /// deployed since the last run. fn tests_condition(tests: &TestsView) -> Condition { if !tests.ran { return Condition { condition_type: "tests".into(), status: Status::Pending, since: None, detail: Some("no tests have been run yet".into()), }; } let since = tests.started_at.as_deref().and_then(parse_instant); let (status, detail) = if !tests.passed { let detail = match (tests.total_passed, tests.total_failed) { (Some(p), Some(f)) => format!("last run failed: {f} failed, {p} passed"), _ => "last test run failed".into(), }; (Status::Degraded, detail) } else if tests.stale { ( Status::Degraded, tests .stale_reason .clone() .unwrap_or_else(|| "test results are stale".into()), ) } else { let detail = match tests.total_passed { Some(p) => format!("tests passing, {p} passed"), None => "tests passing".into(), }; (Status::Ok, detail) }; Condition { condition_type: "tests".into(), status, since, detail: Some(detail), } } /// DNS state as one aggregate condition across a target's monitored records. A /// record whose lookup errored or whose value does not match is `degraded`: a /// wrong or unverifiable record is a real misconfiguration, but a record that /// points the site somewhere dead already shows as a failed `health` check, so /// DNS colors the target yellow and names what is off. All records clean is /// `ok`. One aggregate condition rather than one per record keeps a target that /// watches several records to a single legible row. fn dns_condition(dns: &DnsView) -> Condition { let bad: Vec = dns .records .iter() .filter_map(|r| { if let Some(error) = &r.error { Some(format!( "{} {} lookup failed: {error}", r.name, r.record_type )) } else if !r.matches { Some(format!("{} {} does not match", r.name, r.record_type)) } else { None } }) .collect(); let since = dns.checked_at.as_deref().and_then(parse_instant); let (status, detail) = if bad.is_empty() { ( Status::Ok, format!( "{} record{} match", dns.records.len(), plural(dns.records.len()) ), ) } else { (Status::Degraded, bad.join("; ")) }; Condition { condition_type: "dns".into(), status, since, detail: Some(detail), } } /// CORS state as one aggregate condition across a target's monitored URLs. A /// preflight that failed to run, or that the server answered without allowing /// the origin, is `degraded`: broken CORS breaks a browser client, but it is a /// configuration fault rather than the service being down, so it colors the /// target yellow. All preflights allowed is `ok`. fn cors_condition(cors: &CorsView) -> Condition { let bad: Vec = cors .checks .iter() .filter_map(|c| { if let Some(error) = &c.error { Some(format!("{} preflight failed: {error}", c.url)) } else if !c.passes { Some(format!("{} does not allow {}", c.url, c.origin)) } else { None } }) .collect(); let since = cors.checked_at.as_deref().and_then(parse_instant); let (status, detail) = if bad.is_empty() { ( Status::Ok, format!( "{} preflight check{} pass", cors.checks.len(), plural(cors.checks.len()) ), ) } else { (Status::Degraded, bad.join("; ")) }; Condition { condition_type: "cors".into(), status, since, detail: Some(detail), } } /// The plural suffix for a count: "" for one, "s" otherwise. fn plural(n: usize) -> &'static str { if n == 1 { "" } else { "s" } } /// Uptime as a bar, latency as a magnitude, version as a comparable, each a /// number that *means* something rather than a pre-rendered string. fn target_fields(target: &TargetView) -> Vec { let mut fields = Vec::new(); if let Some(version) = target.health.as_ref().and_then(|h| h.version.clone()) { fields.push(Field::new("version", Value::Version { value: version })); } if let Some(uptime) = target.uptime_24h { fields.push(Field::new( "uptime 24h", Value::Progress { value: uptime, max: 100.0, unit: Some("%".into()), }, )); } if let Some(latency) = target.latency_avg_ms { fields.push(Field::new( "latency 24h", Value::Quantity { value: latency, unit: Some("ms".into()), }, )); } if let Some(checked) = target .health .as_ref() .and_then(|h| parse_instant(&h.checked_at)) { fields.push(Field::new("checked", Value::Instant { value: checked })); } fields } /// A timestamp that fails to parse costs only itself. A viewer that blanks on /// one malformed row is worse than one missing a tooltip. fn parse_instant(raw: &str) -> Option> { DateTime::parse_from_rfc3339(raw) .ok() .map(|d| d.with_timezone(&Utc)) } #[cfg(test)] mod tests { use super::*; fn now() -> DateTime { "2026-07-21T18:24:39Z".parse().unwrap() } fn checked_at() -> String { "2026-07-21T18:24:00Z".into() } fn healthy(name: &str) -> TargetView { TargetView { name: name.into(), label: name.to_uppercase(), health: Some(HealthView { status: HealthStatus::Operational, checked_at: checked_at(), version: Some("1.4.0".into()), error: None, }), uptime_24h: Some(100.0), latency_avg_ms: Some(42.0), tls: None, incident: None, whois: None, backups: Vec::new(), scan_pipeline: None, tests: None, dns: None, cors: None, } } fn node<'a>(p: &'a Payload, id: &str) -> &'a Node { p.node(id).unwrap_or_else(|| panic!("no node {id}")) } #[test] fn a_healthy_target_is_ok_and_structurally_sound() { let p = payload(&[healthy("mnw")], now()); assert_eq!(p.source, SOURCE); assert_eq!(p.schema, ops_status::SCHEMA_VERSION); assert_eq!(p.validate(), Ok(())); assert_eq!(node(&p, "target:mnw").status, Status::Ok); assert_eq!(node(&p, "target:mnw").label, "MNW"); assert_eq!(p.worst_status(), Status::Ok); } #[test] fn uptime_and_latency_are_typed_values_not_strings() { let p = payload(&[healthy("mnw")], now()); let n = node(&p, "target:mnw"); let uptime = n.fields.iter().find(|f| f.label == "uptime 24h").unwrap(); assert_eq!( uptime.value, Value::Progress { value: 100.0, max: 100.0, unit: Some("%".into()) } ); let latency = n.fields.iter().find(|f| f.label == "latency 24h").unwrap(); assert_eq!( latency.value, Value::Quantity { value: 42.0, unit: Some("ms".into()) } ); } #[test] fn an_unreachable_target_is_failed_and_says_why() { let mut t = healthy("mnw"); t.health = Some(HealthView { status: HealthStatus::Unreachable, checked_at: checked_at(), version: None, error: Some("connection timed out".into()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Failed); assert_eq!(n.conditions[0].condition_type, "health"); assert_eq!( n.conditions[0].detail.as_deref(), Some("connection timed out") ); assert_eq!(p.worst_status(), Status::Failed); } #[test] fn a_target_never_checked_is_pending_not_healthy() { let mut t = healthy("new"); t.health = None; let p = payload(&[t], now()); let n = node(&p, "target:new"); assert_eq!(n.status, Status::Pending); assert_eq!( n.conditions[0].detail.as_deref(), Some("no health check recorded yet") ); // No health snapshot means no version and no checked-at field. assert!(n.fields.iter().all(|f| f.label != "version")); assert!(n.fields.iter().all(|f| f.label != "checked")); assert_eq!(p.validate(), Ok(())); } #[test] fn an_expiring_certificate_degrades_an_otherwise_healthy_target() { // The Sando contrast: unlike a promotion gate, an expiring cert is a real // problem with the target and must color it even when health is green. let mut t = healthy("mnw"); t.tls = Some(TlsView { valid: true, days_remaining: 9, checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let tls = n .conditions .iter() .find(|c| c.condition_type == "tls") .unwrap(); assert_eq!(tls.status, Status::Degraded); assert!(tls.detail.as_deref().unwrap().contains("9 days")); } #[test] fn an_expired_certificate_fails_the_target() { let mut t = healthy("mnw"); t.tls = Some(TlsView { valid: true, days_remaining: -3, checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Failed); let tls = n .conditions .iter() .find(|c| c.condition_type == "tls") .unwrap(); assert!( tls.detail .as_deref() .unwrap() .contains("expired 3 days ago") ); } #[test] fn a_healthy_certificate_stays_ok_but_still_reports_its_runway() { let mut t = healthy("mnw"); t.tls = Some(TlsView { valid: true, days_remaining: 60, checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Ok); let tls = n .conditions .iter() .find(|c| c.condition_type == "tls") .unwrap(); assert_eq!(tls.status, Status::Ok); assert!(tls.detail.as_deref().unwrap().contains("60 days remaining")); } #[test] fn an_open_incident_surfaces_with_its_transition_and_start() { let mut t = healthy("mnw"); t.incident = Some(IncidentView { from_status: "operational".into(), to_status: "unreachable".into(), started_at: "2026-07-21T17:00:00Z".into(), }); // Health has recovered on paper but the incident is still open: the node // must not read green while an incident stands. let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Failed); let incident = n .conditions .iter() .find(|c| c.condition_type == "incident") .unwrap(); assert_eq!(incident.status, Status::Failed); assert_eq!( incident.detail.as_deref(), Some("operational to unreachable") ); assert_eq!( incident.since, Some("2026-07-21T17:00:00Z".parse::>().unwrap()) ); } #[test] fn a_failed_whois_lookup_is_degraded_not_failed() { // Registrar WHOIS is flaky; a lookup error is not proof the domain lapsed. let mut t = healthy("mnw"); t.whois = Some(WhoisView { days_remaining: None, checked_at: checked_at(), error: Some("connection reset".into()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); } #[test] fn an_expiring_domain_degrades_the_target() { let mut t = healthy("mnw"); t.whois = Some(WhoisView { days_remaining: Some(12), checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let whois = n .conditions .iter() .find(|c| c.condition_type == "whois") .unwrap(); assert!(whois.detail.as_deref().unwrap().contains("12 days")); } #[test] fn a_whois_check_with_no_signal_emits_no_condition() { let mut t = healthy("mnw"); t.whois = Some(WhoisView { days_remaining: None, checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert!(n.conditions.iter().all(|c| c.condition_type != "whois")); assert_eq!(n.status, Status::Ok); } #[test] fn the_loudest_of_several_problems_wins_the_target() { let mut t = healthy("mnw"); t.health = Some(HealthView { status: HealthStatus::Degraded, checked_at: checked_at(), version: Some("1.4.0".into()), error: Some("unexpected status 503".into()), }); t.tls = Some(TlsView { valid: true, days_remaining: -1, checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); // health is degraded, tls is failed: the target is failed. assert_eq!(node(&p, "target:mnw").status, Status::Failed); } #[test] fn one_targets_failure_does_not_touch_another() { let mut down = healthy("mt"); down.health = Some(HealthView { status: HealthStatus::Error, checked_at: checked_at(), version: None, error: Some("500 Internal Server Error".into()), }); let p = payload(&[healthy("mnw"), down], now()); assert_eq!(node(&p, "target:mnw").status, Status::Ok); assert_eq!(node(&p, "target:mt").status, Status::Failed); assert_eq!(p.worst_status(), Status::Failed); assert_eq!(p.validate(), Ok(())); } #[test] fn an_unknown_incident_status_stays_legible() { let mut t = healthy("mnw"); t.incident = Some(IncidentView { from_status: "operational".into(), to_status: "sideways".into(), started_at: checked_at(), }); let p = payload(&[t], now()); let incident = node(&p, "target:mnw") .conditions .iter() .find(|c| c.condition_type == "incident") .unwrap(); assert_eq!(incident.status, Status::Unknown); } #[test] fn a_malformed_timestamp_costs_only_that_timestamp() { let mut t = healthy("mnw"); t.health = Some(HealthView { status: HealthStatus::Operational, checked_at: "not a timestamp".into(), version: Some("1.4.0".into()), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Ok); assert_eq!(n.conditions[0].since, None); assert!(n.fields.iter().all(|f| f.label != "checked")); } #[test] fn a_stale_backup_degrades_the_target_and_names_the_database() { // The 40-day-stale backup that stayed green by every check that existed. let mut t = healthy("mnw"); t.backups = vec![BackupView { database: "makenotwork".into(), status: "stale".into(), age_hours: Some(960), checked_at: checked_at(), error: None, }]; let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let backup = n .conditions .iter() .find(|c| c.condition_type == "backup:makenotwork") .unwrap(); assert_eq!(backup.status, Status::Degraded); assert!(backup.detail.as_deref().unwrap().contains("960h")); } #[test] fn a_missing_backup_fails_the_target() { let mut t = healthy("mnw"); t.backups = vec![BackupView { database: "makenotwork".into(), status: "missing".into(), age_hours: None, checked_at: checked_at(), error: None, }]; let p = payload(&[t], now()); assert_eq!(node(&p, "target:mnw").status, Status::Failed); } #[test] fn several_databases_read_as_distinct_conditions() { let mut t = healthy("mnw"); t.backups = vec![ BackupView { database: "makenotwork".into(), status: "ok".into(), age_hours: Some(6), checked_at: checked_at(), error: None, }, BackupView { database: "multithreaded".into(), status: "ok".into(), age_hours: Some(7), checked_at: checked_at(), error: None, }, ]; let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Ok); assert!( n.conditions .iter() .any(|c| c.condition_type == "backup:makenotwork") ); assert!( n.conditions .iter() .any(|c| c.condition_type == "backup:multithreaded") ); } #[test] fn a_degraded_scan_pipeline_carries_its_issues() { let mut t = healthy("mnw"); t.scan_pipeline = Some(ScanView { status: "degraded".into(), issues: vec!["thumbnail error rate 22%".into(), "queue stuck: 4".into()], checked_at: checked_at(), error: None, }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let scan = n .conditions .iter() .find(|c| c.condition_type == "scan_pipeline") .unwrap(); assert!( scan.detail .as_deref() .unwrap() .contains("thumbnail error rate 22%") ); assert!(scan.detail.as_deref().unwrap().contains("queue stuck: 4")); } #[test] fn an_unreachable_scan_pipeline_fails_the_target() { let mut t = healthy("mnw"); t.scan_pipeline = Some(ScanView { status: "unreachable".into(), issues: Vec::new(), checked_at: checked_at(), error: Some("502 Bad Gateway".into()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Failed); let scan = n .conditions .iter() .find(|c| c.condition_type == "scan_pipeline") .unwrap(); assert!(scan.detail.as_deref().unwrap().contains("502 Bad Gateway")); } fn tests_view(passed: bool, stale: bool) -> TestsView { TestsView { ran: true, passed, total_passed: Some(226), total_failed: if passed { Some(0) } else { Some(3) }, started_at: Some(checked_at()), stale, stale_reason: if stale { Some("tests are 12 days old (threshold: 7d)".into()) } else { None }, } } fn condition<'a>(n: &'a Node, ty: &str) -> &'a Condition { n.conditions .iter() .find(|c| c.condition_type == ty) .unwrap_or_else(|| panic!("no {ty} condition")) } #[test] fn a_never_run_test_target_is_pending_not_healthy() { let mut t = healthy("mnw"); t.tests = Some(TestsView { ran: false, passed: false, total_passed: None, total_failed: None, started_at: None, stale: true, stale_reason: Some("no tests have been run".into()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); // Pending is quieter than degraded: no evidence is not a failure. assert_eq!(n.status, Status::Pending); assert_eq!( condition(n, "tests").detail.as_deref(), Some("no tests have been run yet") ); } #[test] fn a_failing_test_run_degrades_but_does_not_fail_the_target() { // A red suite is a regression signal; the running service is health's job. let mut t = healthy("mnw"); t.tests = Some(tests_view(false, false)); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let c = condition(n, "tests"); assert_eq!(c.status, Status::Degraded); assert!(c.detail.as_deref().unwrap().contains("3 failed")); } #[test] fn a_stale_but_passing_test_run_degrades_the_target() { let mut t = healthy("mnw"); t.tests = Some(tests_view(true, true)); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); assert!( condition(n, "tests") .detail .as_deref() .unwrap() .contains("12 days old") ); } #[test] fn a_fresh_passing_test_run_leaves_the_target_ok() { let mut t = healthy("mnw"); t.tests = Some(tests_view(true, false)); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Ok); assert_eq!(condition(n, "tests").status, Status::Ok); } #[test] fn a_dns_mismatch_degrades_the_target_and_names_the_record() { let mut t = healthy("mnw"); t.dns = Some(DnsView { records: vec![ DnsRecordView { name: "makenot.work".into(), record_type: "A".into(), matches: true, error: None, }, DnsRecordView { name: "makenot.work".into(), record_type: "MX".into(), matches: false, error: None, }, ], checked_at: Some(checked_at()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); let c = condition(n, "dns"); assert_eq!(c.status, Status::Degraded); assert!(c.detail.as_deref().unwrap().contains("MX does not match")); } #[test] fn a_dns_lookup_error_reads_as_a_flaky_degrade_not_a_mismatch() { let mut t = healthy("mnw"); t.dns = Some(DnsView { records: vec![DnsRecordView { name: "makenot.work".into(), record_type: "TXT".into(), matches: false, error: Some("SERVFAIL".into()), }], checked_at: Some(checked_at()), }); let p = payload(&[t], now()); let c = condition(node(&p, "target:mnw"), "dns"); assert_eq!(c.status, Status::Degraded); assert!( c.detail .as_deref() .unwrap() .contains("lookup failed: SERVFAIL") ); } #[test] fn all_dns_records_matching_stays_ok_and_counts_them() { let mut t = healthy("mnw"); t.dns = Some(DnsView { records: vec![DnsRecordView { name: "makenot.work".into(), record_type: "A".into(), matches: true, error: None, }], checked_at: Some(checked_at()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Ok); // Singular, not "1 records match". assert_eq!( condition(n, "dns").detail.as_deref(), Some("1 record match") ); } #[test] fn a_cors_misconfiguration_degrades_the_target() { let mut t = healthy("mnw"); t.cors = Some(CorsView { checks: vec![CorsCheckView { url: "https://makenot.work/api".into(), origin: "https://app.makenot.work".into(), passes: false, error: None, }], checked_at: Some(checked_at()), }); let p = payload(&[t], now()); let n = node(&p, "target:mnw"); assert_eq!(n.status, Status::Degraded); assert!( condition(n, "cors") .detail .as_deref() .unwrap() .contains("does not allow https://app.makenot.work") ); } #[test] fn passing_cors_preflights_stay_ok() { let mut t = healthy("mnw"); t.cors = Some(CorsView { checks: vec![CorsCheckView { url: "https://makenot.work/api".into(), origin: "https://app.makenot.work".into(), passes: true, error: None, }], checked_at: Some(checked_at()), }); let p = payload(&[t], now()); assert_eq!(node(&p, "target:mnw").status, Status::Ok); } #[test] fn the_new_conditions_do_not_disturb_a_target_that_has_none_of_them() { // A target without test/dns/cors config emits none of the three, exactly // as before, the additive property the shared contract exists to give. let p = payload(&[healthy("mnw")], now()); let n = node(&p, "target:mnw"); assert!(n.conditions.iter().all(|c| c.condition_type != "tests")); assert!(n.conditions.iter().all(|c| c.condition_type != "dns")); assert!(n.conditions.iter().all(|c| c.condition_type != "cors")); assert_eq!(p.validate(), Ok(())); } #[test] fn render_is_a_pure_function_of_state_and_clock() { let a = payload(&[healthy("mnw")], now()); let b = payload(&[healthy("mnw")], now()); assert_eq!( serde_json::to_value(&a).unwrap(), serde_json::to_value(&b).unwrap() ); } #[test] fn pom_declares_no_actions() { let p = payload(&[healthy("mnw")], now()); assert!(p.actions.is_empty()); assert!(node(&p, "target:mnw").actions.is_empty()); } }