Files
mesh-tools/node-tools/internal/approval/approval_test.go
T
jschoubben 809288da69 E-5 marked waits: a wait on a marked event runs only on the operator's warrant for that one wait
mesh_wait goes through the runner's approval gate (node-tools/internal/approval, Gate.Waited): a wait whose
subjects overlap a marked subject of any module the catalog half lists (the newest entry of every followed
branch, with the served entry of each module this runner serves) is an act of the runner's own module and
build (MESH_FINGERPRINT at its start), asked once on operator-channel as "Wait on <names> on <node>?", and
held, subscribing nothing but its ask's decided, until its timeout, its caller's going or the choice. On a
warrant it subscribes, its timeout running from then, and answers {answer, approval}. Before the marks are
read nothing is asked; a wait from a seat is never marked. The state fixture moves to meshtest so the
console's table can hold a gate.

Builds: design 34 every-event §34, §35, §36, §37; design 47 G lead answer A-168

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:41:53 +01:00

529 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package approval
import (
"encoding/json"
"errors"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
seat "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
catalogseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/keys"
"git.novox.be/novox/mesh-sdk/go/seatdef"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
// mem is the runner's state approvals as a fixture (meshtest.State).
type mem = mt.State
func newMem() *mem { return mt.NewState() }
// world is a gate on a fixture mesh: the catalog half, the router's records as the router writes them (mesh-catalog's
// messenger, asks.go), the asks published, the clock.
type world struct {
g *Gate
state *mem
cat *fakeCatalog
records map[string]seat.AsksByAskerIDValue // by `<module>.<id>`
takes bool // the router takes an ask as it is published
asked []seat.Ask
now time.Time
runs int
findings *findings
bounds *bounds
// holders is the assigner's view of the seats' holders, by key; holdersErr says it is not answered yet.
holders map[string][]byte
holdersErr error
// warranted is what mesh-hand-acts.warranted answers, and recorded each ask it answered for.
warranted error
recorded []string
}
// bounds is the runner's key-ttl state bound as a fixture: expire hands a key's expiry to the watch.
type bounds struct {
keys map[string]time.Duration
expired func(string)
}
func (b *bounds) CreateTTL(key string, _ any, ttl time.Duration) error { b.keys[key] = ttl; return nil }
func (b *bounds) Delete(key string) error { delete(b.keys, key); return nil }
func (b *bounds) Watch(_ string, expired func(string)) (func(), error) {
b.expired = expired
return func() {}, nil
}
func (b *bounds) expire(key string) {
delete(b.keys, key)
b.expired(key)
}
func newWorld() *world {
shell := entry("src2:1", []string{"node-login-shell.execute"}, map[string]any{"needs-approval": []any{"wipe"}})
w := &world{state: newMem(), records: map[string]seat.AsksByAskerIDValue{}, takes: true, findings: &findings{},
bounds: &bounds{keys: map[string]time.Duration{}},
holders: map[string][]byte{"operator-channel": []byte(`{"seat":"operator-channel","scope":"mesh","holders":[{"module":"messenger"}]}`)},
now: time.Date(2026, 11, 12, 10, 0, 0, 0, time.UTC),
cat: &fakeCatalog{revision: "7", byFp: map[string]catalogseat.CatalogEntry{"src2:1": shell},
newest: map[string]catalogseat.CatalogEntry{"shell": shell},
defs: map[string]seatdef.Definition{"node-login-shell": {Seat: "node-login-shell",
Verbs: []seatdef.Verb{{Name: "execute", NeedsApproval: true}}}}}}
w.g = New(Deps{Node: "n1", Runner: "node-tools", Catalog: w.cat, State: w.state, Findings: w.findings, Bounds: w.bounds,
Go: func(f func()) { f() },
Holder: HoldersIn(func() (map[string][]byte, error) { return w.holders, w.holdersErr }),
Warranted: func(asker, ask string, open func() (bool, error)) error {
if still, _ := open(); !still {
return nil
}
if w.warranted == nil {
w.recorded = append(w.recorded, asker+"."+ask)
}
return w.warranted
},
Now: func() time.Time { return w.now },
After: func(d time.Duration) <-chan time.Time {
w.now = w.now.Add(d)
c := make(chan time.Time, 1)
c <- w.now
return c
},
Publish: func(module, subject string, body any, id string) error {
if subject != seat.AcceptSubjectAsk(module) {
return nil
}
a := body.(seat.Ask)
w.asked = append(w.asked, a)
if w.takes {
w.records[module+"."+a.ID] = seat.AsksByAskerIDValue{Asker: module, Key: module + "." + a.ID, State: "open",
Headline: a.Headline, Expires: a.Expires, Opened: operatorchannelclient.FormatTime(w.now), OfferedOn: []string{"telegram"}}
}
return nil
},
Record: func(module, id string) (*stdio.StateEntry, error) {
rec, ok := w.records[module+"."+id]
if !ok {
return nil, nil
}
raw, _ := json.Marshal(rec)
return &stdio.StateEntry{Key: module + "." + id, Value: raw}, nil
}})
_ = w.g.Unserved("shell", "src2:1", []string{"wipe", "tidy"})
return w
}
// decide writes the router's record of the newest ask as the router writes a person's choice: its state and warrant.
func (w *world) decide(option string, at time.Time) seat.Ask {
a := w.asked[len(w.asked)-1]
key := "shell." + a.ID
rec := w.records[key]
rec.State = "chosen"
rec.Warrant = &seat.Warrant{Ask: a.ID, Asker: "shell", Outcome: operatorchannelclient.OutcomeChosen, Option: option,
Level: operatorchannelclient.Approve, At: operatorchannelclient.FormatTime(at), AskDigest: operatorchannelclient.Digest(a),
By: &seat.Person{Who: operatorchannelclient.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}
w.records[key] = rec
return a
}
// do makes a call through the gate, counting the runs.
func (w *world) do(c Call) (any, error) {
return w.g.Run(c, func() (any, error) { w.runs++; return "done", nil })
}
// call is a fixture call of the marked tool wipe, or of another tool of the module.
func call(name, args string) Call {
return Call{Address: "shell." + name, Module: "shell", Name: name, Fingerprint: "src2:1", Caller: "agent", Args: json.RawMessage(args),
Description: "Wipes a scratch directory.\nMore words."}
}
// theActAndItsAsk is §8's and §11's rows: the act's fields, and how an ask shows a call's arguments.
func theActAndItsAsk(t *testing.T) {
base := call("wipe", `{"dir":"scratch","n":1}`)
digest := func(c Call) string {
act, err := ActOf(c, "n1")
if err != nil {
t.Fatal(err)
}
d, _ := operatorchannelclient.ActDigest(act)
return d
}
act, _ := ActOf(base, "n1")
want := operatorchannelclient.Act{"address": "shell.wipe", "node": "n1", "module": "shell", "tool": "wipe", "build": "src2:1",
"arg.dir": "scratch", "arg.n": "1"}
if len(act) != len(want) {
t.Fatalf("act %v", act)
}
for k, v := range want {
if act[k] != v {
t.Errorf("act[%s] = %q, want %q", k, act[k], v)
}
}
seen := map[string]string{digest(base): "the call"}
for name, c := range map[string]Call{
"an omitted argument": call("wipe", `{"dir":"scratch"}`),
"another number": call("wipe", `{"dir":"scratch","n":2}`),
"an object": call("wipe", `{"dir":{"name":"scratch"},"n":1}`),
"a changed build": func() Call { c := base; c.Fingerprint = "src2:2"; return c }(),
"another node's seat": {Address: "node-login-shell.execute", Module: "shell", Seat: "node-login-shell", Name: "execute", Fingerprint: "src2:1"},
} {
d := digest(c)
if other, dup := seen[d]; dup {
t.Errorf("%s gives the digest of %s", name, other)
}
seen[d] = name
}
if got, _ := Canonical(json.RawMessage(`{"b":[1.0,2.5e-7,1e21],"a":"é<"}`)); got != `{"a":"é<","b":[1,2.5e-7,1e+21]}` {
t.Errorf("canonical form %s", got)
}
// §11: a path, an address and a long argument are shown in their shape and cut.
text, whole := shown(json.RawMessage(`{"dir":"/home/someone/scratch","host":"10.0.0.7","ok":"yes"}`))
if whole || text != "dir: ‹path›\nhost: ‹address›\nok: yes" {
t.Errorf("shown %q whole %v", text, whole)
}
long, _ := json.Marshal(map[string]string{"text": strings.Repeat("word ", 500)})
if text, whole := shown(long); whole || !strings.HasSuffix(text, "NOT SHOWN IN FULL") || len(text) > PartBytes+len("\nNOT SHOWN IN FULL") {
t.Errorf("a long argument shown whole %v, %d bytes", whole, len(text))
}
// §10: the headline, cut with … to 60 characters, ending Part shown.
for _, tc := range []struct{ name, node, want string }{
{"wipe", "n1", "Run wipe on n1?"},
{strings.Repeat("x", 50), "n1", "Run " + strings.Repeat("x", 50) + "?"},
{strings.Repeat("y", 80), "n1", "Run " + strings.Repeat("y", 54) + "…?"},
} {
if got := headlineOf("Run", tc.name, tc.node, true); got != tc.want || len([]rune(got)) > 60 {
t.Errorf("headline %q, want %q", got, tc.want)
}
}
if got := headlineOf("Run", "wipe", "n1", false); got != "Run wipe on n1? Part shown" {
t.Errorf("headline %q", got)
}
}
// Design 46 7-approval-mark §22: the refusals and answers of a marked call, on the fixture mesh, the router's record and
// the warrant written as the router writes them.
func TestTheRefusalsAndAnswers(t *testing.T) {
t.Run("§8, §10, §11: the act's fields give each its digest, and the ask shows the arguments", theActAndItsAsk)
for _, tc := range []struct {
name string
run func(t *testing.T, w *world)
}{
{"§9: a secret in the arguments is refused asking nothing", func(t *testing.T, w *world) {
_, err := w.g.firstCall(call("wipe", `{"token":"ghp_0123456789abcdefghijABCDEFGHIJ012345"}`))
if err == nil || !strings.Contains(err.Error(), "which no ask may carry") || len(w.asked) != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§9: more than 16 KiB of arguments is refused asking nothing", func(t *testing.T, w *world) {
big, _ := json.Marshal(map[string]string{"text": strings.Repeat("a b ", 5000)})
_, err := w.g.firstCall(call("wipe", string(big)))
if err == nil || !strings.Contains(err.Error(), "more than the 16384") || len(w.asked) != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§10, §12: the published ask's fields, and Not run with the router's record", func(t *testing.T, w *world) {
_, err := w.g.firstCall(call("wipe", `{"dir":"scratch"}`))
if err == nil || !strings.HasPrefix(err.Error(), "Not run: this needs the operator's approval. Asked on telegram at ") ||
!strings.Contains(err.Error(), "make the same call again within 30 minutes of the approval; the ask expires at ") {
t.Fatalf("answer %v", err)
}
if len(w.asked) != 1 {
t.Fatalf("asked %d", len(w.asked))
}
a := w.asked[0]
act, _ := ActOf(call("wipe", `{"dir":"scratch"}`), "n1")
d, _ := operatorchannelclient.ActDigest(act)
if a.About != d || a.Who != "operator" || a.OnExpiry != OnExpiry || a.Headline != "Run wipe on n1?" ||
a.Expires != operatorchannelclient.FormatTime(time.Date(2026, 11, 12, 11, 0, 0, 0, time.UTC)) || len(a.Options) != 2 ||
a.Options[0].ID != "approve" || a.Options[1].ID != "decline" || a.Options[0].Binds != d || a.Options[1].Binds != d ||
a.Options[1].Level != "approve" {
t.Errorf("ask %+v", a)
}
if !strings.HasPrefix(a.Explanation, "Wipes a scratch directory.\nOn n1, called through the mesh's tools, which cannot tell who called.\ndir: scratch\nFingerprint ") {
t.Errorf("explanation %q", a.Explanation)
}
var k operatorchannelclient.Kept
_ = json.Unmarshal(w.state.Vals["n1.ask."+a.ID], &k)
if k.Act["arg.dir"] != "scratch" || string(k.Arguments) != `{"dir":"scratch"}` || string(w.state.Vals["n1.act."+d]) != `"`+a.ID+`"` {
t.Errorf("kept %+v, act key %s", k, w.state.Vals["n1.act."+d])
}
}},
{"§12: with no record within 5 seconds, Not run, asked; not yet taken by the router", func(t *testing.T, w *world) {
w.takes = false
start := w.now
_, err := w.g.firstCall(call("wipe", `{}`))
if err == nil || !strings.HasPrefix(err.Error(), "Not run, asked; not yet taken by the router (ask ") || w.now.Sub(start) > RecordWithin {
t.Fatalf("answer %v after %s", err, w.now.Sub(start))
}
}},
{"§10, §17: a second identical call publishes nothing and is told it still waits", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{"dir":"scratch"}`))
_, err := w.do(call("wipe", `{"dir":"scratch"}`))
if len(w.asked) != 1 || err == nil || !strings.HasPrefix(err.Error(), "Not run: still waiting for the operator's answer") || w.runs != 0 {
t.Fatalf("asked %d, %v, runs %d", len(w.asked), err, w.runs)
}
}},
{"§17: an ask the router holds back as the fourth open one still waits", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
a := w.asked[0]
rec := w.records["shell."+a.ID]
rec.State, rec.HeldSince = "held-back", operatorchannelclient.FormatTime(w.now)
w.records["shell."+a.ID] = rec
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "still waiting") || len(w.asked) != 1 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§14, §15: approved, the same call runs once; made again it is already run and asks anew", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{"dir":"scratch"}`))
w.decide(OptionApprove, w.now.Add(time.Minute))
w.now = w.now.Add(2 * time.Minute)
if got, err := w.do(call("wipe", `{"dir":"scratch"}`)); err != nil || got.(Answered).Answer != "done" || w.runs != 1 ||
!strings.HasPrefix(got.(Answered).Approval, "run on the operator's approval via telegram at 2026-11-12T10:01:00Z, ask ") {
t.Fatalf("%v %v, runs %d", got, err, w.runs)
}
_, err := w.do(call("wipe", `{"dir":"scratch"}`))
if err == nil || !strings.Contains(err.Error(), "was already run; asked again") || w.runs != 1 || len(w.asked) != 2 {
t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked))
}
}},
{"§15: two calls at the same moment run it once", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
w.decide(OptionApprove, w.now)
var wg sync.WaitGroup
var mu sync.Mutex
runs := 0
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, _ = w.g.Run(call("wipe", `{}`), func() (any, error) { mu.Lock(); runs++; mu.Unlock(); return "done", nil })
}()
}
wg.Wait()
if runs != 1 {
t.Fatalf("ran %d times", runs)
}
}},
{"§14: approved for other arguments runs nothing", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{"dir":"scratch"}`))
a := w.decide(OptionApprove, w.now)
rec := w.records["shell."+a.ID]
other, _ := ActOf(call("wipe", `{"dir":"home"}`), "n1")
d, _ := operatorchannelclient.ActDigest(other)
a.Options[0].Binds = d // the ask the person was shown bound another act
rec.Warrant.AskDigest = operatorchannelclient.Digest(a)
w.records["shell."+a.ID] = rec
var k operatorchannelclient.Kept
_ = json.Unmarshal(w.state.Vals["n1.ask."+a.ID], &k)
k.Ask = a
raw, _ := json.Marshal(k)
w.state.Vals["n1.ask."+a.ID] = raw
if _, err := w.do(call("wipe", `{"dir":"scratch"}`)); err == nil || w.runs != 0 || !strings.Contains(err.Error(), "nothing is done") {
t.Fatalf("%v, runs %d", err, w.runs)
}
}},
{"§14: approved 31 minutes ago asks anew", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
w.decide(OptionApprove, w.now)
w.now = w.now.Add(31 * time.Minute)
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "approved too long ago") || w.runs != 0 || len(w.asked) != 2 {
t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked))
}
}},
{"§14, §17: declined, the same call is not asked again for 10 minutes", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
w.decide(OptionDecline, w.now)
w.now = w.now.Add(9 * time.Minute)
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "declined") || len(w.asked) != 1 || w.runs != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
w.now = w.now.Add(2 * time.Minute)
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "asked again") || len(w.asked) != 2 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§14, §17: expired asks anew", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
a := w.asked[0]
rec := w.records["shell."+a.ID]
rec.State = "expired"
w.records["shell."+a.ID] = rec
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "ended expired; asked again") || len(w.asked) != 2 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§14: another asker's warrant runs nothing", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
a := w.decide(OptionApprove, w.now)
rec := w.records["shell."+a.ID]
rec.Warrant.Asker = "mail"
w.records["shell."+a.ID] = rec
if _, err := w.do(call("wipe", `{}`)); err == nil || w.runs != 0 || !strings.Contains(err.Error(), "its warrant does not hold") {
t.Fatalf("%v, runs %d", err, w.runs)
}
}},
{"§17: the router's refusal is said with its words", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
a := w.asked[0]
rec := w.records["shell."+a.ID]
rec.State, rec.Answer = "refused", "the asker has too many open asks"
w.records["shell."+a.ID] = rec
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "the asker has too many open asks") {
t.Fatalf("%v", err)
}
}},
{"§20: a runner's own tool marked is refused unasked, never run", func(t *testing.T, w *world) {
c := call("wipe", `{}`)
c.Own = true
if _, err := w.do(c); err == nil || w.runs != 0 || len(w.asked) != 0 {
t.Fatalf("%v, runs %d, asked %d", err, w.runs, len(w.asked))
}
}},
{"§13: with no binding, an unmarked call runs and a marked one is refused", func(t *testing.T, w *world) {
w.g.d.State = nil
if got, err := w.do(call("tidy", `{}`)); err != nil || got != "done" {
t.Fatalf("unmarked: %v %v", got, err)
}
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "binds no state approvals") || w.runs != 1 {
t.Fatalf("marked: %v, runs %d", err, w.runs)
}
}},
{"§13: an ask over 256 KiB is refused naming the cause", func(t *testing.T, w *world) {
c := call("wipe", `{}`)
c.Description = strings.Repeat("w", 300*1024)
if _, err := w.do(c); err == nil || !strings.Contains(err.Error(), "more than the 262144 a value of the state approvals holds") || len(w.asked) != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§3, §2: a seat verb its seat marks is asked for its holder", func(t *testing.T, w *world) {
c := Call{Address: "node-login-shell.execute", Module: "shell", Seat: "node-login-shell", Name: "execute", Fingerprint: "src2:1", Args: json.RawMessage(`{"command":"ls"}`)}
if _, err := w.do(c); err == nil || !strings.HasPrefix(err.Error(), "Not run") || len(w.asked) != 1 || w.asked[0].Headline != "Run node-login-shell.execute on n1?" {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§16: the run is recorded once; unanswered within five minutes it is a finding, cleared when warranted answers", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{}`))
a := w.decide(OptionApprove, w.now)
stop, _ := w.g.WatchBounds()
defer stop()
w.warranted = errors.New("no responders")
if _, err := w.do(call("wipe", `{}`)); err != nil || w.runs != 1 || len(w.recorded) != 0 {
t.Fatalf("%v, runs %d, recorded %v", err, w.runs, w.recorded)
}
if w.bounds.keys["n1.unanswered."+a.ID] != UnansweredAfter || w.bounds.keys["n1.unrecorded."+a.ID] != KeptFor-SaidBefore {
t.Fatalf("bounds %v", w.bounds.keys)
}
w.bounds.expire("n1.unanswered." + a.ID)
if w.findings.raised[keys.EscapeName("n1."+a.ID)] != KindCallUnanswered {
t.Fatalf("findings %v", w.findings.raised)
}
w.warranted = nil
w.g.Recover() // a start, or the hand-act seat's holder heard again
w.g.Recover()
if len(w.recorded) != 1 || w.recorded[0] != "shell."+a.ID || len(w.findings.raised) != 0 || len(w.bounds.keys) != 0 {
t.Fatalf("recorded %v, findings %v, bounds %v", w.recorded, w.findings.raised, w.bounds.keys)
}
var k operatorchannelclient.Kept
_ = json.Unmarshal(w.state.Vals["n1.ask."+a.ID], &k)
if !k.Recorded || !k.Acted {
t.Fatalf("kept %+v", k)
}
}},
{"§19: with no holder of operator-channel in the view, a marked call is refused asking nothing", func(t *testing.T, w *world) {
w.holders["operator-channel"] = []byte(`{"seat":"operator-channel","scope":"mesh","holders":[]}`)
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "no module holds operator-channel") || len(w.asked) != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
w.holdersErr = errors.New("the first view has not been answered yet")
if _, err := w.do(call("wipe", `{}`)); err == nil || len(w.asked) != 0 {
t.Fatalf("an unread view: %v, asked %d", err, len(w.asked))
}
}},
{"§19: a publish that fails and a record that cannot be read refuse, running nothing", func(t *testing.T, w *world) {
pub := w.g.d.Publish
w.g.d.Publish = func(string, string, any, string) error { return errors.New("the bus refused it") }
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "the ask could not be raised") {
t.Fatalf("%v", err)
}
w.g.d.Publish = pub
w.state.Vals = map[string]json.RawMessage{}
_, _ = w.do(call("wipe", `{}`))
w.g.d.Record = func(string, string) (*stdio.StateEntry, error) { return nil, errors.New("timeout") }
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "the router's record of its ask") || w.runs != 0 {
t.Fatalf("%v, runs %d", err, w.runs)
}
}},
{"§5, §19: a served entry that cannot be read refuses every marked call on that runtime", func(t *testing.T, w *world) {
c := call("wipe", `{}`)
c.Fingerprint = "src2:unknown"
if _, err := w.do(c); err == nil || !strings.Contains(err.Error(), "cannot be read") || len(w.asked) != 0 {
t.Fatalf("%v, asked %d", err, len(w.asked))
}
}},
{"§19: the marks unread, a call waits within 5 seconds and is refused naming mesh_first_views", func(t *testing.T, w *world) {
w.cat.ready = errors.New("the catalog half has no first view of main yet")
waited := time.Duration(0)
w.g.d.Wait = func(d time.Duration) error { waited = d; return w.cat.ready }
if _, err := w.do(call("tidy", `{}`)); err == nil || !strings.Contains(err.Error(), "mesh_first_views") || waited != RecordWithin || w.runs != 0 {
t.Fatalf("%v, waited %s", err, waited)
}
w.g.d.Wait = func(time.Duration) error { w.cat.ready = nil; return nil }
if _, err := w.do(call("tidy", `{}`)); err != nil || w.runs != 1 {
t.Fatalf("read within the wait: %v, runs %d", err, w.runs)
}
}},
{"§19: the state failing refuses, raises approvals-unreadable after five minutes, and clears when read again", func(t *testing.T, w *world) {
stop, _ := w.g.WatchBounds()
defer stop()
w.state.Fail = errors.New("the bus is unreachable")
if _, err := w.do(call("wipe", `{}`)); err == nil || !strings.Contains(err.Error(), "cannot be read") {
t.Fatalf("%v", err)
}
if w.bounds.keys["n1.unreadable.approvals"] != UnreadableAfter || len(w.findings.raised) != 0 {
t.Fatalf("bounds %v, findings %v", w.bounds.keys, w.findings.raised)
}
w.bounds.expire("n1.unreadable.approvals")
if w.findings.raised[keys.EscapeName("n1.approvals")] != KindApprovalsUnreadable {
t.Fatalf("findings %v", w.findings.raised)
}
w.state.Fail = nil
w.g.StateAgain() // a membership change heard, no further call
if len(w.findings.raised) != 0 || len(w.bounds.keys) != 0 {
t.Fatalf("findings %v, bounds %v", w.findings.raised, w.bounds.keys)
}
}},
{"§18: the asks of the last day, one read whole, the marks applied with their sources, a record again and a cancel", func(t *testing.T, w *world) {
_, _ = w.do(call("wipe", `{"dir":"scratch"}`))
list, err := w.g.List()
if err != nil || len(list) != 1 || list[0].State != "asked" || list[0].Address != "shell.wipe" || list[0].Fingerprint == "" {
t.Fatalf("%+v %v", list, err)
}
whole, err := w.g.Read(list[0].Ask)
if err != nil || string(whole.Arguments) != `{"dir":"scratch"}` || !strings.HasPrefix(whole.Digest, "sha256:") {
t.Fatalf("%+v %v", whole, err)
}
applied := w.g.Applied()
if len(applied) != 1 || applied[0].Tools["wipe"] != "entry shell src2:1" || applied[0].Verbs["node-login-shell.execute"] != "seat node-login-shell" || applied[0].Revision != "7" {
t.Fatalf("applied %+v", applied)
}
if ok, src := w.g.Describe("shell", "", "wipe"); !ok || src != "entry shell src2:1" {
t.Errorf("describe %v %q", ok, src)
}
if ok, src := w.g.Describe("", "node-login-shell", "execute"); !ok || src != "seat node-login-shell" {
t.Errorf("describe a seat verb %v %q", ok, src)
}
if err := w.g.Again(list[0].Ask); err == nil {
t.Error("an ask not run was asked its hand-act again")
}
cancelled := ""
w.g.d.Publish = func(module, subject string, body any, id string) error { cancelled = subject + " " + id; return nil }
if err := w.g.Cancel(list[0].Ask); err != nil || cancelled != "mesh.seat.operator-channel.accept.cancel.shell cancel."+list[0].Ask {
t.Fatalf("cancel %v %q", err, cancelled)
}
}},
} {
t.Run(tc.name, func(t *testing.T) { tc.run(t, newWorld()) })
}
}