use super::{Attribution, Cost, Prices, of_ball, of_conversation, select};
use crate::budgets::{BudgetSpend, Scope, StepBill};
use serde_json::json;
use std::path::Path;
const ROOT: &str = "20260717T120000Z-root";
const KID: &str = "20260717T120000Z-root-20260717T120100Z-kid0";
const OTHER: &str = "20260717T130000Z-othr";
fn table() -> Prices {
Prices::from_json(&json!({ "opus": { "input": 1, "output": 2 } }))
}
fn write_step(ws: &Path, conv: &str, seq: &str, input: u64, output: u64, model: Option<&str>) {
let step = ws.join("steps").join(conv).join(seq);
std::fs::create_dir_all(&step).unwrap();
std::fs::write(
step.join("response.json"),
format!(r#"{{"type":"usage","input_tokens":{input},"output_tokens":{output}}}"#),
)
.unwrap();
if let Some(model) = model {
std::fs::write(
step.join("request.json"),
format!(r#"{{"model":"{model}"}}"#),
)
.unwrap();
}
}
fn walk(ws: &Path) -> Vec<StepBill> {
crate::budgets::bills(ws, &Scope::Workspace)
}
#[test]
fn an_absent_table_prices_nothing_and_deletes_the_column() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1_000_000, 0, Some("opus"));
let figure = of_conversation(&walk(dir.path()), ROOT, &Prices::default());
assert_eq!(figure.tokens.input_tokens, 1_000_000);
assert!(figure.cost.is_none(), "empty table โ no cost seat at all");
}
#[test]
fn a_conversation_figure_prices_its_whole_descent() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1_000_000, 500_000, Some("opus"));
write_step(dir.path(), KID, "001", 2_000_000, 0, Some("opus"));
write_step(dir.path(), OTHER, "001", 9_000_000, 0, Some("opus"));
let figure = of_conversation(&walk(dir.path()), ROOT, &table());
assert_eq!(figure.attribution, Attribution::Conversations(1));
assert_eq!(figure.tokens.total_tokens(), 3_500_000);
let cost = figure.cost.unwrap();
assert_eq!(cost.micro_usd, 4_000_000);
assert_eq!(cost.unpriced_tokens, 0);
assert_eq!(cost.usd(), "$4.00");
}
#[test]
fn a_step_on_an_unpriced_model_is_reported_not_rounded_to_free() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1_000_000, 0, Some("opus"));
write_step(dir.path(), ROOT, "002", 7, 3, Some("mystery"));
write_step(dir.path(), ROOT, "003", 5, 0, None);
let cost = of_conversation(&walk(dir.path()), ROOT, &table())
.cost
.unwrap();
assert_eq!(cost.micro_usd, 1_000_000);
assert_eq!(cost.unpriced_tokens, 15);
}
#[test]
fn a_stamped_ball_attributes_to_its_conversations() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1_000_000, 0, Some("opus"));
write_step(dir.path(), OTHER, "001", 2_000_000, 0, Some("opus"));
let roots = vec![ROOT.to_owned(), OTHER.to_owned()];
let figure = of_ball(&walk(dir.path()), &roots, &table());
assert_eq!(figure.attribution, Attribution::Conversations(2));
assert_eq!(figure.cost.unwrap().micro_usd, 3_000_000);
}
#[test]
fn an_unstamped_ball_falls_back_to_the_workspace_and_says_so() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1_000_000, 0, Some("opus"));
write_step(dir.path(), OTHER, "001", 2_000_000, 0, Some("opus"));
let figure = of_ball(&walk(dir.path()), &[], &table());
assert_eq!(figure.attribution, Attribution::Workspace);
assert_eq!(figure.tokens.input_tokens, 3_000_000);
let note = figure.attribution.note().unwrap();
assert_eq!(note.label, "workspace-wide");
assert!(note.hover.contains("upper bound on the ball"), "{note:?}");
}
#[test]
fn attribution_notes_only_what_the_seat_does_not_already_claim() {
assert!(Attribution::Conversations(1).note().is_none());
let note = Attribution::Conversations(3).note().unwrap();
assert_eq!(note.label, "over 3 conversations");
assert!(note.hover.contains('3'));
}
#[test]
fn a_malformed_table_degrades_row_by_row() {
let prices = Prices::from_json(&json!({
"opus": { "input": 3, "output": "nope", "cache_read": -5 },
"broken": "not an object",
}));
assert!(prices.of(Some("broken")).is_none());
assert!(prices.of(Some("absent")).is_none());
assert!(prices.of(None).is_none());
let price = prices.of(Some("opus")).unwrap();
assert_eq!(price.input, 3_000_000);
assert_eq!(price.output, 0, "a non-numeric rate reads zero");
assert_eq!(price.cache_read, 0, "a negative rate reads zero");
assert!(Prices::from_json(&json!([1, 2])).is_empty());
}
#[test]
fn money_renders_at_cent_resolution_without_conflating_small_with_none() {
assert_eq!(Cost::default().usd(), "$0.00");
assert_eq!(
Cost {
micro_usd: 5_000,
unpriced_tokens: 0
}
.usd(),
"<$0.01"
);
assert_eq!(
Cost {
micro_usd: 10_000,
unpriced_tokens: 0
}
.usd(),
"$0.01"
);
assert_eq!(
Cost {
micro_usd: 1_234_567,
unpriced_tokens: 0
}
.usd(),
"$1.23"
);
}
#[test]
fn a_nonsense_rate_saturates_rather_than_wrapping() {
let prices = Prices::from_json(&json!({ "opus": { "input": 1e300 } }));
let price = prices.of(Some("opus")).unwrap();
let cost = price.cost(BudgetSpend {
input_tokens: u64::MAX,
..BudgetSpend::default()
});
assert!(cost > 0, "saturating, never a wrap to a small figure");
}
#[test]
fn selection_takes_each_bill_at_most_once_and_empty_means_everything() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 10, 0, Some("opus"));
write_step(dir.path(), KID, "001", 20, 0, Some("opus"));
write_step(dir.path(), OTHER, "001", 40, 0, Some("opus"));
let bills = walk(dir.path());
assert_eq!(bills.len(), 3);
let once = select(&bills, &[ROOT.to_owned()]);
assert_eq!(
crate::budgets::total(&once).input_tokens,
30,
"root + descent"
);
let twice = select(&bills, &[ROOT.to_owned(), ROOT.to_owned()]);
assert_eq!(
crate::budgets::total(&twice).input_tokens,
30,
"a repeated root does not double-bill"
);
assert_eq!(
crate::budgets::total(&select(&bills, &[])).input_tokens,
70,
"no roots is the whole workspace"
);
}
#[test]
fn a_bill_carries_its_conversation() {
let dir = tempfile::tempdir().unwrap();
write_step(dir.path(), ROOT, "001", 1, 0, Some("opus"));
let bills = walk(dir.path());
assert_eq!(bills.first().map(|b| b.conv.as_str()), Some(ROOT));
}