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) } // Issue 1126 (check of mesh-host #200): the module the mesh provides itself (the private network) comes from mesh-sdk, // a second source beside the indexer's manifests the cases carry, and the input carries no catalogue: a node it is // assigned to still composes, and what it provides is provided. wg := delivery.ProvidedModules()[0] provided := withInput(t, Request{V: Version, Machines: []string{"a"}, Depth: DepthResolve, Cases: []Case{{Name: "change", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}}}}, compose.Input{Nodes: []compose.Node{{Name: "a", Assigned: []string{wg, "demo"}}}}) if v, err := Run(provided); err != nil || !v.Cases[0].Machines["a"].Composes { t.Errorf("a node assigned the mesh's own module: %+v, %v", 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) } }