Files

105 lines
3.6 KiB
Go
Raw Permalink Normal View History

2026-08-23 22:52:13 +02:00
package aiaudit
import "testing"
// Luna list price, 2026-07-30: $0.20 / $0.02 cached / $1.20 per 1M tokens.
func TestCostMicros_lunaListPrice(t *testing.T) {
t.Parallel()
luna, ok := DefaultModelPrice("gpt-5.6-luna")
if !ok {
t.Fatal("gpt-5.6-luna must have a shipped price")
}
if luna.InputPerMTok != 200_000 || luna.CachedInputPerMTok != 20_000 || luna.OutputPerMTok != 1_200_000 {
t.Fatalf("unexpected Luna price: %+v", luna)
}
// 1M input + 1M output = $0.20 + $1.20 = $1.40 = 1_400_000 micros.
if got := CostMicros(luna, 1_000_000, 0, 1_000_000); got != 1_400_000 {
t.Fatalf("got %d micros want 1400000", got)
}
// A realistic enhance call: 4k prompt, 800 completion.
// 4000*0.2 + 800*1.2 per 1M = 800 + 960 = 1760 micros ($0.00176).
if got := CostMicros(luna, 4000, 0, 800); got != 1760 {
t.Fatalf("got %d micros want 1760", got)
}
}
// Cached tokens are a SUBSET of prompt tokens, not extra ones. Billing them on top
// of full input would overstate every repeated system prompt.
func TestCostMicros_cachedTokensAreDiscountedNotAdded(t *testing.T) {
t.Parallel()
luna, _ := DefaultModelPrice("gpt-5.6-luna")
full := CostMicros(luna, 1_000_000, 0, 0) // 1M fresh input = 200000
allCached := CostMicros(luna, 1_000_000, 1_000_000, 0) // 1M cached = 20000
if full != 200_000 {
t.Fatalf("fresh input = %d want 200000", full)
}
if allCached != 20_000 {
t.Fatalf("fully cached input = %d want 20000", allCached)
}
if allCached >= full {
t.Fatal("cached input must be cheaper than fresh input")
}
// Half cached: 500k*0.2 + 500k*0.02 per 1M = 100000 + 10000.
if got := CostMicros(luna, 1_000_000, 500_000, 0); got != 110_000 {
t.Fatalf("half cached = %d want 110000", got)
}
// A provider reporting more cached than prompt tokens must not go negative.
if got := CostMicros(luna, 100, 900, 0); got < 0 {
t.Fatalf("cached > prompt produced negative cost %d", got)
}
}
// Small calls must not round to zero, or per-call spend vanishes at scale.
func TestCostMicros_smallCallsStillCost(t *testing.T) {
t.Parallel()
luna, _ := DefaultModelPrice("gpt-5.6-luna")
if got := CostMicros(luna, 500, 0, 100); got <= 0 {
t.Fatalf("small call cost %d, expected > 0", got)
}
}
func TestCostMicros_unknownModelIsFree(t *testing.T) {
t.Parallel()
if _, ok := DefaultModelPrice("some-local-model"); ok {
t.Fatal("unknown model should not have a shipped price")
}
// Zero price rather than a guessed rate: better to under-report than invent spend.
if got := CostMicros(ModelPrice{}, 1_000_000, 0, 1_000_000); got != 0 {
t.Fatalf("unpriced model cost %d want 0", got)
}
}
func TestNormalizeModel(t *testing.T) {
t.Parallel()
cases := map[string]string{
"gpt-5.6-luna": "gpt-5.6-luna",
"GPT-5.6-Luna": "gpt-5.6-luna",
"openai/gpt-5.6-luna": "gpt-5.6-luna",
"gpt-5.6-luna-2026-07-30": "gpt-5.6-luna",
" openai/GPT-5.6-Terra ": "gpt-5.6-terra",
"mock-llm": "mock-llm",
}
for in, want := range cases {
if got := NormalizeModel(in); got != want {
t.Fatalf("NormalizeModel(%q) = %q want %q", in, got, want)
}
}
// A dated snapshot must still price like its base model.
if _, ok := DefaultModelPrice("gpt-5.6-luna-2026-07-30"); !ok {
t.Fatal("dated snapshot should resolve to the base model price")
}
}
// Three years, so quarter- and year-scale cost questions still have data.
func TestUsageRetentionCoversThreeYears(t *testing.T) {
t.Parallel()
if UsageRetentionDays < 3*365 {
t.Fatalf("usage retention %d days is under 3 years", UsageRetentionDays)
}
if RetentionDays >= UsageRetentionDays {
t.Fatal("prompt bodies must expire long before the cost rollup")
}
}