mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
package test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
)
|
|
|
|
func TestLegacyConfigMigration(t *testing.T) {
|
|
t.Run("onlyLegacyFields", func(t *testing.T) {
|
|
path := writeConfig(t, `
|
|
port: 8080
|
|
generative-language-api-key:
|
|
- "legacy-gemini-1"
|
|
openai-compatibility:
|
|
- name: "legacy-provider"
|
|
base-url: "https://example.com"
|
|
api-keys:
|
|
- "legacy-openai-1"
|
|
amp-upstream-url: "https://amp.example.com"
|
|
amp-upstream-api-key: "amp-legacy-key"
|
|
amp-restrict-management-to-localhost: false
|
|
amp-model-mappings:
|
|
- from: "old-model"
|
|
to: "new-model"
|
|
`)
|
|
cfg, err := config.LoadConfig(path)
|
|
if err != nil {
|
|
t.Fatalf("load legacy config: %v", err)
|
|
}
|
|
if got := len(cfg.GeminiKey); got != 1 || cfg.GeminiKey[0].APIKey != "legacy-gemini-1" {
|
|
t.Fatalf("gemini migration mismatch: %+v", cfg.GeminiKey)
|
|
}
|
|
if got := len(cfg.OpenAICompatibility); got != 1 {
|
|
t.Fatalf("expected 1 openai-compat provider, got %d", got)
|
|
}
|
|
if entries := cfg.OpenAICompatibility[0].APIKeyEntries; len(entries) != 1 || entries[0].APIKey != "legacy-openai-1" {
|
|
t.Fatalf("openai-compat migration mismatch: %+v", entries)
|
|
}
|
|
if cfg.AmpCode.UpstreamURL != "https://amp.example.com" || cfg.AmpCode.UpstreamAPIKey != "amp-legacy-key" {
|
|
t.Fatalf("amp migration failed: %+v", cfg.AmpCode)
|
|
}
|
|
if cfg.AmpCode.RestrictManagementToLocalhost {
|
|
t.Fatalf("expected amp restriction to be false after migration")
|
|
}
|
|
if got := len(cfg.AmpCode.ModelMappings); got != 1 || cfg.AmpCode.ModelMappings[0].From != "old-model" {
|
|
t.Fatalf("amp mappings migration mismatch: %+v", cfg.AmpCode.ModelMappings)
|
|
}
|
|
updated := readFile(t, path)
|
|
if strings.Contains(updated, "generative-language-api-key") {
|
|
t.Fatalf("legacy gemini key still present:\n%s", updated)
|
|
}
|
|
if strings.Contains(updated, "amp-upstream-url") || strings.Contains(updated, "amp-restrict-management-to-localhost") {
|
|
t.Fatalf("legacy amp keys still present:\n%s", updated)
|
|
}
|
|
if strings.Contains(updated, "\n api-keys:") {
|
|
t.Fatalf("legacy openai compat keys still present:\n%s", updated)
|
|
}
|
|
})
|
|
|
|
t.Run("mixedLegacyAndNewFields", func(t *testing.T) {
|
|
path := writeConfig(t, `
|
|
gemini-api-key:
|
|
- api-key: "new-gemini"
|
|
generative-language-api-key:
|
|
- "new-gemini"
|
|
- "legacy-gemini-only"
|
|
openai-compatibility:
|
|
- name: "mixed-provider"
|
|
base-url: "https://mixed.example.com"
|
|
api-key-entries:
|
|
- api-key: "new-entry"
|
|
api-keys:
|
|
- "legacy-entry"
|
|
- "new-entry"
|
|
`)
|
|
cfg, err := config.LoadConfig(path)
|
|
if err != nil {
|
|
t.Fatalf("load mixed config: %v", err)
|
|
}
|
|
if got := len(cfg.GeminiKey); got != 2 {
|
|
t.Fatalf("expected 2 gemini entries, got %d: %+v", got, cfg.GeminiKey)
|
|
}
|
|
seen := make(map[string]struct{}, len(cfg.GeminiKey))
|
|
for _, entry := range cfg.GeminiKey {
|
|
if _, exists := seen[entry.APIKey]; exists {
|
|
t.Fatalf("duplicate gemini key %q after migration", entry.APIKey)
|
|
}
|
|
seen[entry.APIKey] = struct{}{}
|
|
}
|
|
provider := cfg.OpenAICompatibility[0]
|
|
if got := len(provider.APIKeyEntries); got != 2 {
|
|
t.Fatalf("expected 2 openai entries, got %d: %+v", got, provider.APIKeyEntries)
|
|
}
|
|
entrySeen := make(map[string]struct{}, len(provider.APIKeyEntries))
|
|
for _, entry := range provider.APIKeyEntries {
|
|
if _, ok := entrySeen[entry.APIKey]; ok {
|
|
t.Fatalf("duplicate openai key %q after migration", entry.APIKey)
|
|
}
|
|
entrySeen[entry.APIKey] = struct{}{}
|
|
}
|
|
})
|
|
|
|
t.Run("onlyNewFields", func(t *testing.T) {
|
|
path := writeConfig(t, `
|
|
gemini-api-key:
|
|
- api-key: "new-only"
|
|
openai-compatibility:
|
|
- name: "new-only-provider"
|
|
base-url: "https://new-only.example.com"
|
|
api-key-entries:
|
|
- api-key: "new-only-entry"
|
|
ampcode:
|
|
upstream-url: "https://amp.new"
|
|
upstream-api-key: "new-amp-key"
|
|
restrict-management-to-localhost: true
|
|
model-mappings:
|
|
- from: "a"
|
|
to: "b"
|
|
`)
|
|
cfg, err := config.LoadConfig(path)
|
|
if err != nil {
|
|
t.Fatalf("load new config: %v", err)
|
|
}
|
|
if len(cfg.GeminiKey) != 1 || cfg.GeminiKey[0].APIKey != "new-only" {
|
|
t.Fatalf("unexpected gemini entries: %+v", cfg.GeminiKey)
|
|
}
|
|
if len(cfg.OpenAICompatibility) != 1 || len(cfg.OpenAICompatibility[0].APIKeyEntries) != 1 {
|
|
t.Fatalf("unexpected openai compat entries: %+v", cfg.OpenAICompatibility)
|
|
}
|
|
if cfg.AmpCode.UpstreamURL != "https://amp.new" || cfg.AmpCode.UpstreamAPIKey != "new-amp-key" {
|
|
t.Fatalf("unexpected amp config: %+v", cfg.AmpCode)
|
|
}
|
|
})
|
|
|
|
t.Run("duplicateNamesDifferentBase", func(t *testing.T) {
|
|
path := writeConfig(t, `
|
|
openai-compatibility:
|
|
- name: "dup-provider"
|
|
base-url: "https://provider-a"
|
|
api-keys:
|
|
- "key-a"
|
|
- name: "dup-provider"
|
|
base-url: "https://provider-b"
|
|
api-keys:
|
|
- "key-b"
|
|
`)
|
|
cfg, err := config.LoadConfig(path)
|
|
if err != nil {
|
|
t.Fatalf("load duplicate config: %v", err)
|
|
}
|
|
if len(cfg.OpenAICompatibility) != 2 {
|
|
t.Fatalf("expected 2 providers, got %d", len(cfg.OpenAICompatibility))
|
|
}
|
|
for _, entry := range cfg.OpenAICompatibility {
|
|
if len(entry.APIKeyEntries) != 1 {
|
|
t.Fatalf("expected 1 key entry per provider: %+v", entry)
|
|
}
|
|
switch entry.BaseURL {
|
|
case "https://provider-a":
|
|
if entry.APIKeyEntries[0].APIKey != "key-a" {
|
|
t.Fatalf("provider-a key mismatch: %+v", entry.APIKeyEntries)
|
|
}
|
|
case "https://provider-b":
|
|
if entry.APIKeyEntries[0].APIKey != "key-b" {
|
|
t.Fatalf("provider-b key mismatch: %+v", entry.APIKeyEntries)
|
|
}
|
|
default:
|
|
t.Fatalf("unexpected provider base url: %s", entry.BaseURL)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func writeConfig(t *testing.T, content string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
if err := os.WriteFile(path, []byte(strings.TrimSpace(content)+"\n"), 0o644); err != nil {
|
|
t.Fatalf("write temp config: %v", err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func readFile(t *testing.T, path string) string {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read temp config: %v", err)
|
|
}
|
|
return string(data)
|
|
}
|