use super::*;
fn ledger_with(dir: &tempfile::TempDir, lines: &[&str]) -> std::path::PathBuf {
let ledger = savings_log_in(dir.path());
std::fs::create_dir_all(ledger.parent().expect("parent")).expect("mkdir");
std::fs::write(&ledger, format!("{}\n", lines.join("\n"))).expect("write");
ledger
}
fn pct_row(session: &str, tokens_saved: i64, tokens_before: u64) -> String {
serde_json::to_string(&SavingsRow {
ts: now_ts(),
session_id: session.to_string(),
technique: TECHNIQUE_DIVERT.to_string(),
tokens_saved,
tokens_before,
cost_saved_usd: 0.01,
basis: "fixture".to_string(),
model_source: crate::core::session_model::MODEL_SOURCE_LAUNCH_CONFIG.to_string(),
})
.expect("serialize")
}
#[test]
fn fold_sessions_groups_by_session() {
let dir = tempfile::tempdir().expect("temp dir");
let ledger = ledger_with(
&dir,
&[
&pct_row("sess-a", 100, 1_000),
&pct_row("sess-a", 100, 1_000),
&pct_row("sess-b", 500, 1_000),
],
);
let by_session = fold_sessions(&ledger);
assert_eq!(by_session.len(), 2, "two sessions, two totals");
assert_eq!(by_session["sess-a"].tokens_saved, 200);
assert_eq!(by_session["sess-a"].tokens_before, 2_000);
assert_eq!(by_session["sess-a"].rows, 2);
assert_eq!(by_session["sess-b"].tokens_saved, 500);
}
#[test]
fn fold_sessions_of_a_missing_ledger_is_empty() {
let dir = tempfile::tempdir().expect("temp dir");
assert!(fold_sessions(&dir.path().join("nope.jsonl")).is_empty());
}
#[test]
fn average_percent_is_the_mean_of_each_sessions_percent() {
let dir = tempfile::tempdir().expect("temp dir");
let ledger = ledger_with(
&dir,
&[
&pct_row("sess-a", 100, 1_000), &pct_row("sess-b", 3_000, 10_000), &pct_row("sess-c", 500, 1_000), ],
);
let by_session = fold_sessions(&ledger);
let per_session: Vec<u32> = by_session
.values()
.filter_map(|t| t.percent_saved(None))
.collect();
assert_eq!(per_session, vec![10, 30, 50], "the three known percentages");
assert_eq!(
average_percent_saved(&by_session, |_| None),
Some(30),
"the arithmetic mean of 10, 30 and 50"
);
}
#[test]
fn average_of_one_session_is_that_sessions_percent() {
let dir = tempfile::tempdir().expect("temp dir");
let ledger = ledger_with(&dir, &[&pct_row("sess-only", 250, 1_000)]);
let by_session = fold_sessions(&ledger);
assert_eq!(by_session["sess-only"].percent_saved(None), Some(25));
assert_eq!(average_percent_saved(&by_session, |_| None), Some(25));
}
#[test]
fn average_uses_each_sessions_own_actual_token_reading() {
let dir = tempfile::tempdir().expect("temp dir");
let ledger = ledger_with(
&dir,
&[
&pct_row("sess-a", 40_000, 999_999),
&pct_row("sess-b", 10_000, 999_999),
],
);
let by_session = fold_sessions(&ledger);
let actual = |session: &str| match session {
"sess-a" => Some(160_000),
"sess-b" => Some(90_000),
_ => None,
};
assert_eq!(average_percent_saved(&by_session, actual), Some(15));
}
#[test]
fn average_skips_corrupt_rows_and_never_reports_a_false_zero() {
let dir = tempfile::tempdir().expect("temp dir");
let negative = r#"{"ts":"x","session_id":"sess-corrupt","technique":"divert","tokens_saved":-5,"tokens_before":1000,"cost_saved_usd":0.01,"basis":"b","model_source":""}"#;
let ledger = ledger_with(
&dir,
&[
&pct_row("sess-a", 100, 1_000), "{not json at all",
negative,
&pct_row("sess-b", 300, 1_000), ],
);
let by_session = fold_sessions(&ledger);
assert_eq!(
by_session.len(),
2,
"the corrupt line and the negative row contribute no session"
);
assert_eq!(
average_percent_saved(&by_session, |_| None),
Some(20),
"the mean of the valid rows only"
);
let unreadable = fold_sessions(&dir.path().join("missing.jsonl"));
assert_eq!(average_percent_saved(&unreadable, |_| None), None);
}
#[test]
fn average_of_sessions_with_no_denominator_is_none() {
let dir = tempfile::tempdir().expect("temp dir");
let ledger = ledger_with(&dir, &[&pct_row("sess-legacy", 4_000, 0)]);
let by_session = fold_sessions(&ledger);
assert_eq!(by_session["sess-legacy"].percent_saved(None), None);
assert_eq!(average_percent_saved(&by_session, |_| None), None);
}