Files
sub2api/backend/internal/service/openai_oauth_model_support_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

136 lines
5.2 KiB
Go

//go:build unit
package service
import (
"testing"
"github.com/stretchr/testify/require"
)
func newOpenAIOAuthAccountForModelTest() *Account {
return &Account{
ID: 1,
Platform: PlatformOpenAI,
Type: AccountTypeOAuth,
}
}
func TestIsModelSupported_OpenAIOAuthEmptyMapping_ServableModels(t *testing.T) {
account := newOpenAIOAuthAccountForModelTest()
servable := []string{
"", // 空模型交由上层必填校验
"gpt-5.4",
"gpt-5.4-high", // 推理后缀变体
"gpt-5.3-codex",
"gpt-5.1-codex-mini",
"gpt-5",
"codex-mini-latest",
"gpt5.3codexspark", // 别名拼写
"gpt-image-1", // 图像生成模型
"claude-sonnet-4-6", // /v1/messages 调度默认映射兜底
"claude-3-opus-20240229",
"gpt-4o", // 保守 fail-open:非黑名单模型保持允许
"my-custom-alias", // 自定义别名可能由渠道级映射在转发前改写,保持允许
}
for _, model := range servable {
require.True(t, account.IsModelSupported(model), "expected %q to be servable by empty-mapping OpenAI OAuth account", model)
}
}
func TestIsModelSupported_OpenAIOAuthEmptyMapping_RejectsForeignModels(t *testing.T) {
account := newOpenAIOAuthAccountForModelTest()
// Codex 上游必然以不可重试的 400 拒绝这些厂商家族;调度阶段就应跳过
// 该账号,让显式声明支持的 API Key 账号接手(#3662)。
foreign := []string{
"deepseek-v4",
"deepseek-chat",
"glm-4.7",
"kimi-k2",
"k3", // Kimi Code bare ID(无厂商前缀,需精确拒绝)
"k3-256k", // Kimi Code bare ID
"provider/k3", // vendor/model 取 last segment 后仍为 k3
"moonshot-v1-128k",
"gemini-3.0-pro",
"grok-4",
"qwen3-max",
"minimax-m2.5",
"llama-3.3-70b",
"provider/deepseek-v4", // vendor/model 形式取最后一段判定
}
for _, model := range foreign {
require.False(t, account.IsModelSupported(model), "expected %q to be rejected by empty-mapping OpenAI OAuth account", model)
}
}
func TestIsModelSupported_OpenAIOAuthExplicitMappingUnchanged(t *testing.T) {
account := newOpenAIOAuthAccountForModelTest()
account.Credentials = map[string]any{
"model_mapping": map[string]any{
"deepseek-v4": "gpt-5.4",
"k3": "gpt-5.4", // 显式映射优先:bare k3 仍可被账号声明支持
},
}
// 显式映射沿用原有语义:命中映射即支持,未命中即不支持。
require.True(t, account.IsModelSupported("deepseek-v4"))
require.True(t, account.IsModelSupported("k3"))
require.False(t, account.IsModelSupported("glm-4.7"))
}
func TestIsModelSupported_OpenAIOAuthPassthroughAllowsAll(t *testing.T) {
account := newOpenAIOAuthAccountForModelTest()
account.Extra = map[string]any{"openai_passthrough": true}
// 透传模式仅替换认证,模型语义由上游决定,保持"允许所有"。
require.True(t, account.IsModelSupported("deepseek-v4"))
}
func TestIsModelSupported_OpenAIOAuthPassthroughIgnoresLeftoverMapping(t *testing.T) {
account := newOpenAIOAuthAccountForModelTest()
account.Extra = map[string]any{"openai_passthrough": true}
// 账号从"白名单模式"切到透传后,credentials 里常残留旧的非空 model_mapping。
// 透传应无视该白名单,放行不在其中的模型(issue #4936);否则透传账号会被
// 调度期的 IsModelSupported 排除,客户端收到 404 "not supported by any account"。
account.Credentials = map[string]any{
"model_mapping": map[string]any{"gpt-5.4": "gpt-5.4"},
}
require.True(t, account.IsModelSupported("gpt-5.6-sol"), "透传应放行不在残留白名单中的新模型")
require.True(t, account.IsModelSupported("deepseek-v4"), "透传应放行任意模型")
}
func TestIsModelSupported_OpenAIAPIKeyEmptyMappingAllowsAll(t *testing.T) {
account := &Account{
ID: 2,
Platform: PlatformOpenAI,
Type: AccountTypeAPIKey,
}
// API Key 账号(第三方 OpenAI 兼容上游)可服务任意别名,语义不变。
require.True(t, account.IsModelSupported("deepseek-v4"))
require.True(t, account.IsModelSupported("gpt-5.4"))
}
func TestIsModelSupported_NonOpenAIPlatformsUnchanged(t *testing.T) {
anthropic := &Account{ID: 3, Platform: PlatformAnthropic, Type: AccountTypeOAuth}
require.True(t, anthropic.IsModelSupported("claude-sonnet-4-6"))
require.True(t, anthropic.IsModelSupported("deepseek-v4"))
}
func TestIsOpenAIOAuthServableModel(t *testing.T) {
require.True(t, isOpenAIOAuthServableModel("gpt-5.4-high"))
require.True(t, isOpenAIOAuthServableModel(" gpt-5.3-codex "))
require.True(t, isOpenAIOAuthServableModel("claude-3-5-haiku-20241022"))
require.True(t, isOpenAIOAuthServableModel("DeepThink-x")) // 非黑名单前缀,保持允许
require.False(t, isOpenAIOAuthServableModel("DeepSeek-V4")) // 大小写不敏感
require.False(t, isOpenAIOAuthServableModel("qwen3-235b-thinking"))
require.True(t, isOpenAIOAuthServableModel("deepseekcoder")) // 无连字符 → 非黑名单前缀,保持允许
require.False(t, isOpenAIOAuthServableModel("k3"))
require.False(t, isOpenAIOAuthServableModel("k3-256k"))
require.False(t, isOpenAIOAuthServableModel("provider/k3"))
require.True(t, isOpenAIOAuthServableModel("my-k3-alias")) // 非精确 bare ID,自定义别名 fail-open
}