The copied bus composer grants mesh-grants' principal $JS.API.STREAM.CREATE.<stream> and $JS.API.STREAM.INFO.<stream> for each bucket (KV_<module>_<state>), log (LOG_<module>_<log>) and module stream a placed module's manifest declares, each by its own name: NATS wildcards are whole tokens, so nothing narrower than an exact name exists and nothing wider is granted. The writers table lets the grants part create, never update or delete. The operator allowed this one grant on 2026-10-26 08:54, beside the memberships grant of 22:31 (ADR 0547 clause 1 and 5). The expected lists were written again by the controller's own internal/broker with the same change (mesh-control fix/1790-grants-create-buckets); a fixture now places mesh-grants beside the builder's state, log and stream. Issue: 1790
103 lines
4.0 KiB
Go
103 lines
4.0 KiB
Go
package buscompose
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends issue 1377 and novox/hq ADR 0547 §5: this is the controller's bus composer copied, and it composes byte for
|
|
// byte what the controller's composes. testdata/controller-user-lists.json was written by the controller's own
|
|
// internal/broker (mesh-controller branch fix/1945-mesh-grants-publishes-memberships, and fix/1790-grants-create-buckets for the grants part creating declared streams), lists and memberships, from fixtures_test.go compiled there unchanged; composed.conf is the
|
|
// controller's golden of the whole server configuration. A row that differs is the copy drifting from the controller,
|
|
// which is a fault while both compose (until the controller loses its lease, ADR 0547 §13).
|
|
func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) {
|
|
raw, err := os.ReadFile(filepath.Join("testdata", "controller-user-lists.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var want []struct {
|
|
Name, List string
|
|
Missing []string
|
|
Err string
|
|
Memberships json.RawMessage
|
|
}
|
|
if err := json.Unmarshal(raw, &want); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fixtures := UserListFixtures()
|
|
if len(want) != len(fixtures) {
|
|
t.Fatalf("%d fixtures, %d lists the controller composed", len(fixtures), len(want))
|
|
}
|
|
for i, f := range fixtures {
|
|
t.Run(f.Name, func(t *testing.T) {
|
|
if want[i].Name != f.Name {
|
|
t.Fatalf("fixture %q against the controller's %q", f.Name, want[i].Name)
|
|
}
|
|
list, missing, err := ComposedFor(f)
|
|
got := ""
|
|
if err != nil {
|
|
got = err.Error()
|
|
}
|
|
if got != want[i].Err {
|
|
t.Fatalf("error %q, the controller's %q", got, want[i].Err)
|
|
}
|
|
if strings.Join(missing, ",") != strings.Join(want[i].Missing, ",") {
|
|
t.Errorf("left out %v, the controller %v", missing, want[i].Missing)
|
|
}
|
|
if list != want[i].List {
|
|
t.Errorf("the list differs from the controller's:\n%s", list)
|
|
}
|
|
// And every membership the records issue, as the controller's MembershipFor makes it (ADR 0547 §5): the
|
|
// grants part publishes them.
|
|
raw, err := json.Marshal(MembershipsFor(f))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var gotM, wasM any
|
|
if err := json.Unmarshal(raw, &gotM); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(want[i].Memberships, &wasM); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(gotM, wasM) {
|
|
t.Errorf("the memberships differ from the controller's:\n%s", raw)
|
|
}
|
|
})
|
|
}
|
|
t.Run("the controller's golden server configuration", func(t *testing.T) {
|
|
seat := Seat{Name: "telegram-sender", Accepts: []string{"send"}, Emits: []string{"delivered", "failed"}}
|
|
got, err := Compose(
|
|
Server{ClientPort: 4222, MonitoringPort: 8222, StoreDir: "/data",
|
|
TLSCert: "/tls/tls.crt", TLSKey: "/tls/tls.key", TLSCA: "/tls/ca.crt"},
|
|
[]Principal{
|
|
{Kind: KindController, PasswordHash: "$2a$11$cccccccccccccccccccccc"},
|
|
{Kind: KindEnrolment, Node: "one", PasswordHash: "$2a$11$eeeeeeeeeeeeeeeeeeeeee"},
|
|
{Kind: KindNode, Node: "one", PasswordHash: "$2a$11$nnnnnnnnnnnnnnnnnnnnnn"},
|
|
{Kind: KindModule, Node: "one", Module: "telegram", Holds: []Seat{seat},
|
|
Serves: []string{"status"}, PasswordHash: "$2a$11$tttttttttttttttttttttt"},
|
|
{Kind: KindModule, Node: "two", Module: "shop", Uses: []Seat{seat},
|
|
Emits: []string{"order.placed"}, PasswordHash: "$2a$11$ssssssssssssssssssssss"},
|
|
{Kind: KindModule, Node: "two", Module: "audit",
|
|
Consumes: []string{"shop.order.placed"}, PasswordHash: "$2a$11$aaaaaaaaaaaaaaaaaaaaaa"},
|
|
{Kind: KindModule, Node: "one", Module: WatchmanModule, PasswordHash: "$2a$11$wwwwwwwwwwwwwwwwwwwwww"},
|
|
{Kind: KindModule, Node: "one", Module: "nats", SnapshotsTheBus: true,
|
|
Serves: []string{"nats_streams"}, PasswordHash: "$2a$11$bbbbbbbbbbbbbbbbbbbbbb"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want, err := os.ReadFile(filepath.Join("testdata", "composed.conf"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got != string(want) {
|
|
t.Errorf("the configuration differs from the controller's golden:\n%s", got)
|
|
}
|
|
})
|
|
}
|