Files
mesh-sdk/go/asker/asker_test.go
T
jochen 55090da7e0
mesh/merge-gate pass: builds mesh-sdk, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group feat/asks-answered-on-any-channel checking: 3 of 4 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Give an asking module a client that acts on each warrant once, and tell a lost compare-and-set by its code
2026-10-08 18:20:31 +02:00

131 lines
4.0 KiB
Go

package asker
import (
"encoding/json"
"strings"
"testing"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/asks"
)
type mem struct {
vals map[string]json.RawMessage
revs map[string]uint64
n uint64
}
func (m *mem) Get(k string) (*stdio.StateEntry, error) {
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) {
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) {
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) {
raw, _ := json.Marshal(v)
m.n++
m.vals[k], m.revs[k] = raw, m.n
return m.n, nil
}
var now = time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC)
func client() (*Client, map[string][]string) {
sent := map[string][]string{}
c := &Client{Module: "mesh-delivery", State: &mem{vals: map[string]json.RawMessage{}, revs: map[string]uint64{}},
Now: func() time.Time { return now },
Publish: func(subject string, body any, id string) error {
sent[subject] = append(sent[subject], id)
return nil
}}
return c, sent
}
func held() asks.Ask {
return asks.Ask{ID: "d-1", Headline: "Delivery of hq held", Explanation: "Needs you: release it, or stop it.",
Who: asks.Operator, Expires: now.Add(time.Hour), OnExpiry: "the delivery stays held",
Options: []asks.Option{{ID: "release", Label: "Release", Does: "it goes on", Level: asks.Approve}}}
}
func warrant() asks.Warrant {
return asks.Warrant{Ask: "d-1", Asker: "mesh-delivery", Outcome: asks.OutcomeChosen, Option: "release",
Label: "Release", Level: asks.Approve, At: now.Add(time.Minute),
By: &asks.Person{Who: asks.Operator, Kind: "telegram", Identity: "42", Verified: "user id verified"}}
}
func TestAnAskIsKeptThenPublishedUnderTheModulesOwnName(t *testing.T) {
c, sent := client()
if err := c.Ask(held()); err != nil {
t.Fatal(err)
}
if got := sent[asks.AskSubject("mesh-delivery")]; len(got) != 1 || got[0] != "d-1" {
t.Fatalf("%v", sent)
}
if err := c.Ask(held()); err == nil || !strings.Contains(err.Error(), "already made") {
t.Errorf("the same id was asked twice: %v", err)
}
bad := held()
bad.Who = "agent"
if err := c.Ask(bad); err == nil {
t.Error("an ask the contract refuses was published")
}
}
func TestAWarrantIsActedOnOnceAndOnlyForItsOwnAsk(t *testing.T) {
c, _ := client()
_ = c.Ask(held())
acted := 0
act := func(asks.Option, asks.Warrant) error { acted++; return nil }
if h, err := c.Take(warrant(), act); h != Acted || err != nil || acted != 1 {
t.Fatalf("%s %v %d", h, err, acted)
}
if h, _ := c.Take(warrant(), act); h != Done || acted != 1 {
t.Errorf("a redelivery was acted on: %s", h)
}
for why, change := range map[string]func(*asks.Warrant){
"another asker": func(w *asks.Warrant) { w.Asker = "mesh-controller" },
"an ask not made": func(w *asks.Warrant) { w.Ask = "d-9" },
"another level": func(w *asks.Warrant) { w.Level = asks.Acknowledge },
"an option not offered": func(w *asks.Warrant) { w.Option = "delete" },
"after its expiry": func(w *asks.Warrant) { w.At = now.Add(2 * time.Hour) },
} {
c2, _ := client()
_ = c2.Ask(held())
w := warrant()
change(&w)
if h, _ := c2.Take(w, act); h == Acted {
t.Errorf("a warrant from %s was acted on", why)
}
}
if acted != 1 {
t.Errorf("acted %d times", acted)
}
}
func TestAnAskEndedWithoutAChoiceIsRecordedAndNothingDone(t *testing.T) {
c, _ := client()
_ = c.Ask(held())
w := warrant()
w.Outcome, w.Option, w.By = asks.OutcomeExpired, "", nil
if h, err := c.Take(w, func(asks.Option, asks.Warrant) error { t.Fatal("acted"); return nil }); h != Recorded || err != nil {
t.Fatalf("%s %v", h, err)
}
if h, _ := c.Take(warrant(), func(asks.Option, asks.Warrant) error { t.Fatal("acted after it ended"); return nil }); h != Done {
t.Errorf("%s", h)
}
}