Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/tools_test.go
T
jschoubben e5e3d7dce6 CAT-K10: the module's tools read each state it keeps on the bus and do by hand what it does itself
knowledge_head, _operations, _section, _draft, _parts, _asked, _fill, _rebuild_index, _review_again, _integrity,
_move and _move_check, beside the health, highest and marks tools; the manifest lists them and a test compares them with the code.

Issue 1966.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 12:18:11 +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) != 15 {
t.Fatalf("%v", ModuleTools)
}
}