package core import ( "net" "path/filepath" "strings" "testing" ) func TestLoadConfigDefaults(t *testing.T) { // No environment set: every field should have a usable default. cfg, err := LoadConfig() if err != nil { t.Fatalf("LoadConfig: %v", err) } if cfg.Listen != ":7788" { t.Errorf("Listen = %q, want :7788", cfg.Listen) } if !filepath.IsAbs(cfg.DataDir) { t.Errorf("DataDir = %q, want absolute", cfg.DataDir) } if cfg.DBPath != filepath.Join(cfg.DataDir, "vellum.db") { t.Errorf("DBPath = %q, want /vellum.db", cfg.DBPath) } if cfg.LogLevel != "info" { t.Errorf("LogLevel = %q, want info", cfg.LogLevel) } } func TestLoadConfigFromEnv(t *testing.T) { t.Setenv("VELLUM_LISTEN", "127.0.0.1:9999") t.Setenv("VELLUM_DATA_DIR", "/tmp/vellum-data") t.Setenv("VELLUM_MEDIA_DIRS", "/media/a:/media/b") t.Setenv("VELLUM_SESSION_TTL", "48h") cfg, err := LoadConfig() if err != nil { t.Fatalf("LoadConfig: %v", err) } if cfg.Listen != "127.0.0.1:9999" { t.Errorf("Listen = %q", cfg.Listen) } if cfg.DataDir != "/tmp/vellum-data" { t.Errorf("DataDir = %q", cfg.DataDir) } if len(cfg.MediaDirs) != 2 || cfg.MediaDirs[0] != "/media/a" { t.Errorf("MediaDirs = %v", cfg.MediaDirs) } if cfg.SessionTTL.Hours() != 48 { t.Errorf("SessionTTL = %v, want 48h", cfg.SessionTTL) } } func TestLoadConfigTrustedProxies(t *testing.T) { t.Setenv("VELLUM_TRUSTED_PROXIES", "172.16.0.0/12, 127.0.0.1/32") cfg, err := LoadConfig() if err != nil { t.Fatalf("LoadConfig: %v", err) } if len(cfg.TrustedProxies) != 2 { t.Fatalf("TrustedProxies = %v, want 2 entries", cfg.TrustedProxies) } if !cfg.TrustedProxies[0].Contains(net.ParseIP("172.20.0.5")) { t.Errorf("TrustedProxies[0] = %v, want it to contain 172.20.0.5", cfg.TrustedProxies[0]) } if cfg.TrustedProxies[1].Contains(net.ParseIP("172.20.0.5")) { t.Errorf("TrustedProxies[1] = %v, want it to contain only 127.0.0.1", cfg.TrustedProxies[1]) } } func TestLoadConfigRejectsBadTrustedProxy(t *testing.T) { // A malformed entry must fail the boot: silently dropping it would quietly // narrow the trust boundary and the operator would never know. t.Setenv("VELLUM_TRUSTED_PROXIES", "172.16.0.0/12, not-a-cidr") _, err := LoadConfig() if err == nil { t.Fatal("expected error for malformed VELLUM_TRUSTED_PROXIES") } if !strings.Contains(err.Error(), "not-a-cidr") { t.Errorf("error %q should name the offending entry", err) } } func TestCookieSecureDefaultsFromPublicURL(t *testing.T) { tests := []struct { name string public string cookie string // empty means unset want bool }{ {name: "https origin secures the cookie", public: "https://media.example.com", want: true}, {name: "http origin leaves it off", public: "http://media.example.com", want: false}, {name: "unset origin leaves it off", want: false}, {name: "explicit false overrides https", public: "https://media.example.com", cookie: "false", want: false}, {name: "explicit true overrides http", public: "http://media.example.com", cookie: "true", want: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Setenv("VELLUM_PUBLIC_URL", tc.public) // An empty value reads as unset to the env helpers, and t.Setenv // restores the ambient value either way. t.Setenv("VELLUM_COOKIE_SECURE", tc.cookie) cfg, err := LoadConfig() if err != nil { t.Fatalf("LoadConfig: %v", err) } if cfg.PublicURL != tc.public { t.Errorf("PublicURL = %q, want %q", cfg.PublicURL, tc.public) } if cfg.CookieSecure != tc.want { t.Errorf("CookieSecure = %v, want %v", cfg.CookieSecure, tc.want) } }) } } func TestValidateRejectsMissingMediaDir(t *testing.T) { cfg := Config{ DataDir: t.TempDir(), ConfigDir: t.TempDir(), MediaDirs: []string{"/definitely/not/a/real/path"}, } err := cfg.Validate() if err == nil { t.Fatal("expected error for missing media dir") } if !strings.Contains(err.Error(), "/definitely/not/a/real/path") { t.Errorf("error %q should name the offending path", err) } } func TestValidateCreatesDirs(t *testing.T) { base := t.TempDir() cfg := Config{ DataDir: filepath.Join(base, "data"), ConfigDir: filepath.Join(base, "config"), } if err := cfg.Validate(); err != nil { t.Fatalf("Validate: %v", err) } }