155 lines
4.9 KiB
Go
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)
|
|
}
|
|
}
|