use super::*;
use crate::profile::{AppState, ProfileName};
use crate::usage::{UsageInfo, epoch_secs_to_iso, now_epoch_secs};
use std::collections::BTreeMap;
fn reset_in(secs: i64) -> String {
epoch_secs_to_iso(now_epoch_secs() + secs)
}
fn profile(name: &str, threshold: f64, util: f64, reset_secs: i64) -> Profile {
Profile {
name: name.into(),
base_url: None,
api_key: None,
auto_start: false,
env: BTreeMap::new(),
models: Default::default(),
fallback_threshold: Some(threshold),
last_resort: false,
bell_threshold: None,
credentials: None,
usage: Some(UsageInfo {
five_hour: Some(UsageWindow {
utilization: util,
resets_at: Some(reset_in(reset_secs)),
}),
..UsageInfo::default()
}),
fetch_status: None,
provider: None,
third_party_usage: None,
}
}
fn config_with(profiles: Vec<Profile>, active: Option<&str>, chain: Vec<&str>) -> AppConfig {
let names: Vec<ProfileName> = profiles.iter().map(|p| p.name.clone()).collect();
AppConfig {
state: AppState {
active_profile: active.map(Into::into),
profiles: names,
fallback_chain: chain.into_iter().map(Into::into).collect(),
..AppState::default()
},
profiles,
}
}
fn line_text(line: &Line<'static>) -> String {
line.spans.iter().map(|s| s.content.as_ref()).collect()
}
fn resumes_line(lines: &[Line<'static>]) -> Option<String> {
lines.iter().map(line_text).find(|t| t.contains("resumes:"))
}
#[test]
fn all_exhausted_wrap_mode_shows_resumes_hint() {
let a = profile("a", 95.0, 100.0, 3600);
let b = profile("b", 95.0, 100.0, 1800);
let config = config_with(vec![a, b], Some("a"), vec!["a", "b"]);
let app = App::new(config);
let lines = fallback_flow_lines(&app, 60, 20);
let hint =
resumes_line(&lines).expect("resumes hint must render when the whole chain is exhausted");
assert!(
hint.contains("resumes: b in ~"),
"names the soonest-resuming member: {hint}"
);
}
#[test]
fn all_exhausted_wrap_off_active_cleared_shows_resumes_hint() {
let a = profile("a", 95.0, 100.0, 900);
let b = profile("b", 95.0, 100.0, 3600);
let mut config = config_with(vec![a, b], None, vec!["a", "b"]);
config.state.wrap_off = true;
let app = App::new(config);
let lines = fallback_flow_lines(&app, 60, 20);
let hint = resumes_line(&lines)
.expect("resumes hint must render even with no active profile (wrap-off cleared it)");
assert!(hint.contains("resumes: a in ~"), "{hint}");
}
#[test]
fn partially_exhausted_chain_hides_resumes_hint() {
let a = profile("a", 95.0, 100.0, 3600);
let b = profile("b", 95.0, 20.0, 3600);
let config = config_with(vec![a, b], Some("a"), vec!["a", "b"]);
let app = App::new(config);
let lines = fallback_flow_lines(&app, 60, 20);
assert!(
resumes_line(&lines).is_none(),
"must not show when the chain isn't fully exhausted"
);
}
#[test]
fn healthy_chain_hides_resumes_hint() {
let a = profile("a", 95.0, 10.0, 3600);
let b = profile("b", 95.0, 5.0, 3600);
let config = config_with(vec![a, b], Some("a"), vec!["a", "b"]);
let app = App::new(config);
let lines = fallback_flow_lines(&app, 60, 20);
assert!(resumes_line(&lines).is_none());
}