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
102 lines
5.2 KiB
Go
102 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
|
|
|
"nats-tools/internal/keeper"
|
|
)
|
|
|
|
// The catalog half as the keeper plans from it (design 47 G §532, §533, §547, §591): every module some watched branch's
|
|
// newest revision lists, each local name from the first of its watched branches in its registration's order whose entry
|
|
// asks it, another branch asking it with other settings a drift naming that branch; a branch the registration does not
|
|
// name after; a verdict not accepted asks nothing; the claims of every branch; a foundation part and its old sections'
|
|
// caps from the first branch.
|
|
func TestTheCatalogHalfIsReadPerWatchedBranch(t *testing.T) {
|
|
entryOf := func(verdict string, manifest map[string]any) catalog.CatalogEntry {
|
|
manifest["module"] = "m"
|
|
return catalog.CatalogEntry{Module: "m", Verdict: verdict, ManifestDigest: "sha256:" + verdict, Manifest: manifest}
|
|
}
|
|
state := func(opts string) map[string]any {
|
|
e := map[string]any{"name": "drafts", "storage": "key-value", "template": "state"}
|
|
if opts != "" {
|
|
e["options"] = map[string]any{"history": opts}
|
|
}
|
|
return e
|
|
}
|
|
log := map[string]any{"name": "log", "storage": "stream", "template": "log"}
|
|
main := entryOf("accepted", map[string]any{"bus-state": []any{state("")}, "claims": []any{map[string]any{"name": "a"}}})
|
|
feature := entryOf("accepted", map[string]any{"bus-state": []any{state("5"), log}, "claims": []any{map[string]any{"name": "b"}}})
|
|
for _, row := range []struct {
|
|
name string
|
|
branches []Branch
|
|
order []string
|
|
want string
|
|
}{
|
|
{"one branch", []Branch{{Name: "main", Revision: 3, Entries: map[string]catalog.CatalogEntry{"m": main}}}, []string{"main"},
|
|
"m 3 [drafts/key-value/state] [a] false 0;"},
|
|
{"a feature branch asking a local name with other settings drifts, its new one planned",
|
|
[]Branch{{Name: "feature", Revision: 9, Entries: map[string]catalog.CatalogEntry{"m": feature}}, {Name: "main", Revision: 3, Entries: map[string]catalog.CatalogEntry{"m": main}}},
|
|
[]string{"main", "feature"}, "m 3 [drafts/key-value/state log/stream/log] [a b] false 0; drifted m drafts feature from main"},
|
|
{"the registration's order decides", []Branch{{Name: "main", Revision: 3, Entries: map[string]catalog.CatalogEntry{"m": main}}, {Name: "feature", Revision: 9, Entries: map[string]catalog.CatalogEntry{"m": feature}}},
|
|
[]string{"feature"}, "m 9 [drafts/key-value/state log/stream/log] [b a] false 0; drifted m drafts main from feature"},
|
|
{"a verdict not accepted asks nothing", []Branch{{Name: "main", Revision: 3, Entries: map[string]catalog.CatalogEntry{"m": entryOf("refused", map[string]any{"bus-state": []any{state("")}})}}}, []string{"main"},
|
|
"m 3 [] [] false 0;"},
|
|
{"a foundation part, its old sections counted", []Branch{{Name: "main", Revision: 3, Entries: map[string]catalog.CatalogEntry{"m": entryOf("accepted", map[string]any{
|
|
"objects": []any{map[string]any{"name": "sent-objects", "max-mib": 256}}, "logs": []any{map[string]any{"name": "l", "max-mib": 4}}, "streams": []any{map[string]any{"name": "s"}}})}}}, []string{"main"},
|
|
"m 3 [] [] true 1284;"},
|
|
} {
|
|
t.Run(row.name, func(t *testing.T) {
|
|
v, err := ViewOf(row.branches, func(string) []string { return row.order })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got string
|
|
for _, m := range v.Modules {
|
|
es := []string{}
|
|
for _, e := range m.BusState {
|
|
es = append(es, e.Name+"/"+e.Storage+"/"+e.Template)
|
|
}
|
|
got += fmt.Sprintf("%s %d [%s] %v %v %d;", m.Module, m.Revision, strings.Join(es, " "), m.Claims, m.Foundation, m.OldBytes/keeper.MiB)
|
|
}
|
|
for _, d := range v.Drifts {
|
|
got += fmt.Sprintf(" drifted %s %s %s from %s", d.Module, d.Local, d.Branch, d.From)
|
|
}
|
|
if got != row.want {
|
|
t.Errorf("read %q, want %q", got, row.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The keeper's health tool (CAT-N6): healthy naming its pass; unhealthy, naming why, when its pass is old or verdicts
|
|
// wait on its consumer `<node>_nats_keeper` past the bound.
|
|
func TestTheKeepersHealthToolReadsItsStatusAndItsConsumer(t *testing.T) {
|
|
at := time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC)
|
|
jsz := `{"account_details":[{"stream_detail":[{"name":"EVENTS","consumer_detail":[{"name":"anchor_nats_keeper","num_pending":3},{"name":"anchor_nats","num_pending":9}]}]}]}`
|
|
status := keeper.Status{Started: at.Add(-time.Hour), Pass: at.Add(-time.Minute), Heard: at.Add(-time.Minute),
|
|
FirstView: keeper.FirstViewStatus{Owner: keeper.FirstViewOwner, Verb: keeper.FirstViewVerb, Answered: at.Add(-time.Hour)}}
|
|
none := func() (map[string]keeper.Asked, error) { return nil, nil }
|
|
for _, row := range []struct {
|
|
name string
|
|
now time.Time
|
|
state string
|
|
why string
|
|
}{
|
|
{"its pass is fresh", at, "healthy", "last pass 2026-11-04T11:59:00Z"},
|
|
{"its pass and its verdicts are old", at.Add(5 * time.Minute), "unhealthy", "3 accepted verdict(s) wait on its consumer"},
|
|
} {
|
|
t.Run(row.name, func(t *testing.T) {
|
|
a := keeperHealth(func() (keeper.Status, error) { return status, nil }, none,
|
|
func() ([]byte, error) { return []byte(jsz), nil }, func() time.Time { return row.now })
|
|
if a.State != row.state || !strings.Contains(a.Why, row.why) {
|
|
t.Errorf("answered %+v: %s, naming %q", a, row.state, row.why)
|
|
}
|
|
})
|
|
}
|
|
}
|