Files
mesh-controller/internal/broker/composed_test.go
T
jschoubben b4a62e9e6e Delete the declarations' stream once every node runs the new node-engine, and assert it no more (issue 1945, B13)
A node away during the switch finds its last declaration on the stream; once every node's have names the
node-engine registered, no node reads one, so the stream and the nodes' consumers on it go, said once, and a raise
leaves them out (ADR 0539 clause 11).
2026-10-24 17:28:10 +02:00

109 lines
4.3 KiB
Go

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.*.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))
}
}