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
108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
package resolution
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
func recycler() mcat.Manifest {
|
|
return mcat.Manifest{Module: "sonarr",
|
|
Resources: []map[string]any{
|
|
{"id": "recycle-bin", "type": "directory", "path": "/srv/sonarr/recycle", "mode": "0755",
|
|
"owner": "${setting:puid}:${setting:pgid}"},
|
|
{"id": "identity", "type": "file", "path": "/srv/sonarr/identity.env", "mode": "0644",
|
|
"content": "PUID=${setting:puid}\nPGID=${setting:pgid}\n"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func copied(r map[string]any) map[string]any {
|
|
out := map[string]any{}
|
|
for k, v := range r {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// The node's identity reaches the directory's owner, the node's layer over the mesh's, a number given
|
|
// as a string or as a JSON number alike.
|
|
func TestASettingOwnsADirectory(t *testing.T) {
|
|
m := recycler()
|
|
layers := []mcat.Layer{
|
|
{From: "the mesh", Values: map[string]any{"puid": "1000", "pgid": "1000"}},
|
|
{From: "this node", Values: map[string]any{"puid": float64(1001), "pgid": "2000"}},
|
|
}
|
|
r := copied(m.Resources[0])
|
|
if err := settingInto(r, layers, m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r["owner"] != "1001:2000" {
|
|
t.Fatalf("owned by %q, not 1001:2000", r["owner"])
|
|
}
|
|
user := copied(m.Resources[0])
|
|
user["owner"] = "${setting:puid}"
|
|
if err := settingInto(user, layers, m.Module); err != nil || user["owner"] != "1001" {
|
|
t.Fatalf("user alone: %v, %v", user["owner"], err)
|
|
}
|
|
}
|
|
|
|
// Composed for a machine, the way a push writes it: the directory reaches the node-engine owned by the
|
|
// number the node's settings name, never by the placeholder.
|
|
func TestAComposedMachineGetsTheOwnerTheSettingNames(t *testing.T) {
|
|
r := anAdoptedAnchor()
|
|
r.Modules = append(r.Modules, recycler())
|
|
with := anchorRendering(false)
|
|
with.Settings["sonarr"] = []mcat.Layer{{From: "anchor", Values: map[string]any{"puid": "1001", "pgid": "2000"}}}
|
|
composed, err := Compose(r, with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if why, left := composed.LeftOut["sonarr"]; left {
|
|
t.Fatalf("left out: %s", why)
|
|
}
|
|
got := byID(composed.Resources)["sonarr.recycle-bin"]
|
|
if got["owner"] != "1001:2000" {
|
|
t.Fatalf("composed owned by %v, not 1001:2000", got["owner"])
|
|
}
|
|
}
|
|
|
|
// Only a whole number owns a directory: an account's name, text, a sign, a fraction or a value past the
|
|
// kernel's ids is refused by name, and the directory is left as it was. So is a key nothing sets.
|
|
func TestAnOwnerSettingThatIsNoNumberIsRefused(t *testing.T) {
|
|
m := recycler()
|
|
for _, bad := range []any{"abc", "root", "", "-1", "+5", "1000:1000", "10 00", "1000\nx", float64(1.5),
|
|
float64(-3), "4294967295", "99999999999", true} {
|
|
r := copied(m.Resources[0])
|
|
err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": bad, "pgid": "1000"}}}, m.Module)
|
|
if err == nil || !strings.Contains(err.Error(), `"puid"`) || !strings.Contains(err.Error(), "whole number") {
|
|
t.Errorf("%v: %v", bad, err)
|
|
}
|
|
if r["owner"] != m.Resources[0]["owner"] {
|
|
t.Errorf("%v: the owner was changed on refusal: %v", bad, r["owner"])
|
|
}
|
|
}
|
|
r := copied(m.Resources[0])
|
|
if err := settingInto(r, nil, m.Module); err == nil || !strings.Contains(err.Error(), "${setting:puid}") {
|
|
t.Errorf("nothing set: %v", err)
|
|
}
|
|
if err := JudgeSettings(m, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": "media", "pgid": "1000"}}}, false); err == nil ||
|
|
!strings.Contains(err.Error(), "whole number") {
|
|
t.Errorf("a stored name is not refused when judged: %v", err)
|
|
}
|
|
}
|
|
|
|
// An owner is written as the number parsed: "0001" and "00" reach the node-engine as 1 and 0, never as text.
|
|
// Uid 0 is allowed, because a key an owner asks for is the terminal's alone.
|
|
func TestAnOwnerSettingIsWrittenAsTheNumber(t *testing.T) {
|
|
m := recycler()
|
|
r := copied(m.Resources[0])
|
|
if err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": "0001", "pgid": "00"}}}, m.Module); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r["owner"] != "1:0" {
|
|
t.Fatalf("owned by %q, not 1:0", r["owner"])
|
|
}
|
|
}
|