diff --git a/src/lib.rs b/src/lib.rs index f02b1d9..f20e0f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1498,6 +1498,43 @@ mod tests { assert_eq!(event.token_rate_per_sec, 10.0); } + #[test] + fn burn_detector_switches_at_timeout_boundary_and_recovers() { + let mut detector = AgentBurnDetector::new(AgentBurnConfig { + min_rate_per_sec: 1.0, + timeout_secs: 60, + }); + let burning = AgentPulse::from_abtop_status(&json!({ + "interval_ms": 1000, + "token_rate": 10.0, + "sessions_total": 1, + "sessions_active": 1 + })); + let idle = AgentPulse::from_abtop_status(&json!({ + "interval_ms": 1000, + "token_rate": 0.0, + "sessions_total": 1, + "sessions_active": 0 + })); + + assert_eq!( + detector.observe(&burning, 100).state, + AgentBurnState::Active + ); + + let before_timeout = detector.observe(&burning, 159); + assert_eq!(before_timeout.state, AgentBurnState::Active); + assert_eq!(before_timeout.burning_for_secs, Some(59)); + + let at_timeout = detector.observe(&burning, 160); + assert_eq!(at_timeout.state, AgentBurnState::Runaway); + assert_eq!(at_timeout.burning_for_secs, Some(60)); + + let recovery = detector.observe(&idle, 161); + assert_eq!(recovery.state, AgentBurnState::Recovery); + assert_eq!(recovery.burning_for_secs, None); + } + #[test] fn burn_detector_recovers_when_rate_drops_to_zero() { let mut detector = AgentBurnDetector::new(AgentBurnConfig { diff --git a/src/parse.rs b/src/parse.rs index fa90a19..faa21de 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -404,6 +404,43 @@ mod tests { assert_eq!(windows[0].level, "ok"); } + #[test] + fn window_level_changes_on_threshold_boundaries() { + let mut credits = Metric { + used: 74.9, + limit: 100.0, + remaining: 25.1, + percent: 74.9, + }; + + assert_eq!(window_level(Some(&credits), None, 75.0, 90.0), "ok"); + + credits.percent = 75.0; + assert_eq!(window_level(Some(&credits), None, 75.0, 90.0), "warning"); + + credits.percent = 89.9; + assert_eq!(window_level(Some(&credits), None, 75.0, 90.0), "warning"); + + credits.percent = 90.0; + assert_eq!(window_level(Some(&credits), None, 75.0, 90.0), "danger"); + } + + #[test] + fn summarize_metric_clamps_over_limit_percent() { + let payload = json!({ + "usage": { + "rows": [{"credits5Hours": 1500, "creditLimit5Hours": 100}] + } + }); + + let windows = summarize_me(&payload, 75.0, 90.0); + let credits = windows[0].credits.as_ref().unwrap(); + + assert_eq!(credits.remaining, 0.0); + assert_eq!(credits.percent, 999.0); + assert_eq!(windows[0].level, "danger"); + } + #[test] fn peak_percent_returns_max() { let windows = summarize_me(&demo_payload(), 75.0, 90.0);