use terraphim_orchestrator::{CorrectionLevel, NightwatchConfig, NightwatchMonitor};
use terraphim_spawner::health::HealthStatus;
use terraphim_spawner::output::OutputEvent;
use terraphim_types::capability::ProcessId;
#[test]
fn test_nightwatch_accumulates_from_output() {
let mut monitor = NightwatchMonitor::new(NightwatchConfig::default());
let pid = ProcessId::new();
for i in 0..80 {
let event = OutputEvent::Stdout {
process_id: pid,
line: format!("output line {}", i),
};
monitor.observe("test-agent", &event);
}
for i in 0..20 {
let event = OutputEvent::Stderr {
process_id: pid,
line: format!("error line {}", i),
};
monitor.observe("test-agent", &event);
}
for _ in 0..8 {
monitor.observe_health("test-agent", HealthStatus::Healthy);
}
for _ in 0..2 {
monitor.observe_health("test-agent", HealthStatus::Degraded);
}
let ds = monitor.drift_score("test-agent").unwrap();
assert!(ds.score > 0.0, "drift should be non-zero with errors");
assert_eq!(ds.metrics.sample_count, 110);
assert!((ds.metrics.error_rate - 0.20).abs() < 0.01);
assert!((ds.metrics.health_score - 0.80).abs() < 0.01);
assert!(
ds.level >= CorrectionLevel::Minor,
"expected at least Minor drift"
);
}
#[test]
fn test_nightwatch_multi_agent_independent_tracking() {
let mut monitor = NightwatchMonitor::new(NightwatchConfig::default());
let pid = ProcessId::new();
for _ in 0..80 {
monitor.observe(
"agent-a",
&OutputEvent::Stderr {
process_id: pid,
line: "error".to_string(),
},
);
}
for _ in 0..20 {
monitor.observe(
"agent-a",
&OutputEvent::Stdout {
process_id: pid,
line: "ok".to_string(),
},
);
}
for _ in 0..95 {
monitor.observe(
"agent-b",
&OutputEvent::Stdout {
process_id: pid,
line: "ok".to_string(),
},
);
}
for _ in 0..5 {
monitor.observe(
"agent-b",
&OutputEvent::Stderr {
process_id: pid,
line: "warning".to_string(),
},
);
}
monitor.observe_health("agent-b", HealthStatus::Healthy);
let all_scores = monitor.all_drift_scores();
assert_eq!(all_scores.len(), 2);
let score_a = monitor.drift_score("agent-a").unwrap();
let score_b = monitor.drift_score("agent-b").unwrap();
assert!(
score_a.score > score_b.score,
"agent-a (high errors) should have higher drift than agent-b"
);
assert!(score_a.level >= CorrectionLevel::Severe);
assert!(score_b.level <= CorrectionLevel::Minor);
}
#[test]
fn test_nightwatch_reset_isolated_to_agent() {
let mut monitor = NightwatchMonitor::new(NightwatchConfig::default());
let pid = ProcessId::new();
for agent in &["agent-x", "agent-y"] {
for _ in 0..50 {
monitor.observe(
agent,
&OutputEvent::Stderr {
process_id: pid,
line: "error".to_string(),
},
);
}
}
let before_x = monitor.drift_score("agent-x").unwrap().score;
let before_y = monitor.drift_score("agent-y").unwrap().score;
assert!(before_x > 0.5);
assert!(before_y > 0.5);
monitor.reset("agent-x");
let after_x = monitor.drift_score("agent-x").unwrap();
let after_y = monitor.drift_score("agent-y").unwrap();
assert!(after_x.score < f64::EPSILON, "agent-x should be reset");
assert!(
(after_y.score - before_y).abs() < f64::EPSILON,
"agent-y should be unaffected"
);
}