package config import ( "os" "path/filepath" "strings" "testing" "time" ) func writeTemp(t *testing.T, body string) string { t.Helper() path := filepath.Join(t.TempDir(), "warp.yaml") if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } return path } func TestLoadAppliesDefaults(t *testing.T) { path := writeTemp(t, "version: 1\nglobal:\n server_name: warp.example\n") cfg, err := Load(path) if err != nil { t.Fatalf("Load: %v", err) } if got, want := cfg.Listen.Address, ":8008"; got != want { t.Errorf("listen.address = %q, want %q", got, want) } if got, want := cfg.Database.Driver, "sqlite"; got != want { t.Errorf("database.driver = %q, want %q", got, want) } if cfg.Federation.Enabled { t.Error("federation must default to disabled") } if !cfg.Federation.AllowRuntimeToggle { t.Error("federation.allow_runtime_toggle must default to true") } if got, want := cfg.ClientBaseURL(), "https://warp.example"; got != want { t.Errorf("ClientBaseURL() = %q, want %q", got, want) } if cfg.Path() == "" { t.Error("Path() must record the file the config came from") } } func TestLoadRejectsUnknownFields(t *testing.T) { path := writeTemp(t, "version: 1\nglobal:\n server_name: warp.example\n srever_name: typo\n") _, err := Load(path) if err == nil { t.Fatal("expected an error for an unknown field") } if !strings.Contains(err.Error(), "srever_name") { t.Errorf("error should name the offending field, got: %v", err) } } func TestValidateCollectsEveryProblem(t *testing.T) { cfg := Default() cfg.Global.ServerName = "" cfg.Database.Driver = "mysql" cfg.Logging.Level = "chatty" err := cfg.Validate() if err == nil { t.Fatal("expected validation to fail") } for _, want := range []string{"global.server_name", "database.driver", "logging.level"} { if !strings.Contains(err.Error(), want) { t.Errorf("error should mention %s, got: %v", want, err) } } } func TestApplyEnvOverrides(t *testing.T) { cfg := Default() cfg.Global.ServerName = "from-yaml.example" cfg.Federation.Allowlist = []string{"a.example"} env := map[string]string{ "WARP_SERVER_NAME": "from-env.example", "WARP_FEDERATION_ENABLED": "true", "WARP_FEDERATION_ALLOWLIST": "b.example, c.example", "WARP_DB_MAX_OPEN_CONNS": "42", "WARP_SHUTDOWN_TIMEOUT": "5s", "WARP_WELL_KNOWN_CLIENT_BASE_URL": "https://matrix.example", } if err := ApplyEnv(cfg, func(k string) string { return env[k] }); err != nil { t.Fatalf("ApplyEnv: %v", err) } if got, want := cfg.Global.ServerName, "from-env.example"; got != want { t.Errorf("server_name = %q, want %q", got, want) } if !cfg.Federation.Enabled { t.Error("federation.enabled should have been overridden to true") } if got, want := strings.Join(cfg.Federation.Allowlist, ","), "b.example,c.example"; got != want { t.Errorf("allowlist = %q, want %q", got, want) } if got, want := cfg.Database.MaxOpenConns, 42; got != want { t.Errorf("max_open_conns = %d, want %d", got, want) } if got, want := cfg.Listen.ShutdownTimeout, 5*time.Second; got != want { t.Errorf("shutdown_timeout = %s, want %s", got, want) } if got, want := cfg.ClientBaseURL(), "https://matrix.example"; got != want { t.Errorf("ClientBaseURL() = %q, want %q", got, want) } } func TestApplyEnvRejectsMalformedValues(t *testing.T) { cfg := Default() err := ApplyEnv(cfg, func(k string) string { if k == "WARP_FEDERATION_ENABLED" { return "yes-please" } return "" }) if err == nil || !strings.Contains(err.Error(), "WARP_FEDERATION_ENABLED") { t.Fatalf("expected an error naming the variable, got: %v", err) } } func TestApplyEnvEmptyListSentinel(t *testing.T) { cfg := Default() cfg.ClientAPI.CORSAllowedOrigins = []string{"*"} if err := ApplyEnv(cfg, func(k string) string { if k == "WARP_CORS_ALLOWED_ORIGINS" { return "-" } return "" }); err != nil { t.Fatalf("ApplyEnv: %v", err) } if len(cfg.ClientAPI.CORSAllowedOrigins) != 0 { t.Errorf("expected the list to be cleared, got %v", cfg.ClientAPI.CORSAllowedOrigins) } } // TestExampleConfigIsValid keeps warp.yaml.example honest: it must parse // with unknown fields rejected and pass validation as shipped. func TestExampleConfigIsValid(t *testing.T) { path := filepath.Join("..", "..", "..", "warp.yaml.example") if _, err := os.Stat(path); err != nil { t.Skipf("example config not found: %v", err) } cfg, err := Load(path) if err != nil { t.Fatalf("warp.yaml.example does not load: %v", err) } if cfg.Federation.Enabled { t.Error("the shipped example must keep federation off") } if cfg.Registration.Enabled { t.Error("the shipped example must keep registration off") } if cfg.Database.Driver != "sqlite" { t.Errorf("example driver = %q, want sqlite", cfg.Database.Driver) } if cfg.Sync.Presence { t.Error("the shipped example must keep presence off (PLAN.md open question 3)") } // A zero here would mean the example omitted the section and the // validation below is passing on the defaults rather than on the file. if cfg.Sync.TypingTimeout == 0 || cfg.Sync.LongPollTimeout == 0 { t.Error("the shipped example must set the sync timeouts") } if cfg.ClientAPI.RateLimit.Sync.Burst == 0 { t.Error("the shipped example must set the sync rate-limit bucket") } }