use super::*;
#[test]
fn sql_selects_only_completed_route_tasks_in_the_utc_month() {
let store = Store::open_memory().unwrap();
let now = Utc.with_ymd_and_hms(2026, 10, 15, 12, 0, 0).unwrap();
let (start, end) = month_bounds(now).unwrap();
let mut current = task("current", start, 37.2);
current.created_at = (start - chrono::Duration::days(3)).with_timezone(&Local);
store.insert_task(¤t).unwrap();
for (id, at, status, name) in [
(
"old",
start - chrono::Duration::seconds(1),
TaskStatus::Done,
"claude-api-ming",
),
("next", end, TaskStatus::Done, "claude-api-ming"),
("running", now, TaskStatus::Running, "claude-api-ming"),
("other", now, TaskStatus::Done, "claude-api-meet"),
] {
let mut t = task(id, at, 100.0);
t.status = status;
t.custom_agent_name = Some(name.into());
store.insert_task(&t).unwrap();
}
let tasks = store
.api_route_month_tasks("claude-api-ming", start, end)
.unwrap();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].id.as_str(), "current");
assert_eq!(calculate(&route(false), &tasks, now).unwrap().spent, 37.2);
}
#[test]
fn unknown_completed_cost_is_visible_and_reported_cost_is_authoritative() {
let home = tempfile::tempdir().unwrap();
let _guard = crate::support::paths::AidHomeGuard::set(home.path());
crate::cost::clear_feed_for_tests();
let now = Utc::now();
let mut unpriced = task("unpriced", now, 0.0);
unpriced.cost_usd = None;
unpriced.tokens = Some(100);
unpriced.observed_model = Some("claude-opus-99-9".into());
let budget = calculate(&route(false), &[task("reported", now, 37.2), unpriced], now).unwrap();
assert_eq!(budget.spent, 37.2);
assert_eq!(budget.unpriced, 1);
assert_eq!(budget.detail(), "$37.20 / $200 this month (1 tasks unpriced)");
}
#[test]
fn expiration_bonus_is_strictly_within_seven_days_and_below_eighty_percent_pace() {
let before = Utc.with_ymd_and_hms(2026, 10, 25, 0, 0, 0).unwrap();
let after = before + chrono::Duration::seconds(1);
assert_eq!(calculate(&route(false), &[], before).unwrap().pace, 0.0);
assert_eq!(calculate(&route(false), &[], after).unwrap().pace, 1.0);
let now = Utc.with_ymd_and_hms(2026, 10, 26, 0, 0, 0).unwrap();
let threshold = 200.0 * 25.0 / 31.0 * 0.8;
assert_eq!(
calculate(&route(false), &[task("at", now, threshold)], now)
.unwrap()
.pace,
0.0
);
assert_eq!(
calculate(&route(false), &[task("below", now, threshold - 0.01)], now)
.unwrap()
.pace,
1.0
);
let early = Utc.with_ymd_and_hms(2026, 10, 10, 0, 0, 0).unwrap();
let ahead = 200.0 * 9.0 / 31.0 * 1.25;
assert_eq!(
calculate(&route(false), &[task("on-pace", early, ahead - 0.01)], early)
.unwrap()
.pace,
0.0
);
assert_eq!(
calculate(&route(false), &[task("ahead", early, ahead + 0.01)], early)
.unwrap()
.pace,
-2.0
);
}
#[test]
fn launch_checks_completed_budget_without_installing_a_hold_or_changing_task_outcome() {
let home = tempfile::tempdir().unwrap();
let _guard = crate::support::paths::AidHomeGuard::set(home.path());
let store = Store::open_memory().unwrap();
store
.insert_task(&task("finished-over", Utc::now(), 250.0))
.unwrap();
let agent = crate::agent::api_route::ApiRouteAgent(route(false));
let error = crate::agent::api_route::validate_launch(&agent, &store, false).unwrap_err();
assert!(error.to_string().contains("exhausted"));
assert_eq!(
store.get_task("finished-over").unwrap().unwrap().status,
TaskStatus::Done
);
assert!(
!crate::support::paths::aid_dir()
.join("rate-limit-claude-api-ming")
.exists()
);
let bill = crate::agent::api_route::ApiRouteAgent(route(true));
crate::agent::api_route::validate_launch(&bill, &store, false).unwrap();
}
#[test]
fn completed_unreported_real_claude_id_uses_feed_price_even_with_opus_request() {
let home = tempfile::tempdir().unwrap();
let _guard = crate::support::paths::AidHomeGuard::set(home.path());
let feed: crate::cost::price_feed::Feed = serde_json::from_value(serde_json::json!({
"built_at": Utc::now().to_rfc3339(), "stale":false, "age_seconds":0,
"models":[{"id":"anthropic/claude-opus-5.5", "aliases":["opus"],
"input_per_mtok":4.0,"output_per_mtok":20.0}]
}))
.unwrap();
crate::cost::clear_feed_for_tests();
crate::cost::set_feed_for_tests(feed);
let store = Store::open_memory().unwrap();
let mut completed = task("real-id", Utc::now(), 0.0);
completed.cost_usd = None;
completed.tokens = Some(1_000_000);
completed.requested_model = Some("opus".into());
completed.observed_model = Some("claude-opus-5-5".into());
store.insert_task(&completed).unwrap();
let budget = for_route(&route(false), Some(&store)).unwrap();
assert_eq!(budget.spent, 8.8);
assert_eq!(budget.unpriced, 0);
}