diff --git a/node-tools/internal/approval/approval_test.go b/node-tools/internal/approval/approval_test.go index 53dfe47..77cbc66 100644 --- a/node-tools/internal/approval/approval_test.go +++ b/node-tools/internal/approval/approval_test.go @@ -390,6 +390,18 @@ func TestTheRefusalsAndAnswers(t *testing.T) { t.Fatalf("marked: %v, runs %d", err, w.runs) } }}, + {"§13: the fixture state refuses a key the bus refuses, so every key the gate writes here is one the bus holds (A-209)", func(t *testing.T, w *world) { + if _, err := w.state.Put("n1.act.sha256:00", "x"); err == nil { + t.Fatal("the fixture state held a key with a colon, which the bus refuses") + } + if _, err := w.state.Create("n1..ask", "x"); err == nil { + t.Fatal("the fixture state held a key with an empty name") + } + _, _ = w.do(call("wipe", `{"dir":"scratch"}`)) + if len(w.asked) != 1 || len(w.state.Vals) == 0 { + t.Fatalf("the gate kept nothing through the fixture state: asked %d, keys %d", len(w.asked), len(w.state.Vals)) + } + }}, {"§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) diff --git a/node-tools/internal/meshtest/state.go b/node-tools/internal/meshtest/state.go index 87c9e4d..4ec6417 100644 --- a/node-tools/internal/meshtest/state.go +++ b/node-tools/internal/meshtest/state.go @@ -2,14 +2,18 @@ package meshtest import ( "encoding/json" + "fmt" "sort" + "strings" "sync" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/keys" ) // State is a module's state as a fixture, the shape a gate's Store takes: Fail makes every read and write fail, as a bus -// refusing them; Vals are its values by key. +// refusing them; Vals are its values by key. A key the bus refuses (a name outside letters, digits and -/_=, in +// dot-separated names) is refused here too, so no test passes a key the live bus refuses (lead answer A-209). type State struct { mu sync.Mutex Vals map[string]json.RawMessage @@ -18,12 +22,25 @@ type State struct { Fail error } +// checkKey refuses a key the bus would. +func checkKey(k string) error { + for _, part := range strings.Split(k, ".") { + if keys.CheckName("key", part) != nil { + return fmt.Errorf("%q is not a key the bus can hold: letters, digits and -/_=, in dot-separated names", k) + } + } + return nil +} + // NewState is an empty State. func NewState() *State { return &State{Vals: map[string]json.RawMessage{}, revs: map[string]uint64{}} } func (m *State) Get(k string) (*stdio.StateEntry, error) { m.mu.Lock() defer m.mu.Unlock() + if err := checkKey(k); err != nil { + return nil, err + } if m.Fail != nil { return nil, m.Fail } @@ -36,6 +53,9 @@ func (m *State) Get(k string) (*stdio.StateEntry, error) { func (m *State) Create(k string, v any) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() + if err := checkKey(k); err != nil { + return 0, err + } if m.Fail != nil { return 0, m.Fail } @@ -47,6 +67,9 @@ func (m *State) Create(k string, v any) (uint64, error) { func (m *State) Update(k string, v any, rev uint64) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() + if err := checkKey(k); err != nil { + return 0, err + } if m.Fail != nil { return 0, m.Fail } @@ -58,6 +81,9 @@ func (m *State) Update(k string, v any, rev uint64) (uint64, error) { func (m *State) Put(k string, v any) (uint64, error) { m.mu.Lock() defer m.mu.Unlock() + if err := checkKey(k); err != nil { + return 0, err + } if m.Fail != nil { return 0, m.Fail }