package config import ( "log/slog" "testing" "time" ) func setRequired(t *testing.T) { t.Helper() t.Setenv("WIKIJS_URL", "https://wiki.example.com") t.Setenv("WIKIJS_API_KEY", "secret") } func TestLoadDefaults(t *testing.T) { setRequired(t) t.Setenv("WIKIJS_ENABLE_EDIT", "") t.Setenv("WIKIJS_TIMEOUT", "") t.Setenv("MCP_TRANSPORT", "") t.Setenv("MCP_ADDR", "") t.Setenv("MCP_PATH", "") t.Setenv("MCP_STATELESS", "") t.Setenv("MCP_AUTH_TOKEN", "") t.Setenv("LOG_LEVEL", "") cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.Transport != TransportStdio { t.Errorf("Transport = %q, want %q", cfg.Transport, TransportStdio) } if cfg.Addr != DefaultAddr { t.Errorf("Addr = %q, want %q", cfg.Addr, DefaultAddr) } if cfg.Path != DefaultPath { t.Errorf("Path = %q, want %q", cfg.Path, DefaultPath) } if cfg.Timeout != DefaultTimeout { t.Errorf("Timeout = %v, want %v", cfg.Timeout, DefaultTimeout) } if cfg.EnableEdit { t.Error("EnableEdit = true, want false") } if cfg.Stateless { t.Error("Stateless = true, want false") } if cfg.LogLevel != slog.LevelInfo { t.Errorf("LogLevel = %v, want info", cfg.LogLevel) } if err := cfg.Validate(); err != nil { t.Errorf("Validate() error = %v", err) } } func TestLoadCustomValues(t *testing.T) { setRequired(t) t.Setenv("WIKIJS_ENABLE_EDIT", "true") t.Setenv("WIKIJS_TIMEOUT", "5s") t.Setenv("MCP_TRANSPORT", "HTTP") t.Setenv("MCP_ADDR", "127.0.0.1:9000") t.Setenv("MCP_PATH", "/mcp/") t.Setenv("MCP_STATELESS", "1") t.Setenv("MCP_AUTH_TOKEN", "token") t.Setenv("LOG_LEVEL", "debug") cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.Transport != TransportHTTP { t.Errorf("Transport = %q, want %q", cfg.Transport, TransportHTTP) } if cfg.Addr != "127.0.0.1:9000" { t.Errorf("Addr = %q", cfg.Addr) } if cfg.Path != "/mcp/" { t.Errorf("Path = %q", cfg.Path) } if cfg.Timeout != 5*time.Second { t.Errorf("Timeout = %v", cfg.Timeout) } if !cfg.EnableEdit || !cfg.Stateless { t.Errorf("EnableEdit = %v, Stateless = %v, want both true", cfg.EnableEdit, cfg.Stateless) } if cfg.AuthToken != "token" { t.Errorf("AuthToken = %q", cfg.AuthToken) } if cfg.LogLevel != slog.LevelDebug { t.Errorf("LogLevel = %v, want debug", cfg.LogLevel) } } func TestLoadRequired(t *testing.T) { tests := []struct { name string url string apiKey string wantErr bool }{ {name: "missing url", apiKey: "secret", wantErr: true}, {name: "missing key", url: "https://wiki.example.com", wantErr: true}, {name: "present", url: "https://wiki.example.com", apiKey: "secret"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("WIKIJS_URL", tt.url) t.Setenv("WIKIJS_API_KEY", tt.apiKey) cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr { t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr) } }) } } func TestLoadInvalidValues(t *testing.T) { tests := []struct { name string key string value string }{ {name: "edit bool", key: "WIKIJS_ENABLE_EDIT", value: "maybe"}, {name: "stateless bool", key: "MCP_STATELESS", value: "maybe"}, {name: "timeout", key: "WIKIJS_TIMEOUT", value: "soon"}, {name: "log level", key: "LOG_LEVEL", value: "verbose"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { setRequired(t) t.Setenv(tt.key, tt.value) if _, err := Load(); err == nil { t.Fatal("Load() error = nil, want error") } }) } } func TestValidateHTTPAuth(t *testing.T) { tests := []struct { name string addr string token string wantErr bool }{ {name: "all interfaces without token", addr: ":8080", wantErr: true}, {name: "all interfaces with token", addr: ":8080", token: "secret"}, {name: "ipv4 any without token", addr: "0.0.0.0:8080", wantErr: true}, {name: "ipv4 loopback", addr: "127.0.0.1:8080"}, {name: "ipv6 loopback", addr: "[::1]:8080"}, {name: "localhost", addr: "localhost:8080"}, {name: "remote hostname without token", addr: "wiki.internal:8080", wantErr: true}, {name: "missing port", addr: "127.0.0.1", wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := Config{ WikiJSURL: "https://wiki.example.com", WikiJSAPIKey: "secret", Transport: TransportHTTP, Addr: tt.addr, Path: DefaultPath, Timeout: DefaultTimeout, AuthToken: tt.token, } if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr { t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr) } }) } } func TestValidate(t *testing.T) { valid := func() Config { return Config{ WikiJSURL: "https://wiki.example.com", WikiJSAPIKey: "secret", Transport: TransportStdio, Addr: DefaultAddr, Path: DefaultPath, Timeout: DefaultTimeout, } } tests := []struct { name string mutate func(*Config) wantErr bool }{ {name: "valid"}, {name: "missing url", mutate: func(c *Config) { c.WikiJSURL = "" }, wantErr: true}, {name: "missing key", mutate: func(c *Config) { c.WikiJSAPIKey = "" }, wantErr: true}, {name: "relative url", mutate: func(c *Config) { c.WikiJSURL = "wiki.example.com" }, wantErr: true}, {name: "url with query", mutate: func(c *Config) { c.WikiJSURL = "https://wiki.example.com/?a=1" }, wantErr: true}, {name: "ftp url", mutate: func(c *Config) { c.WikiJSURL = "ftp://wiki.example.com" }, wantErr: true}, {name: "bad transport", mutate: func(c *Config) { c.Transport = "sse" }, wantErr: true}, {name: "empty addr", mutate: func(c *Config) { c.Addr = "" }, wantErr: true}, {name: "path without slash", mutate: func(c *Config) { c.Path = "mcp" }, wantErr: true}, {name: "healthz path", mutate: func(c *Config) { c.Path = "/healthz" }, wantErr: true}, {name: "zero timeout", mutate: func(c *Config) { c.Timeout = 0 }, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := valid() if tt.mutate != nil { tt.mutate(&cfg) } if gotErr := cfg.Validate() != nil; gotErr != tt.wantErr { t.Fatalf("Validate() error = %v, wantErr %v", cfg.Validate(), tt.wantErr) } }) } }