Files
2026-09-11 06:14:38 +02:00

155 lines
4.9 KiB
Go

package main
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/example/ollama-fair-gateway/internal/config"
"github.com/example/ollama-fair-gateway/internal/state"
)
func writeBootstrapConfig(t *testing.T, dataDir string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "config.json")
body := `{
"auth":{"api_keys":[{"name":"admin","key":"01234567890123456789012345678901","tenant":"t","subject":"s","scopes":["gateway:admin"]}]},
"workers":[{"name":"remote","url":"http://10.2.10.48:11434","local_system_stats":true}],
"ui":{"enabled":true,"path":"/admin","title":"Bootstrap"},
"storage":{"data_dir":` + quoteJSON(dataDir) + `,"config_file":"gateway-config.json"}
}`
if err := os.WriteFile(path, []byte(body), 0600); err != nil {
t.Fatal(err)
}
return path
}
func quoteJSON(s string) string {
b, _ := json.Marshal(s)
return string(b)
}
func TestLoadEffectiveConfigUsesPersistentOverrideButBootstrapSecrets(t *testing.T) {
dataDir := t.TempDir()
path := writeBootstrapConfig(t, dataDir)
base, err := config.Load(path)
if err != nil {
t.Fatal(err)
}
persistent := *base
persistent.UI.Title = "Persistent"
store := state.NewConfigStore(state.Resolve(base.Storage).Config)
if err := store.Save(&persistent); err != nil {
t.Fatal(err)
}
loaded, err := loadEffectiveConfig(path)
if err != nil {
t.Fatal(err)
}
if !loaded.Persistent || loaded.Config.UI.Title != "Persistent" {
t.Fatalf("persistent override not loaded: %#v", loaded)
}
if got := loaded.Config.Auth.APIKeys[0].Key; got != "01234567890123456789012345678901" {
t.Fatalf("bootstrap secret was not restored, got %q", got)
}
}
func TestRunConfigCheckReportsWritableStorageAndWarning(t *testing.T) {
dataDir := filepath.Join(t.TempDir(), "state")
path := writeBootstrapConfig(t, dataDir)
var out bytes.Buffer
if err := runConfigCheck(path, &out); err != nil {
t.Fatal(err)
}
var got configCheckResult
if err := json.Unmarshal(out.Bytes(), &got); err != nil {
t.Fatal(err)
}
if got.Status != "ok" || !got.StorageWritable || got.Workers != 1 {
t.Fatalf("unexpected result: %+v", got)
}
if len(got.Warnings) == 0 || !strings.Contains(strings.Join(got.Warnings, "\n"), "local_system_stats=true") {
t.Fatalf("expected remote local-system-stats warning, got %#v", got.Warnings)
}
}
func TestRunProbe(t *testing.T) {
ok := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
defer ok.Close()
if err := runProbe(ok.URL, time.Second); err != nil {
t.Fatal(err)
}
bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not ready", http.StatusServiceUnavailable)
}))
defer bad.Close()
if err := runProbe(bad.URL, time.Second); err == nil || !strings.Contains(err.Error(), "503") {
t.Fatalf("expected HTTP 503 probe error, got %v", err)
}
}
func TestComposeDoesNotRepeatEntrypointAndHasHealthcheck(t *testing.T) {
b, err := os.ReadFile("../../docker-compose.yml")
if err != nil {
t.Fatal(err)
}
s := string(b)
if strings.Contains(s, `command: ["/ollama-gateway"`) {
t.Fatal("compose command must not repeat the image ENTRYPOINT")
}
if !strings.Contains(s, `command: ["-config"`) {
t.Fatal("compose command must pass -config as an ENTRYPOINT argument")
}
if !strings.Contains(s, "healthcheck:") || !strings.Contains(s, "/healthz") {
t.Fatal("compose must define a liveness healthcheck")
}
}
func TestConfigWarningsFlagForwardedBypassAndBroadTrustedProxy(t *testing.T) {
cfg := &config.Config{
Auth: config.AuthConfig{
IPBypassUseForwardedIP: true,
TrustedProxies: []string{"10.0.0.0/8", "127.0.0.1/8"},
IPBypass: []config.IPBypassConfig{{
CIDRs: []string{"127.0.0.1/32"},
Tenant: "local",
}},
},
Storage: config.StorageConfig{DataDir: "/var/lib/ollama-gateway"},
}
warnings := strings.Join(configWarnings(cfg), "\n")
if !strings.Contains(warnings, "ip_bypass_use_forwarded_ip=true") {
t.Fatalf("missing forwarded bypass warning: %s", warnings)
}
if !strings.Contains(warnings, `broad CIDR "10.0.0.0/8"`) {
t.Fatalf("missing broad trusted proxy warning: %s", warnings)
}
if strings.Contains(warnings, `broad CIDR "127.0.0.1/8"`) {
t.Fatalf("loopback trusted proxy should not be flagged as broad: %s", warnings)
}
}
func TestConfigWarningsFlagUnlimitedContextPolicy(t *testing.T) {
cfg := &config.Config{
ModelCapabilities: config.ModelCapabilitiesConfig{Context: config.ContextPolicyConfig{MaxRequestedTokens: -1, DefaultWorkerTokens: -1}},
Storage: config.StorageConfig{DataDir: "/var/lib/ollama-gateway"},
}
warnings := strings.Join(configWarnings(cfg), "\n")
if !strings.Contains(warnings, "max_requested_tokens=-1") {
t.Fatalf("missing unlimited context cap warning: %s", warnings)
}
if !strings.Contains(warnings, "default_worker_tokens=-1") {
t.Fatalf("missing model-max fallback warning: %s", warnings)
}
}