From 7289edb276ecd409e654dc935fa471b7f48aa8a1 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 14:12:45 +0200 Subject: [PATCH] asks: the proof sees what the phone shows at a tap, and the question's outcome as its title (hq issue 372) The operator approved on Telegram and saw nothing: the tap was acknowledged with no words and the question kept its title. The proof now checks the toast says the approval was recorded, no earlier than the warrant; that the edited question opens with how it ended; and that the same answer tapped again is said as already recorded, not refused. --- asks/faketelegram_test.go | 38 +++++++++++++++++++++++++++++++++++++- asks/proof_test.go | 21 +++++++++++++++++---- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/asks/faketelegram_test.go b/asks/faketelegram_test.go index 1ad1a1d..fcd9193 100644 --- a/asks/faketelegram_test.go +++ b/asks/faketelegram_test.go @@ -25,10 +25,17 @@ type fakeTelegram struct { arrived chan struct{} sent []tgMessage msgID int64 - taps int // answerCallbackQuery calls + taps int // answerCallbackQuery calls + toasts []toast // what each acknowledgement showed on the phone (issue 372) deleted int } +// toast is a tap's acknowledgement: the short line the phone shows over the chat, and when. +type toast struct { + Text string + At time.Time +} + // tgMessage is one message the bot sent or edited: its chat, its id, its words and its buttons (label โ†’ // callback data). type tgMessage struct { @@ -108,9 +115,12 @@ func (f *fakeTelegram) serve(w http.ResponseWriter, r *http.Request) { f.t.Logf("phone: %s to %d (#%d): %q %v", method, chat, m.ID, firstLine(text), m.Buttons) ok(map[string]any{"message_id": m.ID, "chat": map[string]any{"id": chat, "type": "private"}, "text": text}) case "answerCallbackQuery": + text, _ := body["text"].(string) f.mu.Lock() f.taps++ + f.toasts = append(f.toasts, toast{Text: text, At: time.Now()}) f.mu.Unlock() + f.t.Logf("phone: a tap acknowledged with %q", text) ok(true) case "deleteMessage": f.mu.Lock() @@ -280,3 +290,29 @@ func firstLine(s string) string { } return s } + +// waitForToast waits for a tap's acknowledgement since a time that shows what match wants, and fails saying +// what the phone showed instead. +func (f *fakeTelegram) waitForToast(what string, since time.Time, within time.Duration, match func(string) bool) toast { + f.t.Helper() + deadline := time.Now().Add(within) + for { + f.mu.Lock() + var seen []string + for _, t := range f.toasts { + if t.At.Before(since) { + continue + } + if match(t.Text) { + f.mu.Unlock() + return t + } + seen = append(seen, fmt.Sprintf("%q", t.Text)) + } + f.mu.Unlock() + if time.Now().After(deadline) { + f.t.Fatalf("the phone never showed %s; it showed: %s", what, strings.Join(seen, ", ")) + } + time.Sleep(100 * time.Millisecond) + } +} diff --git a/asks/proof_test.go b/asks/proof_test.go index 19feac0..0c298f2 100644 --- a/asks/proof_test.go +++ b/asks/proof_test.go @@ -161,9 +161,19 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) { if got := a.performed(); len(got) != 1 || got[0]["arg.switch"] != "approve" { t.Fatalf("performed %v, want the one bound act once", got) } - // Every copy says how it ended, its buttons gone. - l.tg.waitFor("the question edited to its outcome", operatorAccount, tapped, 90*time.Second, + // At the tap, the phone says it was recorded, and never before the warrant was (issue 372). + ack := l.tg.waitForToast("the approval confirmed", tapped, 30*time.Second, func(s string) bool { + return strings.HasPrefix(s, "You chose Approve: recorded") + }) + if ack.At.Before(w.At) { + t.Errorf("the phone said recorded at %s, before the warrant at %s", ack.At, w.At) + } + // Every copy says how it ended โ€” as its title, read at a glance โ€” its buttons gone. + edited := l.tg.waitFor("the question edited to its outcome", operatorAccount, tapped, 90*time.Second, func(m tgMessage) bool { return m.Edited && m.ID == firstMessage.ID && len(m.Buttons) == 0 }) + if !strings.HasPrefix(edited.Text, "You chose Approve at ") || !strings.Contains(firstLine(edited.Text), " on Telegram ยท ") { + t.Errorf("the question does not open with how it ended: %q", firstLine(edited.Text)) + } // And the router's record holds who approved what. e, _ := l.routerRecord("messenger_asks", "lab-asker.a1") var rec struct { @@ -181,8 +191,11 @@ func TestTheOperatorsAnswerEndToEnd(t *testing.T) { t.Run("a replayed tap and a redelivered warrant do nothing more", func(t *testing.T) { tapped := time.Now() l.tg.tapped(operatorAccount, operatorAccount, "private", firstMessage.ID, button(firstMessage, "Approve")) - l.tg.waitFor("the refusal of a replayed tap", operatorAccount, tapped, 90*time.Second, - func(m tgMessage) bool { return strings.Contains(m.Text, "That answer was not taken") }) + // The same answer tapped again is what was recorded: the phone says so, and nothing more is done (issue 372: + // a refusal here read as the approval failing). + l.tg.waitForToast("the replayed tap said as already recorded", tapped, 30*time.Second, func(s string) bool { + return strings.HasPrefix(s, "You already chose Approve at ") && strings.HasSuffix(s, ": recorded.") + }) time.Sleep(2 * time.Second) if n := a.wordsOn("a1"); n != 1 { t.Errorf("the router said %d words on one ask", n) -- 2.54.0