Add immutable backend registry foundation
This commit is contained in:
256
internal/backend/registry_test.go
Normal file
256
internal/backend/registry_test.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package backend_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"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 TestNewRegistryValidatesExtraParameters(t *testing.T) {
|
||||
cyclic := map[string]any{}
|
||||
cyclic["self"] = cyclic
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
extraParams map[string]any
|
||||
}{
|
||||
{name: "empty top-level key", extraParams: map[string]any{"": true}},
|
||||
{name: "empty nested key", extraParams: map[string]any{"nested": map[string]int{"": 1}}},
|
||||
{name: "non-string map key", extraParams: map[string]any{"nested": map[int]string{1: "one"}}},
|
||||
{name: "unsupported value", extraParams: map[string]any{"value": make(chan int)}},
|
||||
{name: "cyclic value", extraParams: map[string]any{"value": cyclic}},
|
||||
{name: "NaN", extraParams: map[string]any{"value": math.NaN()}},
|
||||
{name: "positive infinity", extraParams: map[string]any{"value": math.Inf(1)}},
|
||||
{name: "unsafe integer", extraParams: map[string]any{"value": int64(1 << 53)}},
|
||||
{name: "invalid JSON number", extraParams: map[string]any{"value": json.Number("not-a-number")}},
|
||||
}
|
||||
for _, key := range []string{
|
||||
"model",
|
||||
"session_id",
|
||||
"messages",
|
||||
"temperature",
|
||||
"max_tokens",
|
||||
"top_p",
|
||||
"service_tier",
|
||||
"reasoning_effort",
|
||||
"response_format",
|
||||
} {
|
||||
tests = append(tests, struct {
|
||||
name string
|
||||
extraParams map[string]any
|
||||
}{
|
||||
name: "reserved key " + key,
|
||||
extraParams: map[string]any{key: true},
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user