package main import "testing" func TestEndpointProvider(t *testing.T) { t.Run("ollama http endpoint registers under its name", func(t *testing.T) { p, err := endpointProvider("bigbox", "ollama|http://192.168.1.50:11434") if err != nil { t.Fatalf("unexpected error: %v", err) } if p.Name() != "bigbox" { t.Errorf("Name() = %q, want %q", p.Name(), "bigbox") } }) t.Run("openai compatible with key", func(t *testing.T) { if _, err := endpointProvider("gpu", "openai|http://gpu.lan:8000/v1|sk-x"); err != nil { t.Fatalf("unexpected error: %v", err) } }) t.Run("foreman queue registers under its name", func(t *testing.T) { p, err := endpointProvider("m1", "foreman|http://foreman-m1:8080|tok") if err != nil { t.Fatalf("unexpected error: %v", err) } // WithName(name) must win over the Foreman preset's default "foreman". if p.Name() != "m1" { t.Errorf("Name() = %q, want %q", p.Name(), "m1") } }) t.Run("foreman without token", func(t *testing.T) { if _, err := endpointProvider("m5", "foreman|http://foreman-m5:8080"); err != nil { t.Fatalf("unexpected error: %v", err) } }) t.Run("llamaswap registers under its name", func(t *testing.T) { p, err := endpointProvider("ls", "llamaswap|http://swap.lan:8080|tok") if err != nil { t.Fatalf("unexpected error: %v", err) } if p.Name() != "ls" { t.Errorf("Name() = %q, want %q", p.Name(), "ls") } }) t.Run("llamaswap without token", func(t *testing.T) { if _, err := endpointProvider("ls2", "llamaswap|http://swap.lan:8080"); err != nil { t.Fatalf("unexpected error: %v", err) } }) // All llama-swap spellings (hyphenated/TLS variants mirror majordomo's DSN // schemes) must resolve to the llamaswap provider. for _, name := range []string{"llama-swap", "llama-swaps", "llamaswaps"} { t.Run(name+" alias", func(t *testing.T) { p, err := endpointProvider("ls", name+"|https://swap.lan:8080|tok") if err != nil { t.Fatalf("unexpected error: %v", err) } if p.Name() != "ls" { t.Errorf("Name() = %q, want %q", p.Name(), "ls") } }) } for _, bad := range []string{"", "ollama", "noprovider-no-pipe", "mystery|http://x"} { t.Run("rejects "+bad, func(t *testing.T) { if _, err := endpointProvider("n", bad); err == nil { t.Errorf("endpointProvider(%q) = nil error, want error", bad) } }) } } // TestOpenAICompatProvidersResolveOnBothPaths pins the two provider switches // together. kimi and qwen are majordomo built-ins that ARE the openai client at // a different base URL, and two independent places have to know it: // resolveModel's GADFLY_BASE_URL override, and endpointProvider's // GADFLY_ENDPOINT_* parser. Adding a name to one and not the other yields a // provider that works when configured one way and errors the other, for no // reason a user could guess — which is exactly what happened here on the first // pass. Asserting both in one table is what makes the pair fail together. func TestOpenAICompatProvidersResolveOnBothPaths(t *testing.T) { for _, provider := range []string{"openai", "openai-compatible", "kimi", "qwen"} { t.Run(provider+" via GADFLY_ENDPOINT_*", func(t *testing.T) { p, err := endpointProvider("ep", provider+"|https://host.example/v1|sk-x") if err != nil { t.Fatalf("endpointProvider(%q): %v", provider, err) } if p.Name() != "ep" { t.Errorf("Name() = %q, want %q", p.Name(), "ep") } }) t.Run(provider+" via GADFLY_BASE_URL", func(t *testing.T) { t.Setenv("GADFLY_PROVIDER", provider) t.Setenv("GADFLY_BASE_URL", "https://host.example/v1") t.Setenv("GADFLY_API_KEY", "sk-x") t.Setenv("GADFLY_MODEL", "some-model") if _, err := resolveModel(); err != nil { t.Fatalf("resolveModel with GADFLY_PROVIDER=%q: %v", provider, err) } }) } } func TestBuildSpec(t *testing.T) { tests := []struct { name string provider string model string want string }{ {"bare id gets provider prefix", "ollama-cloud", "qwen3-coder:480b-cloud", "ollama-cloud/qwen3-coder:480b-cloud"}, {"bare id local ollama", "ollama", "llama3.1", "ollama/llama3.1"}, {"already has provider passes through", "ollama-cloud", "openai/gpt-4o", "openai/gpt-4o"}, {"slashed model name passes through verbatim", "openai", "openai/meta-llama/Llama-3.1", "openai/meta-llama/Llama-3.1"}, {"failover chain passes through", "ollama-cloud", "anthropic/opus-4.8,ollama-cloud/qwen3-coder:480b-cloud", "anthropic/opus-4.8,ollama-cloud/qwen3-coder:480b-cloud"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := buildSpec(tt.provider, tt.model); got != tt.want { t.Errorf("buildSpec(%q, %q) = %q, want %q", tt.provider, tt.model, got, tt.want) } }) } }