use crate::enums::{AlertSeverity, AlertTrigger};
use crate::types::Duration;
#[derive(Debug, Clone, PartialEq)]
pub struct Alert {
severity: AlertSeverity,
trigger: AlertTrigger,
timestamp: Duration,
message: String,
}
impl Alert {
#[must_use]
pub fn new(
severity: AlertSeverity,
trigger: AlertTrigger,
timestamp: Duration,
message: impl Into<String>,
) -> Self {
Alert {
severity,
trigger,
timestamp,
message: message.into(),
}
}
#[must_use]
pub fn info(trigger: AlertTrigger, timestamp: Duration, message: impl Into<String>) -> Self {
Alert::new(AlertSeverity::Info, trigger, timestamp, message)
}
#[must_use]
pub fn warning(trigger: AlertTrigger, timestamp: Duration, message: impl Into<String>) -> Self {
Alert::new(AlertSeverity::Warning, trigger, timestamp, message)
}
#[must_use]
pub fn critical(
trigger: AlertTrigger,
timestamp: Duration,
message: impl Into<String>,
) -> Self {
Alert::new(AlertSeverity::Critical, trigger, timestamp, message)
}
#[must_use]
pub fn severity(&self) -> AlertSeverity {
self.severity
}
#[must_use]
pub fn trigger(&self) -> &AlertTrigger {
&self.trigger
}
#[must_use]
pub fn timestamp(&self) -> Duration {
self.timestamp
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn is_critical(&self) -> bool {
self.severity.is_critical()
}
#[must_use]
pub fn is_warning_or_higher(&self) -> bool {
self.severity.is_warning_or_higher()
}
}
impl std::fmt::Display for Alert {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {} - {}", self.severity, self.trigger, self.message)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::enums::{MentalHealthPath, SpiralType, StatePath};
#[test]
fn alert_construction() {
let alert = Alert::new(
AlertSeverity::Warning,
AlertTrigger::ThresholdExceeded(
StatePath::MentalHealth(MentalHealthPath::SuicidalDesire),
0.7,
),
Duration::days(100),
"Test message",
);
assert_eq!(alert.severity(), AlertSeverity::Warning);
assert!(alert.trigger().is_threshold());
assert_eq!(alert.timestamp().as_days(), 100);
assert_eq!(alert.message(), "Test message");
}
#[test]
fn alert_info_constructor() {
let alert = Alert::info(
AlertTrigger::custom("test"),
Duration::days(1),
"Info message",
);
assert_eq!(alert.severity(), AlertSeverity::Info);
assert!(!alert.is_critical());
assert!(!alert.is_warning_or_higher());
}
#[test]
fn alert_warning_constructor() {
let alert = Alert::warning(
AlertTrigger::spiral(SpiralType::Stress),
Duration::days(1),
"Warning message",
);
assert_eq!(alert.severity(), AlertSeverity::Warning);
assert!(!alert.is_critical());
assert!(alert.is_warning_or_higher());
}
#[test]
fn alert_critical_constructor() {
let alert = Alert::critical(
AlertTrigger::ThresholdExceeded(
StatePath::MentalHealth(MentalHealthPath::AttemptRisk),
0.8,
),
Duration::days(1),
"Critical message",
);
assert_eq!(alert.severity(), AlertSeverity::Critical);
assert!(alert.is_critical());
assert!(alert.is_warning_or_higher());
}
#[test]
fn alert_display() {
let alert = Alert::new(
AlertSeverity::Warning,
AlertTrigger::spiral(SpiralType::Depression),
Duration::days(50),
"Depression spiral detected",
);
let display = format!("{}", alert);
assert!(display.contains("Warning"));
assert!(display.contains("Depression spiral detected"));
}
#[test]
fn alert_debug() {
let alert = Alert::info(
AlertTrigger::custom("test"),
Duration::days(1),
"Debug test",
);
let debug = format!("{:?}", alert);
assert!(debug.contains("Alert"));
assert!(debug.contains("Info"));
}
#[test]
fn alert_clone() {
let alert1 = Alert::warning(
AlertTrigger::custom("test"),
Duration::days(1),
"Clone test",
);
let alert2 = alert1.clone();
assert_eq!(alert1, alert2);
}
#[test]
fn alert_includes_severity_level() {
let alert = Alert::new(
AlertSeverity::Critical,
AlertTrigger::ThresholdExceeded(
StatePath::MentalHealth(MentalHealthPath::SuicidalDesire),
0.9,
),
Duration::days(100),
"High risk",
);
assert_eq!(alert.severity(), AlertSeverity::Critical);
assert!(alert.is_critical());
}
#[test]
fn high_suicidal_desire_triggers_alert() {
let desire_value = 0.75;
let alert = Alert::warning(
AlertTrigger::ThresholdExceeded(
StatePath::MentalHealth(MentalHealthPath::SuicidalDesire),
desire_value,
),
Duration::days(100),
format!("Suicidal desire at {:.2}", desire_value),
);
assert!(matches!(
alert.trigger(),
AlertTrigger::ThresholdExceeded(path, value)
if *path == StatePath::MentalHealth(MentalHealthPath::SuicidalDesire)
&& (*value - desire_value).abs() < f64::EPSILON
));
}
#[test]
fn entity_starts_with_no_alerts() {
let alerts: Vec<Alert> = Vec::new();
assert!(alerts.is_empty());
}
#[test]
fn entity_can_accumulate_alerts() {
let mut alerts: Vec<Alert> = Vec::new();
alerts.push(Alert::warning(
AlertTrigger::spiral(SpiralType::Stress),
Duration::days(1),
"First alert",
));
alerts.push(Alert::critical(
AlertTrigger::ThresholdExceeded(
StatePath::MentalHealth(MentalHealthPath::SuicidalDesire),
0.8,
),
Duration::days(2),
"Second alert",
));
assert_eq!(alerts.len(), 2);
}
}