diff --git a/rust/src/core/cost_pricing.rs b/rust/src/core/cost_pricing.rs index b03174b8b5..0c70f64a06 100755 --- a/rust/src/core/cost_pricing.rs +++ b/rust/src/core/cost_pricing.rs @@ -915,221 +915,5 @@ impl CostUsagePricing { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_normalize_codex_model() { - assert_eq!(CostUsagePricing::normalize_codex_model("gpt-5"), "gpt-5"); - assert_eq!( - CostUsagePricing::normalize_codex_model("openai/gpt-5"), - "gpt-5" - ); - assert_eq!( - CostUsagePricing::normalize_codex_model("gpt-5-codex"), - "gpt-5" - ); - } - - #[test] - fn test_normalize_claude_model() { - assert_eq!( - CostUsagePricing::normalize_claude_model("claude-sonnet-4-5"), - "claude-sonnet-4-5" - ); - assert_eq!( - CostUsagePricing::normalize_claude_model("anthropic.claude-sonnet-4-5"), - "claude-sonnet-4-5" - ); - } - - #[test] - fn test_codex_cost() { - let cost = CostUsagePricing::codex_cost_usd("gpt-5", 1000, 0, 500); - assert!(cost.is_some()); - let cost = cost.unwrap(); - // 1000 * 1.25e-6 + 500 * 1e-5 = 0.00125 + 0.005 = 0.00625 - assert!((cost - 0.00625).abs() < 1e-10); - } - - #[test] - fn test_claude_cost() { - let cost = CostUsagePricing::claude_cost_usd("claude-haiku-4-5-20251001", 1000, 0, 0, 500); - assert!(cost.is_some()); - } - - #[test] - fn test_opus_4_8_cost() { - // Opus 4.8 bills at $5/1M input + $25/1M output (same tier as 4.5/4.6/4.7). - let cost = CostUsagePricing::claude_cost_usd("claude-opus-4-8", 1_000, 0, 0, 500).unwrap(); - // 1000 * 5e-6 + 500 * 2.5e-5 = 0.005 + 0.0125 = 0.0175 - assert!((cost - 0.0175).abs() < 1e-10); - } - - #[test] - fn test_fable_5_cost() { - // Fable 5 bills at $10/1M input + $50/1M output. - let cost = CostUsagePricing::claude_cost_usd("claude-fable-5", 1_000, 0, 0, 500).unwrap(); - // 1000 * 1e-5 + 500 * 5e-5 = 0.01 + 0.025 = 0.035 - assert!((cost - 0.035).abs() < 1e-10); - } - - #[test] - fn test_claude_input_cost_per_token() { - assert_eq!( - CostUsagePricing::claude_input_cost_per_token("claude-opus-4-8"), - Some(5e-6) - ); - assert_eq!( - CostUsagePricing::claude_input_cost_per_token("claude-fable-5"), - Some(1e-5) - ); - assert_eq!( - CostUsagePricing::claude_input_cost_per_token("totally-unknown-model"), - None - ); - } - - #[test] - fn test_format_model_name() { - assert_eq!( - CostUsagePricing::format_model_name("claude-3.5-sonnet"), - "Sonnet 3.5" - ); - assert_eq!( - CostUsagePricing::format_model_name("claude-opus-4"), - "Opus 4" - ); - assert_eq!(CostUsagePricing::format_model_name("gpt-5"), "GPT-5"); - } - - #[test] - fn test_gpt54_mini_cost() { - let cost = CostUsagePricing::codex_cost_usd("gpt-5.4-mini", 1000, 0, 500); - assert!(cost.is_some()); - // 1000 * 7.5e-7 + 500 * 4.5e-6 = 0.00075 + 0.00225 = 0.003 - assert!((cost.unwrap() - 0.003).abs() < 1e-10); - } - - #[test] - fn test_gpt54_nano_cost() { - let cost = CostUsagePricing::codex_cost_usd("gpt-5.4-nano", 1000, 0, 500); - assert!(cost.is_some()); - // 1000 * 2e-7 + 500 * 1.25e-6 = 0.0002 + 0.000625 = 0.000825 - assert!((cost.unwrap() - 0.000825).abs() < 1e-10); - } - - #[test] - fn test_normalize_gpt54_codex() { - assert_eq!( - CostUsagePricing::normalize_codex_model("gpt-5.4-mini-codex"), - "gpt-5.4-mini" - ); - } - - #[test] - fn test_gpt55_pricing() { - assert_eq!( - CostUsagePricing::normalize_codex_model("openai/gpt-5.5-2026-04-23"), - "gpt-5.5" - ); - assert_eq!( - CostUsagePricing::normalize_codex_model("gpt-5.5-pro-2026-04-23"), - "gpt-5.5-pro" - ); - - let cost = CostUsagePricing::codex_cost_usd("gpt-5.5", 1000, 500, 500); - assert!(cost.is_some()); - assert!((cost.unwrap() - 0.01775).abs() < 1e-10); - } - - #[test] - fn test_format_gpt54_mini() { - assert_eq!( - CostUsagePricing::format_model_name("gpt-5.4-mini"), - "GPT-5.4 Mini" - ); - } - - #[test] - fn test_opus_4_7_cost() { - let cost = CostUsagePricing::claude_cost_usd("claude-opus-4-7", 1000, 0, 0, 500); - assert!(cost.is_some()); - } - - #[test] - fn test_sonnet_4_6_cost() { - let cost = CostUsagePricing::claude_cost_usd("claude-sonnet-4-6", 1000, 0, 0, 500); - assert!(cost.is_some()); - } - - #[test] - fn test_gpt5_pro_cost() { - let cost = CostUsagePricing::codex_cost_usd("gpt-5-pro", 1000, 0, 500); - assert!(cost.is_some()); - // 1000 * 1.5e-5 + 500 * 1.2e-4 = 0.015 + 0.06 = 0.075 - assert!((cost.unwrap() - 0.075).abs() < 1e-10); - } - - #[test] - fn test_gpt56_standard_pricing() { - for (model, expected) in [ - ("gpt-5.6-sol", 0.0332), - ("gpt-5.6-terra", 0.0166), - ("gpt-5.6-luna", 0.00664), - ] { - let cost = CostUsagePricing::codex_cost_usd(model, 1_000, 400, 1_000); - assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); - } - } - - #[test] - fn test_gpt56_long_context_pricing() { - for (model, expected) in [ - ("gpt-5.6-sol", 45.272001), - ("gpt-5.6-terra", 22.6360005), - ("gpt-5.6-luna", 9.0544002), - ] { - let cost = CostUsagePricing::codex_cost_usd(model, 272_001, 272_001, 1_000_000); - assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); - } - } - - #[test] - fn test_gpt56_context_threshold_is_exclusive() { - for (model, expected) in [ - ("gpt-5.6-sol", 0.136), - ("gpt-5.6-terra", 0.068), - ("gpt-5.6-luna", 0.0272), - ] { - let cost = CostUsagePricing::codex_cost_usd(model, 272_000, 272_000, 0); - assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); - } - } - - #[test] - fn test_normalize_gpt56_aliases() { - for model in [ - "gpt-5.6", - "openai/gpt-5.6", - "gpt-5.6-codex", - "gpt-5.6-2099-01-01", - "openai/gpt-5.6-codex-2099-01-01", - ] { - assert_eq!( - CostUsagePricing::normalize_codex_model(model), - "gpt-5.6-sol", - "{model}" - ); - } - } - - #[test] - fn test_codex_display_label() { - assert_eq!( - CostUsagePricing::codex_display_label("gpt-5.3-codex-spark"), - Some("Research Preview") - ); - assert_eq!(CostUsagePricing::codex_display_label("gpt-5.4"), None); - } -} +#[path = "cost_pricing_tests.rs"] +mod tests; diff --git a/rust/src/core/cost_pricing_tests.rs b/rust/src/core/cost_pricing_tests.rs new file mode 100644 index 0000000000..f0552a5505 --- /dev/null +++ b/rust/src/core/cost_pricing_tests.rs @@ -0,0 +1,200 @@ +use super::*; + +#[test] +fn test_normalize_codex_model() { + assert_eq!(CostUsagePricing::normalize_codex_model("gpt-5"), "gpt-5"); + assert_eq!( + CostUsagePricing::normalize_codex_model("openai/gpt-5"), + "gpt-5" + ); + assert_eq!( + CostUsagePricing::normalize_codex_model("gpt-5-codex"), + "gpt-5" + ); +} + +#[test] +fn test_normalize_claude_model() { + assert_eq!( + CostUsagePricing::normalize_claude_model("claude-sonnet-4-5"), + "claude-sonnet-4-5" + ); + assert_eq!( + CostUsagePricing::normalize_claude_model("anthropic.claude-sonnet-4-5"), + "claude-sonnet-4-5" + ); +} + +#[test] +fn test_codex_cost() { + let cost = CostUsagePricing::codex_cost_usd("gpt-5", 1000, 0, 500).unwrap(); + assert!((cost - 0.00625).abs() < 1e-10); +} + +#[test] +fn test_claude_cost() { + assert!( + CostUsagePricing::claude_cost_usd("claude-haiku-4-5-20251001", 1000, 0, 0, 500).is_some() + ); +} + +#[test] +fn test_opus_4_8_cost() { + let cost = CostUsagePricing::claude_cost_usd("claude-opus-4-8", 1_000, 0, 0, 500).unwrap(); + assert!((cost - 0.0175).abs() < 1e-10); +} + +#[test] +fn test_fable_5_cost() { + let cost = CostUsagePricing::claude_cost_usd("claude-fable-5", 1_000, 0, 0, 500).unwrap(); + assert!((cost - 0.035).abs() < 1e-10); +} + +#[test] +fn test_claude_input_cost_per_token() { + assert_eq!( + CostUsagePricing::claude_input_cost_per_token("claude-opus-4-8"), + Some(5e-6) + ); + assert_eq!( + CostUsagePricing::claude_input_cost_per_token("claude-fable-5"), + Some(1e-5) + ); + assert_eq!( + CostUsagePricing::claude_input_cost_per_token("totally-unknown-model"), + None + ); +} + +#[test] +fn test_format_model_name() { + assert_eq!( + CostUsagePricing::format_model_name("claude-3.5-sonnet"), + "Sonnet 3.5" + ); + assert_eq!( + CostUsagePricing::format_model_name("claude-opus-4"), + "Opus 4" + ); + assert_eq!(CostUsagePricing::format_model_name("gpt-5"), "GPT-5"); +} + +#[test] +fn test_gpt54_mini_cost() { + let cost = CostUsagePricing::codex_cost_usd("gpt-5.4-mini", 1000, 0, 500).unwrap(); + assert!((cost - 0.003).abs() < 1e-10); +} + +#[test] +fn test_gpt54_nano_cost() { + let cost = CostUsagePricing::codex_cost_usd("gpt-5.4-nano", 1000, 0, 500).unwrap(); + assert!((cost - 0.000825).abs() < 1e-10); +} + +#[test] +fn test_normalize_gpt54_codex() { + assert_eq!( + CostUsagePricing::normalize_codex_model("gpt-5.4-mini-codex"), + "gpt-5.4-mini" + ); +} + +#[test] +fn test_gpt55_pricing() { + assert_eq!( + CostUsagePricing::normalize_codex_model("openai/gpt-5.5-2026-04-23"), + "gpt-5.5" + ); + assert_eq!( + CostUsagePricing::normalize_codex_model("gpt-5.5-pro-2026-04-23"), + "gpt-5.5-pro" + ); + let cost = CostUsagePricing::codex_cost_usd("gpt-5.5", 1000, 500, 500).unwrap(); + assert!((cost - 0.01775).abs() < 1e-10); +} + +#[test] +fn test_format_gpt54_mini() { + assert_eq!( + CostUsagePricing::format_model_name("gpt-5.4-mini"), + "GPT-5.4 Mini" + ); +} + +#[test] +fn test_opus_4_7_cost() { + assert!(CostUsagePricing::claude_cost_usd("claude-opus-4-7", 1000, 0, 0, 500).is_some()); +} + +#[test] +fn test_sonnet_4_6_cost() { + assert!(CostUsagePricing::claude_cost_usd("claude-sonnet-4-6", 1000, 0, 0, 500).is_some()); +} + +#[test] +fn test_gpt5_pro_cost() { + let cost = CostUsagePricing::codex_cost_usd("gpt-5-pro", 1000, 0, 500).unwrap(); + assert!((cost - 0.075).abs() < 1e-10); +} + +#[test] +fn test_gpt56_standard_pricing() { + for (model, expected) in [ + ("gpt-5.6-sol", 0.0332), + ("gpt-5.6-terra", 0.0166), + ("gpt-5.6-luna", 0.00664), + ] { + let cost = CostUsagePricing::codex_cost_usd(model, 1_000, 400, 1_000); + assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); + } +} + +#[test] +fn test_gpt56_long_context_pricing() { + for (model, expected) in [ + ("gpt-5.6-sol", 45.272001), + ("gpt-5.6-terra", 22.6360005), + ("gpt-5.6-luna", 9.0544002), + ] { + let cost = CostUsagePricing::codex_cost_usd(model, 272_001, 272_001, 1_000_000); + assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); + } +} + +#[test] +fn test_gpt56_context_threshold_is_exclusive() { + for (model, expected) in [ + ("gpt-5.6-sol", 0.136), + ("gpt-5.6-terra", 0.068), + ("gpt-5.6-luna", 0.0272), + ] { + let cost = CostUsagePricing::codex_cost_usd(model, 272_000, 272_000, 0); + assert!((cost.unwrap() - expected).abs() < 1e-10, "{model}"); + } +} + +#[test] +fn test_normalize_gpt56_aliases() { + for model in [ + "gpt-5.6", + "openai/gpt-5.6", + "gpt-5.6-codex", + "gpt-5.6-2099-01-01", + "openai/gpt-5.6-codex-2099-01-01", + ] { + assert_eq!( + CostUsagePricing::normalize_codex_model(model), + "gpt-5.6-sol", + "{model}" + ); + } +} + +#[test] +fn test_codex_display_label() { + assert_eq!( + CostUsagePricing::codex_display_label("gpt-5.3-codex-spark"), + Some("Research Preview") + ); + assert_eq!(CostUsagePricing::codex_display_label("gpt-5.4"), None); +}