Files
mesh-host/internal/composer/composer_test.go
T
jschoubben 9a12f2851f The node-engine owns the composition it alone runs (H1, H2)
ADR 0593 §1 and §7: compose, its bus reader and runtime reader, takein,
validate, the overlay generator, the composer's run and mesh-compose, and
the judgement of a move leave mesh-sdk for the node-engine's internal
packages, so no other module composes and a change to them rebuilds only
the node-engine. The shapes others read (the composer's request and
verdict, the overlay's names, compositions and answers) stay in mesh-sdk.
The node's grant is now a golden file of what the grants part composes,
since the composer of the bus's user list leaves mesh-sdk too.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 18:24:05 +01:00

101 lines
4.6 KiB
Go

package composer
import (
"encoding/json"
"os"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-host/internal/compose"
)
const demo = `{"module": "demo", "version": "1", "claims": [{"name": "node-files"}]}`
// withInput is r with in as its input, as a caller puts it on the wire: the request's shape carries the input as JSON.
func withInput(t *testing.T, r Request, in compose.Input) Request {
t.Helper()
raw, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(raw, &r.Input); err != nil {
t.Fatal(err)
}
return r
}
// The seats a node's assigned modules claim are held there when the request says the holdings come from the claims.
func TestHoldingsComeFromTheClaims(t *testing.T) {
m, err := catalogue.Decode([]byte(demo))
if err != nil {
t.Fatal(err)
}
got := HoldingsOf(map[string]catalogue.Manifest{"demo": m}, []compose.Node{{Name: "b", Assigned: []string{"demo"}}, {Name: "a", Assigned: []string{"demo"}}})
if len(got) != 2 || got[0].Node != "a" || got[0].Claim != "node-files" || got[0].Scope != "node" {
t.Fatalf("holdings = %+v", got)
}
}
// A request the command cannot run is refused, never answered as a verdict: a case named twice, an unknown depth.
func TestARequestItCannotRunIsRefused(t *testing.T) {
if _, err := Run(Request{V: Version, Cases: []Case{{Name: "a"}, {Name: "a"}}}); err == nil {
t.Error("a case named twice runs")
}
if _, err := Run(Request{V: Version, Depth: "deep"}); err == nil {
t.Error("an unknown depth runs")
}
// A case's own placement (issue 1126) composes over a node the input holds, and is refused for one it does not.
placed := withInput(t, Request{V: Version, Machines: []string{"a"}, Depth: DepthResolve,
Cases: []Case{{Name: "trunk", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}},
{Name: "change", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}},
Placements: map[string]*delivery.Placement{"a": {Node: "a", Modules: []delivery.PlacedModule{{Module: "ghost"}}}}}}},
compose.Input{Nodes: []compose.Node{{Name: "a"}}})
if v, err := Run(placed); err != nil || !v.Cases[0].Machines["a"].Composes || v.Cases[1].Machines["a"].Composes ||
!strings.Contains(v.Cases[1].Machines["a"].Reason, "ghost") {
t.Errorf("a case composes its own placement: %+v, %v", v, err)
}
placed.Cases[1].Placements = map[string]*delivery.Placement{"z": {Node: "z"}}
if _, err := Run(placed); err == nil || !strings.Contains(err.Error(), "holds no node for") {
t.Errorf("a case placing a node the input lacks: %v", err)
}
v, err := Run(Request{V: Version, Cases: []Case{{Name: "only-read", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}}}})
if err != nil || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted || v.Cases[0].Machines != nil || v.SDK == "" {
t.Fatalf("a request with no machines: %+v, %v", v, err)
}
}
// The composer judges no manifest (novox/hq ADR 0578 §5): xterm's module.json from mesh-catalog at 835bf22b, whose
// config value falls back to "font-size", is read whatever list of predefined keys a request carries (a group touching
// xterm failed its composition on this, group check 18df81380db3); a module whose verdict in the input refuses it is
// held, composed as its stub, and named in the verdict's words.
func TestTheComposerTakesTheVerdictsOfItsInput(t *testing.T) {
raw, err := os.ReadFile("testdata/catalogue-835bf22b-xterm.module.json")
if err != nil {
t.Fatal(err)
}
r := Request{V: Version, Predefined: catalogue.PredefinedList{"something-else": {Type: "string", Description: "x"}},
Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"xterm": {Raw: raw}}}}}
v, err := Run(r)
if err != nil {
t.Fatal(err)
}
if got := v.Cases[0].Manifests["xterm"]; !got.Accepted {
t.Fatalf("xterm: %+v, want accepted", got)
}
r.Cases[0].Verdicts = map[string]delivery.ManifestVerdict{"xterm": {Module: "xterm", State: delivery.VerdictRefused,
Problems: []string{"falls back to a key not listed"}, Judge: "src2:j"}}
shelf, err := parse(r.Cases[0].Manifests, map[string]ManifestRead{}, r.Cases[0].Verdicts)
if err != nil || !shelf["xterm"].Stubbed {
t.Fatalf("the refused module is not held as its stub: %+v %v", shelf["xterm"], err)
}
if v, err = Run(r); err != nil {
t.Fatal(err)
}
if got := v.Cases[0].Manifests["xterm"]; got.Accepted ||
!strings.Contains(got.Refused, "the indexer's judge (src2:j) refused the manifest of xterm: falls back to a key not listed") {
t.Fatalf("xterm refused by its verdict: %+v, want refused in the verdict's words", got)
}
}