f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
159 lines
4.6 KiB
Go
159 lines
4.6 KiB
Go
package pricing
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNormalizeModelName(t *testing.T) {
|
|
cases := map[string]string{
|
|
"claude-opus-4.7": "claude-opus-4-7",
|
|
"claude-sonnet-4.6": "claude-sonnet-4-6",
|
|
"claude-haiku-4.5": "claude-haiku-4-5",
|
|
"claude-opus-4-8": "claude-opus-4-8",
|
|
"gpt-5.5": "gpt-5-5",
|
|
}
|
|
for in, want := range cases {
|
|
assert.Equal(t, want, NormalizeModelName(in), "input %q", in)
|
|
}
|
|
}
|
|
|
|
func TestResolve(t *testing.T) {
|
|
rates := map[string]int{
|
|
"claude-opus-4-7": 5,
|
|
"claude-opus-4.6": 99,
|
|
"gemini-3.5": 10,
|
|
"gemini-3.5-flash": 20,
|
|
"openai/gpt-5.5": 30,
|
|
"google/gemini-2.5": 40,
|
|
}
|
|
|
|
got, ok := Resolve(rates, "claude-opus-4.7")
|
|
require.True(t, ok, "dotted model should resolve via normalized key")
|
|
assert.Equal(t, 5, got)
|
|
|
|
got, ok = Resolve(rates, "claude-opus-4-7")
|
|
require.True(t, ok, "already-dashed model should resolve exactly")
|
|
assert.Equal(t, 5, got)
|
|
|
|
got, ok = Resolve(rates, "claude-opus-4.6")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 99, got, "exact match must win over normalized fallback")
|
|
|
|
// 1. Case-insensitivity
|
|
got, ok = Resolve(rates, "CLAUDE-OPUS-4-7")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 5, got)
|
|
|
|
// 2. Substring match on canonicalized name
|
|
got, ok = Resolve(rates, "Gemini 3.5 Flash (Medium)")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 20, got, "should match gemini-3.5-flash via substring canonical match")
|
|
|
|
got, ok = Resolve(rates, "Gemini 3.5 Flash (Low)")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 20, got, "should match gemini-3.5-flash via substring canonical match")
|
|
|
|
// 3. Specificity matching (gemini-3.5-flash (len 13) vs gemini-3.5 (len 8))
|
|
got, ok = Resolve(rates, "Gemini 3.5 Flash")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 20, got, "longer canonical match should win")
|
|
|
|
// 4. Provider-prefix handling
|
|
got, ok = Resolve(rates, "gpt-5.5")
|
|
require.True(t, ok, "should resolve without provider prefix if mapped key has it")
|
|
assert.Equal(t, 30, got)
|
|
|
|
got, ok = Resolve(rates, "google/gemini-2.5")
|
|
require.True(t, ok)
|
|
assert.Equal(t, 40, got)
|
|
|
|
got, ok = Resolve(rates, "gemini-2.5")
|
|
require.True(t, ok, "should resolve without prefix if map has prefix")
|
|
assert.Equal(t, 40, got)
|
|
|
|
// 5. Bracketed long-context tag strips to the base model
|
|
got, ok = Resolve(rates, "claude-opus-4.6[1m]")
|
|
require.True(t, ok, "bracketed decoration should strip to base model")
|
|
assert.Equal(t, 99, got)
|
|
|
|
// 6. Trailing release date strips to the base model
|
|
got, ok = Resolve(rates, "claude-opus-4-7-20260101")
|
|
require.True(t, ok, "release-date suffix should strip to base model")
|
|
assert.Equal(t, 5, got)
|
|
|
|
_, ok = Resolve(rates, "unknown-model")
|
|
assert.False(t, ok, "unknown model stays unresolved")
|
|
}
|
|
|
|
func TestResolveProviderPrefixes(t *testing.T) {
|
|
rates := map[string]int{
|
|
"openrouter/owl-alpha": 7,
|
|
"gpt-5.5": 30,
|
|
}
|
|
|
|
_, ok := Resolve(rates, "other/owl-alpha")
|
|
assert.False(t, ok,
|
|
"provider-qualified model must not take another provider's pricing")
|
|
|
|
got, ok := Resolve(rates, "owl-alpha")
|
|
require.True(t, ok, "unqualified model may match a qualified key")
|
|
assert.Equal(t, 7, got)
|
|
|
|
got, ok = Resolve(rates, "openai/gpt-5.5")
|
|
require.True(t, ok, "qualified model may match an unqualified key")
|
|
assert.Equal(t, 30, got)
|
|
}
|
|
|
|
func TestResolveCanonicalDeterminism(t *testing.T) {
|
|
// Two providers share one canonical name: ambiguous, unpriced.
|
|
rates := map[string]int{
|
|
"openai/foo": 1,
|
|
"other/foo": 2,
|
|
}
|
|
_, ok := Resolve(rates, "Foo")
|
|
assert.False(t, ok,
|
|
"multiple provider keys for one canonical name are ambiguous")
|
|
|
|
// A provider-qualified model resolves its own provider's key.
|
|
got, ok := Resolve(rates, "openai/foo[1m]")
|
|
require.True(t, ok, "matching provider key should resolve")
|
|
assert.Equal(t, 1, got)
|
|
|
|
// An unqualified key beats provider-qualified keys.
|
|
withBase := map[string]int{
|
|
"openai/bar": 5,
|
|
"other/bar": 6,
|
|
"bar": 7,
|
|
}
|
|
got, ok = Resolve(withBase, "Bar[1m]")
|
|
require.True(t, ok, "unqualified key should disambiguate")
|
|
assert.Equal(t, 7, got)
|
|
|
|
// Distinct keys tied within one rank stay ambiguous.
|
|
dupes := map[string]int{
|
|
"fo.o": 1,
|
|
"fo-o": 2,
|
|
}
|
|
_, ok = Resolve(dupes, "Foo")
|
|
assert.False(t, ok,
|
|
"duplicate unqualified canonical keys are ambiguous")
|
|
}
|
|
|
|
func TestResolveRejectsArbitrarySubstrings(t *testing.T) {
|
|
rates := map[string]int{
|
|
"openai/gpt-5.5": 30,
|
|
"gemini-3.5-flash": 20,
|
|
}
|
|
|
|
_, ok := Resolve(rates, "gpt-5.5-codex")
|
|
assert.False(t, ok,
|
|
"distinct variant must stay unpriced, not take base pricing")
|
|
|
|
_, ok = Resolve(rates, "wrapped-gemini-3.5-flash-pro")
|
|
assert.False(t, ok,
|
|
"key inside an unrelated longer name must not match")
|
|
}
|