226 lines
6.1 KiB
Go
226 lines
6.1 KiB
Go
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)
|
|
}
|
|
}
|