diff --git a/internal/broker/condition_grant_test.go b/internal/broker/condition_grant_test.go new file mode 100644 index 00000000..2fcc02a4 --- /dev/null +++ b/internal/broker/condition_grant_test.go @@ -0,0 +1,74 @@ +package broker + +import "testing" + +// The operator-channel seat as the messenger declares it once it takes a module's condition: copied from +// mesh-catalog modules/messenger/module.json at 5b235a6e (issue 1217), the seat's accepts, emits and by-caller verbs. +func operatorChannelWithCondition() Seat { + return Seat{Name: "operator-channel", Scope: "mesh", Accepts: []string{"ask", "cancel", "condition"}, + Emits: []string{"decided"}, Serves: []string{"open", "history", "notify", "tell", "asks"}, + ByCaller: []string{"ask", "cancel", "condition", "decided"}, Records: []string{"asks"}} +} + +// 1216/AC1: a module that uses the seat may publish its condition under its own name. +func TestAModuleUsingTheOperatorChannelMayPublishItsConditionUnderItsOwnName(t *testing.T) { + x := perms(t, Principal{Kind: KindModule, Node: "anchor", Module: "web-app", Uses: []Seat{operatorChannelWithCondition()}}) + if !allowed(x.Publish, "mesh.seat.operator-channel.accept.condition.web-app") { + t.Errorf("web-app may not say a condition as itself: %v", x.Publish) + } +} + +// 1216/AC2: it may not publish a condition under another module's name, nor on the wildcard. +func TestAModuleMayNotSayAConditionUnderAnotherName(t *testing.T) { + x := perms(t, Principal{Kind: KindModule, Node: "anchor", Module: "web-app", Uses: []Seat{operatorChannelWithCondition()}}) + for _, subject := range []string{ + "mesh.seat.operator-channel.accept.condition.other-app", + "mesh.seat.operator-channel.accept.condition.*", + "mesh.seat.operator-channel.accept.condition.>", + "mesh.seat.operator-channel.accept.condition", + } { + if allowed(x.Publish, subject) { + t.Errorf("web-app may publish %s", subject) + } + } +} + +// 1216/AC3: the seat's holder may take accept.condition. from the work queue SEAT_OPERATOR_CHANNEL. +func TestTheHolderOfTheOperatorChannelTakesEveryModulesCondition(t *testing.T) { + seat := operatorChannelWithCondition() + holder := perms(t, Principal{Kind: KindModule, Node: "anchor", Module: "messenger", Holds: []Seat{seat}}) + if !allowed(holder.Subscribe, "mesh.seat.operator-channel.accept.condition.web-app") || + !allowed(holder.Subscribe, "mesh.seat.operator-channel.accept.condition.other-app") { + t.Errorf("the holder does not take accept.condition.*: %v", holder.Subscribe) + } + if !allowed(holder.Publish, "$JS.API.CONSUMER.MSG.NEXT.SEAT_OPERATOR_CHANNEL.SEAT_OPERATOR_CHANNEL_worker") { + t.Errorf("the holder may not pull the seat's work queue: %v", holder.Publish) + } + users, _ := Users(Records{Nodes: []string{"anchor"}, Assigned: map[string][]Declared{"anchor": { + {Module: "messenger", Holds: []Seat{seat}}, + {Module: "web-app", Uses: []Seat{seat}}, + }}}) + streams, workers := SeatTrafficObjects(users) + kept := false + for _, s := range streams { + if s.Name == "SEAT_OPERATOR_CHANNEL" { + for _, subject := range s.Subjects { + kept = kept || subjectMatches(subject, "mesh.seat.operator-channel.accept.condition.web-app") + } + } + } + if !kept { + t.Error("the work queue SEAT_OPERATOR_CHANNEL does not keep accept.condition.") + } + taken := false + for _, w := range workers { + if w.Name == "SEAT_OPERATOR_CHANNEL_worker" { + for _, f := range w.Filters { + taken = taken || subjectMatches(f, "mesh.seat.operator-channel.accept.condition.web-app") + } + } + } + if !taken { + t.Error("the seat's worker does not take accept.condition.") + } +}