Files
mesh-host/internal/compose/resolution/files_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

76 lines
4.0 KiB
Go

package resolution
import (
"encoding/json"
"slices"
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// issue 900, ADR 0394 §4: the holder is told the secret places and the paths modules own on its node.
func TestTheHolderOfNodeFilesIsToldSecretPlacesAndOwnedPaths(t *testing.T) {
files := mcat.Manifest{Module: "files", Version: "1", Claims: []mcat.Claim{{Name: mcat.FilesSeat}},
OwnSecrets: mcat.OwnSecrets{"broker": {Path: "/var/lib/files/broker"}}}
host := mcat.Manifest{Module: "hostnames", Version: "1", Claims: []mcat.Claim{{Name: "node-hostname"}},
Resources: []map[string]any{{"id": "hosts", "type": "file", "path": "/etc/hosts"}}}
app := mcat.Manifest{Module: "app", Version: "1",
Resources: []map[string]any{{"id": "conf", "type": "file", "path": "/etc/app/app.conf"}}}
r := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{files, host, app}}
words, err := resolutionFilesWords(r, files, nil, mcat.Rendering{})
if err != nil {
t.Fatal(err)
}
var places []string
if err := json.Unmarshal([]byte(words[mcat.ReadsSecretPlaces]), &places); err != nil || len(places) == 0 {
t.Errorf("no secret places: %v %v", err, words)
}
var owned map[string]string
if err := json.Unmarshal([]byte(words[mcat.FilesOwnedPaths]), &owned); err != nil {
t.Fatalf("no owned paths: %v (%v)", err, words)
}
if !strings.HasPrefix(owned["/etc/hosts"], "hostnames") {
t.Errorf("/etc/hosts is owned by %q, want hostnames", owned["/etc/hosts"])
}
if !strings.HasPrefix(owned["/etc/app/app.conf"], "app") {
t.Errorf("a placed file is owned by %q, want app", owned["/etc/app/app.conf"])
}
// Issue 1126, C3: a seat path that steps back for a seat held here is that holder's, whatever the order of the seat
// table (the controller reads it sorted by name, a node in the order compiled).
resolver := mcat.Manifest{Module: "systemd-resolved", Version: "1", Claims: []mcat.Claim{{Name: mcat.ResolverSeat}},
Resources: []map[string]any{{"id": "stub", "type": "file", "path": mcat.ResolverFile}}}
uplink := mcat.Manifest{Module: "networkmanager", Version: "1", Claims: []mcat.Claim{{Name: "node-uplink"}},
Resources: []map[string]any{{"id": "resolv", "type": "file", "path": mcat.ResolverFile}}}
compiled := seats
t.Cleanup(func() { seats = compiled })
for _, order := range []string{"compiled", "reversed"} {
if order == "reversed" {
seats = append([]mcat.Seat(nil), compiled...)
slices.Reverse(seats)
}
both := mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{files, uplink, resolver}}
w, err := resolutionFilesWords(both, files, nil, mcat.Rendering{})
if err != nil {
t.Fatal(err)
}
var o map[string]string
if err := json.Unmarshal([]byte(w[mcat.FilesOwnedPaths]), &o); err != nil || o[mcat.ResolverFile] != "systemd-resolved ("+mcat.ResolverSeat+")" {
t.Errorf("seat table %s: %s is owned by %q, want systemd-resolved (%s)", order, mcat.ResolverFile, o[mcat.ResolverFile], mcat.ResolverSeat)
}
}
seats = compiled
// Issue 1126, C5 and seed-8: a module carried as it runs (compose.Carried) owns its manifest's paths (OwnsPaths), as
// the controller tells them, and not the paths its running resources are at (a directory the mesh placed).
carried := mcat.Manifest{Module: "de-spiegel", Version: "1", OwnsPaths: []string{"/var/lib/de-spiegel/server.env"},
Resources: []map[string]any{{"id": "state", "type": "directory", "path": "/var/lib/de-spiegel"}}}
w, err := resolutionFilesWords(mcat.Resolution{Node: "anchor", Modules: []mcat.Manifest{files, carried}}, files, nil, mcat.Rendering{})
if err != nil || !strings.Contains(w[mcat.FilesOwnedPaths], `"/var/lib/de-spiegel/server.env":"de-spiegel (its own tools)"`) ||
strings.Contains(w[mcat.FilesOwnedPaths], `"/var/lib/de-spiegel":`) {
t.Errorf("a carried module does not own its manifest's paths alone: %v %v", w[mcat.FilesOwnedPaths], err)
}
if w, _ := resolutionFilesWords(r, app, nil, mcat.Rendering{}); w[mcat.FilesOwnedPaths] != "" {
t.Error("a module not holding node-files is told the owned paths")
}
}