package catalogue import ( "encoding/json" "slices" "strings" "testing" ) // mailManifest is a module owning a restricted stream, as mesh-mail declares it (novox/hq ADR 0363), with // the trusted mergeable file that takes its grants setting; extra is spliced in at the top. func mailManifest(t *testing.T, streams, extra string) string { t.Helper() return `{"module":"mesh-mail","version":"1",` + extra + `"streams":` + streams + `,` + `"resources":[{"id":"state","type":"directory","mode":"0700","place":"."},` + `{"id":"grants","trusted":true,"type":"file","path":"${dir:state}/grants.json","mode":"0600",` + `"merge":"json","takes-settings":["grants"],"content":"{\"grants\": {}}"}]}` } const mailStream = `[{"name":"mail","events":["mail.*.message.added","mail.*.message.changed",` + `"mail.*.message.removed"],"max-age-days":7,"grant":{"config":"grants","events":"mail.{key}.**"}}]` // A module declares a stream of its own (ADR 0363 §1): its events with a variable name (§3), its age and // size with defaults, and a grant by a trusted setting of its own (§4). func TestAModuleDeclaresAStreamOfItsOwn(t *testing.T) { m, err := ParseManifest([]byte(mailManifest(t, mailStream, ""))) if err != nil { t.Fatal(err) } if len(m.Streams) != 1 { t.Fatalf("streams not read: %+v", m.Streams) } s := m.Streams[0] if s.BusName() != "MAIL" || s.Days() != 7 || s.Cap() != StreamDefaultMiB || s.Grant == nil || s.Grant.Config != "grants" || s.Grant.Granted("mesh") != "mail.mesh.**" { t.Fatalf("stream read as %+v", s) } if !s.Carries("mail.work.message.added") || s.Carries("mail.work.body") || !s.Carries("mail.*.message.removed") { t.Fatal("the stream's patterns do not say what it carries") } plain, err := ParseManifest([]byte(`{"module":"feed","version":"1","streams":[{"name":"feed-items","events":["item.**"]}]}`)) if err != nil { t.Fatalf("a stream without a grant was refused: %v", err) } if got := plain.Streams[0]; got.BusName() != "FEED_ITEMS" || got.Days() != StreamDefaultDays || got.Grant != nil { t.Fatalf("defaults read as %+v", got) } } // Registration refuses a stream the mesh could not hold or would leave ambiguous (ADR 0363 §1, §2, §4). func TestAStreamIsNamedBoundedAndGrantedByATrustedConfigValue(t *testing.T) { grants := `"grant":{"config":"grants","events":"mail.{key}.**"}` for _, c := range []struct{ manifest, says string }{ {mailManifest(t, `[{"name":"Mail","events":["mail.**"]}]`, ""), `a stream is named locally`}, {mailManifest(t, `[{"name":"mail.x","events":["mail.**"]}]`, ""), `a stream is named locally`}, {mailManifest(t, `[{"name":"events","events":["mail.**"]}]`, ""), `the mesh's own stream EVENTS`}, {mailManifest(t, `[{"name":"log-x","events":["mail.**"]}]`, ""), `starts with LOG_`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"]},{"name":"mail","events":["x.**"]}]`, ""), `twice`}, {mailManifest(t, `[{"name":"mail","events":[]}]`, ""), `with no events`}, {mailManifest(t, `[{"name":"mail","events":["mail.**.x"]}]`, ""), `nothing may follow it`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"max-age-days":31}]`, ""), `between 1 and 30`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"max-mib":9000}]`, ""), `between 1 and 8192`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"stream":"MAIL"}]`, ""), `{name, events`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"]}]`, `"emits":["mail.mesh.message.added"],`), `which its stream "mail" carries`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"]},{"name":"more","events":["mail.x.*"]}]`, ""), `both carry`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"tags","events":"mail.{key}.**"}}]`, ""), `is not a trusted config value`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"grants","events":"mail.**"}}]`, ""), `exactly once`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],"grant":{"config":"grants","events":"{key}.{key}"}}]`, ""), `exactly once`}, {mailManifest(t, `[{"name":"mail","events":["mail.**"],`+grants+`,"why":1}]`, ""), `{name, events`}, {`{"module":"a.b","version":"1","streams":[{"name":"x","events":["x"]}]}`, `no dot`}, } { _, err := ParseManifest([]byte(c.manifest)) if err == nil { t.Errorf("%s was accepted", c.manifest) continue } if !strings.Contains(err.Error(), c.says) { t.Errorf("%s refused for the wrong reason: %v", c.manifest, err) } } } // A stream name has one owner across the catalogue (ADR 0363 §1). func TestTwoModulesCannotDeclareOneStream(t *testing.T) { a := Manifest{Module: "a", Streams: []StreamDeclaration{{Name: "mail", Events: []string{"x"}}}} b := Manifest{Module: "b", Streams: []StreamDeclaration{{Name: "mail", Events: []string{"y"}}}} got := strings.Join(CatalogueProblems(Shelf{"a": a, "b": b}), "; ") if !strings.Contains(got, "b declares the stream MAIL, which a already declares") { t.Fatalf("a second owner of MAIL was not refused: %s", got) } if got := StreamNameProblems(Shelf{"a": a}); len(got) != 0 { t.Fatalf("one owner refused: %v", got) } } // The setting's value names who hears each key: a module wherever it runs, or on one machine (ADR 0363 §4). func TestARestrictingSettingNamesAModuleOrAModuleOnAMachine(t *testing.T) { var value any if err := json.Unmarshal([]byte(`{"mesh":["reader","person.jochen"],"work":["ace.reader"],"home":["other"],`+ `"Bad.Key":["reader"]}`), &value); err != nil { t.Fatal(err) } if got := GrantedKeys(value, "reader", "ace"); !slices.Equal(got, []string{"mesh", "work"}) { t.Fatalf("on ace reader is granted %v", got) } if got := GrantedKeys(value, "reader", "shanks"); !slices.Equal(got, []string{"mesh"}) { t.Fatalf("on shanks reader is granted %v", got) } if got := GrantedKeys(`{"mesh":["reader"]}`, "reader", "ace"); !slices.Equal(got, []string{"mesh"}) { t.Fatalf("a value held as its JSON text grants %v", got) } if got := GrantedKeys([]any{"reader"}, "reader", "ace"); len(got) != 0 { t.Fatalf("a value of another shape granted %v", got) } } // A consumer hears a restricted stream only where its consumes asks and the setting grants it, and only // by naming the emitter (ADR 0363 §5); what a stream carries whole leaves EVENTS. func TestAConsumerHearsOnlyWhatItAsksAndIsGranted(t *testing.T) { provider, err := ParseManifest([]byte(mailManifest(t, mailStream, `"emits":["account.added"],`))) if err != nil { t.Fatal(err) } shelf := map[string]Manifest{"mesh-mail": provider} var value any _ = json.Unmarshal([]byte(`{"mesh":["reader"],"work":["shanks.reader"]}`), &value) values := func(module, setting string) []any { if module == "mesh-mail" && setting == "grants" { return []any{value} } return nil } reader := Manifest{Module: "reader", Consumes: []string{"mesh-mail.mail.**", "mesh-mail.account.added", "billing.paid"}} parts, touches, onEvents := Hearing(reader, shelf, "ace", values) if !touches || len(parts) != 1 || parts[0].Stream.BusName() != "MAIL" { t.Fatalf("heard %+v", parts) } want := []string{"mail.mesh.message.added", "mail.mesh.message.changed", "mail.mesh.message.removed"} if !slices.Equal(parts[0].Patterns, want) { t.Fatalf("on ace reader hears %v, want %v", parts[0].Patterns, want) } if !slices.Equal(onEvents, []string{"mesh-mail.account.added", "billing.paid"}) { t.Fatalf("left on EVENTS: %v", onEvents) } parts, _, _ = Hearing(reader, shelf, "shanks", values) if len(parts) != 1 || len(parts[0].Patterns) != 6 { t.Fatalf("on shanks reader hears %+v", parts) } narrow := Manifest{Module: "reader", Consumes: []string{"mesh-mail.mail.*.message.added"}} parts, _, _ = Hearing(narrow, shelf, "ace", values) if len(parts) != 1 || !slices.Equal(parts[0].Patterns, []string{"mail.mesh.message.added"}) { t.Fatalf("a narrower consumes heard %+v", parts) } stranger := Manifest{Module: "stranger", Consumes: []string{"mesh-mail.mail.**"}} parts, touches, onEvents = Hearing(stranger, shelf, "ace", values) if len(parts) != 0 || !touches || len(onEvents) != 0 { t.Fatalf("a module not granted heard %+v (touches %v, on EVENTS %v)", parts, touches, onEvents) } everything := Manifest{Module: "audit", Consumes: []string{"**", "*.mail.**"}} parts, touches, _ = Hearing(everything, shelf, "ace", values) if len(parts) != 0 || touches { t.Fatalf("a wildcard emitter reached a module's stream: %+v", parts) } parts, _, _ = Hearing(reader, shelf, "ace", func(string, string) []any { return nil }) if len(parts) != 0 { t.Fatalf("with no setting reader heard %+v", parts) } } // A stream without a grant is heard by whoever asks, within its patterns. func TestAnUnrestrictedStreamIsHeardByWhoeverAsks(t *testing.T) { feed := Manifest{Module: "feed", Streams: []StreamDeclaration{{Name: "items", Events: []string{"item.*.added", "item.*.gone"}}}} reader := Manifest{Module: "reader", Consumes: []string{"feed.item.**", "feed.item.x.added"}} parts, touches, onEvents := Hearing(reader, map[string]Manifest{"feed": feed}, "ace", nil) if !touches || len(onEvents) != 0 || len(parts) != 1 || !slices.Equal(parts[0].Patterns, []string{"item.*.added", "item.*.gone"}) { t.Fatalf("heard %+v, on EVENTS %v", parts, onEvents) } } func TestNarrowingTwoPatterns(t *testing.T) { for _, c := range []struct{ a, b, want string }{ {"mail.**", "mail.*.message.added", "mail.*.message.added"}, {"mail.*.message.added", "mail.mesh.**", "mail.mesh.message.added"}, {"a.*", "*.b", "a.b"}, {"**", "a.**", "a.**"}, {"a.b", "a.c", ""}, {"a", "a.**", ""}, } { got, ok := narrow(c.a, c.b) if (c.want == "") == ok || got != c.want { t.Errorf("narrow(%s, %s) = %q, %v; want %q", c.a, c.b, got, ok, c.want) } } if got := uncovered([]string{"a.**", "a.b", "a.b", "c"}); !slices.Equal(got, []string{"a.**", "c"}) { t.Fatalf("uncovered: %v", got) } } // The mesh's own stream names are written in the broker too; this list must name the same. func TestStreamNamesTheMeshReservesIncludeEveryOwnStream(t *testing.T) { for _, n := range []string{"EVENTS", "CONTROL", "NODES", "ASSIGNMENTS"} { if !slices.Contains(MeshStreamNames, n) { t.Fatalf("%s is missing", n) } } }