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
137 lines
4.9 KiB
Go
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)
|
|
}
|
|
}
|