use super::super::{Axis, BUDGET_EXHAUSTED_REF_PREFIX, Exhausted, check, mark_exhausted};
use super::{repo, seg, usage_line, write_meta, write_response};
use crate::config::Budgets;
use crate::template::{GitRunner, RealGit};
fn tokens(n: u32) -> String {
seg(&usage_line(Some(n), None, None, None))
}
#[test]
fn check_unbounded_never_triggers() {
let r = repo();
write_response(r.path(), "conv", 1, &tokens(1_000_000));
assert_eq!(check(r.path(), "a-b-c-d-e", &Budgets::default()), None);
}
#[test]
fn check_tokens_boundary_below_at_and_over() {
let r = repo();
write_response(r.path(), "conv", 1, &tokens(8));
let below = Budgets {
max_total_tokens: Some(9),
..Budgets::default()
};
assert_eq!(check(r.path(), "conv", &below), None); let at = Budgets {
max_total_tokens: Some(8),
..Budgets::default()
};
assert_eq!(
check(r.path(), "conv", &at),
Some(Exhausted {
axis: Axis::Tokens,
limit: 8,
actual: 8
})
); let over = Budgets {
max_total_tokens: Some(5),
..Budgets::default()
};
assert_eq!(check(r.path(), "conv", &over).unwrap().axis, Axis::Tokens);
}
#[test]
fn check_falls_through_axes_in_order() {
let r = repo();
write_response(r.path(), "conv", 1, &tokens(4));
write_meta(
r.path(),
"conv",
1,
"2026-01-01T00:00:00Z",
"2026-01-01T00:00:50Z",
);
let b = Budgets {
max_total_tokens: Some(100),
max_wall_seconds: Some(40),
max_depth: None,
};
let ex = check(r.path(), "conv", &b).unwrap();
assert_eq!(ex.axis, Axis::Wall);
assert_eq!((ex.limit, ex.actual), (40, 50));
let ok = Budgets {
max_wall_seconds: Some(51),
..Budgets::default()
};
assert_eq!(check(r.path(), "conv", &ok), None);
}
#[test]
fn check_depth_is_positional_allows_at_limit_exhausts_over() {
let r = repo();
let branch = "p-q-r-s-t";
let at = Budgets {
max_depth: Some(2),
..Budgets::default()
};
assert_eq!(check(r.path(), branch, &at), None); let over = Budgets {
max_depth: Some(1),
..Budgets::default()
};
let ex = check(r.path(), branch, &over).unwrap();
assert_eq!(ex.axis, Axis::Depth);
assert_eq!((ex.limit, ex.actual), (1, 2)); }
#[test]
fn check_derives_whole_tree_over_root_for_a_subagent_branch() {
let r = repo();
write_response(r.path(), "t0-r0", 1, &tokens(600)); write_response(r.path(), "t0-r0-t1-c1", 1, &tokens(500)); write_response(r.path(), "t0-r0-t1-s2", 1, &tokens(400)); let b = Budgets {
max_total_tokens: Some(1000),
..Budgets::default()
};
let ex = check(r.path(), "t0-r0-t1-c1", &b).unwrap();
assert_eq!(ex.axis, Axis::Tokens);
assert_eq!((ex.limit, ex.actual), (1000, 1500));
}
#[test]
fn check_wall_also_derives_over_the_whole_tree() {
let r = repo();
write_meta(
r.path(),
"t0-r0",
1,
"2026-01-01T00:00:00Z",
"2026-01-01T00:00:30Z",
);
write_meta(
r.path(),
"t0-r0-t1-c1",
1,
"2026-01-01T00:00:00Z",
"2026-01-01T00:00:25Z",
);
let b = Budgets {
max_wall_seconds: Some(50),
..Budgets::default()
};
let ex = check(r.path(), "t0-r0-t1-c1", &b).unwrap();
assert_eq!(ex.axis, Axis::Wall);
assert_eq!((ex.limit, ex.actual), (50, 55));
}
#[test]
fn check_depth_exhausts_a_deep_subagent_but_not_its_root() {
let r = repo();
let b = Budgets {
max_depth: Some(1),
..Budgets::default()
};
assert_eq!(check(r.path(), "t0-r0", &b), None); let ex = check(r.path(), "t0-r0-t1-c1-t2-g1", &b).unwrap(); assert_eq!(ex.axis, Axis::Depth);
assert_eq!((ex.limit, ex.actual), (1, 2));
}
#[test]
fn mark_exhausted_writes_a_readable_git_native_ref() {
let dir = repo();
let git = RealGit::new();
git.run(dir.path(), &["init", "-b", "conv-x"]).unwrap();
git.run(dir.path(), &["config", "user.email", "b@test.lernie"])
.unwrap();
git.run(dir.path(), &["config", "user.name", "b"]).unwrap();
git.run(dir.path(), &["config", "core.hooksPath", "/dev/null"])
.unwrap();
git.run(dir.path(), &["commit", "--allow-empty", "-m", "base"])
.unwrap();
mark_exhausted(dir.path(), "conv-x", &git).unwrap();
let ref_name = format!("{BUDGET_EXHAUSTED_REF_PREFIX}conv-x");
let out = git
.run_capture(dir.path(), &["for-each-ref", &ref_name])
.unwrap();
assert!(out.contains(&ref_name), "ref not written; got {out:?}");
}
#[test]
fn mark_exhausted_surfaces_git_failure() {
let dir = repo();
let git = RealGit::new();
assert!(mark_exhausted(dir.path(), "conv-x", &git).is_err());
}
#[test]
fn display_names_axis_and_ratio() {
let e = Exhausted {
axis: Axis::Tokens,
limit: 8,
actual: 8,
};
assert_eq!(e.to_string(), "max_total_tokens exhausted (8/8)");
assert_eq!(Axis::Wall.to_string(), "max_wall_seconds");
assert_eq!(Axis::Depth.to_string(), "max_depth");
}