A marked call is refused, asking nothing, when no module holds operator-channel (read from the assigner's view of the seats' holders the runner follows, an unread view refusing too); refused when the served entry cannot be read, the ask cannot be published, the router refuses it or its record cannot be read; and while the state approvals cannot be read or written no marked call runs on the machine, the failed read made again at the next marked call and the next membership change, a bound key raising approvals-unreadable after five minutes and the read again clearing it. Before the catalog half's first view a call waits for it within 5 seconds, on the events the runner hears, and is refused naming mesh_first_views when it is still unread. Each refusal is in the log with the ask's fingerprint, never the arguments. Builds: design 46 7-approval-mark §19, §5 (an unread served entry); lead answers A-157, A-154 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
570 lines
24 KiB
Go
570 lines
24 KiB
Go
package approval
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"sort"
|
||
"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"
|
||
)
|
||
|
||
// mem is the runner's state approvals as a fixture; fail makes every read and write fail, as a bus refusing them.
|
||
type mem struct {
|
||
mu sync.Mutex
|
||
vals map[string]json.RawMessage
|
||
revs map[string]uint64
|
||
n uint64
|
||
fail error
|
||
}
|
||
|
||
func newMem() *mem { return &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} }
|
||
|
||
func (m *mem) Get(k string) (*stdio.StateEntry, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if m.fail != nil {
|
||
return nil, m.fail
|
||
}
|
||
v, ok := m.vals[k]
|
||
if !ok {
|
||
return nil, nil
|
||
}
|
||
return &stdio.StateEntry{Key: k, Value: v, Revision: m.revs[k]}, nil
|
||
}
|
||
func (m *mem) Create(k string, v any) (uint64, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if m.fail != nil {
|
||
return 0, m.fail
|
||
}
|
||
if _, ok := m.vals[k]; ok {
|
||
return 0, stdio.ErrChanged
|
||
}
|
||
return m.put(k, v)
|
||
}
|
||
func (m *mem) Update(k string, v any, rev uint64) (uint64, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if m.fail != nil {
|
||
return 0, m.fail
|
||
}
|
||
if rev == 0 || m.revs[k] != rev {
|
||
return 0, stdio.ErrChanged
|
||
}
|
||
return m.put(k, v)
|
||
}
|
||
func (m *mem) Put(k string, v any) (uint64, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if m.fail != nil {
|
||
return 0, m.fail
|
||
}
|
||
return m.put(k, v)
|
||
}
|
||
func (m *mem) Keys() ([]string, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
if m.fail != nil {
|
||
return nil, m.fail
|
||
}
|
||
var out []string
|
||
for k := range m.vals {
|
||
out = append(out, k)
|
||
}
|
||
sort.Strings(out)
|
||
return out, nil
|
||
}
|
||
func (m *mem) put(k string, v any) (uint64, error) {
|
||
raw, ok := v.(json.RawMessage)
|
||
if !ok {
|
||
raw, _ = json.Marshal(v)
|
||
}
|
||
m.n++
|
||
m.vals[k], m.revs[k] = raw, m.n
|
||
return m.n, nil
|
||
}
|
||
|
||
// 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(tc.name, tc.node, true); got != tc.want || len([]rune(got)) > 60 {
|
||
t.Errorf("headline %q, want %q", got, tc.want)
|
||
}
|
||
}
|
||
if got := headlineOf("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)
|
||
}
|
||
}},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) { tc.run(t, newWorld()) })
|
||
}
|
||
}
|