use claude_cost::{default_pricing, normalize_model_id, Pricing, Usage, DEFAULT_PRICING_TABLE};
#[test]
fn the_table_is_not_empty() {
assert!(!DEFAULT_PRICING_TABLE.is_empty());
}
#[test]
fn the_table_has_no_duplicate_keys() {
let mut keys: Vec<&str> = DEFAULT_PRICING_TABLE.iter().map(|(k, _)| *k).collect();
keys.sort();
let len_before = keys.len();
keys.dedup();
assert_eq!(keys.len(), len_before, "duplicate model keys in table");
}
#[test]
fn every_table_entry_normalizes_to_itself() {
for (key, _) in DEFAULT_PRICING_TABLE {
assert_eq!(normalize_model_id(key), *key, "key not normalized: {key}");
}
}
#[test]
fn cache_aware_cost_byhand_match() {
let p = default_pricing("claude-sonnet-4-5").unwrap();
let usage = Usage {
input_tokens: 423,
output_tokens: 18,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 380,
};
let want = (423.0 * 3.0 + 18.0 * 15.0 + 380.0 * 0.3) / 1_000_000.0;
assert!((p.cost_for(&usage) - want).abs() < 1e-9);
}
#[test]
fn from_bedrock_converse_maps_correctly() {
let usage = Usage::from_bedrock_converse(100, 50, 30, 10);
assert_eq!(usage.input_tokens, 100);
assert_eq!(usage.output_tokens, 50);
assert_eq!(usage.cache_read_input_tokens, 30);
assert_eq!(usage.cache_creation_input_tokens, 10);
}
#[test]
fn cache_hit_flag_matches_cache_read() {
let cold = Usage {
input_tokens: 100,
..Usage::default()
};
let warm = Usage {
input_tokens: 100,
cache_read_input_tokens: 50,
..Usage::default()
};
assert!(!cold.cache_hit());
assert!(warm.cache_hit());
}
#[test]
fn byo_pricing_overrides_table() {
let custom = Pricing {
input_per_mtok: 1.0,
output_per_mtok: 1.0,
cache_read_per_mtok: 0.0,
cache_write_per_mtok: 0.0,
};
let usage = Usage {
input_tokens: 1_000_000,
..Usage::default()
};
assert_eq!(custom.cost_for(&usage), 1.0);
}
#[cfg(feature = "serde")]
#[test]
fn usage_round_trips_through_anthropic_json_shape() {
let json = r#"{
"input_tokens": 423,
"output_tokens": 18,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 380
}"#;
let usage: Usage = serde_json::from_str(json).unwrap();
assert_eq!(usage.input_tokens, 423);
assert_eq!(usage.cache_read_input_tokens, 380);
let p = default_pricing("claude-sonnet-4-5").unwrap();
let want = 0.001653;
assert!((p.cost_for(&usage) - want).abs() < 1e-9);
}
#[cfg(feature = "serde")]
#[test]
fn usage_serde_default_zeros_missing_fields() {
let partial = r#"{"input_tokens": 10, "output_tokens": 5}"#;
let usage: Usage = serde_json::from_str(partial).unwrap();
assert_eq!(usage.cache_read_input_tokens, 0);
assert_eq!(usage.cache_creation_input_tokens, 0);
}