Files
sub2api/backend/internal/service/openai_model_mapping_test.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

350 lines
9.6 KiB
Go

package service
import "testing"
func TestResolveOpenAIForwardModel(t *testing.T) {
tests := []struct {
name string
account *Account
requestedModel string
messagesDispatchMappedModel string
expectedModel string
}{
{
name: "uses messages dispatch model for known claude family",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "claude-opus-4-6",
messagesDispatchMappedModel: "gpt-4o-mini",
expectedModel: "gpt-4o-mini",
},
{
name: "uses exact messages dispatch model for unknown claude family",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "claude-fable-5",
messagesDispatchMappedModel: " gpt-5.6-sol ",
expectedModel: "gpt-5.6-sol",
},
{
name: "nil account uses messages dispatch model",
requestedModel: "claude-fable-5",
messagesDispatchMappedModel: "gpt-5.6-sol",
expectedModel: "gpt-5.6-sol",
},
{
name: "nil account without messages dispatch keeps requested model",
requestedModel: "claude-fable-5",
expectedModel: "claude-fable-5",
},
{
name: "ordinary unknown gpt model has no messages dispatch fallback",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt6",
expectedModel: "gpt6",
},
{
name: "account exact mapping overrides messages dispatch model",
account: &Account{
Credentials: map[string]any{
"model_mapping": map[string]any{
"claude-fable-5": "gpt-5.5",
},
},
},
requestedModel: "claude-fable-5",
messagesDispatchMappedModel: "gpt-5.6-sol",
expectedModel: "gpt-5.5",
},
{
name: "account wildcard mapping overrides messages dispatch model",
account: &Account{
Credentials: map[string]any{
"model_mapping": map[string]any{
"claude-*": "gpt-5.4",
},
},
},
requestedModel: "claude-fable-5",
messagesDispatchMappedModel: "gpt-5.6-sol",
expectedModel: "gpt-5.4",
},
{
name: "account passthrough mapping overrides messages dispatch model",
account: &Account{
Credentials: map[string]any{
"model_mapping": map[string]any{
"claude-fable-5": "claude-fable-5",
},
},
},
requestedModel: "claude-fable-5",
messagesDispatchMappedModel: "gpt-5.6-sol",
expectedModel: "claude-fable-5",
},
{
name: "ordinary codex spark request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt-5.3-codex-spark",
expectedModel: "gpt-5.3-codex-spark",
},
{
name: "ordinary gpt-5.5 request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt-5.5",
expectedModel: "gpt-5.5",
},
{
name: "ordinary gpt-5.5-pro request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt-5.5-pro",
expectedModel: "gpt-5.5-pro",
},
{
name: "ordinary compact-spelled gpt5.5 request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt5.5",
expectedModel: "gpt5.5",
},
{
name: "ordinary namespaced gpt-5.5 request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "openai/gpt-5.5",
expectedModel: "openai/gpt-5.5",
},
{
name: "ordinary compact gpt-5.5 request keeps requested model",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt-5.5-openai-compact",
expectedModel: "gpt-5.5-openai-compact",
},
{
name: "whitespace-only messages dispatch model is ignored",
account: &Account{
Credentials: map[string]any{},
},
requestedModel: "gpt-5.5",
messagesDispatchMappedModel: " ",
expectedModel: "gpt-5.5",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := resolveOpenAIForwardModel(tt.account, tt.requestedModel, tt.messagesDispatchMappedModel); got != tt.expectedModel {
t.Fatalf("resolveOpenAIForwardModel(...) = %q, want %q", got, tt.expectedModel)
}
})
}
}
func TestResolveOpenAICompactForwardModel(t *testing.T) {
tests := []struct {
name string
account *Account
model string
expectedModel string
}{
{
name: "nil account keeps original model",
account: nil,
model: "gpt-5.4",
expectedModel: "gpt-5.4",
},
{
name: "missing compact mapping keeps original model",
account: &Account{
Credentials: map[string]any{},
},
model: "gpt-5.4",
expectedModel: "gpt-5.4",
},
{
name: "exact compact mapping overrides model",
account: &Account{
Credentials: map[string]any{
"compact_model_mapping": map[string]any{
"gpt-5.4": "gpt-5.4-openai-compact",
},
},
},
model: "gpt-5.4",
expectedModel: "gpt-5.4-openai-compact",
},
{
name: "wildcard compact mapping overrides model",
account: &Account{
Credentials: map[string]any{
"compact_model_mapping": map[string]any{
"gpt-5.*": "gpt-5-openai-compact",
},
},
},
model: "gpt-5.4",
expectedModel: "gpt-5-openai-compact",
},
{
name: "passthrough compact mapping remains unchanged",
account: &Account{
Credentials: map[string]any{
"compact_model_mapping": map[string]any{
"gpt-5.4": "gpt-5.4",
},
},
},
model: "gpt-5.4",
expectedModel: "gpt-5.4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := resolveOpenAICompactForwardModel(tt.account, tt.model); got != tt.expectedModel {
t.Fatalf("resolveOpenAICompactForwardModel(...) = %q, want %q", got, tt.expectedModel)
}
})
}
}
func TestNormalizeCodexModel(t *testing.T) {
cases := map[string]string{
"gpt-5.3-codex-spark": "gpt-5.3-codex-spark",
"gpt-5.3-codex-spark-high": "gpt-5.3-codex-spark",
"gpt-5.3-codex-spark-xhigh": "gpt-5.3-codex-spark",
"gpt-5.3": "gpt-5.3-codex",
"gpt-image-2": "gpt-image-2",
"gpt-5.4-nano": "gpt-5.4-nano",
"gpt-5.4-nano-high": "gpt-5.4-nano",
"gpt6": "gpt6",
"claude-opus-4-6": "claude-opus-4-6",
}
for input, expected := range cases {
if got := normalizeCodexModel(input); got != expected {
t.Fatalf("normalizeCodexModel(%q) = %q, want %q", input, got, expected)
}
}
}
func TestNormalizeOpenAIModelForUpstream(t *testing.T) {
tests := []struct {
name string
account *Account
model string
want string
}{
{
name: "oauth routes bare GPT-5.6 alias to Sol",
account: &Account{Type: AccountTypeOAuth},
model: "gpt-5.6",
want: "gpt-5.6-sol",
},
{
name: "oauth routes provider-prefixed GPT-5.6 alias to Sol",
account: &Account{Type: AccountTypeOAuth},
model: "openai/gpt-5.6",
want: "gpt-5.6-sol",
},
{
name: "oauth preserves unknown non codex model",
account: &Account{Type: AccountTypeOAuth},
model: "gemini-3-flash-preview",
want: "gemini-3-flash-preview",
},
{
name: "oauth preserves invalid gpt model",
account: &Account{Type: AccountTypeOAuth},
model: "gpt6",
want: "gpt6",
},
{
name: "oauth normalizes known codex alias",
account: &Account{Type: AccountTypeOAuth},
model: "gpt-5.4-high",
want: "gpt-5.4",
},
{
name: "oauth preserves GPT-5.5 Pro model",
account: &Account{Type: AccountTypeOAuth},
model: "openai/gpt-5.5-pro",
want: "gpt-5.5-pro",
},
{
name: "oauth preserves codex auto review model",
account: &Account{Type: AccountTypeOAuth},
model: "codex-auto-review",
want: "codex-auto-review",
},
{
name: "apikey preserves official bare GPT-5.6 alias",
account: &Account{Type: AccountTypeAPIKey},
model: "gpt-5.6",
want: "gpt-5.6",
},
{
name: "apikey preserves custom compatible model",
account: &Account{Type: AccountTypeAPIKey},
model: "gemini-3-flash-preview",
want: "gemini-3-flash-preview",
},
{
name: "apikey preserves official non codex model",
account: &Account{Type: AccountTypeAPIKey},
model: "gpt-4.1",
want: "gpt-4.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := normalizeOpenAIModelForUpstream(tt.account, tt.model); got != tt.want {
t.Fatalf("normalizeOpenAIModelForUpstream(...) = %q, want %q", got, tt.want)
}
})
}
}
func TestUsageBillingModelCandidatesPreserveCodexAutoReviewModel(t *testing.T) {
candidates := usageBillingModelCandidates("codex-auto-review")
expected := []string{"codex-auto-review"}
if len(candidates) != len(expected) {
t.Fatalf("usageBillingModelCandidates(codex-auto-review) = %#v, want %#v", candidates, expected)
}
for i := range expected {
if candidates[i] != expected[i] {
t.Fatalf("usageBillingModelCandidates(codex-auto-review) = %#v, want %#v", candidates, expected)
}
}
}
func TestUsageBillingModelCandidatesPreserveGPT55ProModel(t *testing.T) {
candidates := usageBillingModelCandidates("openai/gpt-5.5-pro")
expected := []string{"openai/gpt-5.5-pro", "gpt-5.5-pro"}
if len(candidates) != len(expected) {
t.Fatalf("usageBillingModelCandidates(openai/gpt-5.5-pro) = %#v, want %#v", candidates, expected)
}
for i := range expected {
if candidates[i] != expected[i] {
t.Fatalf("usageBillingModelCandidates(openai/gpt-5.5-pro) = %#v, want %#v", candidates, expected)
}
}
}