package resolution import ( "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) func file(content string, protected ...string) map[string]any { r := map[string]any{"id": "conf", "type": "file", "path": "/etc/thing.json", "merge": mcat.MergeJSON, "content": content} if len(protected) > 0 { var as []any for _, p := range protected { as = append(as, p) } r["protected"] = as } return r } func TestAProtectedKeyIsRefusedNotIgnored(t *testing.T) { // A setting quietly dropped is somebody believing they changed something. Refusing says so // while they are looking at it. _, err := ApplySettings(file(`{"socket":"/run/thing.sock","log":"info"}`, "socket"), []mcat.Layer{{From: "node", Values: map[string]any{"socket": "/tmp/mine.sock"}}}) if err == nil { t.Fatal("a protected key was overridden") } if !strings.Contains(err.Error(), "socket") || !strings.Contains(err.Error(), "not settable") { t.Errorf("the refusal does not say which key or why: %v", err) } } func TestTheSameSettingsAlwaysProduceTheSameBytes(t *testing.T) { // A file whose lines move for no reason makes every reconcile look like a change, and a // service reflecting it would restart for ever. // // Note what this defends: Go's JSON encoder sorts map keys, so the stability comes from the // standard library and this passes with the merging removed. It is worth keeping as the thing // that would catch a move to an encoder that does not sort — but it is not evidence about the // code below it, and it was checked. resource := file(`{"b":2,"a":1,"c":3}`) layer := mcat.Layer{From: "node", Values: map[string]any{"z": 26.0, "a": 100.0}} first, err := ApplySettings(resource, []mcat.Layer{layer}) if err != nil { t.Fatal(err) } for i := 0; i < 5; i++ { again, err := ApplySettings(resource, []mcat.Layer{layer}) if err != nil { t.Fatal(err) } if again["content"] != first["content"] { t.Fatal("the same settings produced different bytes") } } } func TestAFileThatDoesNotMergeIsLeftAlone(t *testing.T) { plain := map[string]any{"id": "conf", "type": "file", "path": "/etc/thing", "content": "not structured at all\n"} out, err := ApplySettings(plain, []mcat.Layer{{From: "node", Values: map[string]any{"x": 1}}}) if err != nil { t.Fatal(err) } if out["content"] != "not structured at all\n" { t.Errorf("a file with no merge rule was changed: %v", out["content"]) } } func TestContentThatIsNotJSONIsRefusedWhereSomebodyIsLooking(t *testing.T) { // Rather than on the machine, at apply time, as a file the program cannot read. _, err := ApplySettings(file(`this is not json`), nil) if err == nil { t.Fatal("a module claiming to merge as JSON shipped something else and was accepted") } } // No layer replaces a value the vault fills, at any depth (novox/hq issue 610): searxng's server.secret_key // sits under `server`, a key its file takes, and a layer reaching it was let through by a top-level-only check. func TestNoLayerReplacesAVaultSecretAtAnyDepth(t *testing.T) { file := map[string]any{"id": "settings", "type": "file", "merge": mcat.MergeJSON, "content": `{"server": {"secret_key": "${secret:secret}", "port": 8080}, "ui": {"theme": "simple"}, "token": "${secret:token}"}`} layer := func(values map[string]any) []mcat.Layer { return []mcat.Layer{{From: "anchor", Values: values}} } merged, err := ApplySettings(file, layer(map[string]any{"server": map[string]any{"port": float64(9000)}, "ui": map[string]any{"theme": "dark"}})) if err != nil { t.Fatalf("a key beside the secret was refused: %v", err) } if c := merged["content"].(string); !strings.Contains(c, `"secret_key": "${secret:secret}"`) || !strings.Contains(c, `"port": 9000`) { t.Fatalf("the secret or the setting did not land as meant:\n%s", c) } for name, values := range map[string]map[string]any{ "the nested secret": {"server": map[string]any{"secret_key": "chosen"}}, "the whole map holding it": {"server": "elsewhere"}, "a top-level secret": {"token": "chosen"}, "the secret, emptied": {"server": map[string]any{"secret_key": ""}}, } { if _, err := ApplySettings(file, layer(values)); err == nil || !strings.Contains(err.Error(), "vault") { t.Errorf("%s: %v, want a refusal naming the vault", name, err) } } }