Announce the controller's states, keep every module's word, and grant a read only where its owner announced the state readable; the copied list of the controller's mirrors goes (issue 1790, D1-D4, ADR 0495, ADR 0513 §3)
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
//go:build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/announce"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/testbus"
|
||||
)
|
||||
|
||||
// D1-D3 whole against a real bus and the store (ADR 0495 checks §2, §3, §5; ADR 0513 §3; issue 1790): the controller
|
||||
// says what it keeps and keeps what every module says; a reader of a state its owner announced readable is granted it,
|
||||
// one its owner announced and did not make readable is refused and said, and one of an owner that announced nothing
|
||||
// yet is granted as before and named as not yet announced.
|
||||
func TestTheStatesAnnouncedGrantTheReads(t *testing.T) {
|
||||
open := aMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
js, err := broker.Dial(testbus.URL(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(js.Close)
|
||||
if err := broker.AssertMeshStreams(js); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := js.EnsureControllerBuckets(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
servingBus.Store(js)
|
||||
t.Cleanup(func() { servingBus.Store(nil) })
|
||||
was := inventory.Announcements
|
||||
inventory.Announcements = readAnnouncements
|
||||
t.Cleanup(func() { inventory.Announcements = was })
|
||||
|
||||
// The owner keeps two states, one readable; the reader reads both and a state of a module that says nothing.
|
||||
register := func(m catalogue.Manifest) {
|
||||
t.Helper()
|
||||
if err := inv.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog", Seat: "git",
|
||||
Path: "modules/" + m.Module, BuiltFrom: "c0"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
register(catalogue.Manifest{Module: "owner", Version: "1", State: []catalogue.StateDeclaration{{Name: "open"}, {Name: "closed"}}})
|
||||
register(catalogue.Manifest{Module: "quiet", Version: "1", State: []catalogue.StateDeclaration{{Name: "any"}}})
|
||||
register(catalogue.Manifest{Module: "reader", Version: "1", Reads: []string{"owner.open", "owner.closed", "quiet.any"}})
|
||||
if _, err := inv.AddNode(ctx, "one"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, m := range []string{"owner", "quiet", "reader"} {
|
||||
if _, err := inv.Assign(ctx, "one", m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
watching, stop := context.WithCancel(context.Background())
|
||||
t.Cleanup(stop)
|
||||
if err := announceOwnStates(ctx, js); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := keepAnnouncements(watching, js, open); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
api, _ := jetstream.New(js.Conn())
|
||||
word, _ := announce.Encode(announce.States{Module: "owner", Node: "one",
|
||||
States: []announce.State{{Name: "open", Readable: true}, {Name: "closed"}}})
|
||||
if _, err := api.Publish(ctx, announce.Subject("owner"), word); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
becomes(t, 10*time.Second, "both words kept", func() bool {
|
||||
a, err := readAnnouncements(ctx)
|
||||
return err == nil && len(a) == 2 && a["owner"].Readable("open") && a[broker.ControllerSeat].Readable(broker.PlansBuilding)
|
||||
})
|
||||
|
||||
records, err := inv.BusRecords(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var reader broker.Declared
|
||||
for _, d := range records.Assigned["one"] {
|
||||
if d.Module == "reader" {
|
||||
reader = d
|
||||
}
|
||||
}
|
||||
if !slices.Equal(reader.Reads, []string{"owner.open", "quiet.any"}) || !slices.Equal(reader.ReadsRefused, []string{"owner.closed"}) {
|
||||
t.Fatalf("0495 §3: the reader is granted %v and refused %v", reader.Reads, reader.ReadsRefused)
|
||||
}
|
||||
if got := broker.NotAnnounced(records); !slices.Equal(got, []string{"quiet"}) {
|
||||
t.Fatalf("not yet announced: %v", got)
|
||||
}
|
||||
users, err := broker.Users(records)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, u := range users {
|
||||
if u.Kind != broker.KindModule || u.Module != "reader" {
|
||||
continue
|
||||
}
|
||||
perms, err := broker.PermissionsFor(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if slices.ContainsFunc(perms.Publish, func(s string) bool { return s == "$JS.API.DIRECT.GET.KV_owner_closed.>" }) {
|
||||
t.Fatal("0495 §3: the reader may read the state its owner did not announce readable")
|
||||
}
|
||||
if !slices.Contains(perms.Publish, "$JS.API.DIRECT.GET.KV_owner_open.>") {
|
||||
t.Fatal("0495 §3: the reader may not read the state its owner announced readable")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user