package frombustest import ( "context" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/compose/frombus" "git.novox.be/novox/mesh-sdk/go/delivery" ) // ADR 0570 clause 2 (issue 1954; a3's H5): the in-memory mesh composes a plan from the bus that hands restic to the // provisioner and names its postgres-database, provided by postgres on n1. And one bad module must not drop its node // (issue 774): with a module placed beside it whose built manifest cannot be used, or whose plan refuses, PlansHeld // still hands restic's plan and names the bad one with why, where Plans refuses the whole node. A manifest the indexer // holds that the mesh refuses (power's bar item with attributes its bar places by none of, mesh-catalog ca8d6f33, // 2026-10-30) holds that module alone on its node, in the plans and in the node's composition (Compose), and on a node // it is not placed on holds nothing. func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) { fp := "src2:" + strings.Repeat("9", 64) for _, row := range []struct { name string bad string // the indexer's manifest of a module "backup" placed on n1; empty places none // built is the manifest backup's done build names; empty gives it no build built string why string // words backup's hold says // elsewhere leaves backup in the indexer's modules and places it on no node: nothing is held. elsewhere bool }{ {name: "every module composes"}, {name: "a built manifest that cannot be used", bad: `{"module":"backup","version":"1","build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, built: `{"module":"backup","version":"1","requires":["amqp"]}`, why: "backup"}, {name: "a plan that refuses", bad: `{"module":"backup","version":"1","resources":[{"id":"conf","type":"file","path":"/etc/backup.conf",` + `"mode":"0600","content":"key=${secret:nope}"}]}`, why: "${secret:nope}"}, {name: "an indexer's manifest the mesh refuses", bad: unusableItem, why: `"shows" is none of them`}, {name: "an indexer's manifest the mesh refuses, placed elsewhere", bad: unusableItem, elsewhere: true}, } { b, err := Provisioned() if err != nil { t.Fatal(err) } if row.bad != "" { if !row.elsewhere { p := b.Placement[Node] p.Modules = append(p.Modules, delivery.PlacedModule{Module: "backup"}) b.Placement[Node] = p } digest := delivery.ManifestDigest([]byte(row.bad)) b.Manifests[digest] = []byte(row.bad) b.Module["backup"] = delivery.Module{FP2: fp, Manifest: digest} if row.built != "" { builtDigest := delivery.ManifestDigest([]byte(row.built)) b.Manifests[builtDigest] = []byte(row.built) b.Builds["backup "+fp] = &delivery.BuilderBuild{State: delivery.BuildDone, Manifest: builtDigest, Artifacts: []delivery.BuiltArtifact{{Name: "files", Kind: "archive", Reference: "artifact-store://backup/files/blobs/x", Digest: "sha256:x"}}} } if _, err := frombus.Plans(context.Background(), b, frombus.Mesh{}, frombus.Scope{Node: Node}); err == nil && !row.elsewhere { t.Errorf("%s: Plans composed the node; the row does not refuse it", row.name) } } if row.why != "" || row.bad == "" { // The node's own composition holds by the same rule: every other module composed, the bad one named. fps := FPs() if row.bad != "" { fps["backup"] = fp } out, _, held, err := frombus.Compose(context.Background(), b, frombus.Mesh{}, frombus.Scope{Node: Node}, fps) if err != nil { t.Fatalf("%s: the node's composition: %v", row.name, err) } if key, _ := delivery.ComposedKey(Node, "restic", fps["restic"]); out[key].Module != "restic" { t.Errorf("%s: restic is not composed (held %v)", row.name, held) } if why, ok := held["backup"]; (row.bad == "" && len(held) > 0) || (row.bad != "" && (!ok || !strings.Contains(why, row.why) || len(held) != 1)) { t.Errorf("%s: the node's composition held %v, want backup alone, saying %q", row.name, held, row.why) } } plans, held, err := frombus.PlansHeld(context.Background(), b, frombus.Mesh{}, frombus.Scope{Node: Node}) if err != nil { t.Fatalf("%s: %v", row.name, err) } restic, handed := plans["restic"] if !handed || len(restic.Provisions) != 1 || restic.Provisions[0].Provision != "postgres-database" || restic.Provisions[0].Provider != Node || restic.Provisions[0].ProviderModule != "postgres" { t.Fatalf("%s: the plans are %+v (held %v)", row.name, plans, held) } if row.bad == "" || row.elsewhere { if len(held) > 0 { t.Errorf("%s: held %v", row.name, held) } continue } if why, ok := held["backup"]; !ok || !strings.Contains(why, row.why) || len(held) != 1 { t.Errorf("%s: held %v, want backup alone, saying %q", row.name, held, row.why) } if _, planned := plans["backup"]; planned { t.Errorf("%s: the held module has a plan", row.name) } } } // unusableItem is a manifest the mesh refuses as the node-engine did power's at mesh-catalog ca8d6f33: a bar block item // with attributes its bar places by none of. const unusableItem = `{"module":"backup","version":"1","moved":true,` + `"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},` + `"items":[{"collection":"contribution:node-bar:block",` + `"attributes":{"bar":"bottom","place":"status","shows":"state","state":"power.draw"},"template":"files/bar"}]}`