Files
mesh-catalog/modules/messenger/cmd/messenger/asks_events_test.go
T
jschoubben 695ac0356d Join item 1: the catalogue's modules take the router's ask from operator-channel's contract and client
mesh-sdk holds neither asks nor asker, nor the vault's answer types, and mesh-delivery's
sdk/views is mesh-sdk's go/views (a1-progress.md, the rewrite map for the join): thirteen
modules switch to the operatorchannel contract and client, mesh-sdk's channel package and
meshvault, with go.mod requiring each at the group pin (nats: meshbroker too). The gate
holds the checks mesh-sdk dropped (an ask's id is its key's asking, at least one option),
with a row that fails without them; claude-code refuses a router record whose times do
not parse, as its JSON decode did, with a row.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 15:33:27 +01:00

97 lines
3.2 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
"time"
occ "git.novox.be/novox/mesh-contracts/go/clients/operatorchannelclient"
)
// The router says when an ask starts and ends (ADR 0616 §7): `offered` once when an ask is offered, `closed` once for each of the five ends
// (answered, expired, cancelled by its asker, replaced, ended by the router), with how it ended and never an option, a ticket or a
// proof; a held ask is said when it is released.
func TestTheRouterSaysWhenAnAskIsOfferedAndWhenItEnds(t *testing.T) {
h, tg, _, c, store, _ := newRouter(t)
link(h, store, c, "42", c.now().Add(-2*time.Hour))
type said struct {
event string
body map[string]any
}
var got []said
h.Emit = func(event string, body any) error {
raw, _ := json.Marshal(body)
var m map[string]any
_ = json.Unmarshal(raw, &m)
got = append(got, said{event, m})
return nil
}
ends := func() (out []string) {
for _, s := range got {
if s.event == "closed" {
out = append(out, s.body["asker"].(string)+"."+s.body["id"].(string)+"="+s.body["how"].(string))
}
}
return
}
// Answered.
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
settle(h, c)
if len(got) != 1 || got[0].event != "offered" || got[0].body["id"] != "d-1" || got[0].body["asker"] != "mesh-delivery" {
t.Fatalf("%+v", got)
}
release := ticketFor(tg.sends[0], "Release")
if _, err := h.Chosen("telegram", choiceOf(release, "42", c.now())); err != nil {
t.Fatal(err)
}
// Replaced, then cancelled by its asker.
a := ackAsk(c, "d-2")
a.About = "delivery.hq"
submit(t, h, "mesh-delivery", a)
b := ackAsk(c, "d-3")
b.About = "delivery.hq"
submit(t, h, "mesh-delivery", b)
if err := h.TakeAsk("cancel", "mesh-delivery", json.RawMessage(`{"id":"d-3"}`)); err != nil {
t.Fatal(err)
}
// Expired.
e := heldAsk(c, "d-4")
e.About = "other"
submit(t, h, "mesh-delivery", e)
settle(h, c)
c.pass(3 * time.Hour)
stand(h, c)
h.Tick()
// Ended by the router itself (an unlink or allow-link ends its own asks the same way).
f := ackAsk(c, "d-5")
f.About = "third"
submit(t, h, "mesh-delivery", f)
if err := h.closeAsk("mesh-delivery.d-5", occ.OutcomeCancelled, "root decided to allow a link", nil, "", nil); err != nil {
t.Fatal(err)
}
want := "mesh-delivery.d-1=answered mesh-delivery.d-2=replaced mesh-delivery.d-3=cancelled mesh-delivery.d-4=expired mesh-delivery.d-5=ended by the router"
if gotEnds := strings.Join(ends(), " "); gotEnds != want {
t.Fatalf("the ends %q, want %q", gotEnds, want)
}
// Once each: a second close loses and says nothing; five asks were offered, once each; no option, ticket or proof is in a body.
_ = h.closeAsk("mesh-delivery.d-1", occ.OutcomeCancelled, "again", nil, "", nil)
offered := 0
for _, s := range got {
if s.event == "offered" {
offered++
}
for k := range s.body {
if k != "id" && k != "asker" && k != "headline" && k != "channels" && k != "how" {
t.Errorf("%s carries %s, which the seat's contract does not have", s.event, k)
}
if k == "options" || k == "tickets" || k == "token" || k == "proofs" || k == "warrant" {
t.Errorf("%s carries %s", s.event, k)
}
}
}
if offered != 5 || len(ends()) != 5 {
t.Fatalf("%d offered, %d closed", offered, len(ends()))
}
}