Files
mesh-controller/internal/catalogue/setting_defaults_test.go
T
jochen 76babaea52 Read stored manifests leniently and mark the defaults layer (hq ADR 0262 review)
A strict read of the stored catalogue fails every plan and send once a manifest uses a field an older
controller lacks; registration stays strict. A node named default lost its layer to the name check.
Judge the operator's keys by whole words, and scan a default under any key.
2026-10-08 17:24:32 +02:00

301 lines
13 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
)
// A preference has a default in the definition; the mesh's layer, then the node's, override it; a key
// with a default is not stray; and a value that is the operator's still has none (novox/hq ADR 0262).
func notifier() Manifest {
return Manifest{Module: "notifier",
Settings: map[string]SettingDeclaration{
"font-size": {Kind: KindPreference, Default: float64(10), Why: "readable at a scale of one"},
"width": {Kind: KindPreference, Default: float64(250), Why: "fits a title of forty characters"},
},
Resources: []map[string]any{{"id": "configuration", "type": "file", "path": "/x/notifierrc",
"content": "font = Inter ${setting:font-size}\nwidth = ${setting:width}\n"}},
}
}
func parsed(t *testing.T, m Manifest) error {
t.Helper()
raw, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
_, err = ParseManifest(raw)
return err
}
func TestAnUnsetPreferenceTakesItsDefault(t *testing.T) {
m := notifier()
for _, layers := range [][]Layer{nil, {{From: MeshWideLayer, Values: map[string]any{}}}} {
file := map[string]any{}
for k, v := range m.Resources[0] {
file[k] = v
}
if err := settingInto(file, WithDefaults(m, layers), m.Module); err != nil {
t.Fatal(err)
}
if file["content"] != "font = Inter 10\nwidth = 250\n" {
t.Fatalf("filled as %q", file["content"])
}
}
if err := JudgeSettings(m, nil, false); err != nil {
t.Fatalf("a module whose every key has a default does not compose with no layer: %v", err)
}
}
func TestTheNodeOverTheMeshOverTheDefault(t *testing.T) {
m := notifier()
layers := []Layer{
{From: MeshWideLayer, Values: map[string]any{"width": float64(300)}},
{From: "laptop", Values: map[string]any{"font-size": float64(13), "width": float64(340)}},
}
file := map[string]any{"type": "file", "content": m.Resources[0]["content"]}
if err := settingInto(file, WithDefaults(m, layers), m.Module); err != nil {
t.Fatal(err)
}
if file["content"] != "font = Inter 13\nwidth = 340\n" {
t.Fatalf("filled as %q", file["content"])
}
file = map[string]any{"type": "file", "content": m.Resources[0]["content"]}
if err := settingInto(file, WithDefaults(m, layers[:1]), m.Module); err != nil {
t.Fatal(err)
}
if file["content"] != "font = Inter 10\nwidth = 300\n" {
t.Fatalf("the mesh's layer over the default filled as %q", file["content"])
}
}
// Composed for a machine, the way a push writes it: the default reaches the file, and the node's
// layer overrides it.
func TestAComposedMachineGetsTheDefaultAndTheNodesValue(t *testing.T) {
r := anAdoptedAnchor()
r.Modules = append(r.Modules, notifier())
with := anchorRendering(false)
composed, err := r.Compose(with)
if err != nil {
t.Fatal(err)
}
if why, left := composed.LeftOut["notifier"]; left {
t.Fatalf("the notifier was left out: %s", why)
}
if c := byID(composed.Resources)["notifier.configuration"]["content"]; c != "font = Inter 10\nwidth = 250\n" {
t.Fatalf("composed with no layer as %q", c)
}
with.Settings["notifier"] = []Layer{{From: "anchor", Values: map[string]any{"font-size": float64(13)}}}
if composed, err = r.Compose(with); err != nil {
t.Fatal(err)
}
if c := byID(composed.Resources)["notifier.configuration"]["content"]; c != "font = Inter 13\nwidth = 250\n" {
t.Fatalf("composed with the node's font size as %q", c)
}
}
// Setting a key the module gives a default is not refused as reaching nothing: it overrides the
// default, which is how a running module gains a setting with no gap between.
func TestAKeyWithADefaultIsNotStray(t *testing.T) {
m := notifier()
m.Resources[0]["content"] = "font = Inter ${setting:font-size}\nwidth = ${setting:width}\n"
stray := UnusedSettings(m, []Layer{{From: "laptop", Values: map[string]any{"font-size": float64(13), "colour": "red"}}})
joined := strings.Join(stray, "; ")
if strings.Contains(joined, `"font-size"`) || !strings.Contains(joined, `"colour"`) {
t.Fatalf("stray: %s", joined)
}
}
// A default fills ${setting:…} only: it never becomes a key of a mergeable file (issue 168).
func TestADefaultIsNotMergedIntoAJSONFile(t *testing.T) {
m := notifier()
m.Resources = append(m.Resources, map[string]any{"id": "other", "type": "file", "path": "/x/other.json",
"merge": MergeJSON, "content": `{"keep": 1}`})
out, err := ApplySettings(m.Resources[1], WithDefaults(m, nil))
if err != nil {
t.Fatal(err)
}
if strings.Contains(out["content"].(string), "font-size") {
t.Fatalf("a default reached a mergeable file: %s", out["content"])
}
}
// A value that is the operator's has no default: no layer setting it is refused by name, as before.
func TestAnOperatorsValueWithoutADefaultIsStillRefused(t *testing.T) {
m := notifier()
m.Resources[0]["content"] = "font = Inter ${setting:font-size}\nwidth = ${setting:width}\nfrom = ${setting:domain}\n"
err := JudgeSettings(m, nil, false)
if err == nil || !strings.Contains(err.Error(), "${setting:domain}") {
t.Fatalf("judged %v", err)
}
if strings.Contains(err.Error(), "font-size") {
t.Fatalf("a default was named as set today: %v", err)
}
}
func TestTheParserTakesAPreferenceAndRefusesTheRest(t *testing.T) {
if err := parsed(t, notifier()); err != nil {
t.Fatalf("a preference with a default was refused: %v", err)
}
for _, c := range []struct {
name string
key string
d SettingDeclaration
refuse string
}{
{"no kind", "font-size", SettingDeclaration{Default: float64(10), Why: "x"}, `only a "preference" has a default`},
{"another kind", "font-size", SettingDeclaration{Kind: "operator", Default: float64(10), Why: "x"}, `only a "preference"`},
{"no default", "font-size", SettingDeclaration{Kind: KindPreference, Why: "x"}, "no default"},
{"an empty default", "font-size", SettingDeclaration{Kind: KindPreference, Default: " ", Why: "x"}, "an empty default"},
{"an object", "font-size", SettingDeclaration{Kind: KindPreference, Default: map[string]any{"a": 1.0}, Why: "x"}, "a string, a number or a boolean"},
{"no why", "font-size", SettingDeclaration{Kind: KindPreference, Default: float64(10)}, "no why"},
{"the operator's", "mail-domain", SettingDeclaration{Kind: KindPreference, Default: "example.tld", Why: "x"}, "is the operator's value"},
{"a secret", "api-token", SettingDeclaration{Kind: KindPreference, Default: "x", Why: "x"}, "is the operator's value"},
{"the mesh's word", PortsSetting, SettingDeclaration{Kind: KindPreference, Default: float64(1), Why: "x"}, "the mesh reads"},
{"read by nothing", "height", SettingDeclaration{Kind: KindPreference, Default: float64(300), Why: "x"}, "reaches nothing"},
} {
m := notifier()
m.Resources[0]["content"] = m.Resources[0]["content"].(string) + "x = ${setting:" + c.key + "}\n"
if c.name == "read by nothing" {
m.Resources[0]["content"] = "font = Inter ${setting:font-size}\nwidth = ${setting:width}\n"
}
m.Settings[c.key] = c.d
err := parsed(t, m)
if err == nil || !strings.Contains(err.Error(), c.refuse) {
t.Errorf("%s: parsed %v, want %q", c.name, err, c.refuse)
}
}
}
func TestEveryValueSaysWhereItCameFrom(t *testing.T) {
m := notifier()
m.Resources[0]["content"] = m.Resources[0]["content"].(string) + "x = ${setting:position}\n"
got := Effective(m, []Layer{
{From: MeshWideLayer, Values: map[string]any{"width": float64(300), "position": "top-right"}},
{From: "laptop", Values: map[string]any{"font-size": float64(13)}},
})
want := map[string]string{"font-size": "laptop", "position": MeshWideLayer, "width": MeshWideLayer}
if len(got) != 3 {
t.Fatalf("effective: %+v", got)
}
for _, s := range got {
if s.From != want[s.Key] {
t.Errorf("%s from %q, want %q", s.Key, s.From, want[s.Key])
}
}
if got[0].Key != "font-size" || got[0].Value != float64(13) || got[0].Default != float64(10) {
t.Fatalf("font-size: %+v", got[0])
}
only := Effective(m, nil)
if only[0].From != DefaultLayer || only[0].Value != float64(10) {
t.Fatalf("with no layer: %+v", only)
}
}
// A node may be called `default`. Its layer is a node's like any other: it overrides the module's
// default, it merges into a mergeable file, and it is named among what is set.
func TestANodeCalledDefaultIsANodesLayer(t *testing.T) {
m := notifier()
node := []Layer{{From: DefaultLayer, Values: map[string]any{"font-size": float64(13)}}}
file := map[string]any{"type": "file", "content": m.Resources[0]["content"]}
if err := settingInto(file, WithDefaults(m, node), m.Module); err != nil {
t.Fatal(err)
}
if file["content"] != "font = Inter 13\nwidth = 250\n" {
t.Fatalf("the node called default was dropped: %q", file["content"])
}
out, err := ApplySettings(map[string]any{"id": "j", "type": "file", "merge": MergeJSON, "content": `{}`}, node)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(out["content"].(string), `"font-size": 13`) {
t.Fatalf("the node called default did not merge: %s", out["content"])
}
if got := Effective(m, node); got[0].FromDefault || got[0].Value != float64(13) {
t.Fatalf("the node's value read as the default: %+v", got[0])
}
}
// The operator's own value is told by a whole word of the key's name, not by a part of one.
func TestAKeyIsTheOperatorsByAWholeWord(t *testing.T) {
for _, key := range []string{"max-tokens", "show-hostname", "ghost-opacity", "users-per-page", "mailbox-size",
"font-size", "width", "keyboard-delay", "ipv6-preferred", "client-width"} {
if w := operatorsWord(key); w != "" {
t.Errorf("%s read as the operator's (%s)", key, w)
}
}
for key, word := range map[string]string{"mail-domain": "mail", "site-domain": "domain", "api-key": "key", "admin-password": "password",
"oauth-client-id": "client-id", "db-dsn": "dsn", "public-ip": "ip", "puid": "puid", "dns-zone": "zone",
"webhook": "webhook", "notify_phone": "phone", "cors.origin": "origin", "smtp-pass": "pass",
"backup-passphrase": "passphrase", "data-owner": "owner", "fqdn": "fqdn", "tenant": "tenant", "host": "host"} {
if w := operatorsWord(key); w != word {
t.Errorf("%s: read %q, want %q", key, w, word)
}
}
}
// A default is a value the mesh writes, so the installation check reads it, even under a setting
// called `description` or `why`; a setting's own why is prose and is not read.
func TestTheInstallationCheckReadsADefault(t *testing.T) {
m := notifier()
m.Settings["relay"] = SettingDeclaration{Kind: KindPreference, Default: "relay.acme.be", Why: "as at relay.acme.be"}
m.Settings["description"] = SettingDeclaration{Kind: KindPreference, Default: "notes.acme.be", Why: "x"}
problems := strings.Join(InstallationProblems(m), "; ")
for _, want := range []string{"relay.acme.be at settings.relay.default", "notes.acme.be at settings.description.default"} {
if !strings.Contains(problems, want) {
t.Errorf("not reported: %q in %s", want, problems)
}
}
if strings.Contains(problems, "settings.relay.why") {
t.Errorf("a why was read as a value: %s", problems)
}
}
// A default fills ${setting:…} in what a module contributes and serves, and never replaces a value a
// contribution states itself.
func TestADefaultFillsAContributionAndAServedFactButNotALiteral(t *testing.T) {
m := notifier()
m.Settings["site-title"] = SettingDeclaration{Kind: KindPreference, Default: "Notes", Why: "x"}
contribution := map[string]any{"title": "${setting:site-title}", "width": "fixed", "port": float64(8080)}
got, err := overridden(contribution, WithDefaults(m, nil), "a contribution")
if err != nil {
t.Fatal(err)
}
if got["title"] != "Notes" || got["width"] != "fixed" {
t.Fatalf("contribution: %v", got)
}
got, err = overridden(contribution, WithDefaults(m, []Layer{{From: "laptop", Values: map[string]any{"width": "wide"}}}), "a contribution")
if err != nil || got["width"] != "wide" {
t.Fatalf("a node's value did not override a contribution's own key: %v %v", got, err)
}
served, err := Settle(map[string]any{"name": "${setting:site-title}"}, WithDefaults(m, nil))
if err != nil || served["name"] != "Notes" {
t.Fatalf("served: %v %v", served, err)
}
if _, err := Settle(map[string]any{"name": "${setting:site-title}"}, nil); err == nil {
t.Fatal("a served fact without the defaults was filled")
}
}
// A stored manifest with a key this controller does not know is read without it, and said; the module
// check still refuses it.
func TestAStoredManifestWithAnUnknownKeyIsReadAndRegistrationRefusesIt(t *testing.T) {
raw := []byte(`{"module": "later", "version": "1", "tools": ["later_x"], "a-field-from-later": {"x": 1}}`)
var m Manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("a stored manifest with an unknown key was not read: %v", err)
}
if m.Module != "later" || len(m.Tools) != 1 || !strings.Contains(m.UnknownField(), `"a-field-from-later"`) {
t.Fatalf("read as %+v, unknown %q", m, m.UnknownField())
}
if _, err := ParseManifest(raw); err == nil || !strings.Contains(err.Error(), `unknown field "a-field-from-later"`) {
t.Fatalf("registration took it: %v", err)
}
var known Manifest
if err := json.Unmarshal([]byte(`{"module": "now"}`), &known); err != nil || known.UnknownField() != "" {
t.Fatalf("a known manifest: %v %q", err, known.UnknownField())
}
}