mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
Introduce formatProxyURL helper to sanitize proxy addresses before logging, stripping credentials and path components while preserving host information. Rework model hash computation to sort and deduplicate name/alias pairs with case normalization, ensuring consistent output regardless of input ordering. Add signature-based identification for anonymous OpenAI-compatible provider entries to maintain stable keys across configuration reloads. Replace direct stdout prints with structured logger calls for file change notifications.
188 lines
5.2 KiB
Go
188 lines
5.2 KiB
Go
package diff
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
)
|
|
|
|
func TestDiffOpenAICompatibility(t *testing.T) {
|
|
oldList := []config.OpenAICompatibility{
|
|
{
|
|
Name: "provider-a",
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
|
|
{APIKey: "key-a"},
|
|
},
|
|
Models: []config.OpenAICompatibilityModel{
|
|
{Name: "m1"},
|
|
},
|
|
},
|
|
}
|
|
newList := []config.OpenAICompatibility{
|
|
{
|
|
Name: "provider-a",
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
|
|
{APIKey: "key-a"},
|
|
{APIKey: "key-b"},
|
|
},
|
|
Models: []config.OpenAICompatibilityModel{
|
|
{Name: "m1"},
|
|
{Name: "m2"},
|
|
},
|
|
Headers: map[string]string{"X-Test": "1"},
|
|
},
|
|
{
|
|
Name: "provider-b",
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{APIKey: "key-b"}},
|
|
},
|
|
}
|
|
|
|
changes := DiffOpenAICompatibility(oldList, newList)
|
|
expectContains(t, changes, "provider added: provider-b (api-keys=1, models=0)")
|
|
expectContains(t, changes, "provider updated: provider-a (api-keys 1 -> 2, models 1 -> 2, headers updated)")
|
|
}
|
|
|
|
func TestDiffOpenAICompatibility_RemovedAndUnchanged(t *testing.T) {
|
|
oldList := []config.OpenAICompatibility{
|
|
{
|
|
Name: "provider-a",
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{APIKey: "key-a"}},
|
|
Models: []config.OpenAICompatibilityModel{{Name: "m1"}},
|
|
},
|
|
}
|
|
newList := []config.OpenAICompatibility{
|
|
{
|
|
Name: "provider-a",
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{APIKey: "key-a"}},
|
|
Models: []config.OpenAICompatibilityModel{{Name: "m1"}},
|
|
},
|
|
}
|
|
if changes := DiffOpenAICompatibility(oldList, newList); len(changes) != 0 {
|
|
t.Fatalf("expected no changes, got %v", changes)
|
|
}
|
|
|
|
newList = nil
|
|
changes := DiffOpenAICompatibility(oldList, newList)
|
|
expectContains(t, changes, "provider removed: provider-a (api-keys=1, models=1)")
|
|
}
|
|
|
|
func TestOpenAICompatKeyFallbacks(t *testing.T) {
|
|
entry := config.OpenAICompatibility{
|
|
BaseURL: "http://base",
|
|
Models: []config.OpenAICompatibilityModel{{Alias: "alias-only"}},
|
|
}
|
|
key, label := openAICompatKey(entry, 0)
|
|
if key != "base:http://base" || label != "http://base" {
|
|
t.Fatalf("expected base key, got %s/%s", key, label)
|
|
}
|
|
|
|
entry.BaseURL = ""
|
|
key, label = openAICompatKey(entry, 1)
|
|
if key != "alias:alias-only" || label != "alias-only" {
|
|
t.Fatalf("expected alias fallback, got %s/%s", key, label)
|
|
}
|
|
|
|
entry.Models = nil
|
|
key, label = openAICompatKey(entry, 2)
|
|
if key != "index:2" || label != "entry-3" {
|
|
t.Fatalf("expected index fallback, got %s/%s", key, label)
|
|
}
|
|
}
|
|
|
|
func TestOpenAICompatKey_UsesName(t *testing.T) {
|
|
entry := config.OpenAICompatibility{Name: "My-Provider"}
|
|
key, label := openAICompatKey(entry, 0)
|
|
if key != "name:My-Provider" || label != "My-Provider" {
|
|
t.Fatalf("expected name key, got %s/%s", key, label)
|
|
}
|
|
}
|
|
|
|
func TestOpenAICompatKey_SignatureFallbackWhenOnlyAPIKeys(t *testing.T) {
|
|
entry := config.OpenAICompatibility{
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{{APIKey: "k1"}, {APIKey: "k2"}},
|
|
}
|
|
key, label := openAICompatKey(entry, 0)
|
|
if !strings.HasPrefix(key, "sig:") || !strings.HasPrefix(label, "compat-") {
|
|
t.Fatalf("expected signature key, got %s/%s", key, label)
|
|
}
|
|
}
|
|
|
|
func TestOpenAICompatSignature_EmptyReturnsEmpty(t *testing.T) {
|
|
if got := openAICompatSignature(config.OpenAICompatibility{}); got != "" {
|
|
t.Fatalf("expected empty signature, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestOpenAICompatSignature_StableAndNormalized(t *testing.T) {
|
|
a := config.OpenAICompatibility{
|
|
Name: " Provider ",
|
|
BaseURL: "http://base",
|
|
Models: []config.OpenAICompatibilityModel{
|
|
{Name: "m1"},
|
|
{Name: " "},
|
|
{Alias: "A1"},
|
|
},
|
|
Headers: map[string]string{
|
|
"X-Test": "1",
|
|
" ": "ignored",
|
|
},
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
|
|
{APIKey: "k1"},
|
|
{APIKey: " "},
|
|
},
|
|
}
|
|
b := config.OpenAICompatibility{
|
|
Name: "provider",
|
|
BaseURL: "http://base",
|
|
Models: []config.OpenAICompatibilityModel{
|
|
{Alias: "a1"},
|
|
{Name: "m1"},
|
|
},
|
|
Headers: map[string]string{
|
|
"x-test": "2",
|
|
},
|
|
APIKeyEntries: []config.OpenAICompatibilityAPIKey{
|
|
{APIKey: "k2"},
|
|
},
|
|
}
|
|
|
|
sigA := openAICompatSignature(a)
|
|
sigB := openAICompatSignature(b)
|
|
if sigA == "" || sigB == "" {
|
|
t.Fatalf("expected non-empty signatures, got %q / %q", sigA, sigB)
|
|
}
|
|
if sigA != sigB {
|
|
t.Fatalf("expected normalized signatures to match, got %s / %s", sigA, sigB)
|
|
}
|
|
|
|
c := b
|
|
c.Models = append(c.Models, config.OpenAICompatibilityModel{Name: "m2"})
|
|
if sigC := openAICompatSignature(c); sigC == sigB {
|
|
t.Fatalf("expected signature to change when models change, got %s", sigC)
|
|
}
|
|
}
|
|
|
|
func TestCountOpenAIModelsSkipsBlanks(t *testing.T) {
|
|
models := []config.OpenAICompatibilityModel{
|
|
{Name: "m1"},
|
|
{Name: ""},
|
|
{Alias: ""},
|
|
{Name: " "},
|
|
{Alias: "a1"},
|
|
}
|
|
if got := countOpenAIModels(models); got != 2 {
|
|
t.Fatalf("expected 2 counted models, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestOpenAICompatKeyUsesModelNameWhenAliasEmpty(t *testing.T) {
|
|
entry := config.OpenAICompatibility{
|
|
Models: []config.OpenAICompatibilityModel{{Name: "model-name"}},
|
|
}
|
|
key, label := openAICompatKey(entry, 5)
|
|
if key != "alias:model-name" || label != "model-name" {
|
|
t.Fatalf("expected model-name fallback, got %s/%s", key, label)
|
|
}
|
|
}
|