package backend_test import ( "errors" "strings" "testing" "gitea.maximumdirect.net/eric/promptkit/internal/backend" "gitea.maximumdirect.net/eric/promptkit/internal/domain" ) const validEndpoint = "https://backend.example/v1" func TestRegistryIncludesExactOpenRouterDefinition(t *testing.T) { registry, err := backend.NewRegistry(nil) if err != nil { t.Fatalf("construct registry: %v", err) } definition, err := registry.GetBackend(backend.OpenRouterID) if err != nil { t.Fatalf("look up OpenRouter: %v", err) } if definition.ID != "openrouter" || definition.Endpoint != "https://openrouter.ai/api/v1" || definition.APIKeyEnv != "OPENROUTER_API_KEY" || definition.ExtraParams != nil { t.Fatalf("unexpected OpenRouter definition: %#v", definition) } } func TestRegistryNormalizesUniqueAdditionsAndIsolatesMutations(t *testing.T) { nested := map[string]int{"limit": 2} extraParams := map[string]any{ "count": int64(7), "nested": nested, } registry, err := backend.NewRegistry([]domain.Backend{ { ID: " custom ", Endpoint: " https://custom.example/openai/v1 ", APIKeyEnv: " CUSTOM_API_KEY ", ExtraParams: extraParams, }, { ID: "Custom", Endpoint: validEndpoint, }, }) if err != nil { t.Fatalf("construct registry: %v", err) } nested["limit"] = 99 extraParams["added"] = true got, err := registry.GetBackend("custom") if err != nil { t.Fatalf("look up custom backend: %v", err) } if got.ID != "custom" || got.Endpoint != "https://custom.example/openai/v1" || got.APIKeyEnv != "CUSTOM_API_KEY" { t.Fatalf("unexpected normalized definition: %#v", got) } if count, ok := got.ExtraParams["count"].(int64); !ok || count != 7 { t.Fatalf("integer type or value changed: %#v", got.ExtraParams["count"]) } gotNested, ok := got.ExtraParams["nested"].(map[string]int) if !ok || gotNested["limit"] != 2 { t.Fatalf("container type or value changed: %#v", got.ExtraParams["nested"]) } if _, exists := got.ExtraParams["added"]; exists { t.Fatalf("registry retained caller map: %#v", got.ExtraParams) } gotNested["limit"] = 100 got.ExtraParams["added"] = true again, err := registry.GetBackend("custom") if err != nil { t.Fatalf("look up custom backend again: %v", err) } if again.ExtraParams["nested"].(map[string]int)["limit"] != 2 { t.Fatalf("lookup exposed registry nested map: %#v", again.ExtraParams) } if _, exists := again.ExtraParams["added"]; exists { t.Fatalf("lookup exposed registry map: %#v", again.ExtraParams) } if _, err := registry.GetBackend("Custom"); err != nil { t.Fatalf("backend IDs should be case-sensitive: %v", err) } } func TestNewRegistryRejectsDuplicateIDs(t *testing.T) { tests := []struct { name string additions []domain.Backend wantID string }{ { name: "built-in collision after normalization", additions: []domain.Backend{{ ID: " openrouter ", }}, wantID: "openrouter", }, { name: "consumer collision after normalization", additions: []domain.Backend{ {ID: "custom", Endpoint: validEndpoint}, {ID: " custom ", Endpoint: "https://other.example/v1"}, }, wantID: "custom", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := backend.NewRegistry(tc.additions) if err == nil { t.Fatal("expected duplicate ID error") } if !strings.Contains(err.Error(), tc.wantID) { t.Fatalf("expected error to identify %q, got %v", tc.wantID, err) } }) } } func TestNewRegistryValidatesIDs(t *testing.T) { for _, id := range []string{"", " \t\n "} { t.Run(id, func(t *testing.T) { _, err := backend.NewRegistry([]domain.Backend{{ ID: id, Endpoint: validEndpoint, }}) if err == nil { t.Fatal("expected blank ID error") } }) } } func TestNewRegistryValidatesEndpoints(t *testing.T) { tests := []struct { name string endpoint string }{ {name: "blank", endpoint: ""}, {name: "relative", endpoint: "/v1"}, {name: "missing host", endpoint: "https:///v1"}, {name: "unsupported scheme", endpoint: "ftp://backend.example/v1"}, {name: "user information", endpoint: "https://user@backend.example/v1"}, {name: "query", endpoint: "https://backend.example/v1?mode=chat"}, {name: "empty query", endpoint: "https://backend.example/v1?"}, {name: "fragment", endpoint: "https://backend.example/v1#chat"}, {name: "empty fragment", endpoint: "https://backend.example/v1#"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := backend.NewRegistry([]domain.Backend{{ ID: "custom", Endpoint: tc.endpoint, }}) if err == nil { t.Fatal("expected invalid endpoint error") } }) } } func TestNewRegistryValidatesEnvironmentVariableNames(t *testing.T) { for _, name := range []string{"1API_KEY", "API-KEY", "API KEY", "ÅPI_KEY"} { t.Run(name, func(t *testing.T) { _, err := backend.NewRegistry([]domain.Backend{{ ID: "custom", Endpoint: validEndpoint, APIKeyEnv: name, }}) if err == nil { t.Fatal("expected invalid environment-variable name error") } }) } } func TestNewRegistryRejectsInvalidAndReservedExtraParameters(t *testing.T) { tests := []struct { name string extraParams map[string]any }{ {name: "unsupported value", extraParams: map[string]any{"value": make(chan int)}}, {name: "reserved key", extraParams: map[string]any{"model": "override"}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := backend.NewRegistry([]domain.Backend{{ ID: "custom", Endpoint: validEndpoint, ExtraParams: tc.extraParams, }}) if err == nil { t.Fatal("expected invalid extra parameters error") } }) } } func TestRegistryLookupReportsNotFound(t *testing.T) { registry, err := backend.NewRegistry(nil) if err != nil { t.Fatalf("construct registry: %v", err) } _, err = registry.GetBackend("missing") if !errors.Is(err, backend.ErrBackendNotFound) { t.Fatalf("expected ErrBackendNotFound, got %v", err) } if !strings.Contains(err.Error(), "missing") { t.Fatalf("expected error to identify backend, got %v", err) } }