package manifest import ( "encoding/json" "slices" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-grants/internal/grants" ) // Issue 774, design 47 L3 (ADR 0547 §11, ADR 0561 §3, ADR 0571 §13): grants_lease_check answers from bus state who holds // the controller's lease, every manifest's dependency on the controller with what keeps it, the controller's streams in // use, and the lease write's check run dry, at the write (the three kept to it alone) and before its delivery. The // judgement of each kind of dependency is mesh-sdk's and tested there (catalogue/controller_dependence_test.go, its rows // moved from here, 774/S4); these rows are the tool's own: what it read, the lease, the streams. func TestTheLeaseCheckJudgesEveryDependencyOnTheController(t *testing.T) { const mManifest = `{"module":"mod-m","version":"1","reads":["mesh-controller.declared-packages"]}` controllerHolds := map[string]any{"instance": "controller@novox-be pid 1 since 2026-10-30T14:52:10Z", "epoch": 60, "taken": "2026-10-30T14:52:10Z", "renewed": "2026-10-30T22:00:00Z"} handedAway := map[string]any{"handed-to": "the operator's hand-over", "handed-by": "operator"} type want struct { deps []string // " " with kept as "kept" or "unkept" writePasses bool beforePasses bool reason string // a reason the check at the write must name, or "" bothPhases bool // the check before the delivery must name the reason too handedAway bool durable []string // durable consumers of KV_mesh-controller_assignments } for _, row := range []struct { name string manifests map[string]string lease map[string]any want want setup func(b *fakeBus) }{ {"a read of the lease alone is no dependency, and the controller's own manifest is not judged", map[string]string{"mod-l": `{"module":"mod-l","version":"1","reads":["mesh-controller.lease"]}`, "mesh-controller": `{"module":"mesh-controller","version":"1","invokes":["seat:mesh-controller.status"]}`}, controllerHolds, want{writePasses: true, beforePasses: true}, nil}, {"no manifest read from the indexer fails both phases, though nothing read depends on the controller", map[string]string{}, controllerHolds, want{reason: "no manifest was read from the indexer (it holds 0 modules)", bothPhases: true}, nil}, {"a module whose manifest digest is missing fails both phases, named", map[string]string{"mod-l": `{"module":"mod-l","version":"1"}`, "mod-m": mManifest}, controllerHolds, want{reason: "manifest of mod-m could not be read", bothPhases: true}, func(b *fakeBus) { delete(b.states[grants.ReadManifests], delivery.ManifestDigest([]byte(mManifest))) }}, {"a manifest the indexer's judge refuses fails both phases, named", map[string]string{"mod-l": `{"module":"mod-l","version":"1"}`, "mod-r": `{"module":"mod-r","version":"1","waits":["mesh-controller.checked"]}`}, controllerHolds, want{reason: "manifest of mod-r could not be read", bothPhases: true}, func(b *fakeBus) { b.refuse("mod-r", `{"module":"mod-r","version":"1","waits":["mesh-controller.checked"]}`, "waits on what no record keeps") }}, {"a lease already handed away leaves nothing to write", map[string]string{}, handedAway, want{handedAway: true, reason: "there is nothing to write: the lease is handed to the operator's hand-over"}, nil}, } { t.Run(row.name, func(t *testing.T) { b := newFakeBus() for m, raw := range row.manifests { b.manifestOf(m, raw) } b.put(grants.ReadLease, grants.LeaseKey, row.lease) if row.setup != nil { row.setup(b) } b.streams = []map[string]any{ {"name": "KV_mesh-controller_assignments", "consumers": 3, "messages": 2991, "subjects_held": 40, "last": "2026-10-30T12:55:13Z"}, {"name": "KV_mesh-controller_rollouts", "consumers": 0, "messages": 6652}, {"name": "KV_mesh-grants_memberships", "consumers": 243, "messages": 1573}, } b.consumers = map[string][]map[string]any{"KV_mesh-controller_assignments": {{"name": "0AYfjxta"}, {"name": "1g9XJFUX"}, {"name": "grants-assignments"}}} raw, err := partOn(b).LeaseCheck() if err != nil { t.Fatal(err) } var got grants.LeaseAnswer data, _ := json.Marshal(raw) if err := json.Unmarshal(data, &got); err != nil { t.Fatal(err) } var deps []string for _, d := range got.Dependencies { kept := "unkept" if d.Kept != "" { kept = "kept" } deps = append(deps, d.Module+" "+d.Kind+" "+d.What+" "+kept) } if !slices.Equal(deps, row.want.deps) { t.Errorf("dependencies %q, want %q", deps, row.want.deps) } if got.Check.Phase != grants.PhaseWrite || got.Check.Passes != row.want.writePasses { t.Errorf("check at the write passes=%v (%v), want %v", got.Check.Passes, got.Check.Reasons, row.want.writePasses) } if got.Before.Phase != grants.PhaseBefore || got.Before.Passes != row.want.beforePasses { t.Errorf("check before the delivery passes=%v (%v), want %v", got.Before.Passes, got.Before.Reasons, row.want.beforePasses) } if row.want.reason != "" && !slices.ContainsFunc(got.Check.Reasons, func(r string) bool { return strings.Contains(r, row.want.reason) }) { t.Errorf("reasons %q name nothing like %q", got.Check.Reasons, row.want.reason) } if row.want.bothPhases && !slices.ContainsFunc(got.Before.Reasons, func(r string) bool { return strings.Contains(r, row.want.reason) }) { t.Errorf("reasons before the delivery %q name nothing like %q", got.Before.Reasons, row.want.reason) } if got.Lease.HandedAway != row.want.handedAway { t.Errorf("lease handed away %v (%s), want %v", got.Lease.HandedAway, got.Lease.Says, row.want.handedAway) } if !row.want.handedAway && (got.Lease.Since == nil || got.Lease.Since.Format("15:04") != "14:52" || !strings.Contains(got.Lease.Says, "the controller holds the lease") || !slices.Equal(got.Lease.Does, grants.DoesWhileController)) { t.Errorf("lease %+v: the controller's instance since 14:52, and what it does while it holds it", got.Lease) } if len(got.Streams) != 1 || got.Streams[0].Stream != "KV_mesh-controller_assignments" || got.Streams[0].Consumers != 3 || !slices.Equal(got.Streams[0].Durable, []string{"grants-assignments"}) || !slices.Equal(got.Idle, []string{"KV_mesh-controller_rollouts"}) { t.Errorf("streams %+v idle %v: the controller's assignments bucket with its one durable consumer, the rollouts idle, no other module's", got.Streams, got.Idle) } }) } }