vibe-proxy/backend/internal/watcher/synthesizer/config_test.go
2026-08-24 00:10:41 +02:00

1270 lines
37 KiB
Go

package synthesizer
import (
"strconv"
"strings"
"testing"
"time"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func TestNewConfigSynthesizer(t *testing.T) {
synth := NewConfigSynthesizer()
if synth == nil {
t.Fatal("expected non-nil synthesizer")
}
}
func TestConfigSynthesizer_Synthesize_NilContext(t *testing.T) {
synth := NewConfigSynthesizer()
auths, err := synth.Synthesize(nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 0 {
t.Fatalf("expected empty auths, got %d", len(auths))
}
}
func TestConfigSynthesizer_Synthesize_NilConfig(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: nil,
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 0 {
t.Fatalf("expected empty auths, got %d", len(auths))
}
}
func TestConfigSynthesizer_GeminiKeys(t *testing.T) {
tests := []struct {
name string
geminiKeys []config.GeminiKey
wantLen int
validate func(*testing.T, []*coreauth.Auth)
}{
{
name: "single gemini key",
geminiKeys: []config.GeminiKey{
{APIKey: "test-key-123", Prefix: "team-a"},
},
wantLen: 1,
validate: func(t *testing.T, auths []*coreauth.Auth) {
if auths[0].Provider != "gemini" {
t.Errorf("expected provider gemini, got %s", auths[0].Provider)
}
if auths[0].Prefix != "team-a" {
t.Errorf("expected prefix team-a, got %s", auths[0].Prefix)
}
if auths[0].Label != "gemini-apikey" {
t.Errorf("expected label gemini-apikey, got %s", auths[0].Label)
}
if auths[0].Attributes["api_key"] != "test-key-123" {
t.Errorf("expected api_key test-key-123, got %s", auths[0].Attributes["api_key"])
}
if auths[0].Metadata != nil {
t.Errorf("expected metadata to be nil when disable_cooling not set, got %v", auths[0].Metadata)
}
if auths[0].Status != coreauth.StatusActive {
t.Errorf("expected status active, got %s", auths[0].Status)
}
},
},
{
name: "gemini key disable cooling",
geminiKeys: []config.GeminiKey{
{APIKey: "test-key-123", Prefix: "team-a", DisableCooling: boolPointer(true)},
},
wantLen: 1,
validate: func(t *testing.T, auths []*coreauth.Auth) {
if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v {
t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"])
}
},
},
{
name: "gemini key with base url and proxy",
geminiKeys: []config.GeminiKey{
{
APIKey: "api-key",
BaseURL: "https://custom.api.com",
ProxyURL: "http://proxy.local:8080",
Prefix: "custom",
},
},
wantLen: 1,
validate: func(t *testing.T, auths []*coreauth.Auth) {
if auths[0].Attributes["base_url"] != "https://custom.api.com" {
t.Errorf("expected base_url https://custom.api.com, got %s", auths[0].Attributes["base_url"])
}
if auths[0].ProxyURL != "http://proxy.local:8080" {
t.Errorf("expected proxy_url http://proxy.local:8080, got %s", auths[0].ProxyURL)
}
},
},
{
name: "gemini key with headers",
geminiKeys: []config.GeminiKey{
{
APIKey: "api-key",
Headers: map[string]string{"X-Custom": "value"},
},
},
wantLen: 1,
validate: func(t *testing.T, auths []*coreauth.Auth) {
if auths[0].Attributes["header:X-Custom"] != "value" {
t.Errorf("expected header:X-Custom=value, got %s", auths[0].Attributes["header:X-Custom"])
}
},
},
{
name: "empty api key skipped",
geminiKeys: []config.GeminiKey{
{APIKey: ""},
{APIKey: " "},
{APIKey: "valid-key"},
},
wantLen: 1,
},
{
name: "multiple gemini keys",
geminiKeys: []config.GeminiKey{
{APIKey: "key-1", Prefix: "a"},
{APIKey: "key-2", Prefix: "b"},
{APIKey: "key-3", Prefix: "c"},
},
wantLen: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: tt.geminiKeys,
},
Now: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != tt.wantLen {
t.Fatalf("expected %d auths, got %d", tt.wantLen, len(auths))
}
if tt.validate != nil && len(auths) > 0 {
tt.validate(t, auths)
}
})
}
}
func TestConfigSynthesizer_InteractionsKeys(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
InteractionsKey: []config.GeminiKey{{
APIKey: "interactions-key",
BaseURL: "https://interactions.example.com",
ProxyURL: "http://proxy.local:8080",
Prefix: "native",
Headers: map[string]string{"X-Custom": "value"},
}},
},
Now: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
IDGenerator: NewStableIDGenerator(),
}
auths, errSynthesize := synth.Synthesize(ctx)
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
if len(auths) != 1 {
t.Fatalf("auth count = %d, want 1", len(auths))
}
auth := auths[0]
if auth.Provider != "gemini-interactions" {
t.Fatalf("provider = %q, want gemini-interactions", auth.Provider)
}
if auth.Label != "interactions-apikey" {
t.Fatalf("label = %q, want interactions-apikey", auth.Label)
}
if auth.Prefix != "native" {
t.Fatalf("prefix = %q, want native", auth.Prefix)
}
if auth.ProxyURL != "http://proxy.local:8080" {
t.Fatalf("proxy URL = %q, want http://proxy.local:8080", auth.ProxyURL)
}
if got := auth.Attributes["api_key"]; got != "interactions-key" {
t.Fatalf("api_key = %q, want interactions-key", got)
}
if got := auth.Attributes["base_url"]; got != "https://interactions.example.com" {
t.Fatalf("base_url = %q, want https://interactions.example.com", got)
}
if got := auth.Attributes["header:X-Custom"]; got != "value" {
t.Fatalf("header:X-Custom = %q, want value", got)
}
}
func TestConfigSynthesizer_ClaudeKeys(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
ClaudeKey: []config.ClaudeKey{
{
APIKey: "sk-ant-api-xxx",
Prefix: "main",
BaseURL: "https://api.anthropic.com",
DisableCooling: boolPointer(true),
RebuildMidSystemMessage: true,
FingerprintProfile: "claude-code-cli",
Models: []config.ClaudeModel{
{Name: "claude-3-opus"},
{Name: "claude-3-sonnet"},
},
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if auths[0].Provider != "claude" {
t.Errorf("expected provider claude, got %s", auths[0].Provider)
}
if auths[0].Label != "claude-apikey" {
t.Errorf("expected label claude-apikey, got %s", auths[0].Label)
}
if auths[0].Prefix != "main" {
t.Errorf("expected prefix main, got %s", auths[0].Prefix)
}
if auths[0].Attributes["api_key"] != "sk-ant-api-xxx" {
t.Errorf("expected api_key sk-ant-api-xxx, got %s", auths[0].Attributes["api_key"])
}
if auths[0].Attributes["config_index"] != "0" {
t.Errorf("expected config_index 0, got %s", auths[0].Attributes["config_index"])
}
if _, ok := auths[0].Attributes["models_hash"]; !ok {
t.Error("expected models_hash in attributes")
}
if got := auths[0].Attributes["rebuild_mid_system_message"]; got != "true" {
t.Errorf("expected rebuild_mid_system_message=true, got %s", got)
}
if got := auths[0].Attributes["fingerprint_profile"]; got != "claude-code-cli" {
t.Errorf("expected fingerprint_profile=claude-code-cli, got %s", got)
}
if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v {
t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"])
}
}
func TestConfigSynthesizer_ClaudeKeys_SkipsEmptyAndHeaders(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
ClaudeKey: []config.ClaudeKey{
{APIKey: ""}, // empty, should be skipped
{APIKey: " "}, // whitespace, should be skipped
{APIKey: "valid-key", Headers: map[string]string{"X-Custom": "value"}},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth (empty keys skipped), got %d", len(auths))
}
if auths[0].Attributes["header:X-Custom"] != "value" {
t.Errorf("expected header:X-Custom=value, got %s", auths[0].Attributes["header:X-Custom"])
}
}
func TestConfigSynthesizer_CodexKeys(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
CodexKey: []config.CodexKey{
{
APIKey: "codex-key-123",
Prefix: "dev",
BaseURL: "https://api.openai.com",
ProxyURL: "http://proxy.local",
Websockets: true,
AlphaSearch: true,
DisableCooling: boolPointer(true),
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if auths[0].Provider != "codex" {
t.Errorf("expected provider codex, got %s", auths[0].Provider)
}
if auths[0].Label != "codex-apikey" {
t.Errorf("expected label codex-apikey, got %s", auths[0].Label)
}
if auths[0].ProxyURL != "http://proxy.local" {
t.Errorf("expected proxy_url http://proxy.local, got %s", auths[0].ProxyURL)
}
if auths[0].Attributes["websockets"] != "true" {
t.Errorf("expected websockets=true, got %s", auths[0].Attributes["websockets"])
}
if auths[0].Attributes[coreauth.AttributeCodexAlphaSearch] != "true" {
t.Errorf("expected codex_alpha_search=true, got %s", auths[0].Attributes[coreauth.AttributeCodexAlphaSearch])
}
if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v {
t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"])
}
}
func TestConfigSynthesizer_XAIKeys(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
XAIKey: []config.XAIKey{{
APIKey: "xai-key-123",
Prefix: "grok",
BaseURL: "https://api.x.ai/v1",
ProxyURL: "http://proxy.local",
Websockets: true,
AlphaSearch: true,
DisableCooling: boolPointer(true),
Headers: map[string]string{"X-Custom": "value"},
Models: []config.XAIModel{{Name: "grok-4.5", Alias: "grok-latest"}},
}},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, errSynthesize := synth.Synthesize(ctx)
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
if len(auths) != 1 {
t.Fatalf("auth count = %d, want 1", len(auths))
}
auth := auths[0]
if auth.Provider != "xai" {
t.Fatalf("provider = %q, want xai", auth.Provider)
}
if auth.Label != "xai-apikey" {
t.Fatalf("label = %q, want xai-apikey", auth.Label)
}
if auth.Attributes["websockets"] != "true" {
t.Fatalf("websockets = %q, want true", auth.Attributes["websockets"])
}
if _, exists := auth.Attributes[coreauth.AttributeCodexAlphaSearch]; exists {
t.Fatal("xAI auth unexpectedly contains codex_alpha_search")
}
if auth.Attributes["base_url"] != "https://api.x.ai/v1" {
t.Fatalf("base_url = %q, want https://api.x.ai/v1", auth.Attributes["base_url"])
}
if auth.Attributes["header:X-Custom"] != "value" {
t.Fatalf("custom header = %q, want value", auth.Attributes["header:X-Custom"])
}
if auth.Attributes["models_hash"] == "" {
t.Fatal("models_hash is empty")
}
if auth.ProxyURL != "http://proxy.local" {
t.Fatalf("proxy URL = %q, want http://proxy.local", auth.ProxyURL)
}
if disabled, ok := auth.Metadata["disable_cooling"].(bool); !ok || !disabled {
t.Fatalf("disable_cooling = %#v, want true", auth.Metadata["disable_cooling"])
}
}
func TestConfigSynthesizer_XAIKeys_AllowsEmptyAPIKeyWithBaseURL(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
XAIKey: []config.CodexKey{
{
APIKey: "",
BaseURL: "https://custom-xai.example.com",
Headers: map[string]string{"Custom-Auth": "secret"},
},
{
APIKey: " ",
BaseURL: "https://custom-xai-2.example.com",
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 2 {
t.Fatalf("expected 2 auths for empty API keys with base URL, got %d", len(auths))
}
if auths[0].Attributes["base_url"] != "https://custom-xai.example.com" {
t.Fatalf("expected base_url=https://custom-xai.example.com, got %s", auths[0].Attributes["base_url"])
}
if auths[0].Attributes["header:Custom-Auth"] != "secret" {
t.Fatalf("expected header:Custom-Auth=secret, got %s", auths[0].Attributes["header:Custom-Auth"])
}
if auths[0].Attributes["auth_kind"] != "apikey" {
t.Fatalf("expected auth_kind=apikey, got %s", auths[0].Attributes["auth_kind"])
}
if _, exists := auths[0].Attributes["api_key"]; exists {
t.Fatalf("expected no api_key attribute for empty key, got %s", auths[0].Attributes["api_key"])
}
}
func TestConfigSynthesizer_ClaudeKeys_AllowsEmptyAPIKeyWithBaseURL(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
ClaudeKey: []config.ClaudeKey{
{
APIKey: "",
BaseURL: "https://custom-claude.example.com",
Headers: map[string]string{"Custom-Auth": "secret"},
},
{
APIKey: " ",
BaseURL: "https://custom-claude-2.example.com",
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 2 {
t.Fatalf("expected 2 auths for empty API keys with base URL, got %d", len(auths))
}
if auths[0].Attributes["base_url"] != "https://custom-claude.example.com" {
t.Fatalf("expected base_url=https://custom-claude.example.com, got %s", auths[0].Attributes["base_url"])
}
if auths[0].Attributes["header:Custom-Auth"] != "secret" {
t.Fatalf("expected header:Custom-Auth=secret, got %s", auths[0].Attributes["header:Custom-Auth"])
}
if auths[0].Attributes["auth_kind"] != "apikey" {
t.Fatalf("expected auth_kind=apikey, got %s", auths[0].Attributes["auth_kind"])
}
if _, exists := auths[0].Attributes["api_key"]; exists {
t.Fatalf("expected no api_key attribute for empty key, got %s", auths[0].Attributes["api_key"])
}
}
func TestConfigSynthesizer_GeminiKeys_AllowsEmptyAPIKeyWithBaseURL(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: []config.GeminiKey{
{
APIKey: "",
BaseURL: "https://custom-gemini.example.com",
Headers: map[string]string{"Custom-Auth": "secret"},
},
},
InteractionsKey: []config.GeminiKey{
{
APIKey: "",
BaseURL: "https://custom-interactions.example.com",
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 2 {
t.Fatalf("expected 2 auths for empty API keys with base URL, got %d", len(auths))
}
if auths[0].Attributes["base_url"] != "https://custom-gemini.example.com" {
t.Fatalf("expected base_url=https://custom-gemini.example.com, got %s", auths[0].Attributes["base_url"])
}
if auths[0].Attributes["header:Custom-Auth"] != "secret" {
t.Fatalf("expected header:Custom-Auth=secret, got %s", auths[0].Attributes["header:Custom-Auth"])
}
if auths[0].Attributes["auth_kind"] != "apikey" {
t.Fatalf("expected auth_kind=apikey, got %s", auths[0].Attributes["auth_kind"])
}
if _, exists := auths[0].Attributes["api_key"]; exists {
t.Fatalf("expected no api_key attribute for empty key, got %s", auths[0].Attributes["api_key"])
}
if auths[1].Attributes["base_url"] != "https://custom-interactions.example.com" {
t.Fatalf("expected base_url=https://custom-interactions.example.com, got %s", auths[1].Attributes["base_url"])
}
}
func TestConfigSynthesizer_CodexKeys_SkipsEmptyAndHeaders(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
CodexKey: []config.CodexKey{
{APIKey: ""}, // empty key without base URL, should be skipped
{APIKey: " "}, // whitespace key without base URL, should be skipped
{APIKey: "valid-key", Headers: map[string]string{"Authorization": "Bearer xyz"}},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth (empty keys skipped), got %d", len(auths))
}
if auths[0].Attributes["header:Authorization"] != "Bearer xyz" {
t.Errorf("expected header:Authorization=Bearer xyz, got %s", auths[0].Attributes["header:Authorization"])
}
if _, exists := auths[0].Attributes[coreauth.AttributeCodexAlphaSearch]; exists {
t.Fatal("default alpha-search=false unexpectedly generated codex_alpha_search")
}
}
func TestConfigSynthesizer_CodexKeys_AllowsEmptyAPIKeyWithBaseURL(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
CodexKey: []config.CodexKey{
{
APIKey: "",
BaseURL: "https://custom-codex.example.com",
Headers: map[string]string{"Custom-Auth": "secret"},
},
{
APIKey: " ",
BaseURL: "https://custom-codex-2.example.com",
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 2 {
t.Fatalf("expected 2 auths for empty API keys with base URL, got %d", len(auths))
}
if auths[0].Attributes["base_url"] != "https://custom-codex.example.com" {
t.Fatalf("expected base_url=https://custom-codex.example.com, got %s", auths[0].Attributes["base_url"])
}
if auths[0].Attributes["header:Custom-Auth"] != "secret" {
t.Fatalf("expected header:Custom-Auth=secret, got %s", auths[0].Attributes["header:Custom-Auth"])
}
if auths[0].Attributes["auth_kind"] != "apikey" {
t.Fatalf("expected auth_kind=apikey, got %s", auths[0].Attributes["auth_kind"])
}
if _, exists := auths[0].Attributes["api_key"]; exists {
t.Fatalf("expected no api_key attribute for empty key, got %s", auths[0].Attributes["api_key"])
}
}
func TestConfigSynthesizer_OpenAICompat(t *testing.T) {
tests := []struct {
name string
compat []config.OpenAICompatibility
wantLen int
}{
{
name: "with APIKeyEntries",
compat: []config.OpenAICompatibility{
{
Name: "CustomProvider",
BaseURL: "https://custom.api.com",
DisableCooling: boolPointer(true),
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: "key-1"},
{APIKey: "key-2"},
},
},
},
wantLen: 2,
},
{
name: "empty APIKeyEntries included (legacy)",
compat: []config.OpenAICompatibility{
{
Name: "EmptyKeys",
BaseURL: "https://empty.api.com",
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: ""},
{APIKey: " "},
},
},
},
wantLen: 2,
},
{
name: "without APIKeyEntries (fallback)",
compat: []config.OpenAICompatibility{
{
Name: "NoKeyProvider",
BaseURL: "https://no-key.api.com",
},
},
wantLen: 1,
},
{
name: "empty name defaults",
compat: []config.OpenAICompatibility{
{
Name: "",
BaseURL: "https://default.api.com",
},
},
wantLen: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
OpenAICompatibility: tt.compat,
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != tt.wantLen {
t.Fatalf("expected %d auths, got %d", tt.wantLen, len(auths))
}
if tt.name == "with APIKeyEntries" {
for i := range auths {
if v, ok := auths[i].Metadata["disable_cooling"].(bool); !ok || !v {
t.Fatalf("expected auth[%d].disable_cooling=true, got %v", i, auths[i].Metadata["disable_cooling"])
}
}
}
})
}
}
func TestConfigSynthesizer_OpenAICompat_UsesNamespacedProviderKey(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "kimi",
BaseURL: "https://kimi-compatible.example.com/v1",
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: "test-key"},
},
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
auth := auths[0]
if auth.Provider != "openai-compatible-kimi" {
t.Fatalf("provider = %q, want openai-compatible-kimi", auth.Provider)
}
if auth.Attributes["provider_key"] != "openai-compatible-kimi" {
t.Fatalf("provider_key = %q, want openai-compatible-kimi", auth.Attributes["provider_key"])
}
if auth.Attributes["compat_name"] != "kimi" {
t.Fatalf("compat_name = %q, want kimi", auth.Attributes["compat_name"])
}
if auth.Attributes["config_index"] != "0" {
t.Fatalf("config_index = %q, want 0", auth.Attributes["config_index"])
}
}
func TestConfigSynthesizer_VertexCompat(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
VertexCompatAPIKey: []config.VertexCompatKey{
{
APIKey: "vertex-key-123",
BaseURL: "https://vertex.googleapis.com",
Prefix: "vertex-prod",
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if auths[0].Provider != "vertex" {
t.Errorf("expected provider vertex, got %s", auths[0].Provider)
}
if auths[0].Label != "vertex-apikey" {
t.Errorf("expected label vertex-apikey, got %s", auths[0].Label)
}
if auths[0].Prefix != "vertex-prod" {
t.Errorf("expected prefix vertex-prod, got %s", auths[0].Prefix)
}
}
func TestConfigSynthesizer_VertexCompat_SkipsEmptyAndHeaders(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
VertexCompatAPIKey: []config.VertexCompatKey{
{APIKey: "", BaseURL: "https://vertex.api"}, // empty key creates auth without api_key attr
{APIKey: " ", BaseURL: "https://vertex.api"}, // whitespace key creates auth without api_key attr
{APIKey: "valid-key", BaseURL: "https://vertex.api", Headers: map[string]string{"X-Vertex": "test"}},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Vertex compat doesn't skip empty keys - it creates auths without api_key attribute
if len(auths) != 3 {
t.Fatalf("expected 3 auths, got %d", len(auths))
}
// First two should not have api_key attribute
if _, ok := auths[0].Attributes["api_key"]; ok {
t.Error("expected first auth to not have api_key attribute")
}
if _, ok := auths[1].Attributes["api_key"]; ok {
t.Error("expected second auth to not have api_key attribute")
}
// Third should have headers
if auths[2].Attributes["header:X-Vertex"] != "test" {
t.Errorf("expected header:X-Vertex=test, got %s", auths[2].Attributes["header:X-Vertex"])
}
}
func TestConfigSynthesizer_OpenAICompat_WithModelsHash(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "TestProvider",
BaseURL: "https://test.api.com",
Models: []config.OpenAICompatibilityModel{
{Name: "model-a"},
{Name: "model-b"},
},
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: "key-with-models"},
},
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if _, ok := auths[0].Attributes["models_hash"]; !ok {
t.Error("expected models_hash in attributes")
}
if auths[0].Attributes["api_key"] != "key-with-models" {
t.Errorf("expected api_key key-with-models, got %s", auths[0].Attributes["api_key"])
}
}
func TestConfigSynthesizer_OpenAICompat_FallbackWithModels(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "NoKeyWithModels",
BaseURL: "https://nokey.api.com",
Models: []config.OpenAICompatibilityModel{
{Name: "model-x"},
},
Headers: map[string]string{"X-API": "header-value"},
// No APIKeyEntries - should use fallback path
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if _, ok := auths[0].Attributes["models_hash"]; !ok {
t.Error("expected models_hash in fallback path")
}
if auths[0].Attributes["header:X-API"] != "header-value" {
t.Errorf("expected header:X-API=header-value, got %s", auths[0].Attributes["header:X-API"])
}
}
func TestConfigSynthesizer_VertexCompat_WithModels(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
VertexCompatAPIKey: []config.VertexCompatKey{
{
APIKey: "vertex-key",
BaseURL: "https://vertex.api",
Models: []config.VertexCompatModel{
{Name: "gemini-pro", Alias: "pro"},
{Name: "gemini-ultra", Alias: "ultra"},
},
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 1 {
t.Fatalf("expected 1 auth, got %d", len(auths))
}
if _, ok := auths[0].Attributes["models_hash"]; !ok {
t.Error("expected models_hash in vertex auth with models")
}
}
func TestConfigSynthesizer_IDStability(t *testing.T) {
cfg := &config.Config{
GeminiKey: []config.GeminiKey{
{APIKey: "stable-key", Prefix: "test"},
},
}
// Generate IDs twice with fresh generators
synth1 := NewConfigSynthesizer()
ctx1 := &SynthesisContext{
Config: cfg,
Now: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
IDGenerator: NewStableIDGenerator(),
}
auths1, _ := synth1.Synthesize(ctx1)
synth2 := NewConfigSynthesizer()
ctx2 := &SynthesisContext{
Config: cfg,
Now: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
IDGenerator: NewStableIDGenerator(),
}
auths2, _ := synth2.Synthesize(ctx2)
if auths1[0].ID != auths2[0].ID {
t.Errorf("same config should produce same ID: got %q and %q", auths1[0].ID, auths2[0].ID)
}
}
func TestConfigSynthesizer_RejectsInvalidWeightsForAllAPIKeyTypes(t *testing.T) {
invalidWeight := config.MaxCredentialWeight + 1
tests := []struct {
name string
cfg *config.Config
wantPath string
}{
{
name: "gemini",
cfg: &config.Config{GeminiKey: []config.GeminiKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "gemini-api-key[0].weight",
},
{
name: "interactions",
cfg: &config.Config{InteractionsKey: []config.GeminiKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "interactions-api-key[0].weight",
},
{
name: "claude",
cfg: &config.Config{ClaudeKey: []config.ClaudeKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "claude-api-key[0].weight",
},
{
name: "codex",
cfg: &config.Config{CodexKey: []config.CodexKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "codex-api-key[0].weight",
},
{
name: "xai",
cfg: &config.Config{XAIKey: []config.XAIKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "xai-api-key[0].weight",
},
{
name: "openai compatibility",
cfg: &config.Config{OpenAICompatibility: []config.OpenAICompatibility{{
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{APIKey: "key", Weight: &invalidWeight}},
}}},
wantPath: "openai-compatibility[0].api-key-entries[0].weight",
},
{
name: "vertex",
cfg: &config.Config{VertexCompatAPIKey: []config.VertexCompatKey{{APIKey: "key", Weight: &invalidWeight}}},
wantPath: "vertex-api-key[0].weight",
},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
auths, errSynthesize := NewConfigSynthesizer().Synthesize(&SynthesisContext{
Config: testCase.cfg,
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
})
if errSynthesize == nil {
t.Fatal("Synthesize() accepted an invalid credential weight")
}
if auths != nil {
t.Fatalf("Synthesize() auths = %#v, want nil", auths)
}
if !strings.Contains(errSynthesize.Error(), "synthesize config API key auths: "+testCase.wantPath) {
t.Fatalf("Synthesize() error = %q, want contextual path %q", errSynthesize, testCase.wantPath)
}
})
}
}
func TestConfigSynthesizer_OmittedWeightRemainsUnset(t *testing.T) {
auths, errSynthesize := NewConfigSynthesizer().Synthesize(&SynthesisContext{
Config: &config.Config{GeminiKey: []config.GeminiKey{{APIKey: "key"}}},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
})
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
if len(auths) != 1 {
t.Fatalf("auth count = %d, want 1", len(auths))
}
if _, exists := auths[0].Attributes[coreauth.AttributeWeight]; exists {
t.Fatal("omitted weight was added to synthesized attributes")
}
}
func TestConfigSynthesizer_NormalizesNonPositiveWeightToZero(t *testing.T) {
weight := -5
auths, errSynthesize := NewConfigSynthesizer().Synthesize(&SynthesisContext{
Config: &config.Config{GeminiKey: []config.GeminiKey{{APIKey: "key", Weight: &weight}}},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
})
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
if len(auths) != 1 {
t.Fatalf("auth count = %d, want 1", len(auths))
}
if gotWeight := auths[0].Attributes[coreauth.AttributeWeight]; gotWeight != "0" {
t.Fatalf("weight = %q, want 0", gotWeight)
}
}
func TestConfigSynthesizer_PropagatesWeightsForAllAPIKeyTypes(t *testing.T) {
weight := func(value int) *int { return &value }
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: []config.GeminiKey{{APIKey: "gemini", Weight: weight(1)}},
InteractionsKey: []config.GeminiKey{{APIKey: "interactions", Weight: weight(2)}},
ClaudeKey: []config.ClaudeKey{{APIKey: "claude", Weight: weight(3)}},
CodexKey: []config.CodexKey{{APIKey: "codex", Weight: weight(4)}},
XAIKey: []config.XAIKey{{APIKey: "xai", Weight: weight(5)}},
OpenAICompatibility: []config.OpenAICompatibility{{
Name: "compat",
BaseURL: "https://compat.example.com",
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{
APIKey: "compat",
Weight: weight(6),
}},
}},
VertexCompatAPIKey: []config.VertexCompatKey{{APIKey: "vertex", Weight: weight(7)}},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, errSynthesize := synth.Synthesize(ctx)
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
if len(auths) != 7 {
t.Fatalf("auth count = %d, want 7", len(auths))
}
for index, auth := range auths {
wantWeight := strconv.Itoa(index + 1)
if gotWeight := auth.Attributes[coreauth.AttributeWeight]; gotWeight != wantWeight {
t.Fatalf("auth[%d] weight = %q, want %q", index, gotWeight, wantWeight)
}
}
}
func TestConfigSynthesizer_AllProviders(t *testing.T) {
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: []config.GeminiKey{
{APIKey: "gemini-key"},
},
ClaudeKey: []config.ClaudeKey{
{APIKey: "claude-key"},
},
CodexKey: []config.CodexKey{
{APIKey: "codex-key"},
},
XAIKey: []config.XAIKey{
{APIKey: "xai-key"},
},
OpenAICompatibility: []config.OpenAICompatibility{
{Name: "compat", BaseURL: "https://compat.api"},
},
VertexCompatAPIKey: []config.VertexCompatKey{
{APIKey: "vertex-key", BaseURL: "https://vertex.api"},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, err := synth.Synthesize(ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(auths) != 6 {
t.Fatalf("expected 6 auths, got %d", len(auths))
}
providers := make(map[string]bool)
for _, a := range auths {
providers[a.Provider] = true
}
expected := []string{"gemini", "claude", "codex", "xai", "openai-compatible-compat", "vertex"}
for _, p := range expected {
if !providers[p] {
t.Errorf("expected provider %s not found", p)
}
}
}
func TestConfigSynthesizer_RequestRetry(t *testing.T) {
zero := 0
positive := 2
negative := -1
synth := NewConfigSynthesizer()
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: []config.GeminiKey{
{APIKey: "gemini-zero", RequestRetry: &zero},
{APIKey: "gemini-positive", RequestRetry: &positive},
{APIKey: "gemini-negative", RequestRetry: &negative},
{APIKey: "gemini-unset"},
},
InteractionsKey: []config.GeminiKey{
{APIKey: "interactions-zero", RequestRetry: &zero},
},
ClaudeKey: []config.ClaudeKey{
{APIKey: "claude-positive", RequestRetry: &positive},
},
CodexKey: []config.CodexKey{
{APIKey: "codex-zero", RequestRetry: &zero},
},
XAIKey: []config.XAIKey{
{APIKey: "xai-positive", RequestRetry: &positive},
},
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "compat",
BaseURL: "https://compat.api",
RequestRetry: &zero,
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: "compat-key"},
},
},
},
VertexCompatAPIKey: []config.VertexCompatKey{
{APIKey: "vertex-positive", BaseURL: "https://vertex.api", RequestRetry: &positive},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, errSynthesize := synth.Synthesize(ctx)
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
want := map[string]any{
"gemini-zero": 0,
"gemini-positive": 2,
"gemini-negative": nil,
"gemini-unset": nil,
"interactions-zero": 0,
"claude-positive": 2,
"codex-zero": 0,
"xai-positive": 2,
"compat-key": 0,
"vertex-positive": 2,
}
got := make(map[string]any, len(auths))
for _, auth := range auths {
key := auth.Attributes["api_key"]
if auth.Metadata == nil {
got[key] = nil
continue
}
if value, exists := auth.Metadata["request_retry"]; exists {
got[key] = value
continue
}
got[key] = nil
}
for key, expected := range want {
actual, exists := got[key]
if !exists {
t.Fatalf("missing synthesized auth for %s", key)
}
if actual != expected {
t.Fatalf("%s request_retry = %v, want %v", key, actual, expected)
}
}
}
func TestConfigSynthesizer_RequestScopedErrors(t *testing.T) {
synth := NewConfigSynthesizer()
rules := []config.RequestScopedErrorRule{
{
Status: 400,
Match: []string{"maximum_context_length"},
Action: "stop",
},
}
ctx := &SynthesisContext{
Config: &config.Config{
GeminiKey: []config.GeminiKey{
{APIKey: "gemini-key", RequestScopedErrors: rules},
},
InteractionsKey: []config.GeminiKey{
{APIKey: "interactions-key", RequestScopedErrors: rules},
},
ClaudeKey: []config.ClaudeKey{
{APIKey: "claude-key", RequestScopedErrors: rules},
},
CodexKey: []config.CodexKey{
{APIKey: "codex-key", BaseURL: "https://codex.api", RequestScopedErrors: rules},
},
XAIKey: []config.CodexKey{
{APIKey: "xai-key", BaseURL: "https://xai.api", RequestScopedErrors: rules},
},
OpenAICompatibility: []config.OpenAICompatibility{
{
Name: "compat",
BaseURL: "https://compat.api",
RequestScopedErrors: rules,
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
{APIKey: "compat-key"},
},
},
},
},
Now: time.Now(),
IDGenerator: NewStableIDGenerator(),
}
auths, errSynthesize := synth.Synthesize(ctx)
if errSynthesize != nil {
t.Fatalf("Synthesize() error = %v", errSynthesize)
}
for _, auth := range auths {
if auth.Metadata == nil {
t.Fatalf("auth %s has nil metadata", auth.ID)
}
val, exists := auth.Metadata["request_scoped_errors"]
if !exists {
t.Fatalf("auth %s missing request_scoped_errors in metadata", auth.ID)
}
extracted, ok := val.([]config.RequestScopedErrorRule)
if !ok || len(extracted) != 1 || extracted[0].Action != "stop" {
t.Fatalf("auth %s unexpected request_scoped_errors: %#v", auth.ID, val)
}
}
}