use super::*;
use chrono::Duration;
fn make_hit(provider: &'static str, reset: Option<DateTime<Utc>>) -> UsageLimit {
UsageLimit {
provider,
scope: UsageLimitScope::Session,
reset_at: reset,
raw: "test".to_string(),
}
}
#[test]
fn compute_resume_uses_provider_reset_time_with_jitter() {
let cfg = UsageLimitConfig::default();
let target = Utc::now() + Duration::seconds(120);
let hit = make_hit("claude", Some(target));
let (scheduled, fallback_used) = compute_resume_at(&hit, &cfg);
assert!(!fallback_used);
let diff = (scheduled - target).num_seconds();
assert!(
(30..=31).contains(&diff),
"expected ~30s jitter, got {diff}"
);
}
#[test]
fn compute_resume_falls_back_when_reset_is_none() {
let cfg = UsageLimitConfig::default();
let hit = make_hit("codex", None);
let before = Utc::now();
let (scheduled, fallback_used) = compute_resume_at(&hit, &cfg);
assert!(fallback_used);
let secs = (scheduled - before).num_seconds();
assert!(
(3600..=3700).contains(&secs),
"expected ~3630s fallback, got {secs}"
);
}
#[test]
fn compute_resume_respects_per_provider_fallback_override() {
let mut cfg = UsageLimitConfig::default();
cfg.providers.insert(
"copilot".to_string(),
UsageLimitProviderOverride {
fallback_secs: Some(60),
..Default::default()
},
);
let hit = make_hit("copilot", None);
let before = Utc::now();
let (scheduled, _) = compute_resume_at(&hit, &cfg);
let secs = (scheduled - before).num_seconds();
assert!((60..=100).contains(&secs), "got {secs}");
}
#[test]
fn compute_resume_caps_at_max_wait_secs() {
let cfg = UsageLimitConfig {
max_wait_secs: 10,
..Default::default()
};
let target = Utc::now() + Duration::days(365);
let hit = make_hit("claude", Some(target));
let before = Utc::now();
let (scheduled, _) = compute_resume_at(&hit, &cfg);
let secs = (scheduled - before).num_seconds();
assert!(secs <= 12, "expected ≤10s cap (got {secs})");
}
#[test]
fn compute_resume_clamps_past_reset_to_near_now() {
let cfg = UsageLimitConfig::default();
let target = Utc::now() - Duration::seconds(60);
let hit = make_hit("claude", Some(target));
let before = Utc::now();
let (scheduled, _) = compute_resume_at(&hit, &cfg);
let secs = (scheduled - before).num_seconds();
assert!((25..=35).contains(&secs), "got {secs}");
}
#[test]
fn enabled_for_respects_global_and_per_provider() {
let mut cfg = UsageLimitConfig::default();
assert!(cfg.enabled_for("claude"));
cfg.enabled = false;
assert!(!cfg.enabled_for("claude"));
cfg.enabled = true;
cfg.providers.insert(
"codex".to_string(),
UsageLimitProviderOverride {
enabled: Some(false),
..Default::default()
},
);
assert!(!cfg.enabled_for("codex"));
assert!(cfg.enabled_for("claude"));
}
#[test]
fn default_max_attempts_is_twelve() {
let cfg = UsageLimitConfig::default();
assert_eq!(cfg.max_attempts, 12);
}
#[test]
fn max_attempts_round_trips_through_toml() {
let parsed: UsageLimitConfig = toml::from_str("max_attempts = 0\n").unwrap();
assert_eq!(parsed.max_attempts, 0);
let parsed: UsageLimitConfig = toml::from_str("max_attempts = 50\n").unwrap();
assert_eq!(parsed.max_attempts, 50);
}
#[test]
fn log_event_hit_carries_through_all_fields() {
let hit = UsageLimit {
provider: "claude",
scope: UsageLimitScope::Weekly,
reset_at: Some(Utc::now() + Duration::seconds(60)),
raw: "boom".to_string(),
};
let when = Utc::now() + Duration::seconds(90);
let kind = log_event_hit(&hit, "incident-xyz", Some(when), true);
match kind {
crate::session_log::LogEventKind::UsageLimitHit {
provider,
scope,
reset_at,
scheduled_resume_at,
fallback_used,
incident_id,
raw,
} => {
assert_eq!(provider, "claude");
assert_eq!(scope, "weekly");
assert!(reset_at.is_some());
assert!(scheduled_resume_at.is_some());
assert!(fallback_used);
assert_eq!(incident_id, "incident-xyz");
assert_eq!(raw.as_deref(), Some("boom"));
}
_ => panic!("expected UsageLimitHit"),
}
}
#[test]
fn to_log_event_hit_generates_fresh_incident() {
let hit = UsageLimit {
provider: "codex",
scope: UsageLimitScope::Session,
reset_at: None,
raw: "boom".to_string(),
};
let a = to_log_event_hit(hit.clone());
let b = to_log_event_hit(hit);
match (a, b) {
(
crate::session_log::LogEventKind::UsageLimitHit {
incident_id: i1, ..
},
crate::session_log::LogEventKind::UsageLimitHit {
incident_id: i2, ..
},
) => assert_ne!(i1, i2, "orphan emissions must each get a fresh incident id"),
_ => panic!("expected UsageLimitHit"),
}
}
#[test]
fn resume_message_per_provider_override() {
let mut cfg = UsageLimitConfig::default();
assert_eq!(cfg.resume_message_for("claude"), "Continue");
cfg.providers.insert(
"copilot".to_string(),
UsageLimitProviderOverride {
resume_message: Some("Please continue.".to_string()),
..Default::default()
},
);
assert_eq!(cfg.resume_message_for("copilot"), "Please continue.");
assert_eq!(cfg.resume_message_for("claude"), "Continue");
}