The router refused such an ask with 'no channel', so the asker waited for an Approve nobody saw. It is now held as an open ask with the per-kind reason, judged again when the kinds or a channel's standing change and every minute, and the knowledge base says held and why.
143 lines
5.2 KiB
Go
143 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
)
|
|
|
|
// A held ask (novox/hq ADR 0397, issue 926): an ask that breaks no rule but that no ready channel can carry now
|
|
// is kept, not refused, and offered the moment a channel can carry it.
|
|
|
|
// withoutVerifiedSender is the controller's word on the kinds when Telegram's claim lacks verified-sender, as
|
|
// on 2026-10-16 (issue 930).
|
|
func withoutVerifiedSender(h *Holder) {
|
|
h.Bench.SetKinds([]KindInfo{
|
|
{Kind: "telegram", Module: "telegram", Node: "anchor", Capabilities: []string{"deliver", "reaches-away", "choice", "edit", "exact-render"}},
|
|
{Kind: "desktop", Module: "desk-channel", Node: "anchor", Capabilities: []string{"deliver", "choice", "edit", "exact-render", "private"}},
|
|
})
|
|
}
|
|
|
|
func withVerifiedSender(h *Holder) {
|
|
h.Bench.SetKinds([]KindInfo{
|
|
{Kind: "telegram", Module: "telegram", Node: "anchor", Capabilities: []string{"deliver", "reaches-away", "choice", "edit", "verified-sender", "exact-render"}},
|
|
{Kind: "desktop", Module: "desk-channel", Node: "anchor", Capabilities: []string{"deliver", "choice", "edit", "exact-render", "private"}},
|
|
})
|
|
}
|
|
|
|
func recordJSON(t *testing.T, store *memAsks, key string) string {
|
|
t.Helper()
|
|
raw, _ := json.Marshal(store.asks[key])
|
|
return string(raw)
|
|
}
|
|
|
|
func TestAnApprovalAskNoChannelCanCarryIsHeldAndItsAskerToldWhy(t *testing.T) {
|
|
h, tg, dt, c, store, w := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
withoutVerifiedSender(h)
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
for _, g := range w.of("mesh-delivery") {
|
|
t.Errorf("the asker was told it ended: %+v", g)
|
|
}
|
|
rec, kept := store.asks["mesh-delivery.d-1"]
|
|
if !kept || rec.State != StateHeld || len(rec.Tickets) != 0 {
|
|
t.Fatalf("not held: %+v", rec)
|
|
}
|
|
for _, want := range []string{"Telegram cannot prove who answered", "held"} {
|
|
if !strings.Contains(recordJSON(t, store, "mesh-delivery.d-1"), want) {
|
|
t.Errorf("the record does not say %q: %s", want, recordJSON(t, store, "mesh-delivery.d-1"))
|
|
}
|
|
}
|
|
if strings.Contains(rec.HeldWhy, "no channel") && !strings.Contains(rec.HeldWhy, "Telegram") {
|
|
t.Errorf("only no channel: %q", rec.HeldWhy)
|
|
}
|
|
if len(tg.sends) != 0 || len(dt.sends) != 0 {
|
|
t.Errorf("a held ask was shown: %d %d", len(tg.sends), len(dt.sends))
|
|
}
|
|
}
|
|
|
|
func TestAHeldAskIsOfferedWhenTheKindsChange(t *testing.T) {
|
|
h, tg, _, c, store, w := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
withoutVerifiedSender(h)
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
c.pass(time.Minute)
|
|
stand(h, c)
|
|
h.Tick() // still held: the reason is unchanged
|
|
if rec := store.asks["mesh-delivery.d-1"]; rec.State != StateHeld || len(tg.sends) != 0 {
|
|
t.Fatalf("offered too soon: %+v", rec)
|
|
}
|
|
withVerifiedSender(h)
|
|
h.Tick()
|
|
settle(h, c)
|
|
rec := store.asks["mesh-delivery.d-1"]
|
|
if rec.State != StateOpen || len(rec.Tickets) != 3 || rec.HeldWhy != "" {
|
|
t.Fatalf("not offered: %+v", rec)
|
|
}
|
|
if len(tg.sends) != 1 || labelsOf(tg.sends[0]) != "Release, Stop, Silence for a week" {
|
|
t.Fatalf("telegram was sent %+v", tg.sends)
|
|
}
|
|
if got := w.of("mesh-delivery"); len(got) != 0 {
|
|
t.Errorf("an offer is not an end: %+v", got)
|
|
}
|
|
// Answered as any ask is.
|
|
out, err := h.Chosen("telegram", choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now()))
|
|
if err != nil || out != "chosen" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
}
|
|
|
|
func TestAHeldAskExpiresAsAnyOpenAskDoes(t *testing.T) {
|
|
h, _, _, c, store, w := newRouter(t)
|
|
withoutVerifiedSender(h)
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
c.pass(3 * time.Hour)
|
|
stand(h, c)
|
|
h.Tick()
|
|
got := w.of("mesh-delivery")
|
|
if len(got) != 1 || got[0].Outcome != asks.OutcomeExpired || store.asks["mesh-delivery.d-1"].State != string(asks.OutcomeExpired) {
|
|
t.Fatalf("%+v %+v", got, store.asks["mesh-delivery.d-1"])
|
|
}
|
|
}
|
|
|
|
func TestAHeldAskCountsTowardTheOpenAsksOfItsAsker(t *testing.T) {
|
|
h, _, _, c, _, w := newRouter(t)
|
|
withoutVerifiedSender(h)
|
|
for i := 1; i <= 4; i++ {
|
|
a := heldAsk(c, "d-"+string(rune('0'+i)))
|
|
a.About = "about-" + string(rune('0'+i))
|
|
submit(t, h, "mesh-delivery", a)
|
|
}
|
|
got := w.of("mesh-delivery")
|
|
if len(got) != 1 || got[0].Outcome != asks.OutcomeRefused || !strings.Contains(got[0].Words, "already has 3 open asks") {
|
|
t.Fatalf("%+v", got)
|
|
}
|
|
}
|
|
|
|
func TestTheAsksVerbShowsAHeldAskWithSinceAndWhy(t *testing.T) {
|
|
h, _, _, c, _, _ := newRouter(t)
|
|
withoutVerifiedSender(h)
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
raw, _ := json.Marshal(h.AsksNow())
|
|
for _, want := range []string{`"state":"held"`, `"held_since"`, `"held_why":"`, "Telegram cannot prove who answered"} {
|
|
if !strings.Contains(string(raw), want) {
|
|
t.Errorf("the asks verb lacks %q: %s", want, raw)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAHeldAskNamesEveryKindAndWhyWhenNoChannelIsReady(t *testing.T) {
|
|
h, _, _, c, store, _ := newRouter(t)
|
|
h.Bench.Stand("telegram", Standing{Ready: false, Why: "no bot token", At: c.now()})
|
|
h.Bench.Stand("desktop", Standing{Ready: false, Why: "no session", At: c.now()})
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
why := store.asks["mesh-delivery.d-1"].HeldWhy
|
|
if store.asks["mesh-delivery.d-1"].State != StateHeld || !strings.Contains(why, "Telegram: no bot token") || !strings.Contains(why, "the desk: no session") {
|
|
t.Fatalf("%q", why)
|
|
}
|
|
}
|