use crate::usage::cards;
use crate::usage::data::{CacheStats, ModelCacheStat};
use crate::usage::pricing::{
CacheCost, CostOrigin, DEFAULT_UNKNOWN_INPUT_PER_M, DEFAULT_UNKNOWN_OUTPUT_PER_M, ModelRates,
PricingConfig,
};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::Terminal;
use ratatui::backend::TestBackend;
use ratatui::layout::Rect;
fn shipped_table() -> PricingConfig {
toml::from_str(include_str!("../../usage_pricing.toml.example"))
.expect("shipped usage_pricing.toml.example must parse as PricingConfig")
}
const EPS: f64 = 1e-9;
fn approx(actual: f64, expected: f64) {
assert!(
(actual - expected).abs() < EPS,
"expected {expected}, got {actual}"
);
}
#[test]
fn rates_for_reads_exact_table_entry() {
let rates = shipped_table().rates_for("glm-5.3-flash");
assert_eq!(rates.origin, CostOrigin::Priced);
approx(rates.input_per_m, 0.15);
approx(rates.output_per_m, 0.50);
approx(rates.cache_read_per_m, 0.03);
approx(rates.cache_write_per_m, 0.1875);
}
#[test]
fn rates_for_applies_cache_defaults_when_absent() {
let rates = shipped_table().rates_for("glm-5.1");
assert_eq!(rates.origin, CostOrigin::Priced);
approx(rates.input_per_m, 0.95);
approx(rates.cache_read_per_m, 0.095);
approx(rates.cache_write_per_m, 1.1875);
}
#[test]
fn rates_for_unknown_model_is_estimated() {
let rates = shipped_table().rates_for("zzz-unlisted-model-9z");
assert_eq!(rates.origin, CostOrigin::Estimated);
approx(rates.input_per_m, DEFAULT_UNKNOWN_INPUT_PER_M);
approx(rates.output_per_m, DEFAULT_UNKNOWN_OUTPUT_PER_M);
approx(rates.cache_read_per_m, DEFAULT_UNKNOWN_INPUT_PER_M * 0.1);
approx(rates.cache_write_per_m, DEFAULT_UNKNOWN_INPUT_PER_M * 1.25);
}
#[test]
fn cache_cost_matches_issue_evidence() {
const CACHED: i64 = 14_103_232;
let rates = shipped_table().rates_for("glm-5.3-flash");
let cost = CacheCost::compute(CACHED, 0, &rates);
approx(cost.read_cost, 0.42309696);
approx(cost.full_rate_cost, 2.1154848);
approx(cost.saved, 1.69238784);
approx(cost.write_cost, 0.0);
}
#[test]
fn cache_cost_bills_writes_at_the_write_rate() {
let rates = shipped_table().rates_for("glm-5.3-flash");
let cost = CacheCost::compute(0, 2_000_000, &rates);
approx(cost.write_cost, 0.375);
approx(cost.read_cost, 0.0);
approx(cost.saved, 0.0);
}
#[test]
fn saved_is_full_rate_minus_read_cost() {
let rates = ModelRates {
input_per_m: 3.0,
output_per_m: 15.0,
cache_write_per_m: 3.75,
cache_read_per_m: 0.3,
origin: CostOrigin::Priced,
};
let cost = CacheCost::compute(1_000_000, 1_000_000, &rates);
approx(cost.read_cost, 0.3);
approx(cost.full_rate_cost, 3.0);
approx(cost.saved, cost.full_rate_cost - cost.read_cost);
approx(cost.saved, 2.7);
}
#[test]
fn cache_read_rate_agrees_with_billing_path() {
let table = shipped_table();
let rates = table.rates_for("glm-5.3-flash");
let cached: u32 = 14_103_232;
let full = table.calculate_cost_with_cache("glm-5.3-flash", 0, 0, 0, cached);
approx(CacheCost::compute(cached as i64, 0, &rates).read_cost, full);
}
#[test]
fn per_model_sql_counts_and_money_match_hand_arithmetic() {
use crate::usage::data::{
CACHE_CAPABLE_WHERE, CACHE_COST_SELECT_COLS, CACHE_MODEL_EXPR, CACHE_STATS_SELECT_COLS,
CacheStats, ModelCacheStat,
};
use crate::usage::pricing::CostOrigin;
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE sessions (id TEXT, model TEXT);
CREATE TABLE messages (
session_id TEXT, input_tokens INTEGER,
cache_creation_tokens INTEGER, cache_read_tokens INTEGER
);
INSERT INTO sessions VALUES ('s1','glm-5.3-flash');
INSERT INTO sessions VALUES ('s2','glm-5.1');
INSERT INTO sessions VALUES ('s3','local-gguf');
-- glm-5.3-flash: 1M read, 100K written, 500K plain input.
INSERT INTO messages VALUES ('s1', 500000, 100000, 1000000);
-- glm-5.1: 2M read, nothing written, the table declares no cache rates.
INSERT INTO messages VALUES ('s2', 0, 0, 2000000);
-- non-caching provider: both cache columns NULL, must be excluded.
INSERT INTO messages VALUES ('s3', 100, NULL, NULL);",
)
.unwrap();
let sql = format!(
"SELECT {CACHE_MODEL_EXPR}, {CACHE_STATS_SELECT_COLS}, {CACHE_COST_SELECT_COLS} \
FROM messages m JOIN sessions s ON m.session_id = s.id \
WHERE {CACHE_CAPABLE_WHERE} GROUP BY {CACHE_MODEL_EXPR}"
);
let mut stmt = conn.prepare(&sql).unwrap();
let raw: Vec<(String, i64, i64, i64)> = stmt
.query_map([], |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?)))
.unwrap()
.map(Result::unwrap)
.collect();
let table = shipped_table();
let mut per_model: Vec<ModelCacheStat> = raw
.into_iter()
.map(|(model, cached, total, cache_creation)| {
let rates = table.rates_for(&model);
ModelCacheStat {
model,
cache_hit_pct: cached as f64 / total as f64 * 100.0,
cached_tokens: cached,
cache_creation_tokens: cache_creation,
cost: CacheCost::compute(cached, cache_creation, &rates),
input_per_m: rates.input_per_m,
cache_read_per_m: rates.cache_read_per_m,
estimated: rates.origin == CostOrigin::Estimated,
}
})
.collect();
per_model.sort_by(|a, b| a.model.cmp(&b.model));
assert_eq!(
per_model.len(),
2,
"the non-caching model must be excluded: {per_model:?}"
);
let low = &per_model[0];
assert_eq!(low.model, "glm-5.1");
assert_eq!(low.cached_tokens, 2_000_000);
assert_eq!(low.cache_creation_tokens, 0);
assert!(!low.estimated);
approx(low.cost.read_cost, 0.19);
approx(low.cost.write_cost, 0.0);
approx(low.cost.full_rate_cost, 1.90);
approx(low.cost.saved, 1.71);
let flash = &per_model[1];
assert_eq!(flash.model, "glm-5.3-flash");
assert_eq!(flash.cached_tokens, 1_000_000);
assert_eq!(flash.cache_creation_tokens, 100_000);
assert!((flash.cache_hit_pct - 62.5).abs() < 1e-9);
approx(flash.cost.read_cost, 0.03);
approx(flash.cost.write_cost, 0.01875);
approx(flash.cost.full_rate_cost, 0.15);
approx(flash.cost.saved, 0.12);
let totals = CacheStats {
per_model,
..CacheStats::default()
}
.cost_totals();
approx(totals.read_cost, 0.22);
approx(totals.write_cost, 0.01875);
approx(totals.full_rate_cost, 2.05);
approx(totals.saved, 1.83);
}
fn breakdown_cache() -> CacheStats {
let rates = shipped_table().rates_for("glm-5.3-flash");
let cached = 14_103_232;
CacheStats {
cache_hit_pct: 97.0,
cached_tokens: cached,
total_input_tokens: 14_508_042,
per_model: vec![ModelCacheStat {
model: "glm-5.3-flash".to_string(),
cache_hit_pct: 97.0,
cached_tokens: cached,
cache_creation_tokens: 0,
cost: CacheCost::compute(cached, 0, &rates),
input_per_m: rates.input_per_m,
cache_read_per_m: rates.cache_read_per_m,
estimated: rates.origin == CostOrigin::Estimated,
}],
}
}
fn render_text(width: u16, height: u16, draw: impl FnOnce(&mut ratatui::Frame, Rect)) -> String {
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw(f, area);
})
.unwrap();
terminal
.backend()
.buffer()
.content()
.iter()
.map(|c| c.symbol().to_string())
.collect()
}
#[test]
fn breakdown_overlay_shows_cached_tokens_and_saved_money() {
let cache = Some(breakdown_cache());
let text = render_text(100, 30, |f, area| {
cards::render_cache_breakdown(f, &cache, area)
});
assert!(text.contains("Cache Efficiency: prices"), "title:\n{text}");
assert!(text.contains("glm-5.3-flash"), "model name:\n{text}");
assert!(text.contains("14.1M"), "cached token count:\n{text}");
assert!(text.contains("saved"), "saved column:\n{text}");
assert!(text.contains("$1.69"), "saved money:\n{text}");
assert!(text.contains("TOTAL"), "total row:\n{text}");
assert!(
text.contains("97% of input tokens were cache reads"),
"cache-read share:\n{text}"
);
}
#[test]
fn breakdown_overlay_says_so_when_there_is_no_cache_data() {
let empty = Some(CacheStats::default());
let text = render_text(100, 30, |f, area| {
cards::render_cache_breakdown(f, &empty, area)
});
assert!(text.contains("No cache data"), "empty overlay:\n{text}");
}
#[test]
fn enter_opens_the_prices_and_esc_closes_them() {
use crate::usage::dashboard::{CACHE_CARD, DashboardState, UsageKeyOutcome, decide};
let press = |code| KeyEvent::new(code, KeyModifiers::NONE);
let mut state = DashboardState {
focused_card: CACHE_CARD,
..DashboardState::default()
};
assert_eq!(
decide(&mut state, press(KeyCode::Enter)),
UsageKeyOutcome::Consumed
);
assert!(state.cache_detail_open, "Enter opens the breakdown");
assert_eq!(
decide(&mut state, press(KeyCode::Esc)),
UsageKeyOutcome::Consumed
);
assert!(!state.cache_detail_open, "Esc closes the breakdown");
}
#[test]
fn dashboard_draws_the_breakdown_over_the_grid_and_footer_stays() {
let mut state = crate::usage::dashboard::DashboardState {
focused_card: crate::usage::dashboard::CACHE_CARD,
cache_detail_open: true,
..crate::usage::dashboard::DashboardState::default()
};
state.data.cache = Some(breakdown_cache());
let text = render_text(100, 40, |f, area| {
crate::usage::dashboard::render(f, &state, area)
});
assert!(
text.contains("glm-5.3-flash"),
"breakdown model row:\n{text}"
);
assert!(text.contains("$1.69"), "breakdown saved money:\n{text}");
assert!(
text.contains("Esc: close"),
"the way out must stay:\n{text}"
);
assert!(
!text.contains("Enter: details"),
"Enter does nothing while the breakdown is open:\n{text}"
);
assert!(
!text.contains("T/W/M/A: period"),
"period keys are inert while the breakdown is open:\n{text}"
);
}