package broker import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" ) // 1945/AC1: a node's composition bucket keeps five revisions of a key and is named for its node; the bucket of digests // likewise; a node name a subject would widen is refused. func TestTheCompositionBuckets(t *testing.T) { cfg, err := ComposedConfig("ace") if err != nil || cfg.Bucket != "composed-ace" || cfg.History != 5 { t.Fatalf("ComposedConfig(ace) = %+v, %v", cfg, err) } if d := ComposedDigestsConfig(); d.Bucket != delivery.ControllerComposedBucket || d.History != 5 { t.Errorf("ComposedDigestsConfig = %+v", d) } if _, err := ComposedConfig("a.b"); err == nil { t.Errorf("a dotted node got a bucket") } } // 1945/AC1: a node-engine reads its own compositions and its own wants and nothing of another node's, nor the digests; // the controller writes a node's bucket by its own subjects. func TestANodeReadsOnlyItsOwnCompositions(t *testing.T) { reads, err := ComposedReads("ace") if err != nil { t.Fatal(err) } all := strings.Join(reads, " ") for _, want := range []string{"KV_composed-ace", "$KV.mesh-delivery-assigner_wants.want.ace.>"} { if !strings.Contains(all, want) { t.Errorf("a node-engine of ace cannot reach %s: %v", want, reads) } } for _, never := range []string{"composed-g14", delivery.ControllerComposedBucket, "$KV.composed-ace", "want.*", "want.>"} { if strings.Contains(all, never) { t.Errorf("a node-engine of ace is granted %s: %v", never, reads) } } if w, err := ComposedWrites("ace"); err != nil || w != "$KV.composed-ace.>" { t.Errorf("ComposedWrites(ace) = %q, %v", w, err) } if _, err := ComposedReads("a*"); err == nil { t.Errorf("a node name with a wildcard was granted") } } // 1945 (for issue 1944): a node-engine asks its own machine's backup holder to back a data module up before it moves // and when it last was, and no other machine's. func TestANodeAgentAsksItsOwnBackupHolder(t *testing.T) { asks, err := NodeAgentAsks("ace") if err != nil || strings.Join(asks, " ") != "mesh.seat.node-backup.tool.now.ace mesh.seat.node-backup.tool.backed-up.ace" { t.Errorf("NodeAgentAsks(ace) = %v, %v", asks, err) } p, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace", PasswordHash: "x"}) if err != nil { t.Fatal(err) } all := strings.Join(p.Publish, " ") for _, want := range append(asks, "$JS.API.DIRECT.GET.KV_composed-ace.>") { if !strings.Contains(all, want) { t.Errorf("a node-engine of ace may not publish %s", want) } } if !strings.Contains(strings.Join(p.Subscribe, " "), "mesh.mod.restic.event.backed-up") { t.Errorf("a node-engine of ace does not hear a backup taken: %v", p.Subscribe) } if strings.Contains(all, "node-backup.tool.now.*") || strings.Contains(all, "KV_composed-g14") { t.Errorf("a node-engine of ace reaches another machine: %v", p.Publish) } } // recordingRaiser records what a raise asserts. type recordingRaiser struct{ streams, consumers []string } func (r *recordingRaiser) EnsureStream(s Stream) error { r.streams = append(r.streams, s.Name) return nil } func (r *recordingRaiser) EnsureConsumer(c Consumer) error { r.consumers = append(r.consumers, c.Stream+"/"+c.Name) return nil } // 1945/AC14 (unit half): once the declarations' stream is retired, a raise asserts every stream of the mesh but it, // and no node's consumer of a declaration; before, a raise asserts both (ADR 0539 clause 11). func TestARaiseAfterTheDeclarationsAssertsNoneOfThem(t *testing.T) { before := &recordingRaiser{} if err := Raise(before, []string{"ace"}); err != nil { t.Fatal(err) } if !strings.Contains(strings.Join(before.streams, " "), DeclarationsStream) || !strings.Contains(strings.Join(before.consumers, " "), "NODES/ace") { t.Fatalf("before the retirement a raise asserts %v and %v", before.streams, before.consumers) } after := &recordingRaiser{} if err := RaiseAfterDeclarations(after); err != nil { t.Fatal(err) } if strings.Contains(strings.Join(after.streams, " "), DeclarationsStream) || strings.Contains(strings.Join(after.consumers, " "), "NODES/") { t.Errorf("after the retirement a raise asserts %v and %v", after.streams, after.consumers) } if len(after.streams) != len(before.streams)-1 { t.Errorf("after the retirement %d streams are asserted, before %d", len(after.streams), len(before.streams)) } } // 1945 (the review of A1, (b) and H1): the operator's account, a person who may invoke every tool, writes release 6's // hand path: a want of any node, each node's compositions, the digests, and a node's object bucket with what replacing // an object needs; a person with fewer tools writes none of it. func TestTheOperatorWritesTheHandPath(t *testing.T) { users, err := Users(Records{Nodes: []string{"ace", "novox"}, People: map[string][]string{"jochen": {"*"}, "guest": {"mesh-controller.status"}}}) if err != nil { t.Fatal(err) } for _, u := range users { if u.Kind != KindPerson { continue } perms, err := PermissionsFor(u) if err != nil { t.Fatal(err) } for _, s := range []string{"$KV.mesh-delivery-assigner_wants.want.>", "$KV.composed-ace.>", "$KV.composed-novox.>", "$KV.mesh-controller_composed.>", "$O.declaration-files-ace.>", "$JS.API.DIRECT.GET.OBJ_declaration-files-ace.>", "$JS.API.STREAM.MSG.GET.OBJ_declaration-files-ace", "$JS.API.STREAM.PURGE.OBJ_declaration-files-ace"} { found := false for _, p := range perms.Publish { found = found || p == s } if found != (u.Module == "jochen") { t.Errorf("%s may publish %s: %v", u.Module, s, found) } } } c, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x"}) if err != nil { t.Fatal(err) } found := false for _, p := range c.Publish { found = found || p == "mesh.seat.mesh-controller.event.bucket-deleted" } if !found { t.Errorf("the controller may not say bucket-deleted") } }