Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

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")
}