Files
mesh-host/internal/compose/resolution/reads_test.go
T
jschoubben d68e241724 Take the catalogue's composition rules into the node-engine
ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk
carries no composition rule. The catalogue functions only the
node-engine's composition reaches (Resolve, Resolution's Compose,
Declaration, DataItems, LeftOut, Rules and the rest, the settings,
placeholder and template refusals, the rendering helpers) are copied
with their tests into internal/compose/resolution; a method of a
mesh-sdk shape becomes a function taking it first. The composition
calls its own copies, and the captured catalogue its tests read lives
here now, shared by mesh-compose's tests.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:15:55 +01:00

82 lines
4.1 KiB
Go

package resolution
import (
"encoding/json"
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// novox/hq ADR 0310 §5: the holder is told, by the composition, where its node keeps a secret — the directory
// of every module's own secret, every credential file and grant directory, the vault's directories and the
// place the operator-sealed secrets are kept — and no other module is told.
func TestTheHolderOfNodeReadsIsToldWhereItsNodeKeepsASecret(t *testing.T) {
with := mcat.Rendering{ArtifactStore: "anchor.internal:5101",
Needed: map[string]map[string]string{mcat.RuntimeModule: {"broker": "sealed-credential"}, "reads": {"broker": "own"}, "desk": {"broker": "own"}}}
goRuntime := mcat.Manifest{Module: mcat.RuntimeModule, Version: "1",
OwnSecrets: mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh/" + mcat.RuntimeModule + "/broker"}},
Build: &mcat.Build{Artifacts: []mcat.Artifact{{Name: "runtime", Kind: mcat.ArtifactBundle, Language: "go",
System: "arch", From: "cmd/node-tools"}}}}
goRuntime, err := goRuntime.Resolve([]mcat.Built{{Name: "runtime", Kind: mcat.ArtifactBundle,
Reference: mcat.ArtifactStoreScheme + mcat.RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}})
if err != nil {
t.Fatal(err)
}
reads := aToolsModule(t, "reads", "tools/index.js")
reads.RunsAs, reads.SecretsOwner = "mesh-reads", "mesh-reads"
reads.Claims = []mcat.Claim{{Name: mcat.ReadsSeat}}
reads.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/mesh-reads/broker"}}
desk := aToolsModule(t, "desk", "tools/index.js")
desk.RunsAs, desk.SecretsOwner = "desk", "desk"
desk.OwnSecrets = mcat.OwnSecrets{"broker": {Path: "/var/lib/desk/broker"}}
// The vault as the catalogue declares it: its directories have no path of their own, and the node places them.
vault, err := placedManifest(catalogueResolved(t, "mesh-vault", mcat.Built{Name: "code", Kind: mcat.ArtifactBundle,
Reference: mcat.ArtifactStoreScheme + "mesh-vault/code/blobs/" + bundleDigest, Digest: bundleDigest}), with)
if err != nil {
t.Fatal(err)
}
app := mcat.Manifest{Module: "app", Version: "1", Secrets: map[string]string{"postgres-database": "/etc/app/db.secret"},
OwnSecrets: mcat.OwnSecrets{"token": {Path: "/home/ops/.config/app/token"}}}
r := mcat.Resolution{Node: "anchor", Account: "ops", Modules: []mcat.Manifest{reads, desk, vault, app, goRuntime}}
owns, err := resolutionOwnRuntimes(r, with)
if err != nil {
t.Fatal(err)
}
var given map[string]map[string]string
for _, p := range owns {
if p["id"] == "reads."+mcat.OwnRuntimeID() {
if err := json.Unmarshal([]byte(p["env"].(map[string]string)[mcat.RuntimeToolEnv]), &given); err != nil {
t.Fatal(err)
}
}
if p["id"] == "desk."+mcat.OwnRuntimeID() && strings.Contains(p["env"].(map[string]string)[mcat.RuntimeToolEnv], mcat.ReadsSecretPlaces) {
t.Error("a module not holding node-reads is told the node's secret places")
}
}
var places []string
if err := json.Unmarshal([]byte(given["reads"][mcat.ReadsSecretPlaces]), &places); err != nil {
t.Fatalf("the holder is not told its node's secret places: %v (%v)", err, given)
}
want := []string{"/etc/app/db.secret", "/home/ops/.config/app", "/var/lib/desk", "/var/lib/mesh-reads",
"/var/lib/mesh-vault", "/var/lib/mesh/" + mcat.RuntimeModule}
if strings.Join(places, " ") != strings.Join(want, " ") {
t.Errorf("secret places:\n got %v\nwant %v", places, want)
}
}
// The review of #257: a secret place the controller cannot resolve fails the composition, naming the module,
// rather than leaving the list one place shorter.
func TestASecretPlaceThatCannotBeResolvedFailsTheComposition(t *testing.T) {
r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{
{Module: "app", Version: "1", Keeps: "${dir:keep}/sealed"},
}}
if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil || !strings.Contains(err.Error(), "app") {
t.Errorf("an unresolved place was dropped: %v", err)
}
r.Modules[0].Keeps = "relative/place"
if _, err := resolutionSecretPlaces(r, mcat.Rendering{}); err == nil {
t.Error("a relative place was dropped")
}
}