//! Pure formatting functions for CLI display output. //! //! Each function takes data types and writes formatted output to a `String`, //! keeping display logic separate from async I/O for testability. use std::fmt::Write; use crate::db::{DnsCheckRow, IncidentRow, PruneResult, RouteCheckRow, TlsCheckRow, WhoisCheckRow}; use crate::types::{ DnsCheckResult, HealthSnapshot, LatencyStats, TestRun, TestStaleness, VersionRow, WhoisResult, }; /// Strip terminal control characters from an untrusted remote string before it /// is written to the operator's terminal. /// /// A monitored host's health `version`/`uptime`/`error`, a whois/DNS error, a /// mesh peer's fields, or *any* TCP service on an ssh_banner target can embed raw /// ANSI escape sequences (ESC/CSI) that clear or rewrite the screen, e.g. a fake /// all-clear, the moment they're printed on `pom status`/`mesh`. Every such /// value passes through here at the display sink. Keeps printable UTF-8; drops C0/C1 controls (incl. ESC, CR, LF) and DEL. fn scrub(s: &str) -> String { s.chars().filter(|c| !c.is_control()).collect() } /// Format a single health snapshot as a human-readable line. pub fn format_health_snapshot(s: &HealthSnapshot) -> String { let mut out = String::new(); write!(out, "[{}] {}: {}", s.status.icon(), s.target, s.status).unwrap(); write!(out, " ({}ms)", s.response_time_ms).unwrap(); if let Some(details) = &s.details { if let Some(v) = &details.version { write!(out, " v{}", scrub(v)).unwrap(); } if let Some(u) = &details.uptime { write!(out, " up {}", scrub(u)).unwrap(); } } writeln!(out).unwrap(); if let Some(err) = &s.error { writeln!(out, " {}", scrub(err)).unwrap(); } out } /// Format a list of health snapshots for CLI display. pub fn format_health_snapshots(snapshots: &[HealthSnapshot]) -> String { let mut out = String::new(); for s in snapshots { out.push_str(&format_health_snapshot(s)); } out } /// Format a test run result for CLI display. pub fn format_test_result(target_name: &str, run: &TestRun) -> String { let mut out = String::new(); let result = if run.passed { "PASSED" } else { "FAILED" }; writeln!(out, "{target_name}: {result}").unwrap(); if let Some(d) = run.duration_secs { writeln!(out, "Duration: {d}s").unwrap(); } if let (Some(p), Some(f)) = (run.summary.total_passed, run.summary.total_failed) { writeln!(out, "Tests: {p} passed, {f} failed").unwrap(); } for step in &run.summary.steps { let mark = if step.passed { "PASS" } else { "FAIL" }; writeln!(out, " {mark} {}", step.name).unwrap(); } if !run.passed { writeln!(out, "\nRaw output:\n{}", run.raw_output).unwrap(); } out } /// Format a single target's status block for CLI display. #[allow(clippy::too_many_arguments)] pub fn format_status_target( name: &str, label: &str, health: Option<&HealthSnapshot>, latency: Option<&LatencyStats>, tls: Option<&TlsCheckRow>, route_checks: Option<&[RouteCheckRow]>, dns_checks: Option<&[DnsCheckRow]>, whois: Option<&WhoisCheckRow>, test: Option<&TestRun>, staleness: Option<&TestStaleness>, incident: Option<&IncidentRow>, ) -> String { let mut out = String::new(); writeln!(out, "=== {name} ({label}) ===").unwrap(); if let Some(h) = health { write!(out, " Health: [{}] {}", h.status.icon(), h.status).unwrap(); write!(out, " ({}ms)", h.response_time_ms).unwrap(); if let Some(d) = &h.details && let Some(v) = &d.version { write!(out, " v{}", scrub(v)).unwrap(); } writeln!(out).unwrap(); } else { writeln!(out, " Health: no data").unwrap(); } if let Some(l) = latency { writeln!( out, " Latency (24h): avg {:.0}ms, p95 {}ms, range {}-{}ms ({} samples)", l.avg_ms, l.p95_ms, l.min_ms, l.max_ms, l.sample_count ) .unwrap(); } if let Some(t) = tls { if let Some(ref err) = t.error { writeln!(out, " TLS: [ERR] {}: {}", t.host, scrub(err)).unwrap(); } else if t.days_remaining <= 0 { writeln!( out, " TLS: [ERR] {}: EXPIRED (expired {})", t.host, t.not_after ) .unwrap(); } else if t.days_remaining <= 14 { writeln!( out, " TLS: [WARN] {}: {}d remaining (expires {})", t.host, t.days_remaining, t.not_after ) .unwrap(); } else { writeln!( out, " TLS: [OK] {}: {}d remaining (expires {})", t.host, t.days_remaining, t.not_after ) .unwrap(); } } if let Some(checks) = route_checks && !checks.is_empty() { let total = checks.len(); let ok_count = checks.iter().filter(|c| c.ok).count(); if ok_count == total { writeln!(out, " Routes: {ok_count}/{total} OK").unwrap(); } else { let failed: Vec<&str> = checks .iter() .filter(|c| !c.ok) .map(|c| c.path.as_str()) .collect(); writeln!( out, " Routes: {ok_count}/{total} (FAIL: {})", failed.join(", ") ) .unwrap(); } } if let Some(checks) = dns_checks && !checks.is_empty() { let total = checks.len(); let ok_count = checks.iter().filter(|c| c.matches).count(); if ok_count == total { writeln!(out, " DNS: {ok_count}/{total} match").unwrap(); } else { let failed: Vec = checks .iter() .filter(|c| !c.matches) .map(|c| format!("{} {}", c.name, c.record_type)) .collect(); writeln!( out, " DNS: {ok_count}/{total} (MISMATCH: {})", failed.join(", ") ) .unwrap(); } } if let Some(w) = whois { if let Some(ref err) = w.error { writeln!(out, " WHOIS: [ERR] {}: {}", w.domain, scrub(err)).unwrap(); } else if let Some(days) = w.days_remaining { if days <= 0 { writeln!(out, " WHOIS: [ERR] {}: EXPIRED", w.domain).unwrap(); } else if days <= 30 { writeln!(out, " WHOIS: [WARN] {}: {}d remaining", w.domain, days).unwrap(); } else { writeln!(out, " WHOIS: [OK] {}: {}d remaining", w.domain, days).unwrap(); } } } if let Some(t) = test { let result = if t.passed { "PASSED" } else { "FAILED" }; write!(out, " Tests: {result}").unwrap(); if let Some(d) = t.duration_secs { write!(out, " ({d}s)").unwrap(); } writeln!(out).unwrap(); if let (Some(p), Some(f)) = (t.summary.total_passed, t.summary.total_failed) { writeln!(out, " {p} passed, {f} failed").unwrap(); } } else { writeln!(out, " Tests: no data").unwrap(); } if let Some(s) = staleness && s.stale && let Some(reason) = &s.reason { writeln!(out, " Tests: STALE ({})", scrub(reason)).unwrap(); } if let Some(inc) = incident { writeln!( out, " Incident: [ACTIVE] {} since {}", inc.to_status, inc.started_at ) .unwrap(); } writeln!(out).unwrap(); out } /// Format health check history for CLI display. pub fn format_health_history(history: &[HealthSnapshot]) -> String { if history.is_empty() { return "No health check history.\n".to_string(); } let mut out = String::new(); for h in history { writeln!( out, "[{}] {}: {} ({}ms) {}", h.status.icon(), h.target, h.status, h.response_time_ms, h.checked_at ) .unwrap(); } out } /// Format test run history for CLI display. pub fn format_test_history(history: &[TestRun]) -> String { if history.is_empty() { return "No test run history.\n".to_string(); } let mut out = String::new(); for r in history { let result = if r.passed { "PASS" } else { "FAIL" }; write!(out, "[{result}] {}", r.target).unwrap(); if let Some(d) = r.duration_secs { write!(out, " ({d}s)").unwrap(); } write!(out, " {}", r.started_at).unwrap(); if let (Some(p), Some(f)) = (r.summary.total_passed, r.summary.total_failed) { write!(out, " ({p} passed, {f} failed)").unwrap(); } writeln!(out).unwrap(); } out } /// Format regression warnings for CLI display. pub fn format_regressions(regressions: &[String]) -> String { let mut out = String::new(); writeln!(out, "\nREGRESSIONS (passed last run, failed now):").unwrap(); for name in regressions { writeln!(out, " {name}").unwrap(); } out } /// Format test duration trend for CLI display. pub fn format_test_duration_trend(durations: &[(String, i64)], drift: Option<&str>) -> String { let mut out = String::new(); if !durations.is_empty() { write!(out, " Duration trend (last {}): ", durations.len()).unwrap(); let strs: Vec = durations.iter().map(|(_, d)| format!("{d}s")).collect(); writeln!(out, "{}", strs.join(", ")).unwrap(); } if let Some(msg) = drift { writeln!(out, " DRIFT: {msg}").unwrap(); } out } /// Format DNS check results and WHOIS results for CLI display. pub fn format_dns_results(dns_results: &[DnsCheckResult], whois_results: &[WhoisResult]) -> String { let mut out = String::new(); if !dns_results.is_empty() { writeln!(out, "DNS Records:").unwrap(); for r in dns_results { if let Some(ref err) = r.error { writeln!(out, " [ERR] {} {}: {}", r.name, r.record_type, scrub(err)).unwrap(); } else if r.matches { writeln!(out, " [OK] {} {}: {:?}", r.name, r.record_type, r.actual).unwrap(); } else { writeln!( out, " [FAIL] {} {}: expected {:?}, got {:?}", r.name, r.record_type, r.expected, r.actual ) .unwrap(); } } } if !whois_results.is_empty() { if !dns_results.is_empty() { writeln!(out).unwrap(); } writeln!(out, "WHOIS:").unwrap(); for w in whois_results { if let Some(ref err) = w.error { writeln!(out, " [ERR] {}: {}", w.domain, scrub(err)).unwrap(); } else { let days_str = w.days_remaining.map_or_else( || "expiry unknown".to_string(), |d| format!("{d}d remaining"), ); let registrar_str = w.registrar.as_deref().unwrap_or("unknown registrar"); writeln!(out, " [OK] {}: {days_str} ({registrar_str})", w.domain).unwrap(); } } } out } /// Format prune results for CLI display. pub fn format_prune(result: &PruneResult, days: i64) -> String { format!( "Pruned {} health checks, {} test runs, {} test details, {} peer heartbeats, {} alerts, {} TLS checks, {} incidents, {} route checks, {} DNS checks, {} WHOIS checks, {} backup checks, {} systemd checks, {} CA bundle checks, {} synckit fleet checks older than {} days.\n", result.health, result.tests, result.test_details, result.heartbeats, result.alerts, result.tls, result.incidents, result.routes, result.dns, result.whois, result.backups, result.systemd, result.ca_bundle, result.synckit_fleet, days ) } /// Format the `pom versions` roll-up as one aligned table. /// /// Every cell is a value a monitored target chose, so all of them are scrubbed. /// A missing value prints as `-`: the table answers "what is live everywhere", /// and a row that has to be left out of the answer is itself the finding. pub fn format_versions(rows: &[VersionRow]) -> String { if rows.is_empty() { return "No targets configured.\n".to_string(); } let cells: Vec<[String; 6]> = rows .iter() .map(|r| { [ scrub(&r.target), r.version.as_deref().map_or_else(dash, scrub), r.git_sha.as_deref().map_or_else(dash, short_sha), r.version_since.as_deref().map_or_else(dash, minute_stamp), r.checked_at.as_deref().map_or_else(dash, minute_stamp), r.commits_behind.map_or_else(dash, |n| n.to_string()), ] }) .collect(); // SINCE is when this version was first seen, not when it was deployed: // nothing tells PoM about a deploy, and the column header should not // promise more than a poller can know. const HEADERS: [&str; 6] = ["TARGET", "VERSION", "SHA", "SINCE", "CHECKED", "BEHIND"]; let widths: Vec = (0..HEADERS.len()) .map(|i| { cells .iter() .map(|row| row[i].chars().count()) .chain(std::iter::once(HEADERS[i].len())) .max() .unwrap_or(0) }) .collect(); let mut out = String::new(); write_row(&mut out, &HEADERS.map(String::from), &widths); for (row, source) in cells.iter().zip(rows) { write_row(&mut out, row, &widths); // The blank in the BEHIND column has a reason whenever a count was // attempted and did not come back. Printing it under the row keeps the // table one line per target while still saying what happened. if let Some(err) = &source.behind_error { writeln!(out, " behind: {}", scrub(err)).unwrap(); } } out } /// Write one table row, every column but the last padded to its width. fn write_row(out: &mut String, cells: &[String; 6], widths: &[usize]) { for (i, cell) in cells.iter().enumerate() { if i + 1 == cells.len() { writeln!(out, "{cell}").unwrap(); } else { let pad = widths[i].saturating_sub(cell.chars().count()); write!(out, "{cell}{:pad$} ", "", pad = pad).unwrap(); } } } fn dash() -> String { "-".to_string() } /// First 8 characters of a commit id, enough to identify it by eye. fn short_sha(sha: &str) -> String { scrub(sha).chars().take(8).collect() } /// RFC 3339 down to the minute: seconds and offset are noise in a table whose /// rows are minutes to hours apart. Anything that does not look like a /// timestamp is passed through rather than truncated into nonsense. fn minute_stamp(ts: &str) -> String { let scrubbed = scrub(ts); match (scrubbed.len() >= 16, scrubbed.get(..16)) { (true, Some(head)) if scrubbed.as_bytes()[10] == b'T' => head.replacen('T', " ", 1), _ => scrubbed, } } /// Format mesh data (from JSON) for human-readable CLI display. pub fn format_mesh(data: &serde_json::Value) -> String { let Some(instances) = data.get("instances").and_then(|v| v.as_object()) else { return "No mesh data available.\n".to_string(); }; let mut out = String::new(); for (name, instance_data) in instances { let instance = instance_data.get("instance"); let id = instance .and_then(|i| i.get("id")) .and_then(|v| v.as_str()) .unwrap_or("?"); let version = instance .and_then(|i| i.get("version")) .and_then(|v| v.as_str()) .unwrap_or("?"); writeln!(out, "=== {} ===", scrub(name)).unwrap(); writeln!(out, " ID: {}", scrub(id)).unwrap(); writeln!(out, " Version: {}", scrub(version)).unwrap(); // Targets if let Some(targets) = instance_data.get("targets").and_then(|v| v.as_object()) { for (target_name, target_data) in targets { let status = target_data .get("status") .and_then(|v| v.as_str()) .unwrap_or("?"); let ms = target_data .get("response_time_ms") .and_then(serde_json::Value::as_i64); let ms_str = ms.map(|m| format!(" ({m}ms)")).unwrap_or_default(); writeln!( out, " Target {}: {}{ms_str}", scrub(target_name), scrub(status) ) .unwrap(); } } // Peers if let Some(peers) = instance_data.get("peers").and_then(|v| v.as_object()) { for (peer_name, peer_data) in peers { let status = peer_data .get("status") .and_then(|v| v.as_str()) .unwrap_or("?"); let latency = peer_data .get("latency_ms") .and_then(serde_json::Value::as_u64) .map(|ms| format!(" ({ms}ms)")) .unwrap_or_default(); writeln!( out, " Peer {}: {}{latency}", scrub(peer_name), scrub(status) ) .unwrap(); } } // Error fallback if let Some(err) = instance_data.get("error").and_then(|v| v.as_str()) { writeln!(out, " ({})", scrub(err)).unwrap(); } writeln!(out).unwrap(); } out } #[cfg(test)] mod tests { use super::*; use crate::types::*; #[test] fn scrub_strips_ansi_and_control_chars() { // A monitored host trying to clear/rewrite the operator's terminal. let hostile = "1.0\u{1b}[2J\u{1b}[1;1H FAKE ALL-CLEAR\r\n"; let cleaned = scrub(hostile); assert!(!cleaned.contains('\u{1b}'), "ESC must be stripped"); assert!(!cleaned.contains('\r') && !cleaned.contains('\n')); assert_eq!(cleaned, "1.0[2J[1;1H FAKE ALL-CLEAR"); // Printable non-ASCII Unicode is preserved. assert_eq!(scrub("v1.2 \u{2014} ok"), "v1.2 \u{2014} ok"); } #[test] fn health_snapshot_scrubs_hostile_error() { let s = HealthSnapshot { id: None, target: "mnw".to_string(), status: HealthStatus::Error, checked_at: "2026-07-07T00:00:00+00:00".to_string(), response_time_ms: 5, details: None, error: Some("boom\u{1b}[2Jcleared".to_string()), }; let out = format_health_snapshot(&s); assert!( !out.contains('\u{1b}'), "ESC from a remote error must not reach the terminal" ); } // format_health_snapshot #[test] fn health_snapshot_operational_with_details() { let s = HealthSnapshot { id: None, target: "mnw".to_string(), status: HealthStatus::Operational, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 95, details: Some(HealthDetails { version: Some("1.2.0".to_string()), git_sha: None, uptime: Some("5d 3h".to_string()), checks: None, monitoring: None, }), error: None, }; let out = format_health_snapshot(&s); assert!(out.contains("[OK]")); assert!(out.contains("mnw")); assert!(out.contains("operational")); assert!(out.contains("(95ms)")); assert!(out.contains("v1.2.0")); assert!(out.contains("up 5d 3h")); } #[test] fn health_snapshot_unreachable_with_error() { let s = HealthSnapshot { id: None, target: "api".to_string(), status: HealthStatus::Unreachable, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 0, details: None, error: Some("connection refused".to_string()), }; let out = format_health_snapshot(&s); assert!(out.contains("[DOWN]")); assert!(out.contains("unreachable")); assert!(out.contains("connection refused")); } #[test] fn health_snapshot_degraded_no_details() { let s = HealthSnapshot { id: None, target: "svc".to_string(), status: HealthStatus::Degraded, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 2500, details: None, error: None, }; let out = format_health_snapshot(&s); assert!(out.contains("[WARN]")); assert!(out.contains("degraded")); assert!(out.contains("(2500ms)")); assert!(!out.contains("up ")); assert!(!out.contains(" v")); } #[test] fn health_snapshot_error_status() { let s = HealthSnapshot { id: None, target: "db".to_string(), status: HealthStatus::Error, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 500, details: None, error: Some("500 internal server error".to_string()), }; let out = format_health_snapshot(&s); assert!(out.contains("[ERR]")); assert!(out.contains("error")); assert!(out.contains("500 internal server error")); } #[test] fn health_snapshots_multiple() { let snapshots = vec![ HealthSnapshot { id: None, target: "a".to_string(), status: HealthStatus::Operational, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 50, details: None, error: None, }, HealthSnapshot { id: None, target: "b".to_string(), status: HealthStatus::Degraded, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 3000, details: None, error: None, }, ]; let out = format_health_snapshots(&snapshots); assert!(out.contains("[OK]")); assert!(out.contains("[WARN]")); assert!(out.contains('a')); assert!(out.contains('b')); } // format_test_result #[test] fn test_result_passed() { let run = TestRun { id: None, target: "mnw".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: Some("2026-03-10T00:02:00Z".to_string()), duration_secs: Some(120), exit_code: Some(0), passed: true, summary: TestSummary { steps: vec![ StepResult { name: "cargo check".to_string(), passed: true, }, StepResult { name: "cargo test".to_string(), passed: true, }, ], total_passed: Some(759), total_failed: Some(0), details: vec![], }, raw_output: String::new(), filter: None, }; let out = format_test_result("mnw", &run); assert!(out.contains("mnw: PASSED")); assert!(out.contains("Duration: 120s")); assert!(out.contains("Tests: 759 passed, 0 failed")); assert!(out.contains("PASS cargo check")); assert!(out.contains("PASS cargo test")); assert!(!out.contains("Raw output")); } #[test] fn test_result_failed_shows_raw_output() { let run = TestRun { id: None, target: "mnw".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: Some("2026-03-10T00:01:00Z".to_string()), duration_secs: Some(60), exit_code: Some(1), passed: false, summary: TestSummary { steps: vec![ StepResult { name: "cargo check".to_string(), passed: true, }, StepResult { name: "cargo test".to_string(), passed: false, }, ], total_passed: Some(750), total_failed: Some(9), details: vec![], }, raw_output: "thread 'test_foo' panicked at 'assertion failed'".to_string(), filter: None, }; let out = format_test_result("mnw", &run); assert!(out.contains("mnw: FAILED")); assert!(out.contains("PASS cargo check")); assert!(out.contains("FAIL cargo test")); assert!(out.contains("750 passed, 9 failed")); assert!(out.contains("Raw output:")); assert!(out.contains("assertion failed")); } #[test] fn test_result_no_duration_or_counts() { let run = TestRun { id: None, target: "svc".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: None, duration_secs: None, exit_code: None, passed: true, summary: TestSummary { steps: vec![], total_passed: None, total_failed: None, details: vec![], }, raw_output: String::new(), filter: None, }; let out = format_test_result("svc", &run); assert!(out.contains("svc: PASSED")); assert!(!out.contains("Duration:")); assert!(!out.contains("Tests:")); } // format_status_target #[test] fn status_target_with_health_and_tests() { let health = HealthSnapshot { id: None, target: "mnw".to_string(), status: HealthStatus::Operational, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 95, details: Some(HealthDetails { version: Some("2.1.0".to_string()), git_sha: None, uptime: None, checks: None, monitoring: None, }), error: None, }; let test = TestRun { id: None, target: "mnw".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: Some("2026-03-10T00:01:00Z".to_string()), duration_secs: Some(60), exit_code: Some(0), passed: true, summary: TestSummary { steps: vec![], total_passed: Some(100), total_failed: Some(0), details: vec![], }, raw_output: String::new(), filter: None, }; let out = format_status_target( "mnw", "MakeNotWork", Some(&health), None, None, None, None, None, Some(&test), None, None, ); assert!(out.contains("=== mnw (MakeNotWork) ===")); assert!(out.contains("Health: [OK] operational (95ms) v2.1.0")); assert!(out.contains("Tests: PASSED (60s)")); assert!(out.contains("100 passed, 0 failed")); } #[test] fn status_target_no_data() { let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, None, ); assert!(out.contains("=== mnw (MakeNotWork) ===")); assert!(out.contains("Health: no data")); assert!(out.contains("Tests: no data")); } #[test] fn status_target_health_only() { let health = HealthSnapshot { id: None, target: "mnw".to_string(), status: HealthStatus::Degraded, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 2000, details: None, error: None, }; let out = format_status_target( "mnw", "MakeNotWork", Some(&health), None, None, None, None, None, None, None, None, ); assert!(out.contains("Health: [WARN] degraded (2000ms)")); assert!(out.contains("Tests: no data")); } #[test] fn status_target_failed_tests() { let test = TestRun { id: None, target: "mnw".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: None, duration_secs: None, exit_code: Some(1), passed: false, summary: TestSummary { steps: vec![], total_passed: Some(80), total_failed: Some(5), details: vec![], }, raw_output: String::new(), filter: None, }; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, Some(&test), None, None, ); assert!(out.contains("Tests: FAILED")); assert!(out.contains("80 passed, 5 failed")); } // format_status_target with TLS #[test] fn status_target_tls_ok() { let tls = TlsCheckRow { id: 1, target: "mnw".to_string(), host: "makenot.work".to_string(), valid: true, days_remaining: 47, not_before: "2026-01-10T00:00:00Z".to_string(), not_after: "2026-04-27T00:00:00Z".to_string(), subject: "CN=makenot.work".to_string(), issuer: "CN=Let's Encrypt".to_string(), checked_at: "2026-03-11T00:00:00Z".to_string(), error: None, webpki_trusted: Some(true), platform_trusted: Some(true), webpki_error: None, platform_error: None, }; let out = format_status_target( "mnw", "MakeNotWork", None, None, Some(&tls), None, None, None, None, None, None, ); assert!(out.contains("TLS: [OK] makenot.work")); assert!(out.contains("47d remaining")); assert!(out.contains("expires 2026-04-27")); } #[test] fn status_target_tls_warning() { let tls = TlsCheckRow { id: 1, target: "mnw".to_string(), host: "makenot.work".to_string(), valid: true, days_remaining: 12, not_before: "2026-01-10T00:00:00Z".to_string(), not_after: "2026-03-23T00:00:00Z".to_string(), subject: "CN=makenot.work".to_string(), issuer: "CN=Let's Encrypt".to_string(), checked_at: "2026-03-11T00:00:00Z".to_string(), error: None, webpki_trusted: Some(true), platform_trusted: Some(true), webpki_error: None, platform_error: None, }; let out = format_status_target( "mnw", "MakeNotWork", None, None, Some(&tls), None, None, None, None, None, None, ); assert!(out.contains("TLS: [WARN] makenot.work")); assert!(out.contains("12d remaining")); } #[test] fn status_target_tls_error() { let tls = TlsCheckRow { id: 1, target: "mnw".to_string(), host: "makenot.work".to_string(), valid: false, days_remaining: 0, not_before: String::new(), not_after: String::new(), subject: String::new(), issuer: String::new(), checked_at: "2026-03-11T00:00:00Z".to_string(), error: Some("connection refused".to_string()), webpki_trusted: Some(false), platform_trusted: Some(false), webpki_error: Some("TCP connect failed".to_string()), platform_error: Some("TCP connect failed".to_string()), }; let out = format_status_target( "mnw", "MakeNotWork", None, None, Some(&tls), None, None, None, None, None, None, ); assert!(out.contains("TLS: [ERR] makenot.work")); assert!(out.contains("connection refused")); } // format_status_target with incident #[test] fn status_target_with_active_incident() { let incident = IncidentRow { id: 1, target: "mnw".to_string(), started_at: "2026-03-11T14:30:00Z".to_string(), ended_at: None, duration_secs: None, from_status: "operational".to_string(), to_status: "degraded".to_string(), }; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, Some(&incident), ); assert!(out.contains("Incident: [ACTIVE] degraded since 2026-03-11T14:30:00Z")); } #[test] fn status_target_no_incident() { let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, None, ); assert!(!out.contains("Incident")); } // format_status_target with latency #[test] fn status_target_with_latency() { let latency = LatencyStats { min_ms: 95, max_ms: 210, avg_ms: 120.0, p95_ms: 180, sample_count: 288, }; let out = format_status_target( "mnw", "MakeNotWork", None, Some(&latency), None, None, None, None, None, None, None, ); assert!(out.contains("Latency (24h): avg 120ms, p95 180ms, range 95-210ms (288 samples)")); } #[test] fn status_target_without_latency() { let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, None, ); assert!(!out.contains("Latency")); } // format_health_history #[test] fn health_history_empty() { let out = format_health_history(&[]); assert_eq!(out, "No health check history.\n"); } #[test] fn health_history_with_entries() { let history = vec![ HealthSnapshot { id: Some(2), target: "mnw".to_string(), status: HealthStatus::Operational, checked_at: "2026-03-10T01:00:00Z".to_string(), response_time_ms: 120, details: None, error: None, }, HealthSnapshot { id: Some(1), target: "mnw".to_string(), status: HealthStatus::Degraded, checked_at: "2026-03-10T00:00:00Z".to_string(), response_time_ms: 2500, details: None, error: None, }, ]; let out = format_health_history(&history); assert!(out.contains("[OK] mnw")); assert!(out.contains("(120ms)")); assert!(out.contains("2026-03-10T01:00:00Z")); assert!(out.contains("[WARN] mnw")); assert!(out.contains("(2500ms)")); } // format_test_history #[test] fn test_history_empty() { let out = format_test_history(&[]); assert_eq!(out, "No test run history.\n"); } #[test] fn test_history_with_entries() { let history = vec![ TestRun { id: Some(2), target: "mnw".to_string(), started_at: "2026-03-10T01:00:00Z".to_string(), finished_at: None, duration_secs: Some(120), exit_code: Some(0), passed: true, summary: TestSummary { steps: vec![], total_passed: Some(810), total_failed: Some(0), details: vec![], }, raw_output: String::new(), filter: None, }, TestRun { id: Some(1), target: "mnw".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: None, duration_secs: None, exit_code: Some(1), passed: false, summary: TestSummary { steps: vec![], total_passed: None, total_failed: None, details: vec![], }, raw_output: String::new(), filter: None, }, ]; let out = format_test_history(&history); assert!(out.contains("[PASS] mnw (120s) 2026-03-10T01:00:00Z")); assert!(out.contains("810 passed, 0 failed")); assert!(out.contains("[FAIL] mnw 2026-03-10T00:00:00Z")); } #[test] fn test_history_no_duration_no_counts() { let history = vec![TestRun { id: Some(1), target: "svc".to_string(), started_at: "2026-03-10T00:00:00Z".to_string(), finished_at: None, duration_secs: None, exit_code: None, passed: true, summary: TestSummary { steps: vec![], total_passed: None, total_failed: None, details: vec![], }, raw_output: String::new(), filter: None, }]; let out = format_test_history(&history); // Should not have duration or counts assert!(!out.contains('(')); assert!(out.contains("[PASS] svc 2026-03-10T00:00:00Z")); } // format_prune #[test] fn prune_formatting() { let result = PruneResult { health: 5, tests: 3, test_details: 15, heartbeats: 10, alerts: 2, tls: 1, incidents: 4, routes: 0, dns: 8, whois: 2, backups: 1, systemd: 6, ca_bundle: 2, synckit_fleet: 7, }; let out = format_prune(&result, 30); assert_eq!( out, "Pruned 5 health checks, 3 test runs, 15 test details, 10 peer heartbeats, 2 alerts, 1 TLS checks, 4 incidents, 0 route checks, 8 DNS checks, 2 WHOIS checks, 1 backup checks, 6 systemd checks, 2 CA bundle checks, 7 synckit fleet checks older than 30 days.\n" ); } #[test] fn prune_zero_records() { let result = PruneResult { health: 0, tests: 0, test_details: 0, heartbeats: 0, alerts: 0, tls: 0, incidents: 0, routes: 0, dns: 0, whois: 0, backups: 0, systemd: 0, ca_bundle: 0, synckit_fleet: 0, }; let out = format_prune(&result, 7); assert!(out.contains("Pruned 0 health checks, 0 test runs, 0 test details, 0 peer heartbeats, 0 alerts, 0 TLS checks, 0 incidents, 0 route checks, 0 DNS checks, 0 WHOIS checks, 0 backup checks, 0 systemd checks, 0 CA bundle checks, 0 synckit fleet checks older than 7 days.")); } // format_mesh #[test] fn mesh_no_instances() { let data = serde_json::json!({}); let out = format_mesh(&data); assert_eq!(out, "No mesh data available.\n"); } #[test] fn mesh_empty_instances() { let data = serde_json::json!({ "instances": {} }); let out = format_mesh(&data); // Empty map, no output lines beyond the empty string assert!(out.is_empty()); } #[test] fn mesh_single_instance_with_targets_and_peers() { let data = serde_json::json!({ "instances": { "hetzner": { "instance": { "id": "uuid-123", "version": "0.2.0" }, "targets": { "mnw": { "status": "operational", "response_time_ms": 95 } }, "peers": { "astra": { "status": "online", "latency_ms": 42 } } } } }); let out = format_mesh(&data); assert!(out.contains("=== hetzner ===")); assert!(out.contains("ID: uuid-123")); assert!(out.contains("Version: 0.2.0")); assert!(out.contains("Target mnw: operational (95ms)")); assert!(out.contains("Peer astra: online (42ms)")); } #[test] fn mesh_missing_instance_details() { let data = serde_json::json!({ "instances": { "node-1": {} } }); let out = format_mesh(&data); assert!(out.contains("=== node-1 ===")); assert!(out.contains("ID: ?")); assert!(out.contains("Version: ?")); } #[test] fn mesh_instance_with_error() { let data = serde_json::json!({ "instances": { "node-2": { "error": "connection refused" } } }); let out = format_mesh(&data); assert!(out.contains("=== node-2 ===")); assert!(out.contains("(connection refused)")); } #[test] fn mesh_target_without_response_time() { let data = serde_json::json!({ "instances": { "node": { "instance": { "id": "x", "version": "1.0" }, "targets": { "svc": { "status": "unreachable" } } } } }); let out = format_mesh(&data); assert!(out.contains("Target svc: unreachable")); // No (Xms) suffix assert!(!out.contains("Target svc: unreachable (")); } #[test] fn mesh_peer_without_latency() { let data = serde_json::json!({ "instances": { "node": { "instance": { "id": "x", "version": "1.0" }, "peers": { "other": { "status": "missing" } } } } }); let out = format_mesh(&data); assert!(out.contains("Peer other: missing")); // No (Xms) suffix assert!(!out.contains("Peer other: missing (")); } #[test] fn mesh_multiple_instances() { let data = serde_json::json!({ "instances": { "alpha": { "instance": { "id": "a1", "version": "0.1.0" } }, "beta": { "instance": { "id": "b2", "version": "0.2.0" } } } }); let out = format_mesh(&data); assert!(out.contains("=== alpha ===")); assert!(out.contains("=== beta ===")); assert!(out.contains("ID: a1")); assert!(out.contains("ID: b2")); } // format_status_target with staleness #[test] fn status_target_stale_by_version() { let staleness = TestStaleness { stale: true, reason: Some("version changed: 0.1.8 -> 0.1.9".to_string()), current_version: Some("0.1.9".to_string()), tested_version: Some("0.1.8".to_string()), last_test_at: Some("2026-03-10T00:00:00Z".to_string()), days_since_test: Some(1), }; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, Some(&staleness), None, ); assert!(out.contains("Tests: STALE")); assert!(out.contains("version changed: 0.1.8 -> 0.1.9")); } #[test] fn status_target_stale_by_age() { let staleness = TestStaleness { stale: true, reason: Some("tests are 10 days old (threshold: 7d)".to_string()), current_version: Some("0.1.9".to_string()), tested_version: Some("0.1.9".to_string()), last_test_at: Some("2026-03-01T00:00:00Z".to_string()), days_since_test: Some(10), }; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, Some(&staleness), None, ); assert!(out.contains("Tests: STALE")); assert!(out.contains("tests are 10 days old")); } #[test] fn status_target_not_stale() { let staleness = TestStaleness { stale: false, reason: None, current_version: Some("0.1.9".to_string()), tested_version: Some("0.1.9".to_string()), last_test_at: Some("2026-03-10T00:00:00Z".to_string()), days_since_test: Some(1), }; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, Some(&staleness), None, ); assert!(!out.contains("STALE")); } #[test] fn status_target_no_staleness_data() { let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, None, ); assert!(!out.contains("STALE")); } // format_status_target with routes #[test] fn status_target_all_routes_ok() { let checks = vec![ RouteCheckRow { id: 1, target: "mnw".to_string(), path: "/".to_string(), status_code: 200, ok: true, response_time_ms: 50, checked_at: "2026-03-13T00:00:00Z".to_string(), error: None, }, RouteCheckRow { id: 2, target: "mnw".to_string(), path: "/docs".to_string(), status_code: 200, ok: true, response_time_ms: 60, checked_at: "2026-03-13T00:00:00Z".to_string(), error: None, }, ]; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, Some(&checks), None, None, None, None, None, ); assert!(out.contains("Routes: 2/2 OK")); } #[test] fn status_target_some_routes_failing() { let checks = vec![ RouteCheckRow { id: 1, target: "mnw".to_string(), path: "/".to_string(), status_code: 200, ok: true, response_time_ms: 50, checked_at: "2026-03-13T00:00:00Z".to_string(), error: None, }, RouteCheckRow { id: 2, target: "mnw".to_string(), path: "/docs/faq".to_string(), status_code: 404, ok: false, response_time_ms: 30, checked_at: "2026-03-13T00:00:00Z".to_string(), error: Some("HTTP 404".to_string()), }, RouteCheckRow { id: 3, target: "mnw".to_string(), path: "/pricing".to_string(), status_code: 500, ok: false, response_time_ms: 20, checked_at: "2026-03-13T00:00:00Z".to_string(), error: Some("HTTP 500".to_string()), }, ]; let out = format_status_target( "mnw", "MakeNotWork", None, None, None, Some(&checks), None, None, None, None, None, ); assert!(out.contains("Routes: 1/3 (FAIL: /docs/faq, /pricing)")); } // format_versions fn version_row(target: &str, version: Option<&str>, sha: Option<&str>) -> VersionRow { VersionRow { target: target.to_string(), label: format!("{target} label"), version: version.map(String::from), git_sha: sha.map(String::from), checked_at: Some("2026-07-29T18:04:37.123456+00:00".to_string()), version_since: Some("2026-07-28T09:00:00+00:00".to_string()), commits_behind: Some(8), behind_error: None, } } #[test] fn versions_table_aligns_columns_and_shortens_the_sha() { let rows = vec![ version_row("mnw", Some("0.11.0"), Some("6402bf4e9c1d2e3f4a5b")), version_row("multithreaded", Some("0.4.2"), Some("aaaabbbbcccc")), ]; let out = format_versions(&rows); let lines: Vec<&str> = out.lines().collect(); assert!(lines[0].starts_with("TARGET")); // The header pads to the widest target, so every column starts at the // same offset on every line. let version_col = lines[0].find("VERSION").unwrap(); assert_eq!(lines[1].find("0.11.0"), Some(version_col)); assert_eq!(lines[2].find("0.4.2"), Some(version_col)); assert!(out.contains("6402bf4e "), "sha shortened to 8: {out}"); assert!(!out.contains("6402bf4e9c1d")); assert!( out.contains("2026-07-29 18:04"), "timestamp to the minute: {out}" ); } #[test] fn versions_table_prints_a_dash_for_every_missing_value() { let rows = vec![VersionRow { target: "mt".to_string(), label: "Multithreaded".to_string(), version: None, git_sha: None, checked_at: None, version_since: None, commits_behind: None, behind_error: None, }]; let out = format_versions(&rows); let row = out.lines().nth(1).unwrap(); assert_eq!( row.split_whitespace().collect::>(), ["mt", "-", "-", "-", "-", "-"] ); } #[test] fn versions_table_says_why_a_count_is_blank() { let mut row = version_row("mnw", Some("0.11.0"), Some("6402bf4e")); row.commits_behind = None; row.behind_error = Some("git rev-list exited 128: bad revision".to_string()); let out = format_versions(&[row]); assert!(out.contains("behind: git rev-list exited 128"), "{out}"); } #[test] fn versions_table_scrubs_a_hostile_version_string() { // Same terminal-injection surface as every other display sink: the // version and sha are whatever the monitored target chose to send. let mut row = version_row("mnw", Some("1.0\u{1b}[2J FAKE"), Some("6402bf4e")); row.behind_error = Some("boom\u{1b}[2J".to_string()); let out = format_versions(&[row]); assert!(!out.contains('\u{1b}'), "ESC must not reach the terminal"); } #[test] fn versions_empty_config_says_so() { assert_eq!(format_versions(&[]), "No targets configured.\n"); } #[test] fn minute_stamp_passes_through_anything_that_is_not_a_timestamp() { assert_eq!(minute_stamp("2026-07-29T18:04:37Z"), "2026-07-29 18:04"); assert_eq!(minute_stamp("whenever"), "whenever"); assert_eq!(minute_stamp("2026-07-29 18:04:37"), "2026-07-29 18:04:37"); } #[test] fn status_target_no_route_checks() { let out = format_status_target( "mnw", "MakeNotWork", None, None, None, None, None, None, None, None, None, ); assert!(!out.contains("Routes")); } }