mesh/merge-gate head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: builds messenger, telegram → novox; no bus step; every machine composes with the c…
mesh/repo-check head b7d9590fdc95 merged into trunk e1c8965ddbf1 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
Issue 372's fix with issue 639's one line edited an answered ask down to 'You chose X at HH:MM · headline', so the operator could not read back what they approved. The headline stays the title, the summary, points, link and Details stay the body, and how it ended and who acts on it become the note. Telegram cuts the body from its end past 4096 characters, never the title or the note.
186 lines
7.1 KiB
Go
186 lines
7.1 KiB
Go
package main
|
|
|
|
// What the operator sees when they answer a question (issue 372): at once, where they answered, that it was
|
|
// recorded — never before it was — or why it was not; then the question itself says how it ended, with no
|
|
// answers left on it.
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// inOrder is a channel that keeps what it was given in one line, sends and edits alike.
|
|
type inOrder struct {
|
|
*fakeChannel
|
|
log *[]string
|
|
}
|
|
|
|
func (o inOrder) Send(m Message) (string, error) {
|
|
*o.log = append(*o.log, "send "+m.Title)
|
|
return o.fakeChannel.Send(m)
|
|
}
|
|
|
|
func (o inOrder) Edit(id string, m Message) error {
|
|
*o.log = append(*o.log, "edit "+m.Title)
|
|
return o.fakeChannel.Edit(id, m)
|
|
}
|
|
|
|
// failingAsks is the router's state refusing to record an ask's end.
|
|
type failingAsks struct {
|
|
*memAsks
|
|
err error
|
|
}
|
|
|
|
func (f failingAsks) UpdateAsk(k string, r AskRecord, rev uint64) (uint64, error) {
|
|
return 0, f.err
|
|
}
|
|
|
|
func TestAnApprovalIsConfirmedWhereItWasGivenAndThenTheQuestionSaysHowItEnded(t *testing.T) {
|
|
h, tg, _, c, store, _ := newRouter(t)
|
|
var log []string
|
|
h.Away = inOrder{tg, &log}
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
log = nil
|
|
ch := choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now())
|
|
if out, err := h.Chosen("telegram", ch); err != nil || out != "chosen" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
want := []string{
|
|
"send You chose Release: recorded, and mesh-delivery was told.",
|
|
"edit Delivery of hq held for 36 hours",
|
|
}
|
|
if strings.Join(log, "\n") != strings.Join(want, "\n") {
|
|
t.Fatalf("the operator saw, in order:\n%s\nwant:\n%s", strings.Join(log, "\n"), strings.Join(want, "\n"))
|
|
}
|
|
if confirmed := tg.sends[len(tg.sends)-1]; confirmed.Reply != ch.ID || confirmed.Body != "" {
|
|
t.Errorf("the confirmation is not a reply of one line to the tap: %+v", confirmed)
|
|
}
|
|
// The same tap redelivered is acted on once: nothing more is said, and the question is edited once.
|
|
if out, _ := h.Chosen("telegram", ch); out != "chosen" || len(log) != 2 {
|
|
t.Errorf("a redelivered tap was said again: %s %q", out, log)
|
|
}
|
|
}
|
|
|
|
// A Decline is confirmed as what it is, in the option's own word.
|
|
func TestADeclineIsConfirmedInItsOwnWord(t *testing.T) {
|
|
h, tg, _, c, store, _ := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
if out, err := h.Chosen("telegram", choiceOf(ticketFor(tg.sends[0], "Stop"), "42", c.now())); err != nil || out != "chosen" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
if got := tg.sends[len(tg.sends)-1].Title; got != "You chose Stop: recorded, and mesh-delivery was told." {
|
|
t.Errorf("confirmed as %q", got)
|
|
}
|
|
}
|
|
|
|
// The confirmation never claims what did not happen: an end that could not be recorded is said as refused,
|
|
// with why, and nothing says "recorded"; a warrant the bus did not take is said as not yet told.
|
|
func TestTheConfirmationNeverClaimsWhatDidNotHappen(t *testing.T) {
|
|
t.Run("not recorded", func(t *testing.T) {
|
|
h, tg, _, c, store, w := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
h.Asks = failingAsks{store, errors.New("the bucket is away")}
|
|
before := len(tg.sends)
|
|
out, err := h.Chosen("telegram", choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now()))
|
|
if err == nil || out != "refused" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
for _, m := range tg.sends[before:] {
|
|
if strings.Contains(m.Title+m.Body, "recorded,") || strings.Contains(m.Title, "You chose") {
|
|
t.Errorf("an answer not recorded was confirmed: %+v", m)
|
|
}
|
|
}
|
|
last := tg.sends[len(tg.sends)-1]
|
|
if last.Title != "That answer was not taken" || !strings.Contains(last.Body, "could not be recorded") || last.Reply == "" {
|
|
t.Errorf("the refusal does not say why, in reply to the tap: %+v", last)
|
|
}
|
|
if len(w.of("mesh-delivery")) != 0 || len(tg.edits) != 0 {
|
|
t.Error("an answer not recorded was told or shown as the question's end")
|
|
}
|
|
})
|
|
t.Run("not told", func(t *testing.T) {
|
|
h, tg, _, c, store, _ := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
h.Publish = func(subject string, body any, id string) error { return errors.New("the bus is away") }
|
|
if out, err := h.Chosen("telegram", choiceOf(ticketFor(tg.sends[0], "Release"), "42", c.now())); err != nil || out != "chosen" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
if got := tg.sends[len(tg.sends)-1].Title; got != "You chose Release: recorded; mesh-delivery is told as soon as the bus takes it." {
|
|
t.Errorf("confirmed as %q", got)
|
|
}
|
|
var edited Message
|
|
for _, m := range tg.edits {
|
|
edited = m
|
|
}
|
|
if !strings.Contains(edited.Note, "\nTold to mesh-delivery as soon as the bus takes it") {
|
|
t.Errorf("the question claims its asker was told: %q", edited.Note)
|
|
}
|
|
})
|
|
}
|
|
|
|
// A refused answer says why, in reply to the tap, with nothing claimed done.
|
|
func TestARefusedAnswerSaysWhyInReplyToTheTap(t *testing.T) {
|
|
h, tg, _, c, store, _ := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
release := ticketFor(tg.sends[0], "Release")
|
|
_, _ = h.Chosen("telegram", choiceOf(release, "42", c.now()))
|
|
again := choiceOf(ticketFor(tg.sends[0], "Stop"), "42", c.now())
|
|
again.ID = "tg-tap-again"
|
|
if out, _ := h.Chosen("telegram", again); out != "refused" {
|
|
t.Fatalf("a second answer to an answered question was taken: %s", out)
|
|
}
|
|
last := tg.sends[len(tg.sends)-1]
|
|
if last.Reply != "tg-tap-again" || last.Title != "That answer was not taken" ||
|
|
!strings.HasSuffix(last.Body, "Nothing was done.") || len(strings.TrimSuffix(last.Body, "Nothing was done.")) < 10 {
|
|
t.Errorf("the refusal: %+v", last)
|
|
}
|
|
if _, ok := Check(last.Text()); !ok {
|
|
t.Error("the refusal carries what may not leave")
|
|
}
|
|
}
|
|
|
|
// A second tap on the answer just recorded — the phone's spinner still turning, or a redelivery after a restart
|
|
// — is said as recorded, not refused as if it failed, and nothing more is done (issue 372).
|
|
func TestTheSameAnswerTappedTwiceIsSaidAsRecorded(t *testing.T) {
|
|
h, tg, _, c, store, w := newRouter(t)
|
|
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
|
submit(t, h, "mesh-delivery", heldAsk(c, "d-1"))
|
|
settle(h, c)
|
|
release := ticketFor(tg.sends[0], "Release")
|
|
first := choiceOf(release, "42", c.now())
|
|
first.ID = "tg-tap-1"
|
|
if out, err := h.Chosen("telegram", first); err != nil || out != "chosen" {
|
|
t.Fatalf("%s %v", out, err)
|
|
}
|
|
edits := len(tg.edits)
|
|
second := choiceOf(release, "42", c.now())
|
|
second.ID = "tg-tap-2"
|
|
if out, err := h.Chosen("telegram", second); err != nil || out != "chosen" {
|
|
t.Fatalf("the second tap: %s %v", out, err)
|
|
}
|
|
last := tg.sends[len(tg.sends)-1]
|
|
if last.Reply != "tg-tap-2" || last.Title != "You already chose Release at 12:00: recorded." {
|
|
t.Errorf("the second tap was answered %+v", last)
|
|
}
|
|
for _, m := range tg.sends {
|
|
if m.Title == "That answer was not taken" {
|
|
t.Errorf("a tap on the answer recorded was refused: %+v", m)
|
|
}
|
|
}
|
|
if len(w.of("mesh-delivery")) != 1 || len(tg.edits) != edits {
|
|
t.Error("the second tap did something more")
|
|
}
|
|
}
|