package broker import ( "errors" "reflect" "slices" "strings" "testing" ) // mailStream is mesh-mail's own stream as the bus holds it (novox/hq ADR 0363). var mailStream = ModuleStream{Module: "mesh-mail", Name: "MAIL", Events: []string{"mail.*.message.added", "mail.*.message.changed", "mail.*.message.removed"}, MaxAgeDays: 7, MaxMiB: 1024, Restricted: true} // mailHeard is what a reader granted the account `mesh` hears of it. var mailHeard = Heard{Stream: "MAIL", Emitter: "mesh-mail", Restricted: true, Filters: []string{ "mesh.stream.mesh-mail.event.mail.mesh.message.added", "mesh.stream.mesh-mail.event.mail.mesh.message.changed"}} // newsStream is a module's stream without a grant: heard by whoever asks (ADR 0363 §5), the tool runner included. var newsStream = ModuleStream{Module: "news", Name: "NEWS", Events: []string{"story.**"}, MaxAgeDays: 7, MaxMiB: 16} var newsHeard = Heard{Stream: "NEWS", Emitter: "news", Filters: []string{"mesh.stream.news.event.story.>"}} // A module's stream is its own: subjects EVENTS does not claim, its age and size, kept by limits (ADR 0363 §1, §2). func TestAModuleStreamTakesSubjectsEventsDoesNot(t *testing.T) { s := mailStream.Stream() if s.Name != "MAIL" || s.Retention != RetentionLimits || s.MaxAge != 7*24*60*60 || s.MaxBytes != 1024<<20 || s.DiscardNew { t.Fatalf("composed as %+v", s) } want := []string{"mesh.stream.mesh-mail.event.mail.*.message.added", "mesh.stream.mesh-mail.event.mail.*.message.changed", "mesh.stream.mesh-mail.event.mail.*.message.removed"} if !reflect.DeepEqual(s.Subjects, want) { t.Fatalf("subjects %v", s.Subjects) } for _, own := range MeshStreams() { for _, a := range own.Subjects { for _, b := range s.Subjects { if SubjectsOverlap(a, b) { t.Fatalf("%s claims %s, which overlaps %s of %s: the bus would refuse the stream", s.Name, b, a, own.Name) } } } } if WirePattern("mail.**") != "mail.>" { t.Fatal("`**` is spelled `>` on the wire") } } // The provider publishes its stream's events on their subjects and nowhere on EVENTS for them; a consumer // granted a part hears it through its own consumer on the stream, and one not granted has nothing there // (ADR 0363 §2, §5). func TestTheProviderPublishesItsStreamAndOnlyAGrantedConsumerHearsIt(t *testing.T) { provider, err := PermissionsFor(Principal{Kind: KindModule, Node: "novox", Module: "mesh-mail", Streams: []ModuleStream{mailStream}}) if err != nil { t.Fatal(err) } if !MayPublish(provider, "mesh.stream.mesh-mail.event.mail.work.message.added") { t.Fatal("the provider may not publish its own stream's event") } if MayPublish(provider, "mesh.mod.mesh-mail.event.mail.work.message.added") { t.Fatal("the provider may publish its stream's event on EVENTS") } if MayPublish(provider, "mesh.stream.other.event.mail.work.message.added") { t.Fatal("the provider may publish on another module's stream") } reader := Principal{Kind: KindModule, Node: "ace", Module: "reader", Hears: []Heard{mailHeard}} perms, err := PermissionsFor(reader) if err != nil { t.Fatal(err) } for _, s := range []string{"$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_reader", "$JS.API.CONSUMER.INFO.MAIL.ace_reader", "$JS.ACK.MAIL.ace_reader.1"} { if !MayPublish(perms, s) { t.Errorf("the granted reader may not publish %s", s) } } if !MaySubscribe(perms, "mesh.stream.mesh-mail.event.mail.mesh.message.added") || MaySubscribe(perms, "mesh.stream.mesh-mail.event.mail.work.message.added") { t.Fatal("the reader's subscription is not what it was granted") } if MayPublish(perms, "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_other") { t.Fatal("the reader may pull another's consumer") } stranger, err := PermissionsFor(Principal{Kind: KindModule, Node: "ace", Module: "stranger", Consumes: []string{"billing.paid"}}) if err != nil { t.Fatal(err) } if MayPublish(stranger, "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_stranger") || MaySubscribe(stranger, "mesh.stream.mesh-mail.event.mail.mesh.message.added") { t.Fatal("a module not granted reaches the stream") } } // A granted consumer of its own account has a durable consumer on the stream, with its granted subjects as // filters, and an EVENTS consumer that hears what it gives up on delivered again (ADR 0363 §5, §7); a module of its // own account is no module the runtime carries, so the runtime reads neither (ADR 0376 §1). func TestAGrantedConsumerHasAConsumerOnTheStream(t *testing.T) { records := Records{Nodes: []string{"ace", "novox"}, Assigned: map[string][]Declared{ "novox": {{Module: "mesh-mail", Streams: []ModuleStream{mailStream}}}, "ace": {{Module: RuntimeModule}, {Module: "reader", RunsAs: "reader", Hears: []Heard{mailHeard}, HearsAStream: true}, {Module: "stranger", HearsAStream: true}}, }} users, err := Users(records) if err != nil { t.Fatal(err) } var onMail, onEvents []Consumer for _, c := range ConsumersOf(users) { switch c.Consumer.Stream { case "MAIL": onMail = append(onMail, c.Consumer) case EventsStream: onEvents = append(onEvents, c.Consumer) } } if len(onMail) != 1 || onMail[0].Name != "ace_reader" || !slices.Equal(onMail[0].Filters, mailHeard.Filters) { t.Fatalf("on MAIL: %+v", onMail) } if len(onEvents) != 1 || onEvents[0].Name != "ace_reader" || !slices.Equal(onEvents[0].Filters, []string{AgainFilter("ace_reader")}) { t.Fatalf("on EVENTS: %+v", onEvents) } for _, u := range users { if u.Node != "ace" || (u.Kind != KindNodeTools && u.Module != "reader") { continue } perms, err := PermissionsFor(u) if err != nil { t.Fatal(err) } reads := MayPublish(perms, "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_reader") && MayPublish(perms, "$JS.ACK.MAIL.ace_reader.1") if u.Kind == KindNodeTools && reads { t.Fatal("the runtime may read the consumer on MAIL of a module of its own account") } if u.Kind == KindModule && !reads { t.Fatal("the reader's own account may not read its consumer on MAIL") } if MayPublish(perms, "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_stranger") { t.Fatal("a consumer no module is granted may be read") } } } // **The tool runner never hears a restricted stream** (ADR 0376 §1, §2): a module it carries, granted part of // MAIL, gets no consumer on MAIL, the tool runner's account no grant to ask about, pull or acknowledge one there, // and its membership no word to bind one; a module of its own account granted the same part gets all three, on // its own account. The grant that composes nothing is said. A stream without a grant is heard through the tool // runner as before. func TestTheToolRunnerNeverHearsARestrictedStream(t *testing.T) { records := Records{Nodes: []string{"ace", "novox"}, Assigned: map[string][]Declared{ "novox": {{Module: "mesh-mail", Streams: []ModuleStream{mailStream}}, {Module: "news", Streams: []ModuleStream{newsStream}}}, "ace": {{Module: RuntimeModule}, {Module: "carried", Hears: []Heard{mailHeard, newsHeard}, HearsAStream: true}, {Module: "mailer", RunsAs: "mailer", Hears: []Heard{mailHeard}, HearsAStream: true}}, }} users, err := Users(records) if err != nil { t.Fatal(err) } var onMail, onNews []string for _, c := range ConsumersOf(users) { switch c.Consumer.Stream { case "MAIL": onMail = append(onMail, c.Consumer.Name) case "NEWS": onNews = append(onNews, c.Consumer.Name) } } if !slices.Equal(onMail, []string{"ace_mailer"}) { t.Fatalf("consumers on MAIL: %v, want only the mailer's", onMail) } if !slices.Equal(onNews, []string{"ace_carried"}) { t.Fatalf("consumers on NEWS: %v, want the carried module's as before", onNews) } for _, u := range users { if u.Node != "ace" { continue } perms, err := PermissionsFor(u) if err != nil { t.Fatal(err) } switch { case u.Kind == KindNodeTools: for _, s := range perms.Publish { if strings.Contains(s, ".MAIL.") { t.Errorf("the tool runner's account may publish %s", s) } } for _, s := range perms.Subscribe { if strings.HasPrefix(s, "mesh.stream.mesh-mail.") { t.Errorf("the tool runner's account may subscribe %s", s) } } if !MayPublish(perms, "$JS.API.CONSUMER.MSG.NEXT.NEWS.ace_carried") { t.Error("the tool runner may no longer read the carried module's consumer on a stream without a grant") } case u.Kind == KindModule && u.Module == "carried": if MayPublish(perms, "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_carried") { t.Error("a module without an account of its own may pull a consumer on MAIL") } case u.Kind == KindModule && u.Module == "mailer": for _, s := range []string{"$JS.API.CONSUMER.INFO.MAIL.ace_mailer", "$JS.API.CONSUMER.MSG.NEXT.MAIL.ace_mailer", "$JS.ACK.MAIL.ace_mailer.1"} { if !MayPublish(perms, s) { t.Errorf("the mailer's own account may not publish %s", s) } } } } where := Placements{Nodes: map[string][]string{}} carried := MembershipFor("ace", records.Assigned["ace"][1], where) if !reflect.DeepEqual(carried.Hears, []HeardIssued{{Stream: "NEWS", Consumer: "ace_carried"}}) { t.Fatalf("the carried module is told to bind %+v", carried.Hears) } mailer := MembershipFor("ace", records.Assigned["ace"][2], where) if !reflect.DeepEqual(mailer.Hears, []HeardIssued{{Stream: "MAIL", Consumer: "ace_mailer"}}) { t.Fatalf("the mailer is told to bind %+v", mailer.Hears) } withheld := WithheldOf(records) if len(withheld) != 1 || withheld[0] != (Withheld{Node: "ace", Module: "carried", Stream: "MAIL", Emitter: "mesh-mail"}) { t.Fatalf("withheld %+v", withheld) } if said := withheld[0].Said(); !strings.Contains(said, "carried on ace") || !strings.Contains(said, "MAIL") || !strings.Contains(said, "runs-as") { t.Fatalf("said %q", said) } } // The membership says where a module's stream events go and which consumers on module streams to bind // (ADR 0363 §2, §8). func TestTheMembershipNamesTheStreamAndWhatIsHeard(t *testing.T) { where := Placements{Nodes: map[string][]string{}} provider := MembershipFor("novox", Declared{Module: "mesh-mail", Streams: []ModuleStream{mailStream}}, where) if len(provider.Streams) != 1 || provider.Streams[0].Stream != "MAIL" || provider.Streams[0].Subject != "mesh.stream.mesh-mail.event.{event}" || !slices.Equal(provider.Streams[0].Events, mailStream.Events) { t.Fatalf("the provider's membership: %+v", provider.Streams) } reader := MembershipFor("ace", Declared{Module: "reader", RunsAs: "reader", Hears: []Heard{mailHeard}}, where) if !reflect.DeepEqual(reader.Hears, []HeardIssued{{Stream: "MAIL", Consumer: "ace_reader"}}) { t.Fatalf("the reader's membership: %+v", reader.Hears) } if none := MembershipFor("ace", Declared{Module: "stranger", HearsAStream: true}, where); none.Hears != nil { t.Fatalf("a module not granted is told to bind %+v", none.Hears) } } type fakePruner struct { on map[string][]string removed []string } func (f *fakePruner) ConsumerNames(stream string) ([]string, error) { return f.on[stream], nil } func (f *fakePruner) RemoveConsumer(stream, name string) error { f.removed = append(f.removed, stream+"/"+name) return nil } // A consumer that lost its grant has its consumer on the stream removed by the next assertion (ADR 0363 §6). func TestAConsumerThatLostItsGrantIsRemoved(t *testing.T) { f := &fakePruner{on: map[string][]string{"MAIL": {"ace_reader", "ace_former"}}} wanted := []Consumer{{Name: "ace_reader", Stream: "MAIL"}, {Name: "ace_former", Stream: EventsStream}} removed, err := PruneHeard(f, []ModuleStream{mailStream}, wanted) if err != nil { t.Fatal(err) } if !slices.Equal(removed, []string{"MAIL/ace_former"}) || !slices.Equal(f.removed, removed) { t.Fatalf("removed %v", removed) } } type refusingRaiser struct { fakePruner refuse bool } func (r *refusingRaiser) EnsureStream(Stream) error { return nil } func (r *refusingRaiser) EnsureConsumer(Consumer) error { if r.refuse { return errors.New("consumer update refused") } return nil } // A narrowing the bus refuses on a restricted stream removes the consumer, so its wider filters deliver nothing // more; on an unrestricted stream the refusal is only said (review of PR 338, ADR 0363 §6). func TestARefusedNarrowingOnARestrictedStreamRemovesTheConsumer(t *testing.T) { c := Consumer{Name: "ace_reader", Stream: "MAIL", Filters: mailHeard.Filters[:1]} r := &refusingRaiser{refuse: true} err := EnsureHeard(r, c, true) if err == nil || !strings.Contains(err.Error(), "REMOVED") || !slices.Equal(r.removed, []string{"MAIL/ace_reader"}) { t.Fatalf("restricted: %v, removed %v", err, r.removed) } r = &refusingRaiser{refuse: true} if err := EnsureHeard(r, c, false); err == nil || len(r.removed) != 0 { t.Fatalf("unrestricted: %v, removed %v", err, r.removed) } r = &refusingRaiser{} if err := EnsureHeard(r, c, true); err != nil || len(r.removed) != 0 { t.Fatalf("accepted: %v, removed %v", err, r.removed) } }