//! 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, WhoisResult}; /// 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, "[{}] {} \u{2014} {}", 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{v}").unwrap(); } if let Some(u) = &details.uptime { write!(out, " up {u}").unwrap(); } } writeln!(out).unwrap(); if let Some(err) = &s.error { writeln!(out, " {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{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] {} \u{2014} {err}", t.host).unwrap(); } else if t.days_remaining <= 0 { writeln!(out, " TLS: [ERR] {} \u{2014} EXPIRED (expired {})", t.host, t.not_after).unwrap(); } else if t.days_remaining <= 14 { writeln!(out, " TLS: [WARN] {} \u{2014} {}d remaining (expires {})", t.host, t.days_remaining, t.not_after).unwrap(); } else { writeln!(out, " TLS: [OK] {} \u{2014} {}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] {} \u{2014} {err}", w.domain).unwrap(); } else if let Some(days) = w.days_remaining { if days <= 0 { writeln!(out, " WHOIS: [ERR] {} \u{2014} EXPIRED", w.domain).unwrap(); } else if days <= 30 { writeln!(out, " WHOIS: [WARN] {} \u{2014} {}d remaining", w.domain, days).unwrap(); } else { writeln!(out, " WHOIS: [OK] {} \u{2014} {}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 \u{2014} {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, "[{}] {} \u{2014} {} ({}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, " \u{2014} {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] {} {} \u{2014} {err}", r.name, r.record_type).unwrap(); } else if r.matches { writeln!(out, " [OK] {} {} \u{2014} {:?}", r.name, r.record_type, r.actual).unwrap(); } else { writeln!(out, " [FAIL] {} {} \u{2014} 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] {} \u{2014} {err}", w.domain).unwrap(); } else { let days_str = w.days_remaining .map(|d| format!("{d}d remaining")) .unwrap_or_else(|| "expiry unknown".to_string()); let registrar_str = w.registrar.as_deref().unwrap_or("unknown registrar"); writeln!(out, " [OK] {} \u{2014} {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 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, days ) } /// 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, "=== {name} ===").unwrap(); writeln!(out, " ID: {id}").unwrap(); writeln!(out, " Version: {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(|v| v.as_i64()); let ms_str = ms.map(|m| format!(" ({m}ms)")).unwrap_or_default(); writeln!(out, " Target {target_name}: {status}{ms_str}").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(|v| v.as_u64()) .map(|ms| format!(" ({ms}ms)")) .unwrap_or_default(); writeln!(out, " Peer {peer_name}: {status}{latency}").unwrap(); } } // Error fallback if let Some(err) = instance_data.get("error").and_then(|v| v.as_str()) { writeln!(out, " ({err})").unwrap(); } writeln!(out).unwrap(); } out } #[cfg(test)] mod tests { use super::*; use crate::types::*; // --- 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()), 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()), 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, }; 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, }; 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()), }; 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, }; 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 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, }; 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 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)")); } #[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")); } }