Files
mesh-catalog/modules/nats/cmd/nats-tools/broker_test.go
T
jschoubben bf60d6a8bb NATS3 (rest): the bus module plans from the catalog half of every watched branch, not from manifest-accepted
The keeper follows meshcatalogclient's branch follower and the seat list on its views reader, and plans again at each
move of a branch's view, the registrations or the seat list. Each local name is planned from the first of the module's
watched branches in its registration's order; another branch asking it with other settings is said drifted, naming
the branch. A module of the new form whose resolved seat definition has a verb or an event gets its sent store; one
including only parts, and a foundation part, none. A module in no watched branch's newest revision loses its plans
key, its entries orphans. The sum of the caps counts the old sections the entries declare. The manifest consumes the
catalog half's and the seat list's events and invokes their verbs; manifest-accepted and indexer_bus_state go.

Builds: design 47 G §344, §433, §532, §533, §537, §538, §547, §591

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:45:49 +01:00

151 lines
7.4 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"nats-tools/internal/keeper"
)
// The seat's reading verbs and nats_history (design 25 § 12.3; CAT-N7): list and show answer every entry the keeper
// made with its settings asked and held and its fill against its cap; check names what is missing or differs; orphans
// what no manifest asks for; history one entry's events in order. Each from the keeper's own state and the bus's
// monitoring endpoint.
func TestTheSeatsReadingVerbsAnswerFromTheKeeperAndTheBus(t *testing.T) {
plan := func(local, storage, template string, options string) keeper.Plan {
e := catalogue.BusStateEntry{Name: local, Storage: storage, Template: template}
if options != "" {
_ = json.Unmarshal([]byte(options), &e.Options)
}
p, err := keeper.PlanOf("m", e)
if err != nil {
t.Fatal(err)
}
return p
}
held := map[string]keeper.Asked{"m": {Module: "m", Made: map[string]keeper.Made{
"drafts": {Plan: plan("drafts", "key-value", "state", ""), Created: true},
"log": {Plan: plan("log", "stream", "log", `{"max-mib":16}`), Created: true, Drift: "m_log: max-bytes asked 16777216, held 33554432"},
"old": {Plan: plan("old", "stream", "events", ""), Created: true, Orphaned: true},
"later": {Plan: plan("later", "key-value", "state", ""), Waiting: "a grant not composed yet"},
}}}
jsz := `{"account_details":[{"stream_detail":[
{"name":"KV_m_drafts","config":{"name":"KV_m_drafts","retention":"limits","discard":"new","max_bytes":67108864,"max_msgs_per_subject":1,"max_msg_size":262144},"state":{"messages":3,"bytes":1048576}},
{"name":"OBJ_m_drafts-objects","config":{"name":"OBJ_m_drafts-objects","retention":"limits","discard":"new","max_bytes":16777216},"state":{"messages":0,"bytes":0}},
{"name":"m_log","config":{"name":"m_log","retention":"limits","discard":"new","max_bytes":33554432,"max_msg_size":262144},"state":{"messages":9,"bytes":9000}}]}]}`
tools := map[string]stdio.Tool{}
for _, tl := range brokerTools(func() (map[string]keeper.Asked, error) { return held, nil },
func() (keeper.Status, error) {
return keeper.Status{Limits: keeper.Limits{AccountBytes: 2048 << 20, ServerBytes: 4096 << 20}, OldBytes: 256 << 20}, nil
},
func() ([]byte, error) { return []byte(jsz), nil },
func(module, local string, limit int) ([]keeper.Event, error) {
return []keeper.Event{{Module: module, Local: local, Why: "first"}, {Module: module, Local: local, Why: "second"}}[:limit], nil
}) {
tools[tl.Name] = tl
}
for _, row := range []struct {
tool string
args map[string]any
want []string
err string
}{
// Each answers the seat's contract (mesh-contracts meshbroker ListAnswer, Held, CheckAnswer, OrphansAnswer).
{"mesh-broker.list", nil, []string{`"entries":[`, `"bus-name":"m_drafts"`, `"local-name":"drafts"`, `"storage-type":"key-value"`,
`"KV_m_drafts":{"max-bytes":67108864`, `"fill":{"bytes":1048576,"cap":83886080,"count":3}`, `"options":{"max-mib":16}`,
// The binding of an entry the bus holds is the one its created carries (design 25 bindings-first-view §2).
`"binding":{"bucket":"m_drafts","local":"drafts","objects":"m_drafts-objects","storage":"key-value","template":"state"}`}, ""},
// The sum of the caps against both limits (design 47 G §547, §591): drafts 64+16, log 16, old 1024 (orphaned, still
// held), and the old sections the catalog graph's entries declare, 256; later is not made.
{"mesh-broker.list", nil, []string{`"caps":{"account-limit-mib":2048,"server-limit-mib":4096,"sum-mib":1376`}, ""},
{"mesh-broker.show", map[string]any{"module": "m", "local-name": "log"}, []string{`"bus-name":"m_log"`, `"template":"log"`,
`"m_log":{"max-bytes":33554432`}, ""},
{"mesh-broker.show", map[string]any{"module": "m", "local-name": "later"}, []string{`"local-name":"later"`}, ""},
{"mesh-broker.show", map[string]any{"module": "m", "local-name": "none"}, nil, `made no entry "none" of "m"`},
{"mesh-broker.check", nil, []string{`{"differences":[{`, `"local-name":"log"`, `"asked":{"m_log":{"max-bytes":16777216`,
`"held":{"m_log":{"max-bytes":33554432`}, ""},
{"mesh-broker.check", map[string]any{"module": "x"}, []string{`{"differences":[]}`}, ""},
{"mesh-broker.orphans", nil, []string{`{"entries":[{`, `"local-name":"old"`}, ""},
{"nats_history", map[string]any{"module": "m", "local": "log", "limit": float64(1)}, []string{`"why":"first"`}, ""},
{"nats_history", map[string]any{"module": "m"}, nil, "names one entry"},
} {
t.Run(fmt.Sprintf("%s %v", row.tool, row.args), func(t *testing.T) {
args := row.args
if args == nil {
args = map[string]any{}
}
out, err := tools[row.tool].Run(args)
if row.err != "" {
if err == nil || !strings.Contains(err.Error(), row.err) {
t.Fatalf("answered %v (%v): refused, naming %q", out, err, row.err)
}
return
}
raw, _ := json.Marshal(out)
for _, w := range row.want {
if !strings.Contains(string(raw), w) {
t.Errorf("%s lacks %s", raw, w)
}
}
// list's binding is, field by field, the binding the entry's created carries (the event's body takes the plan's binding whole).
if row.tool == "mesh-broker.list" {
var got struct {
Entries []struct {
Local string `json:"local-name"`
Binding map[string]any `json:"binding"`
} `json:"entries"`
}
var created map[string]any
wire, _ := json.Marshal(held["m"].Made["drafts"].Plan.Binding)
_ = json.Unmarshal(raw, &got)
_ = json.Unmarshal(wire, &created)
for _, e := range got.Entries {
if e.Local == "drafts" && !reflect.DeepEqual(e.Binding, created) {
t.Errorf("list names the binding %v for drafts, its created carries %v", e.Binding, created)
}
}
}
// An entry the bus does not hold has no binding.
if row.args["local-name"] == "later" && strings.Contains(string(raw), `"binding"`) {
t.Errorf("show names a binding for later, whose objects the bus does not hold: %s", raw)
}
if row.tool == "mesh-broker.check" && strings.Contains(string(raw), `"local-name":"drafts"`) {
t.Errorf("check names drafts, which the bus holds as asked: %s", raw)
}
})
}
}
// The server's storage limit is read from the monitoring document's config (design 47 G §547); none is 0.
func TestTheServersStorageLimitIsReadFromItsMonitor(t *testing.T) {
for doc, want := range map[string]int64{`{"config":{"max_storage":5368709120}}`: 5368709120, `{"config":{"max_storage":-1}}`: 0, `{}`: 0} {
got, err := serverStorageLimit(func(context.Context, string) ([]byte, error) { return []byte(doc), nil })(context.Background())
if err != nil || got != want {
t.Errorf("%s: %d (%v), want %d", doc, got, err, want)
}
}
}
// nats_plans answers each module's entries as last planned, narrowed by a module (design 47 G §538).
func TestNatsPlansReadsTheKeepersPlans(t *testing.T) {
read := func() (map[string]keeper.Planned, error) {
return map[string]keeper.Planned{
"a": {Module: "a", Digest: "sha256:1", Revision: 3, Entries: []catalogue.BusStateEntry{{Name: "drafts"}}},
"b": {Module: "b", Digest: "sha256:2", Revision: 4},
}, nil
}
tool := plansTool(read)
out, err := tool.Run(map[string]any{"module": "a"})
raw, _ := json.Marshal(out)
if err != nil || !strings.Contains(string(raw), `"entries":["drafts"]`) || !strings.Contains(string(raw), `"digest":"sha256:1"`) || strings.Contains(string(raw), `"module":"b"`) {
t.Errorf("nats_plans answered %s (%v)", raw, err)
}
}