use super::*;
#[test]
fn gap_boundary_returns_0_when_no_gap() {
let entries = vec![(1000, 10.0), (2000, 20.0), (3000, 30.0)];
assert_eq!(gap_boundary(&entries, 5000), 0);
}
#[test]
fn gap_boundary_returns_0_when_util_changed() {
let entries = vec![(1000, 10.0), (3_700_000, 20.0)];
assert_eq!(gap_boundary(&entries, 3_600_000), 0);
}
#[test]
fn gap_boundary_returns_0_when_gap_too_small() {
let entries = vec![(1000, 10.0), (2000, 10.0)];
assert_eq!(gap_boundary(&entries, 5000), 0);
}
#[test]
fn gap_boundary_cuts_at_idle_gap() {
let entries = vec![
(1000, 30.0),
(3_700_000, 30.0), ];
assert_eq!(gap_boundary(&entries, 600_000), 1);
}
#[test]
fn gap_boundary_finds_most_recent_gap() {
let entries = vec![
(1000, 30.0),
(3_700_000, 30.0), (4_000_000, 31.0),
(8_000_000, 31.0), ];
assert_eq!(gap_boundary(&entries, 600_000), 3);
}
#[test]
fn gap_boundary_clock_step_backwards_no_panic() {
let now = 1_200_000_000u64;
let entries = vec![
(now, 30.0),
(now - 3_600_000, 30.0), ];
assert_eq!(gap_boundary(&entries, 600_000), 0);
}
fn make_win(util: f64) -> UsageWindow {
UsageWindow {
utilization: util,
resets_at: None,
}
}
fn make_info(five_h: Option<f64>, seven_d: Option<f64>) -> UsageInfo {
UsageInfo {
plan: None,
five_hour: five_h.map(make_win),
seven_day: seven_d.map(make_win),
seven_day_opus: None,
seven_day_sonnet: None,
extra_usage: None,
}
}
#[test]
fn five_h_idle_gap_yields_burst_rate() {
let now = crate::usage::now_ms();
let history = vec![
((now - 3_900_000), make_info(Some(30.0), None)), ((now - 300_000), make_info(Some(30.0), None)), ((now - 240_000), make_info(Some(31.0), None)),
((now - 180_000), make_info(Some(32.0), None)),
((now - 120_000), make_info(Some(32.5), None)),
((now - 60_000), make_info(Some(33.0), None)),
];
let five_h = make_win(33.0);
let rates = compute_burn_rates_from_history(
&history,
&[("5h", &five_h)],
5,
30 * 60 * 1000,
10 * 60 * 1000,
);
let rate = rates.get("5h").copied().flatten().unwrap();
assert!((rate - 36.0).abs() < 3.0, "rate={rate}");
}
#[test]
fn seven_d_gap_cut_disabled_preserves_idle() {
let now = crate::usage::now_ms();
let history = vec![
((now - 70 * 3_600_000), make_info(None, Some(5.0))),
((now - 58 * 3_600_000), make_info(None, Some(8.0))),
((now - 46 * 3_600_000), make_info(None, Some(11.0))),
((now - 34 * 3_600_000), make_info(None, Some(14.0))),
((now - 22 * 3_600_000), make_info(None, Some(17.0))),
((now - 10 * 3_600_000), make_info(None, Some(19.0))),
((now - 4 * 3_600_000), make_info(None, Some(20.0))),
];
let seven_d = make_win(21.0);
let rates =
compute_burn_rates_from_history(&history, &[("7d", &seven_d)], 50, 24 * 60 * 60 * 1000, 0);
let rate = rates.get("7d").copied().flatten().unwrap();
assert!(
rate < 5.0,
"rate={rate} should be diluted by full history span"
);
}
#[test]
fn seven_d_gap_cut_enabled_overstates_during_activity() {
let now = crate::usage::now_ms();
let history = vec![
((now - 70 * 3_600_000), make_info(None, Some(5.0))),
((now - 58 * 3_600_000), make_info(None, Some(8.0))),
((now - 46 * 3_600_000), make_info(None, Some(11.0))),
((now - 34 * 3_600_000), make_info(None, Some(14.0))), ((now - 22 * 3_600_000), make_info(None, Some(14.0))), ((now - 10 * 3_600_000), make_info(None, Some(17.0))),
((now - 4 * 3_600_000), make_info(None, Some(19.0))),
];
let seven_d = make_win(20.0);
let rates = compute_burn_rates_from_history(
&history,
&[("7d", &seven_d)],
50,
24 * 60 * 60 * 1000,
10 * 60 * 1000,
);
let rate = rates.get("7d").copied().flatten().unwrap();
assert!(
rate > 0.2,
"rate={rate} should be higher after cutting idle span"
);
}
#[test]
fn steady_drain_no_gap_no_cut() {
let now = crate::usage::now_ms();
let history = vec![
((now - 600_000), make_info(Some(10.0), None)),
((now - 480_000), make_info(Some(12.0), None)),
((now - 360_000), make_info(Some(14.0), None)),
((now - 240_000), make_info(Some(16.0), None)),
((now - 120_000), make_info(Some(18.0), None)),
];
let five_h = make_win(20.0);
let rates = compute_burn_rates_from_history(
&history,
&[("5h", &five_h)],
5,
30 * 60 * 1000,
10 * 60 * 1000,
);
let rate = rates.get("5h").copied().flatten().unwrap();
assert!((rate - 60.0).abs() < 5.0, "rate={rate}");
}