117 lines
3.9 KiB
Go
117 lines
3.9 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// loadYAML writes body to a temp file and loads it, failing the test if Load
|
|
// errors. Every test here wants "Load accepted this", so the helper asserts it
|
|
// once instead of repeating the check.
|
|
func loadYAML(t *testing.T, body string) *Config {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load rejected the config: %v", err)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// TestLoadIgnoresUnknownKeys pins a decision STATUS.md lists as open: Load
|
|
// unmarshals leniently, so a key the struct does not know is silently dropped
|
|
// instead of rejected. yaml.KnownFields(true) would catch it, and was
|
|
// deliberately NOT adopted: it is a behaviour change for anyone carrying extra
|
|
// keys in their config.
|
|
//
|
|
// The reason this is pinned rather than left alone is that leniency has already
|
|
// cost this project twice (two test fixtures were caught by a stray key, not by
|
|
// what they were testing). If the decision ever flips, the failure below is the
|
|
// reminder to update STATUS.md, AGENTS.md and the README together.
|
|
func TestLoadIgnoresUnknownKeys(t *testing.T) {
|
|
t.Run("a misspelled top-level block is dropped", func(t *testing.T) {
|
|
// The real trap from the field: the struct tag is "blocklist", so
|
|
// writing "block:" produces a plausible-looking server that has
|
|
// quietly kept every blocklist default. The correctly spelled
|
|
// block below disables the blocklist so the config validates at all
|
|
// — which is itself the sharper half of the pin: if the typo were
|
|
// honoured, block: {enabled: true} with no sources would fail
|
|
// Validate and the server would refuse to start.
|
|
cfg := loadYAML(t, `
|
|
blocklist:
|
|
enabled: false
|
|
block:
|
|
enabled: true
|
|
response_ip: 10.0.0.1
|
|
response_ttl: 5s
|
|
`)
|
|
if cfg.Block.Enabled {
|
|
t.Fatal("the misspelled block: block was applied; Load is no longer lenient " +
|
|
"and this pin is stale")
|
|
}
|
|
if cfg.Block.ResponseIP != Default().Block.ResponseIP {
|
|
t.Fatalf("blocklist.response_ip = %q, want the default %q",
|
|
cfg.Block.ResponseIP, Default().Block.ResponseIP)
|
|
}
|
|
})
|
|
|
|
t.Run("the same block spelled correctly is applied", func(t *testing.T) {
|
|
// The control for the case above: the lenient path is the
|
|
// difference between these two keys, not Load ignoring the file.
|
|
cfg := loadYAML(t, `
|
|
blocklist:
|
|
enabled: false
|
|
response_ip: 10.0.0.1
|
|
response_ttl: 5s
|
|
`)
|
|
if cfg.Block.Enabled {
|
|
t.Fatal("blocklist.enabled: false was not applied")
|
|
}
|
|
if cfg.Block.ResponseIP != "10.0.0.1" {
|
|
t.Fatalf("blocklist.response_ip = %q, want 10.0.0.1", cfg.Block.ResponseIP)
|
|
}
|
|
})
|
|
|
|
t.Run("a misspelled nested key is dropped", func(t *testing.T) {
|
|
cfg := loadYAML(t, `
|
|
blocklist:
|
|
enabled: false
|
|
api:
|
|
basepath: /console
|
|
`)
|
|
if cfg.API.BasePath != Default().API.BasePath {
|
|
t.Fatalf("api.base_path = %q, want the default %q — leniency changed",
|
|
cfg.API.BasePath, Default().API.BasePath)
|
|
}
|
|
})
|
|
|
|
t.Run("an unknown top-level key is dropped", func(t *testing.T) {
|
|
cfg := loadYAML(t, `
|
|
blocklist:
|
|
enabled: false
|
|
telemetry:
|
|
endpoint: https://example.invalid
|
|
`)
|
|
if cfg.API.Enabled != Default().API.Enabled {
|
|
t.Fatalf("an unknown key changed the config: api.enabled = %v", cfg.API.Enabled)
|
|
}
|
|
})
|
|
|
|
t.Run("a known key with a bad TYPE is still fatal", func(t *testing.T) {
|
|
// The asymmetry is the point: a typo is invisible, a wrong value type
|
|
// is a startup failure. Leniency covers unknown keys only.
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
if err := os.WriteFile(path, []byte("blocklist:\n enabled: false\nserver:\n max_concurrent: many\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := Load(path); err == nil {
|
|
t.Fatal("Load accepted a string where a number belongs; the strictness " +
|
|
"contrast this pin documents is gone")
|
|
}
|
|
})
|
|
}
|