package approval import ( "context" "reflect" "sort" "testing" condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/views" catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" gate "git.novox.be/novox/mesh-sdk/go/approval" "git.novox.be/novox/mesh-sdk/go/seatdef" ) // fakeCatalog is a catalog half as a fixture: entries by fingerprint, the newest by module, the seat definitions. type fakeCatalog struct { ready error revision string byFp map[string]catalogseat.CatalogEntry newest map[string]catalogseat.CatalogEntry defs map[string]seatdef.Definition } func (c *fakeCatalog) Ready() error { return c.ready } func (c *fakeCatalog) Revision() string { return c.revision } func (c *fakeCatalog) Served(fp string) (gate.Entry, bool, string) { e, ok := c.byFp[fp] return Entry(e), ok, "not-found" } func (c *fakeCatalog) Newest(m string) (gate.Entry, bool) { e, ok := c.newest[m] return Entry(e), ok } func (c *fakeCatalog) Definitions() map[string]seatdef.Definition { return c.defs } func (c *fakeCatalog) Entries() []gate.Entry { var out []gate.Entry for _, e := range c.newest { out = append(out, Entry(e)) } return out } func entry(fp string, serves []string, manifest map[string]any) catalogseat.CatalogEntry { return catalogseat.CatalogEntry{Module: "shell", Fp: fp, Serves: serves, Manifest: manifest} } // Design 46 7-approval-mark §5, §3, §4 and design 34 every-event §33: the marks a module's calls are gated by, derived from // fixture catalog halves and served entries. func TestTheMarks(t *testing.T) { login := map[string]seatdef.Definition{"node-login-shell": {Seat: "node-login-shell", Verbs: []seatdef.Verb{{Name: "execute", NeedsApproval: true}, {Name: "sessions"}}}} v1 := entry("src2:1", []string{"node-login-shell.execute", "node-login-shell.sessions"}, map[string]any{"needs-approval": []any{"wipe"}}) v2 := entry("src2:2", []string{"node-login-shell.execute", "node-login-shell.sessions"}, map[string]any{"needs-approval": []any{"erase", "node-login-shell.sessions"}}) mail := catalogseat.CatalogEntry{Module: "mail", Fp: "src2:m", Manifest: map[string]any{ "needs-approval": []any{"mail.received"}, "emits": []any{"mail.received"}, "streams": []any{map[string]any{"name": "MAIL", "events": []any{"mail.**"}, "grant": map[string]any{"config": "readers", "events": "mail.{key}.**"}}}}} for _, tc := range []struct { name string cat *fakeCatalog module string fp string tools []string verbs map[string]string subjects []string unread bool }{ {name: "a seat's mark stands for every holder, and the holder's own adds up", cat: &fakeCatalog{revision: "7", byFp: map[string]catalogseat.CatalogEntry{"src2:1": v1}, newest: map[string]catalogseat.CatalogEntry{"shell": v1}, defs: login}, module: "shell", fp: "src2:1", tools: []string{"wipe"}, verbs: map[string]string{"node-login-shell.execute": "seat node-login-shell"}}, {name: "a holder's needs-approval cannot unmark its seat's verb, and marks another", cat: &fakeCatalog{revision: "8", byFp: map[string]catalogseat.CatalogEntry{"src2:2": v2}, newest: map[string]catalogseat.CatalogEntry{"shell": v2}, defs: login}, module: "shell", fp: "src2:2", tools: []string{"erase"}, verbs: map[string]string{"node-login-shell.execute": "seat node-login-shell", "node-login-shell.sessions": "entry shell src2:2"}}, {name: "across a version change the union stands: a dropped mark is asked while the old bundle runs", cat: &fakeCatalog{revision: "9", byFp: map[string]catalogseat.CatalogEntry{"src2:1": v1, "src2:2": v2}, newest: map[string]catalogseat.CatalogEntry{"shell": v2}, defs: login}, module: "shell", fp: "src2:1", tools: []string{"erase", "wipe"}, verbs: map[string]string{"node-login-shell.execute": "seat node-login-shell", "node-login-shell.sessions": "entry shell src2:2"}}, {name: "a served entry that cannot be read leaves the newest marks, every marked call refused", cat: &fakeCatalog{revision: "9", newest: map[string]catalogseat.CatalogEntry{"shell": v2}, defs: login}, module: "shell", fp: "src2:1", tools: []string{"erase"}, verbs: map[string]string{"node-login-shell.execute": "seat node-login-shell", "node-login-shell.sessions": "entry shell src2:2"}, unread: true}, {name: "a bundle with no fingerprint has no served entry", cat: &fakeCatalog{revision: "9", defs: login}, module: "shell", unread: true}, {name: "a marked event and a stream with a grant give their subjects and each .>", cat: &fakeCatalog{revision: "3", byFp: map[string]catalogseat.CatalogEntry{"src2:m": mail}, newest: map[string]catalogseat.CatalogEntry{"mail": mail}}, module: "mail", fp: "src2:m", subjects: []string{"mesh.mod.mail.event.mail.received", "mesh.mod.mail.event.mail.received.>", "mesh.stream.mail.event.mail.>", "mesh.stream.mail.event.mail.received", "mesh.stream.mail.event.mail.received.>"}}, } { t.Run(tc.name, func(t *testing.T) { m := gate.Derive(tc.cat, tc.module, tc.fp) if got := keysOf(m.Tools); !reflect.DeepEqual(got, tc.tools) { t.Errorf("tools %v, want %v", got, tc.tools) } if tc.verbs == nil { tc.verbs = map[string]string{} } if !reflect.DeepEqual(m.Verbs, tc.verbs) { t.Errorf("verbs %v, want %v", m.Verbs, tc.verbs) } if got := keysOf(m.Subjects); !reflect.DeepEqual(got, tc.subjects) { t.Errorf("subjects %v, want %v", got, tc.subjects) } if (m.Unread != "") != tc.unread || m.Revision != tc.cat.revision { t.Errorf("unread %q, revision %q", m.Unread, m.Revision) } }) } // §4: a bundle whose marks name a tool it does not register is not loaded, its finding mark-unserved raised; a bundle that // serves it clears it, and a catalog without the mark too. t.Run("mark-unserved raised and cleared", func(t *testing.T) { cat := &fakeCatalog{revision: "7", byFp: map[string]catalogseat.CatalogEntry{"src2:1": v1}, newest: map[string]catalogseat.CatalogEntry{"shell": v1}, defs: login} w := &findings{} g := gate.New(gate.Deps{Node: "n1", Runner: "node-tools", Catalog: cat, Findings: Findings(w, "n1", "node-tools", nil)}) if got := g.Unserved("shell", "src2:1", []string{"other"}); !reflect.DeepEqual(got, []string{"wipe"}) || w.raised[keys.EscapeName("n1.shell")] != gate.KindMarkUnserved { t.Fatalf("unserved %v, findings %v", got, w.raised) } if got := g.Unserved("shell", "src2:1", []string{"wipe"}); got != nil || w.raised[keys.EscapeName("n1.shell")] != "" { t.Fatalf("a bundle serving the mark: unserved %v, findings %v", got, w.raised) } _ = g.Unserved("shell", "src2:1", nil) cat.byFp["src2:1"], cat.newest["shell"] = entry("src2:1", nil, nil), entry("src2:1", nil, nil) if got := g.Unserved("shell", "src2:1", nil); got != nil || w.raised[keys.EscapeName("n1.shell")] != "" { t.Fatalf("a catalog without the mark: unserved %v, findings %v", got, w.raised) } }) } // findings is the runner's held writer as a fixture: each finding raised now, by id. type findings struct { raised map[string]string // pends are the keys pending in the held writer's statement; pended every key ever pended. pends map[string]views.PendingKey pended []string } func (f *findings) Pend(p views.PendingKey) { if f.pends == nil { f.pends = map[string]views.PendingKey{} } f.pends[p.Key] = p f.pended = append(f.pended, p.Key) } func (f *findings) Judged(key string) { delete(f.pends, key) } func (f *findings) Write(_ context.Context, x condseat.Finding) error { if f.raised == nil { f.raised = map[string]string{} } f.raised[x.ID] = x.Kind return nil } func (f *findings) Clear(_ context.Context, _, id, _, _ string) error { delete(f.raised, id) return nil } func keysOf(set map[string]string) []string { var out []string for k := range set { out = append(out, k) } sort.Strings(out) return out }