package main import ( "fmt" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" "nats-tools/internal/keeper" ) // The keeper's inputs (design 25 ยง 12.2; CAT-N2): a manifest-accepted read with the indexer's revision when it carries // one (0 when not yet), its entries whole; the first view of indexer_bus_state read bare or as the tool runner wraps it, // an error answer an error; anything else refused, naming why. func TestTheKeepersInputsAreRead(t *testing.T) { for _, row := range []struct { name, body, want, refused string view bool }{ {name: "accepted with its revision", body: `{"v":1,"module":"m","digest":"sha256:a","judge":"j","revision":7,"bus-state":[{"name":"drafts","storage":"key-value","template":"state"}]}`, want: "m sha256:a 7 [drafts/key-value/state]"}, {name: "accepted before the event carries a revision", body: `{"v":1,"module":"m","digest":"sha256:b","judge":"j","bus-state":[]}`, want: "m sha256:b 0 []"}, {name: "accepted naming no module", body: `{"v":1,"digest":"x"}`, refused: "names no module"}, {name: "first view as the tool runner answers it", view: true, body: `{"result":{"revision":12,"modules":[{"module":"m","digest":"d","revision":11,"bus-state":[{"name":"log","storage":"stream","template":"log"}]}]}}`, want: "12: m d 11 [log/stream/log]"}, {name: "first view bare", view: true, body: `{"revision":3,"modules":[]}`, want: "3:"}, {name: "first view answering an error", view: true, body: `{"error":"not granted"}`, refused: "not granted"}, {name: "first view that is none", view: true, body: `[1,2]`, refused: "is not a first view"}, } { t.Run(row.name, func(t *testing.T) { var got string var err error if row.view { v, e := FirstViewOf([]byte(row.body)) err = e got = fmt.Sprintf("%d:", v.Revision) for _, m := range v.Modules { got += " " + line(m.Module, m.Digest, m.Revision, m.BusState) } } else { a, e := AcceptedOf([]byte(row.body)) err = e got = line(a.Module, a.Digest, a.Revision, a.BusState) } if row.refused != "" { if err == nil || !strings.Contains(err.Error(), row.refused) { t.Fatalf("read %s (%v): refused, naming %q", got, err, row.refused) } return } if err != nil || got != row.want { t.Errorf("read %q (%v), want %q", got, err, row.want) } }) } } func line(module, digest string, revision uint64, entries []catalogue.BusStateEntry) string { es := []string{} for _, e := range entries { es = append(es, e.Name+"/"+e.Storage+"/"+e.Template) } return fmt.Sprintf("%s %s %d [%s]", module, digest, revision, strings.Join(es, " ")) } // 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 `_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) } }) } }