A runner serving the indexer's tools refused every call until its own catalog half was read, and that read is a call of the same seat verbs through the same gate: G could not start. The four seat verbs the catalog half is read by now run unmarked while it is unread; every other call waits and is refused as before. Builds: design 47 G lead answers A-353, A-359 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
134 lines
5.5 KiB
Go
134 lines
5.5 KiB
Go
package approval
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.novox.be/novox/mesh-sdk/go/operatorask"
|
||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||
)
|
||
|
||
// call is a fixture call of the marked tool wipe.
|
||
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."}
|
||
}
|
||
|
||
// Design 46 7-approval-mark §8, §10, §11: the act's fields give each call its digest, and the ask shows a call's arguments
|
||
// in their shape and its headline cut (the gate's other cases run on the tool runner's fixture mesh, node-tools
|
||
// internal/approval).
|
||
func TestTheActAndItsAsk(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, _ := operatorask.ActDigest(act)
|
||
return d
|
||
}
|
||
act, _ := ActOf(base, "n1")
|
||
want := operatorask.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)
|
||
}
|
||
}
|
||
|
||
// unread is a catalog half whose first view is not read yet.
|
||
type unread struct{}
|
||
|
||
func (unread) Ready() error { return errors.New("the registrations are not answered") }
|
||
func (unread) Revision() string { return "" }
|
||
func (unread) Served(string) (Entry, bool, string) { return Entry{}, false, "not read" }
|
||
func (unread) Newest(string) (Entry, bool) { return Entry{}, false }
|
||
func (unread) Definitions() map[string]seatdef.Definition { return nil }
|
||
func (unread) Entries() []Entry { return nil }
|
||
|
||
// Design 47 G lead answers A-353, A-359: while its catalog half is unread, the gate lets through the four seat verbs that
|
||
// half is read by, told from the call's seat and verb, and holds every other call as before.
|
||
func TestAnUnreadCatalogHalfLetsItsOwnReadsThrough(t *testing.T) {
|
||
waited := 0
|
||
g := New(Deps{Node: "n1", Runner: "node-tools", Catalog: unread{}, Wait: func(time.Duration) error {
|
||
waited++
|
||
return errors.New("still unread")
|
||
}})
|
||
for _, tc := range []struct {
|
||
seat, name string
|
||
runs bool
|
||
}{
|
||
{"mesh-catalog", "registrations", true},
|
||
{"mesh-catalog", "revision", true},
|
||
{"mesh-catalog", "entry", true},
|
||
{"mesh-delivery", "source", true},
|
||
{"mesh-catalog", "register", false},
|
||
{"mesh-delivery", "registrations", false},
|
||
{"", "registrations", false}, // a module's tool of that name is no seat verb
|
||
{"", "revision", false},
|
||
} {
|
||
ran := false
|
||
address := tc.seat + "." + tc.name
|
||
if tc.seat == "" {
|
||
address = "mesh-delivery-indexer." + tc.name
|
||
}
|
||
_, err := g.Run(Call{Address: address, Module: "mesh-delivery-indexer", Seat: tc.seat, Name: tc.name, Fingerprint: "fp1",
|
||
Caller: "node-tools"}, func() (any, error) { ran = true; return "ok", nil })
|
||
if ran != tc.runs || (err == nil) != tc.runs {
|
||
t.Errorf("%s: ran %v (%v), want ran %v", address, ran, err, tc.runs)
|
||
}
|
||
if !tc.runs && (err == nil || !strings.Contains(err.Error(), "are not read yet")) {
|
||
t.Errorf("%s: refused with %v, want the marks not read yet", address, err)
|
||
}
|
||
}
|
||
if waited != 4 {
|
||
t.Errorf("the other calls waited for the catalog half %d times, want 4 (the four start reads wait for nothing)", waited)
|
||
}
|
||
}
|