Scaffolds the server module and everything Phase 0 of PLAN.md calls for: a binary that boots, serves its discovery endpoints and nothing else yet. - config: one YAML file plus per-field WARP_* environment overrides, unknown fields rejected, validation that reports every problem at once - logging and metrics: slog, a Prometheus registry, per-route HTTP instrumentation labelled by pattern rather than path - httputil: the Matrix error envelope, error-returning handlers, CORS, request logging, panic recovery, and a router over http.ServeMux - endpoints: /_matrix/client/versions, /.well-known/matrix/client, /.well-known/matrix/server (gated), /healthz, /metrics, and M_UNRECOGNIZED for everything else - storage: driver registry, shared database/sql base, checksummed migration runner, sqlite and postgres drivers, pocketbase stub - settings: server_settings table behind a cached service, so the federation and registration switches change without a restart - federation: the gate that makes federation a runtime mode - cmd/warp: serve, migrate, generate-keys, version - Makefile, golangci-lint v2, GitHub Actions CI (Linux and Windows tests, race on Linux, storage tests against PostgreSQL, lint, boot smoke test), Dockerfile, example config and docs Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
4.7 KiB
Go
164 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|