Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/tools_test.go
T
jschoubben 715bc088c4 CAT-P3: the page's model is a tool, its re-read a hand act, an unanswered source is said and asked again on a backoff
knowledge_overview answers the page's model as data (reads); knowledge_overview_reread asks one source's verb again by hand, no other, and
records the hand act (acts). A source marked from its verb is asked again alone from 10 seconds, doubling to 5 minutes, its first answer clearing
the mark; one marked from its subscription asks nothing while silent and the verb once on its next event. The module's health carries the page's
last rebuild and says it is degraded while a source is unanswered, staying healthy.

Issue 1968.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 12:31:55 +01:00

137 lines
4.9 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
)
func callTool(t *testing.T, tools []toolRun, name string, args map[string]any) (map[string]any, error) {
t.Helper()
for _, tool := range tools {
if tool.name == name {
out, err := tool.run(args)
if err != nil {
return nil, err
}
raw, _ := json.Marshal(out)
var m map[string]any
_ = json.Unmarshal(raw, &m)
return m, nil
}
}
t.Fatalf("no tool %s", name)
return nil, nil
}
type toolRun struct {
name string
run func(map[string]any) (any, error)
}
// Every state the module keeps on the bus has a tool that reads it, and every act it does by itself one that does it by
// hand (to-be 55 §1.1, AC54): the head, the history, a section, a draft, the parts' verdicts, the parts in flight, the fill,
// the index, the review asked again, the integrity of the sections, the move and its check.
func TestEveryStateTheModuleKeepsHasAToolThatReadsIt(t *testing.T) {
w, f := ledgerWorld(t)
var tools []toolRun
for _, tool := range moduleTools(w.k, callerOf) {
tools = append(tools, toolRun{tool.Name, tool.Run})
}
r, _ := w.k.ReserveNumber("author@g14", KindDecision, "a test record")
p := valid(r.Number)
p.Sections[0].Text = "The merge window of ADR 0276 §1 waits too long."
key := map[string]any{"number": float64(r.Number)}
if _, err := w.k.Open("author@g14", ProposeAsk{Kind: KindDecision, Number: r.Number, Title: p.Title, Topic: p.Topic, Date: p.Date, Deciders: p.Deciders, Clauses: p.Clauses, Links: p.Links, Homes: p.Homes}); err != nil {
t.Fatal(err)
}
if _, err := w.k.Write("author@g14", Key{KindDecision, r.Number}, "Context", p.Sections[0].Text); err != nil {
t.Fatal(err)
}
d, err := callTool(t, tools, DraftTool, key)
if err != nil || d["author"] != "author@g14" || len(d["sections"].([]any)) != 1 {
t.Fatalf("%v %v", err, d)
}
for _, s := range p.Sections[1:] {
_, _ = w.k.Write("author@g14", Key{KindDecision, r.Number}, s.Name, s.Text)
}
if _, err := w.k.SubmitDraft("author@g14", Key{KindDecision, r.Number}, ""); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
h, err := callTool(t, tools, HeadTool, key)
if err != nil || h["entry"] == nil {
t.Fatalf("%v %v", err, h)
}
ops, err := callTool(t, tools, OperationsTool, key)
if err != nil || len(ops["operations"].([]any)) < 6 {
t.Fatalf("%v %v", err, ops)
}
var first map[string]any
for _, o := range ops["operations"].([]any) {
op := o.(map[string]any)
if op["body"] != nil {
t.Fatalf("the history shows a body: %v", op)
}
if first == nil && op["operation"] == OpWriteSection {
first = op
}
}
sec, err := callTool(t, tools, SectionTool, map[string]any{"number": key["number"], "sequence": first["sequence"]})
if err != nil || sec["text"] != p.Sections[0].Text || sec["title"] != "Context" {
t.Fatalf("%v %v", err, sec)
}
parts, err := callTool(t, tools, PartsTool, key)
if err != nil || len(parts["parts"].([]any)) != 5 {
t.Fatalf("%v %v", err, parts)
}
if _, err := callTool(t, tools, AskedTool, map[string]any{}); err != nil {
t.Fatal(err)
}
if fill, err := callTool(t, tools, FillTool, map[string]any{}); err != nil || fill["keys"] == nil {
t.Fatalf("%v %v", err, fill)
}
if st, err := callTool(t, tools, IndexTool, map[string]any{}); err != nil || st["built"] != true {
t.Fatalf("%v %v", err, st)
}
// The integrity of the sections: all well, then one damaged text in the history is named.
if res, err := callTool(t, tools, IntegrityTool, map[string]any{}); err != nil || len(res["damaged"].([]any)) != 0 {
t.Fatalf("%v %v", err, res)
}
hist := f.hist[InstanceOf(Key{KindDecision, r.Number})]
for i := range hist {
if hist[i].Operation == OpWriteSection && strings.Contains(string(hist[i].Body), "Considered Options") {
hist[i].Body = mustJSON(t, map[string]any{"title": "Considered Options", "text": "damaged", "digest": SectionDigest("1. **Keep it.** Rejected.")})
}
}
if res, _ := callTool(t, tools, IntegrityTool, key); len(res["damaged"].([]any)) == 0 {
t.Fatalf("a damaged section was not named: %v", res)
}
// An accepted record has no review to ask again; a proposed one does.
if _, err := callTool(t, tools, AgainTool, key); err == nil {
t.Fatal("a review of an accepted record was asked again")
}
q, _ := w.k.ReserveNumber("author@g14", KindDecision, "another")
if _, err := w.k.Propose("author@g14", valid(q.Number)); err != nil {
t.Fatal(err)
}
w.judge.review = `{"verdict":"changes","findings":["x"]}`
w.reviewAll(t)
w.judge.review = ""
if _, err := w.k.ReviewAgain("author@g14", Key{KindDecision, q.Number}); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
if e := mustGet(t, w.k.Store, Key{KindDecision, q.Number}); e.Current.Status != StatusAccepted {
t.Fatalf("a review asked again: %s", e.Current.Status)
}
// The move's tools need the database beside the ledger.
if _, err := callTool(t, tools, MoveCheckTool, map[string]any{}); err == nil {
t.Fatal("a move check without a database")
}
if len(ModuleTools) != 18 {
t.Fatalf("%v", ModuleTools)
}
}