| 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) | |
| } | |
| } | |