204 lines
5.8 KiB
Go
204 lines
5.8 KiB
Go
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
|
|
||
|
|
package config
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestLoadDefaults(t *testing.T) {
|
||
|
|
// Clear all FAVORITTER_ env vars to test defaults.
|
||
|
|
for _, key := range []string{
|
||
|
|
"FAVORITTER_DB_PATH", "FAVORITTER_LISTEN", "FAVORITTER_BASE_PATH",
|
||
|
|
"FAVORITTER_EXTERNAL_URL", "FAVORITTER_UPLOAD_DIR", "FAVORITTER_MAX_UPLOAD_SIZE",
|
||
|
|
"FAVORITTER_SESSION_LIFETIME", "FAVORITTER_ARGON2_MEMORY", "FAVORITTER_ARGON2_TIME",
|
||
|
|
"FAVORITTER_ARGON2_PARALLELISM", "FAVORITTER_RATE_LIMIT", "FAVORITTER_ADMIN_USERNAME",
|
||
|
|
"FAVORITTER_ADMIN_PASSWORD", "FAVORITTER_SITE_NAME", "FAVORITTER_DEV_MODE",
|
||
|
|
"FAVORITTER_TRUSTED_PROXIES",
|
||
|
|
} {
|
||
|
|
t.Setenv(key, "")
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg := Load()
|
||
|
|
|
||
|
|
if cfg.DBPath != "./data/favoritter.db" {
|
||
|
|
t.Errorf("DBPath = %q, want default", cfg.DBPath)
|
||
|
|
}
|
||
|
|
if cfg.Listen != ":8080" {
|
||
|
|
t.Errorf("Listen = %q, want :8080", cfg.Listen)
|
||
|
|
}
|
||
|
|
if cfg.BasePath != "" {
|
||
|
|
t.Errorf("BasePath = %q, want empty (root)", cfg.BasePath)
|
||
|
|
}
|
||
|
|
if cfg.MaxUploadSize != 10<<20 {
|
||
|
|
t.Errorf("MaxUploadSize = %d, want %d", cfg.MaxUploadSize, 10<<20)
|
||
|
|
}
|
||
|
|
if cfg.SessionLifetime != 720*time.Hour {
|
||
|
|
t.Errorf("SessionLifetime = %v, want 720h", cfg.SessionLifetime)
|
||
|
|
}
|
||
|
|
if cfg.SiteName != "Favoritter" {
|
||
|
|
t.Errorf("SiteName = %q, want Favoritter", cfg.SiteName)
|
||
|
|
}
|
||
|
|
if cfg.DevMode {
|
||
|
|
t.Error("DevMode should be false by default")
|
||
|
|
}
|
||
|
|
if cfg.RateLimit != 60 {
|
||
|
|
t.Errorf("RateLimit = %d, want 60", cfg.RateLimit)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLoadFromEnv(t *testing.T) {
|
||
|
|
t.Setenv("FAVORITTER_DB_PATH", "/custom/db.sqlite")
|
||
|
|
t.Setenv("FAVORITTER_LISTEN", ":9090")
|
||
|
|
t.Setenv("FAVORITTER_BASE_PATH", "/faves")
|
||
|
|
t.Setenv("FAVORITTER_EXTERNAL_URL", "https://faves.example.com/")
|
||
|
|
t.Setenv("FAVORITTER_UPLOAD_DIR", "/custom/uploads")
|
||
|
|
t.Setenv("FAVORITTER_MAX_UPLOAD_SIZE", "20971520")
|
||
|
|
t.Setenv("FAVORITTER_SESSION_LIFETIME", "48h")
|
||
|
|
t.Setenv("FAVORITTER_ARGON2_MEMORY", "131072")
|
||
|
|
t.Setenv("FAVORITTER_ARGON2_TIME", "5")
|
||
|
|
t.Setenv("FAVORITTER_ARGON2_PARALLELISM", "4")
|
||
|
|
t.Setenv("FAVORITTER_RATE_LIMIT", "100")
|
||
|
|
t.Setenv("FAVORITTER_ADMIN_USERNAME", "admin")
|
||
|
|
t.Setenv("FAVORITTER_ADMIN_PASSWORD", "secret")
|
||
|
|
t.Setenv("FAVORITTER_SITE_NAME", "Mine Favoritter")
|
||
|
|
t.Setenv("FAVORITTER_DEV_MODE", "true")
|
||
|
|
t.Setenv("FAVORITTER_TRUSTED_PROXIES", "10.0.0.0/8,192.168.1.0/24")
|
||
|
|
|
||
|
|
cfg := Load()
|
||
|
|
|
||
|
|
if cfg.DBPath != "/custom/db.sqlite" {
|
||
|
|
t.Errorf("DBPath = %q", cfg.DBPath)
|
||
|
|
}
|
||
|
|
if cfg.Listen != ":9090" {
|
||
|
|
t.Errorf("Listen = %q", cfg.Listen)
|
||
|
|
}
|
||
|
|
if cfg.BasePath != "/faves" {
|
||
|
|
t.Errorf("BasePath = %q, want /faves", cfg.BasePath)
|
||
|
|
}
|
||
|
|
// External URL should have trailing slash stripped.
|
||
|
|
if cfg.ExternalURL != "https://faves.example.com" {
|
||
|
|
t.Errorf("ExternalURL = %q, want trailing slash stripped", cfg.ExternalURL)
|
||
|
|
}
|
||
|
|
if cfg.MaxUploadSize != 20971520 {
|
||
|
|
t.Errorf("MaxUploadSize = %d", cfg.MaxUploadSize)
|
||
|
|
}
|
||
|
|
if cfg.SessionLifetime != 48*time.Hour {
|
||
|
|
t.Errorf("SessionLifetime = %v", cfg.SessionLifetime)
|
||
|
|
}
|
||
|
|
if cfg.Argon2Memory != 131072 {
|
||
|
|
t.Errorf("Argon2Memory = %d", cfg.Argon2Memory)
|
||
|
|
}
|
||
|
|
if cfg.Argon2Time != 5 {
|
||
|
|
t.Errorf("Argon2Time = %d", cfg.Argon2Time)
|
||
|
|
}
|
||
|
|
if cfg.Argon2Parallelism != 4 {
|
||
|
|
t.Errorf("Argon2Parallelism = %d", cfg.Argon2Parallelism)
|
||
|
|
}
|
||
|
|
if cfg.RateLimit != 100 {
|
||
|
|
t.Errorf("RateLimit = %d", cfg.RateLimit)
|
||
|
|
}
|
||
|
|
if cfg.AdminUsername != "admin" {
|
||
|
|
t.Errorf("AdminUsername = %q", cfg.AdminUsername)
|
||
|
|
}
|
||
|
|
if cfg.AdminPassword != "secret" {
|
||
|
|
t.Errorf("AdminPassword = %q", cfg.AdminPassword)
|
||
|
|
}
|
||
|
|
if cfg.SiteName != "Mine Favoritter" {
|
||
|
|
t.Errorf("SiteName = %q", cfg.SiteName)
|
||
|
|
}
|
||
|
|
if !cfg.DevMode {
|
||
|
|
t.Error("DevMode should be true")
|
||
|
|
}
|
||
|
|
if len(cfg.TrustedProxies) != 2 {
|
||
|
|
t.Errorf("TrustedProxies: got %d, want 2", len(cfg.TrustedProxies))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTrustedProxiesParsing(t *testing.T) {
|
||
|
|
t.Setenv("FAVORITTER_TRUSTED_PROXIES", "10.0.0.0/8, 192.168.0.0/16, 127.0.0.1")
|
||
|
|
|
||
|
|
cfg := Load()
|
||
|
|
|
||
|
|
if len(cfg.TrustedProxies) != 3 {
|
||
|
|
t.Fatalf("TrustedProxies: got %d, want 3", len(cfg.TrustedProxies))
|
||
|
|
}
|
||
|
|
// 127.0.0.1 without CIDR should become 127.0.0.1/32.
|
||
|
|
last := cfg.TrustedProxies[2]
|
||
|
|
if ones, _ := last.Mask.Size(); ones != 32 {
|
||
|
|
t.Errorf("bare IP mask = /%d, want /32", ones)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTrustedProxiesInvalid(t *testing.T) {
|
||
|
|
t.Setenv("FAVORITTER_TRUSTED_PROXIES", "not-an-ip, 10.0.0.0/8")
|
||
|
|
|
||
|
|
cfg := Load()
|
||
|
|
|
||
|
|
// Invalid entries are skipped; valid ones remain.
|
||
|
|
if len(cfg.TrustedProxies) != 1 {
|
||
|
|
t.Errorf("TrustedProxies: got %d, want 1 (invalid skipped)", len(cfg.TrustedProxies))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBasePathNormalization(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
input string
|
||
|
|
want string
|
||
|
|
}{
|
||
|
|
{"/", ""},
|
||
|
|
{"", ""},
|
||
|
|
{"/faves", "/faves"},
|
||
|
|
{"/faves/", "/faves"},
|
||
|
|
{"faves", "/faves"},
|
||
|
|
{"/sub/path/", "/sub/path"},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
got := normalizePath(tt.input)
|
||
|
|
if got != tt.want {
|
||
|
|
t.Errorf("normalizePath(%q) = %q, want %q", tt.input, got, tt.want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDevModeFlag(t *testing.T) {
|
||
|
|
t.Setenv("FAVORITTER_DEV_MODE", "true")
|
||
|
|
cfg := Load()
|
||
|
|
if !cfg.DevMode {
|
||
|
|
t.Error("DevMode should be true when env is 'true'")
|
||
|
|
}
|
||
|
|
|
||
|
|
t.Setenv("FAVORITTER_DEV_MODE", "false")
|
||
|
|
cfg = Load()
|
||
|
|
if cfg.DevMode {
|
||
|
|
t.Error("DevMode should be false when env is 'false'")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExternalHostname(t *testing.T) {
|
||
|
|
cfg := &Config{ExternalURL: "https://faves.example.com/base"}
|
||
|
|
if got := cfg.ExternalHostname(); got != "faves.example.com" {
|
||
|
|
t.Errorf("ExternalHostname = %q, want faves.example.com", got)
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg = &Config{}
|
||
|
|
if got := cfg.ExternalHostname(); got != "" {
|
||
|
|
t.Errorf("empty ExternalURL: ExternalHostname = %q, want empty", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBaseURL(t *testing.T) {
|
||
|
|
// With external URL configured.
|
||
|
|
cfg := &Config{ExternalURL: "https://faves.example.com"}
|
||
|
|
if got := cfg.BaseURL("localhost:8080"); got != "https://faves.example.com" {
|
||
|
|
t.Errorf("BaseURL with external = %q", got)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Without external URL — falls back to request host.
|
||
|
|
cfg = &Config{BasePath: "/faves"}
|
||
|
|
if got := cfg.BaseURL("myhost.local:8080"); got != "https://myhost.local:8080/faves" {
|
||
|
|
t.Errorf("BaseURL fallback = %q", got)
|
||
|
|
}
|
||
|
|
}
|