//! Test-suite alert/recovery messages. Domain half of the Alerter split; //! shared dispatch/cooldown plumbing lives in the parent module. //! //! Distinct from [`super::test_duration`], which fires on a suite that still //! passes but has slowed down. These fire on pass/fail transitions. use tracing::instrument; use super::{AlertCategory, AlertMeta, Alerter}; impl Alerter { #[instrument(skip_all)] pub async fn send_test_failure_alert( &self, target: &str, label: &str, failed: Option, exit_code: Option, detail: &str, ) { let alert_key = format!("tests:{target}"); let subject = match failed { Some(n) => format!("[PoM] {label}: {n} test(s) failing"), None => format!("[PoM] {label}: test suite failed to complete"), }; let body = format!( "Target: {label} ({target})\n\ Failed: {}\n\ Exit code: {}\n\ Detail: {detail}\n\ Instance: {}\n\ Time: {}\n\n\ - PoM", failed.map_or_else(|| "unknown".to_string(), |n| n.to_string()), exit_code.map_or_else(|| "none".to_string(), |c| c.to_string()), self.instance_name, chrono::Utc::now().to_rfc3339(), ); // A suite that never produced a count did not run at all (SSH refused, // missing checkout, timeout), which is worse than a known count of // failures: it means the target is unverified rather than broken. let priority = if failed.is_some() { "high" } else { "critical" }; self.fire_failure( &subject, &body, priority, "pom-tests", Some(target), AlertMeta { key: &alert_key, category: AlertCategory::TestFailure, from: None, to: None, error: Some(detail), }, ) .await; } #[instrument(skip_all)] pub async fn send_test_recovery(&self, target: &str, label: &str) { let alert_key = format!("tests:{target}"); let subject = format!("[PoM] {label}: test suite passing again"); let body = format!( "Target: {label} ({target})\n\ The test suite passed on its latest scheduled run.\n\ Instance: {}\n\ Time: {}\n\n\ - PoM", self.instance_name, chrono::Utc::now().to_rfc3339(), ); self.fire_recovery( &subject, &body, AlertMeta { key: &alert_key, category: AlertCategory::TestRecovery, from: None, to: None, error: None, }, ) .await; } }