298 lines
12 KiB
Go
298 lines
12 KiB
Go
package auth
|
|
|
|
import (
|
|
"testing"
|
|
|
|
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
)
|
|
|
|
func TestAttachResolvedAPIKeyModelInfoUsesSelectedCredential(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
manager.SetConfig(&internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{
|
|
{
|
|
APIKey: "key-high",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public-model",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}},
|
|
}},
|
|
},
|
|
{
|
|
APIKey: "key-max",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public-model",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"max"}},
|
|
}},
|
|
},
|
|
}})
|
|
|
|
authHigh := configuredCapabilityTestAuth("auth-high", "key-high")
|
|
authMax := configuredCapabilityTestAuth("auth-max", "key-max")
|
|
registerCapabilityTestAuth(t, manager, authHigh)
|
|
registerCapabilityTestAuth(t, manager, authMax)
|
|
|
|
assertResolvedThinkingLevels(t, manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, authHigh, "tenant/public-model", "shared-upstream"), "high")
|
|
assertResolvedThinkingLevels(t, manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, authMax, "tenant/public-model", "shared-upstream"), "max")
|
|
}
|
|
|
|
func TestAttachResolvedAPIKeyModelInfoUsesExactDuplicateCredentialConfig(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
highModels := []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public-model",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}},
|
|
}}
|
|
maxModels := []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public-model",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"max"}},
|
|
}}
|
|
manager.SetConfig(&internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{
|
|
{APIKey: "shared-key", Prefix: "tenant", Models: highModels},
|
|
{APIKey: "shared-key", Prefix: "tenant", Models: maxModels},
|
|
}})
|
|
|
|
authHigh := configuredCapabilityTestAuth("auth-duplicate-high", "shared-key")
|
|
authHigh.Attributes[AttributeConfigIndex] = "0"
|
|
authMax := configuredCapabilityTestAuth("auth-duplicate-max", "shared-key")
|
|
authMax.Attributes[AttributeConfigIndex] = "1"
|
|
registerCapabilityTestAuth(t, manager, authHigh)
|
|
registerCapabilityTestAuth(t, manager, authMax)
|
|
|
|
assertResolvedThinkingLevels(t, manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, authHigh, "tenant/public-model", "shared-upstream"), "high")
|
|
assertResolvedThinkingLevels(t, manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, authMax, "tenant/public-model", "shared-upstream"), "max")
|
|
}
|
|
|
|
func TestAttachResolvedAPIKeyModelInfoPrefersExactConfiguredSuffix(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
auth := configuredCapabilityTestAuth("auth-suffix", "key-suffix")
|
|
manager.SetConfig(&internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{{
|
|
APIKey: "key-suffix",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{
|
|
{Name: "shared-upstream(high)", Alias: "public-high", Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}}},
|
|
{Name: "shared-upstream(low)", Alias: "public-low", Thinking: ®istry.ThinkingSupport{Levels: []string{"low"}}},
|
|
{Name: "alias-upstream", Alias: "public(high)", Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}}},
|
|
{Name: "alias-upstream", Alias: "public(low)", Thinking: ®istry.ThinkingSupport{Levels: []string{"low"}}},
|
|
},
|
|
}}})
|
|
registerCapabilityTestAuth(t, manager, auth)
|
|
|
|
req := manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, auth, "tenant/public-low", "shared-upstream(low)")
|
|
assertResolvedThinkingLevels(t, req, "low")
|
|
|
|
models, _, _, routing := manager.executionModelCandidatesWithAlias(auth, "tenant/shared-upstream(low)")
|
|
if len(models) != 1 || models[0] != "shared-upstream(low)" {
|
|
t.Fatalf("direct suffixed models = %v, want [shared-upstream(low)]", models)
|
|
}
|
|
directReq := attachResolvedAPIKeyModelInfo(routing, cliproxyexecutor.Request{}, auth, "tenant/shared-upstream(low)", models[0])
|
|
assertResolvedThinkingLevels(t, directReq, "low")
|
|
|
|
aliasModels, _, _, aliasRouting := manager.executionModelCandidatesWithAlias(auth, "tenant/public(low)")
|
|
if len(aliasModels) != 1 || aliasModels[0] != "alias-upstream(low)" {
|
|
t.Fatalf("suffixed alias models = %v, want [alias-upstream(low)]", aliasModels)
|
|
}
|
|
aliasReq := attachResolvedAPIKeyModelInfo(aliasRouting, cliproxyexecutor.Request{}, auth, "tenant/public(low)", aliasModels[0])
|
|
assertResolvedThinkingLevels(t, aliasReq, "low")
|
|
}
|
|
|
|
func TestAPIKeyModelRoutingClonesPublishedConfig(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
cfg := &internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{{
|
|
APIKey: "key-clone",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}},
|
|
}},
|
|
}}}
|
|
manager.SetConfig(cfg)
|
|
cfg.ClaudeKey[0].Models[0].Alias = "mutated"
|
|
cfg.ClaudeKey[0].Models[0].Thinking.Levels[0] = "max"
|
|
|
|
auth := configuredCapabilityTestAuth("auth-clone", "key-clone")
|
|
registerCapabilityTestAuth(t, manager, auth)
|
|
models, _, _, routing := manager.executionModelCandidatesWithAlias(auth, "tenant/public")
|
|
if len(models) != 1 || models[0] != "shared-upstream" {
|
|
t.Fatalf("cloned execution models = %v, want [shared-upstream]", models)
|
|
}
|
|
req := attachResolvedAPIKeyModelInfo(routing, cliproxyexecutor.Request{}, auth, "tenant/public", models[0])
|
|
assertResolvedThinkingLevels(t, req, "high")
|
|
}
|
|
|
|
func TestAPIKeyModelRoutingKeepsOneExecutionSnapshotAcrossReload(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
auth := configuredCapabilityTestAuth("auth-reload", "key-reload")
|
|
buildConfig := func(level string) *internalconfig.Config {
|
|
return &internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{{
|
|
APIKey: "key-reload",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{{
|
|
Name: "shared-upstream", Alias: "public",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{level}},
|
|
}},
|
|
}}}
|
|
}
|
|
manager.SetConfig(buildConfig("high"))
|
|
registerCapabilityTestAuth(t, manager, auth)
|
|
models, _, _, oldRouting := manager.executionModelCandidatesWithAlias(auth, "tenant/public")
|
|
if len(models) != 1 || models[0] != "shared-upstream" {
|
|
t.Fatalf("execution models = %v, want [shared-upstream]", models)
|
|
}
|
|
|
|
manager.SetConfig(buildConfig("max"))
|
|
oldReq := attachResolvedAPIKeyModelInfo(oldRouting, cliproxyexecutor.Request{}, auth, "tenant/public", models[0])
|
|
assertResolvedThinkingLevels(t, oldReq, "high")
|
|
newReq := manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, auth, "tenant/public", models[0])
|
|
assertResolvedThinkingLevels(t, newReq, "max")
|
|
}
|
|
|
|
func TestAttachResolvedAPIKeyModelInfoSupportsKeylessOpenAICompatibility(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
manager.SetConfig(&internalconfig.Config{OpenAICompatibility: []internalconfig.OpenAICompatibility{{
|
|
Name: "keyless",
|
|
Prefix: "tenant",
|
|
BaseURL: "https://example.com/v1",
|
|
Models: []internalconfig.OpenAICompatibilityModel{
|
|
{
|
|
Name: "shared-upstream", Alias: "public-model", ForceMapping: true, IsCompat: true,
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}},
|
|
},
|
|
{
|
|
Name: "fallback-upstream", Alias: "public-model",
|
|
Thinking: ®istry.ThinkingSupport{Levels: []string{"high"}},
|
|
},
|
|
},
|
|
}}})
|
|
auth := &Auth{
|
|
ID: "auth-keyless",
|
|
Provider: "openai-compatibility:keyless",
|
|
Prefix: "tenant",
|
|
Attributes: map[string]string{
|
|
AttributeSource: "config:keyless[0]",
|
|
"compat_name": "keyless",
|
|
"provider_key": "openai-compatibility:keyless",
|
|
},
|
|
}
|
|
registerCapabilityTestAuth(t, manager, auth)
|
|
models, _, aliasResult, routing := manager.executionModelCandidatesWithAlias(auth, "tenant/public-model")
|
|
if len(models) != 2 || models[0] != "shared-upstream" || models[1] != "fallback-upstream" {
|
|
t.Fatalf("keyless execution models = %v, want [shared-upstream fallback-upstream]", models)
|
|
}
|
|
if !aliasResult.ForceMapping || aliasResult.UpstreamModel != "shared-upstream" {
|
|
t.Fatalf("keyless force mapping result = %+v, want shared-upstream force mapping", aliasResult)
|
|
}
|
|
fallbackAliasResult := resolveAttemptAliasResult(routing, auth, "tenant/public-model", "fallback-upstream", aliasResult)
|
|
if fallbackAliasResult.ForceMapping {
|
|
t.Fatalf("fallback alias result = %+v, want force mapping disabled", fallbackAliasResult)
|
|
}
|
|
req := attachResolvedAPIKeyModelInfo(routing, cliproxyexecutor.Request{}, auth, "tenant/public-model", models[0])
|
|
assertResolvedThinkingLevels(t, req, "high")
|
|
info, ok := ResolvedAPIKeyModelInfo(req)
|
|
if !ok || info == nil || !info.IsCompat {
|
|
t.Fatal("OpenAI compatibility model IsCompat = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestAttachResolvedAPIKeyModelInfoBindsUnknownConfiguredCapability(t *testing.T) {
|
|
manager := NewManager(nil, nil, nil)
|
|
auth := configuredCapabilityTestAuth("auth-fallback", "key-fallback")
|
|
manager.SetConfig(&internalconfig.Config{ClaudeKey: []internalconfig.ClaudeKey{{
|
|
APIKey: "key-fallback",
|
|
Prefix: "tenant",
|
|
Models: []internalconfig.ClaudeModel{{Name: "unknown-upstream", Alias: "unknown-public"}},
|
|
}}})
|
|
registerCapabilityTestAuth(t, manager, auth)
|
|
|
|
req := manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, auth, "tenant/unknown-public", "unknown-upstream")
|
|
info, ok := ResolvedAPIKeyModelInfo(req)
|
|
if !ok || info == nil || info.UserDefined || info.Thinking != nil {
|
|
t.Fatalf("ResolvedAPIKeyModelInfo() = (%+v, %t), want authoritative empty capability", info, ok)
|
|
}
|
|
fallbackReq := manager.attachResolvedAPIKeyModelInfo(cliproxyexecutor.Request{}, auth, "tenant/not-configured", "not-configured")
|
|
if fallbackInfo, fallbackOK := ResolvedAPIKeyModelInfo(fallbackReq); fallbackOK || fallbackInfo != nil {
|
|
t.Fatalf("unconfigured model info = (%+v, %t), want registry fallback", fallbackInfo, fallbackOK)
|
|
}
|
|
}
|
|
|
|
func registerCapabilityTestAuth(t *testing.T, manager *Manager, auth *Auth) {
|
|
t.Helper()
|
|
registered, errRegister := manager.Register(t.Context(), auth)
|
|
if errRegister != nil {
|
|
t.Fatalf("Register() error = %v", errRegister)
|
|
}
|
|
if registered == nil {
|
|
t.Fatal("Register() returned nil auth")
|
|
}
|
|
}
|
|
|
|
func configuredCapabilityTestAuth(id, apiKey string) *Auth {
|
|
return &Auth{
|
|
ID: id,
|
|
Provider: "claude",
|
|
Prefix: "tenant",
|
|
Attributes: map[string]string{
|
|
AttributeAuthKind: AuthKindAPIKey,
|
|
AttributeAPIKey: apiKey,
|
|
AttributeSource: "config:claude[0]",
|
|
},
|
|
}
|
|
}
|
|
|
|
func assertResolvedThinkingLevels(t *testing.T, req cliproxyexecutor.Request, want ...string) {
|
|
t.Helper()
|
|
info, ok := ResolvedAPIKeyModelInfo(req)
|
|
if !ok || info == nil || info.Thinking == nil {
|
|
t.Fatalf("ResolvedAPIKeyModelInfo() = (%+v, %t), want thinking levels %v", info, ok, want)
|
|
}
|
|
if len(info.Thinking.Levels) != len(want) {
|
|
t.Fatalf("thinking levels = %v, want %v", info.Thinking.Levels, want)
|
|
}
|
|
for i := range want {
|
|
if info.Thinking.Levels[i] != want[i] {
|
|
t.Fatalf("thinking levels = %v, want %v", info.Thinking.Levels, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCodexAPIKeyModelIsCompat(t *testing.T) {
|
|
cfg := &internalconfig.Config{CodexKey: []internalconfig.CodexKey{{
|
|
APIKey: "codex-key",
|
|
BaseURL: "https://compat.example.com/v1",
|
|
Models: []internalconfig.CodexModel{
|
|
{Name: "deepseek-v4-flash", Alias: "deepseek-alias", IsCompat: true},
|
|
{Name: "gpt-5.4", Alias: "codex-native"},
|
|
},
|
|
}}}
|
|
auth := &Auth{
|
|
Provider: "codex",
|
|
Attributes: map[string]string{
|
|
AttributeAuthKind: AuthKindAPIKey,
|
|
AttributeAPIKey: "codex-key",
|
|
"base_url": "https://compat.example.com/v1",
|
|
},
|
|
}
|
|
|
|
if !CodexAPIKeyModelIsCompat(cfg, auth, "deepseek-v4-flash") {
|
|
t.Fatal("upstream name IsCompat = false, want true")
|
|
}
|
|
if !CodexAPIKeyModelIsCompat(cfg, auth, "deepseek-alias") {
|
|
t.Fatal("alias IsCompat = false, want true")
|
|
}
|
|
if !CodexAPIKeyModelIsCompat(cfg, auth, "deepseek-v4-flash(high)") {
|
|
t.Fatal("suffix model IsCompat = false, want true")
|
|
}
|
|
if CodexAPIKeyModelIsCompat(cfg, auth, "gpt-5.4") {
|
|
t.Fatal("native model IsCompat = true, want false")
|
|
}
|
|
if CodexAPIKeyModelIsCompat(cfg, auth, "missing-model") {
|
|
t.Fatal("missing model IsCompat = true, want false")
|
|
}
|
|
if CodexAPIKeyModelIsCompat(cfg, &Auth{Provider: "claude", Attributes: auth.Attributes}, "deepseek-v4-flash") {
|
|
t.Fatal("non-codex provider IsCompat = true, want false")
|
|
}
|
|
if CodexAPIKeyModelIsCompat(nil, auth, "deepseek-v4-flash") {
|
|
t.Fatal("nil config IsCompat = true, want false")
|
|
}
|
|
}
|