mesh/merge-gate pass: builds desk-channel, messenger → novox; 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, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
dunst.started says the notifier's bundle started again: the notifications stay on screen and only the answers they waited for are lost. Forgetting their ids made the router's message a second popup beside a dead one. The desk now keeps the ids and says answers-lost-at; the router's message replaces each notification in place. Whether a message is said again is now its own (last said before the answers were lost), so a router that started after the loss still says it and a repeated standing says nothing. It is sent to the desk directly, not through the outbox, whose failure path moves a warning to Telegram; a failed send is tried again at the next standing.
204 lines
9.2 KiB
Go
204 lines
9.2 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
)
|
|
|
|
// Issue 384: the desk's notifier started again and the desk showed, from its own memory, nine old popups —
|
|
// resolved, silenced and cancelled ones among them. Now the desk says in its standing when the answers were
|
|
// lost, and this router says again only what is still open and unanswered: with the desk showing one open
|
|
// message with an answer, one silenced, one open asking nothing and one resolved, one is said again under its
|
|
// handle (in place of its message), with its answers; one is edited to the silenced words, with no answers
|
|
// and no urgency; nothing is said for the others. A standing that repeats the time says nothing more; its
|
|
// ticket is one-time still, and the channel that reaches the operator away hears nothing of it.
|
|
func TestLostAnswersGetOnlyWhatIsOpenAndUnansweredSaidAgain(t *testing.T) {
|
|
h, tg, dt, c, _, w := newRouter(t)
|
|
h.Start()
|
|
c.pass(2 * time.Minute)
|
|
// Open, with a question the desk carries.
|
|
open := cond("machine.shanks.units", Warning, "2 failed services on shanks")
|
|
open.Raised = c.now()
|
|
h.Condition(EventRaised, open)
|
|
settle(h, c)
|
|
if len(dt.sends) != 1 {
|
|
t.Fatalf("desk %d", len(dt.sends))
|
|
}
|
|
ask := ackAsk(c, "silence-units")
|
|
ask.About = open.Key
|
|
submit(t, h, ConditionOwner, ask)
|
|
if e := dt.edits["g14=1"]; ticketFor(e, "Silence for a week") == "" {
|
|
t.Fatalf("the question was not put on the open message: %+v", e)
|
|
}
|
|
// Silenced, with a question of its own, and its message says it is silenced.
|
|
runFor(h, c, 20*time.Minute)
|
|
sil := cond("machine.ace.agent-home", Warning, "Agent-home shrank")
|
|
sil.Raised = c.now()
|
|
h.Condition(EventRaised, sil)
|
|
settle(h, c)
|
|
if len(dt.sends) != 2 {
|
|
t.Fatalf("desk %d", len(dt.sends))
|
|
}
|
|
stand(h, c) // the channels' word, fresh again after the minutes passed
|
|
silAsk := ackAsk(c, "silence-home")
|
|
silAsk.About = sil.Key
|
|
submit(t, h, ConditionOwner, silAsk)
|
|
if e := dt.edits["g14=2"]; ticketFor(e, "Silence for a week") == "" {
|
|
t.Fatalf("the question was not put on the message to be silenced: %+v", e)
|
|
}
|
|
sil.SilencedTill, sil.SilencedWhy, sil.At = c.now().Add(7*24*time.Hour), "known", c.now()
|
|
h.Condition(EventChanged, sil)
|
|
if e := dt.edits["g14=2"]; !strings.HasPrefix(e.Title, "Silenced until") {
|
|
t.Fatalf("the silence was not said: %+v", e)
|
|
}
|
|
// Open, told, and asking nothing: not unanswered, so not said again.
|
|
runFor(h, c, 20*time.Minute)
|
|
info := cond("seat.mesh-controller.assign-by-hand", Warning, "Assign keeps being fixed by hand")
|
|
info.Raised = c.now()
|
|
h.Condition(EventRaised, info)
|
|
settle(h, c)
|
|
if len(dt.sends) != 3 {
|
|
t.Fatalf("desk %d", len(dt.sends))
|
|
}
|
|
// Resolved, and its clearing said.
|
|
runFor(h, c, 20*time.Minute)
|
|
res := cond("seat.operator-channel.asks-undelivered", Warning, "Questions for you not delivered")
|
|
res.Raised = c.now()
|
|
h.Condition(EventRaised, res)
|
|
settle(h, c)
|
|
if len(dt.sends) != 4 {
|
|
t.Fatalf("desk %d", len(dt.sends))
|
|
}
|
|
h.Condition(EventCleared, Condition{Key: res.Key, At: c.now()})
|
|
runFor(h, c, 3*time.Minute)
|
|
if e := dt.edits["g14=4"]; !strings.HasPrefix(e.Title, "Resolved: ") {
|
|
t.Fatalf("the clearing was not said: %+v", e)
|
|
}
|
|
silencedBefore := dt.edits["g14=2"]
|
|
delete(dt.edits, "g14=2") // so the edit said again is seen as such
|
|
sends, edits, replaced, tgSaid := len(dt.sends), len(dt.edits), len(dt.replaced), saidOn(tg)
|
|
// The desk's notifier started again: its standing says when the answers were lost.
|
|
c.pass(time.Minute)
|
|
newSince := c.now()
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: newSince})
|
|
runFor(h, c, 2*time.Minute)
|
|
if len(dt.sends) != sends {
|
|
t.Fatalf("a new message was sent instead of one under its handle: %+v", dt.sends[sends:])
|
|
}
|
|
if len(dt.replaced) != replaced+1 || dt.replaced[replaced].id != "g14=1" {
|
|
t.Fatalf("the open question was not said again under its handle, once and alone: %+v", dt.replaced[replaced:])
|
|
}
|
|
again := dt.replaced[replaced].m
|
|
if ticketFor(again, "Silence for a week") == "" || !strings.Contains(again.Title, headlineOf(open.Key)) {
|
|
t.Fatalf("said again without its answer or its words: %+v", again)
|
|
}
|
|
if e, said := dt.edits["g14=2"]; !said || !strings.HasPrefix(e.Title, "Silenced until") || len(e.Actions) != 0 || e.Urgent ||
|
|
!e.Quiet || e.Title != silencedBefore.Title {
|
|
t.Fatalf("the silenced one was not edited to the silenced words again: %+v (said %v)", e, said)
|
|
}
|
|
if len(dt.edits) != edits+1 {
|
|
t.Fatalf("an edit reached a message that is not silenced: %+v", dt.edits)
|
|
}
|
|
if e := dt.edits["g14=4"]; !strings.HasPrefix(e.Title, "Resolved: ") {
|
|
t.Fatalf("the resolved one was changed: %+v", e)
|
|
}
|
|
if saidOn(tg) != tgSaid {
|
|
t.Fatalf("the channel that reaches the operator away heard of the desk's screen: %+v %+v", tg.sends, tg.edits)
|
|
}
|
|
// The standing said again with the same time, and one with an older time: nothing more.
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: newSince})
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: newSince.Add(-time.Hour)})
|
|
runFor(h, c, 2*time.Minute)
|
|
if len(dt.replaced) != replaced+1 || len(dt.sends) != sends {
|
|
t.Fatalf("said again more than once: replaced %d, sends %d", len(dt.replaced)-replaced, len(dt.sends)-sends)
|
|
}
|
|
// Its ticket is one-time still: one warrant, however often it is tapped, and refused from another machine.
|
|
tk := ticketFor(again, "Silence for a week")
|
|
ch := choiceOf(tk, "g14", c.now())
|
|
ch.Sender.Authenticated = false
|
|
if got, err := h.Chosen("desktop", ch); got != "chosen" || err != nil {
|
|
t.Fatalf("the answer on the message said again: %s, %v", got, err)
|
|
}
|
|
ch.ID = "c-second"
|
|
_, _ = h.Chosen("desktop", ch) // the same tap again: said as recorded (issue 372), nothing more done
|
|
other := choiceOf(tk, "shanks", c.now())
|
|
other.Sender.Authenticated = false
|
|
if got, _ := h.Chosen("desktop", other); got != "refused" {
|
|
t.Fatalf("the ticket taken from another machine after it was used: %s", got)
|
|
}
|
|
chosen := 0
|
|
for _, wt := range w.of(ConditionOwner) {
|
|
if wt.Outcome == asks.OutcomeChosen {
|
|
chosen++
|
|
}
|
|
}
|
|
if chosen != 1 {
|
|
t.Fatalf("warrants for one ticket: %d", chosen)
|
|
}
|
|
}
|
|
|
|
// Whether a message is said again is its own: last said before the answers were lost. A loss before it was
|
|
// last said says nothing; a router that started again (it remembers no standing it acted on) says it again
|
|
// once, and then nothing more. A loss while the desk cannot send is said once it can — never pushed to the
|
|
// channel that reaches the operator away. Telegram, whose answers are never lost, says no time and gets nothing.
|
|
func TestLostAnswersAreJudgedPerMessageAndWaitForTheDesk(t *testing.T) {
|
|
h, tg, dt, c, _, _ := newRouter(t)
|
|
h.Start()
|
|
c.pass(2 * time.Minute)
|
|
open := cond("machine.shanks.units", Warning, "2 failed services on shanks")
|
|
open.Raised = c.now()
|
|
h.Condition(EventRaised, open)
|
|
settle(h, c)
|
|
ask := ackAsk(c, "silence-units")
|
|
ask.About = open.Key
|
|
submit(t, h, ConditionOwner, ask)
|
|
runFor(h, c, time.Minute) // the question reaches Telegram too, as its own message
|
|
replaced, tgSaid := len(dt.replaced), saidOn(tg)
|
|
// Lost before the message was last said: its answers are those of the notifier that runs now.
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: open.Raised.Add(-time.Minute)})
|
|
h.Stand("telegram", Standing{Ready: true, At: c.now()})
|
|
runFor(h, c, time.Minute)
|
|
if len(dt.replaced) != replaced || saidOn(tg) != tgSaid {
|
|
t.Fatalf("said again for answers that were not lost: desk %+v, telegram %+v", dt.replaced[replaced:], tg.sends)
|
|
}
|
|
// Lost while the desk cannot send: waits, and goes to the desk once it can — never to Telegram.
|
|
c.pass(time.Minute)
|
|
lost := c.now()
|
|
dt.notReady = errorf("notifications paused")
|
|
h.Stand("desktop", Standing{Ready: false, Why: "notifications paused", At: c.now(), AnswersLostAt: lost})
|
|
runFor(h, c, 2*time.Minute)
|
|
if len(dt.replaced) != replaced || saidOn(tg) != tgSaid {
|
|
t.Fatalf("said while the desk cannot send: desk %+v, telegram %+v", dt.replaced[replaced:], tg.sends)
|
|
}
|
|
dt.notReady = nil
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: lost})
|
|
runFor(h, c, time.Minute)
|
|
if len(dt.replaced) != replaced+1 || saidOn(tg) != tgSaid {
|
|
t.Fatalf("once the desk can send: desk %+v, telegram %+v", dt.replaced[replaced:], tg.sends)
|
|
}
|
|
// The router started again, remembering no standing it acted on: the message was said since, so nothing.
|
|
h.shows = map[string]time.Time{}
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: lost})
|
|
runFor(h, c, time.Minute)
|
|
if len(dt.replaced) != replaced+1 {
|
|
t.Fatalf("said again twice for one loss: %+v", dt.replaced[replaced:])
|
|
}
|
|
// A send that fails is tried again at the next standing, on the desk alone.
|
|
c.pass(time.Minute)
|
|
lost = c.now()
|
|
dt.fail = errorf("no session answers")
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: lost})
|
|
dt.fail = nil
|
|
runFor(h, c, time.Minute)
|
|
if len(dt.replaced) != replaced+1 || saidOn(tg) != tgSaid {
|
|
t.Fatalf("after a failed send: desk %+v, telegram %+v", dt.replaced[replaced:], tg.sends)
|
|
}
|
|
h.Stand("desktop", Standing{Ready: true, At: c.now(), AnswersLostAt: lost})
|
|
if len(dt.replaced) != replaced+2 || saidOn(tg) != tgSaid {
|
|
t.Fatalf("the failed send was not tried again on the desk: desk %+v, telegram %+v", dt.replaced[replaced:], tg.sends)
|
|
}
|
|
}
|