// SPDX-License-Identifier: AGPL-3.0-only package config import ( "encoding/json" "testing" ) func validConfig() Config { return Config{ SchemaVersion: 1, Service: Service{Name: "example-site", AllowedHost: "example.test"}, Build: Build{Package: "./cmd/site", Binary: "example-site", Branch: "main"}, Deployment: Deployment{ Strategy: "blue_green", Root: "/opt/example-site", LockFile: "/run/lock/example-site.lock", StateFile: "/opt/example-site/state.json", HealthPath: "/healthz", ReadinessPath: "/readyz", CandidateTimeoutSecs: 30, Smoke: []Smoke{{Path: "/", Contains: "Example"}}, BlueGreen: &BlueGreen{ CaddyConfig: "/etc/caddy/Caddyfile", CaddyHandler: "/etc/caddy/example.caddy", CaddyHandlerTemplate: "/etc/example/caddy.template", BootstrapActive: "blue", Blue: Slot{Unit: "example-blue.service", Address: "127.0.0.1:8090", Link: "/opt/example-site/slots/blue"}, Green: Slot{Unit: "example-green.service", Address: "127.0.0.1:8091", Link: "/opt/example-site/slots/green"}, }, }, } } func TestValidateAcceptsBlueGreen(t *testing.T) { if err := validConfig().Validate(); err != nil { t.Fatal(err) } } func TestValidateRejectsHostileValues(t *testing.T) { tests := map[string]func(*Config){ "unknown strategy": func(c *Config) { c.Deployment.Strategy = "shell" }, "nonloopback": func(c *Config) { c.Deployment.BlueGreen.Blue.Address = "203.0.113.7:80" }, "root path": func(c *Config) { c.Deployment.Root = "/" }, "traversal": func(c *Config) { c.Build.Package = "./cmd/../secret" }, "shared slot": func(c *Config) { c.Deployment.BlueGreen.Green.Link = c.Deployment.BlueGreen.Blue.Link }, "bad smoke": func(c *Config) { c.Deployment.Smoke[0].Path = "https://attacker.test/" }, } for name, mutate := range tests { t.Run(name, func(t *testing.T) { cfg := validConfig() mutate(&cfg) if err := cfg.Validate(); err == nil { t.Fatal("expected validation failure") } }) } } func TestUnknownJSONFieldRejected(t *testing.T) { b, err := json.Marshal(validConfig()) if err != nil { t.Fatal(err) } var raw map[string]any if err := json.Unmarshal(b, &raw); err != nil { t.Fatal(err) } raw["surprise"] = true b, _ = json.Marshal(raw) _ = b // Load exercises strict decoding from disk in command tests. }