14 "github.com/stretchr/testify/require"
17 func TestOwnerTalksToTheAgentThroughTelegram(t *testing.T) {
18 server := newModelFake(t)
19 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
20 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Hello from the agent.")}, 100, 0, 5))
23 telegram := newTelegramFake(t)
24 bot, _ := telegramStartTestBot(t, telegram)
25 socket := daemonStartTestWithBot(t, server, bot)
26 terminal := terminalStartTestClient(t, socket)
27 terminal.WaitFor(t, "[connected to OneChat]")
29 telegram.Add(TelegramUpdate{
31 Message: telegramOwnerMessage("hi from the phone"),
34 terminal.WaitFor(t, "[telegram] hi from the phone\n[working]\nHello from the agent.\n[done]")
35 telegramWaitForSent(t, telegram, "Hello from the agent.")
36 require.True(t, slices.ContainsFunc(telegram.Calls(), func(call TelegramFakeCall) bool {
37 return call.method == "sendChatAction" && call.params["action"] == "typing"
40 terminal.Type("hi from the desk")
41 require.Equal(t, []string{"Hello from the agent.", "[terminal] hi from the desk", "Hello from the agent."}, telegramWaitForSent(t, telegram, "Hello from the agent.", "[terminal] hi from the desk", "Hello from the agent."))
44 func TestBotIgnoresEveryoneButTheOwner(t *testing.T) {
45 telegram := newTelegramFake(t)
46 bot, logs := telegramStartTestBot(t, telegram)
47 socket := daemonStartTestWithBot(t, newModelFake(t), bot)
48 terminal := terminalStartTestClient(t, socket)
49 terminal.WaitFor(t, "[connected to OneChat]")
51 group := telegramOwnerMessage("secret in a group")
52 group.Chat = TelegramChat{
57 stranger := telegramOwnerMessage("secret of a stranger")
61 unsigned := telegramOwnerMessage("secret with no sender")
64 for id, message := range []*TelegramMessage{group, stranger, unsigned, nil} {
65 telegram.Add(TelegramUpdate{
66 UpdateID: int64(10 + id),
71 require.Eventually(t, func() bool {
72 offset, err := telegramLoadOffset(bot.offsetPath)
73 return err == nil && offset == 14
74 }, 10*time.Second, time.Millisecond)
76 require.Empty(t, telegram.Sent())
77 require.NotContains(t, terminal.Output(), "secret")
78 require.NotContains(t, logs.String(), "secret")
79 require.Contains(t, logs.String(), "message ignored")
82 func TestBotAnswersThatOnlyTextIsSupported(t *testing.T) {
83 telegram := newTelegramFake(t)
84 bot, _ := telegramStartTestBot(t, telegram)
85 daemonStartTestWithBot(t, newModelFake(t), bot)
87 telegram.Add(TelegramUpdate{
89 Message: telegramOwnerMessage(""),
92 telegramWaitForSent(t, telegram, telegramTextOnly)
95 func TestStopFromTelegramInterruptsTheTurn(t *testing.T) {
96 server := newModelFake(t)
97 arrived := make(chan struct{})
98 release := make(chan struct{})
103 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
108 telegram := newTelegramFake(t)
109 bot, _ := telegramStartTestBot(t, telegram)
110 daemonStartTestWithBot(t, server, bot)
112 telegram.Add(TelegramUpdate{
114 Message: telegramOwnerMessage("a long task"),
118 telegram.Add(TelegramUpdate{
120 Message: telegramOwnerMessage(stopCommand),
123 telegramWaitForSent(t, telegram, "Error: interrupted")
125 telegram.Add(TelegramUpdate{
127 Message: telegramOwnerMessage(stopCommand),
130 telegramWaitForSent(t, telegram, "Error: interrupted", "No turn runs.")
132 telegram.Add(TelegramUpdate{
134 Message: telegramOwnerMessage(stopCommand + " BlueFox"),
137 telegramWaitForSent(t, telegram, "No turn runs.", "No subagent BlueFox runs.")
140 func TestBotShowsOnlyTheStartAndTheEndOfASubagent(t *testing.T) {
141 telegram := newTelegramFake(t)
142 server := newModelFake(t)
143 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
144 last := subagentTestLastItem(call)
146 case subagentTestCall(call):
147 // The report comes after the reply of the turn of the spawn.
148 require.Eventually(t, func() bool {
149 return slices.Contains(telegram.Sent(), "Started.")
150 }, 10*time.Second, time.Millisecond)
151 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("There are 3 files.")}, 100, 0, 5))
152 case last["type"] == "function_call_output":
153 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Started.")}, 100, 0, 5))
154 case strings.HasPrefix(subagentTestText(last), "["):
155 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("It counted 3 files.")}, 100, 0, 5))
157 modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "spawn", `{"tasks":["Count the files."],"effort":null}`)}, 100, 0, 5))
161 bot, _ := telegramStartTestBot(t, telegram)
162 daemonStartTestWithBot(t, server, bot)
163 telegram.Add(TelegramUpdate{
165 Message: telegramOwnerMessage("Count the files in a subagent."),
168 require.Eventually(t, func() bool {
169 return slices.Contains(telegram.Sent(), "It counted 3 files.")
170 }, 10*time.Second, time.Millisecond)
172 sent := telegram.Sent()
173 name := strings.TrimSuffix(strings.TrimPrefix(sent[0], "["), "] started")
174 require.True(t, subagentNameValid(name), sent[0])
175 require.Equal(t, []string{
176 "[" + name + "] started",
178 "[" + name + "] finished",
179 "It counted 3 files.",
183 func TestBotSkipsTheWorkOfWhatsAppAndTheSchedules(t *testing.T) {
185 ready: make(chan struct{}, 1),
190 Source: whatsappSource,
191 Text: "[WhatsApp] Ana Souza",
195 Source: scheduleSource,
196 Text: "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
198 require.Empty(t, bot.queue)
201 func TestBotStartsAtTheStoredOffset(t *testing.T) {
202 server := newModelFake(t)
203 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
204 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("ok")}, 100, 0, 5))
207 telegram := newTelegramFake(t)
208 bot, _ := telegramStartTestBot(t, telegram)
209 require.NoError(t, telegramSaveOffset(bot.offsetPath, 10))
211 telegram.Add(TelegramUpdate{
213 Message: telegramOwnerMessage("handled before the restart"),
216 telegram.Add(TelegramUpdate{
218 Message: telegramOwnerMessage("new after the restart"),
221 socket := daemonStartTestWithBot(t, server, bot)
222 terminal := terminalStartTestClient(t, socket)
223 terminal.WaitFor(t, "[connected to OneChat]")
224 telegramWaitForSent(t, telegram, "ok")
226 require.Equal(t, float64(10), telegram.Calls()[0].params["offset"])
227 require.NotContains(t, terminal.Output(), "handled before the restart")
229 require.Eventually(t, func() bool {
230 offset, err := telegramLoadOffset(bot.offsetPath)
231 return err == nil && offset == 11
232 }, 10*time.Second, time.Millisecond)
235 func TestBotTriesAgainAfterFailures(t *testing.T) {
236 server := newModelFake(t)
237 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
238 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("ok")}, 100, 0, 5))
241 telegram := newTelegramFake(t)
242 failures := map[string]int{
244 "sendRichMessage": 1,
247 telegram.fail = func(call TelegramFakeCall) (int, string) {
248 if failures[call.method] == 0 {
252 failures[call.method]--
253 if call.method == "sendRichMessage" {
254 return http.StatusTooManyRequests, "Too Many Requests: retry after 1"
257 return http.StatusBadGateway, "Bad Gateway"
260 bot, logs := telegramStartTestBot(t, telegram)
261 daemonStartTestWithBot(t, server, bot)
263 telegram.Add(TelegramUpdate{
265 Message: telegramOwnerMessage("hello"),
268 telegramWaitForSent(t, telegram, "ok")
269 require.Contains(t, logs.String(), "502")
270 require.Contains(t, logs.String(), "429")
271 require.NotContains(t, logs.String(), telegramTestToken)
274 func TestDeliverTriesAgainAfterATimeout(t *testing.T) {
275 telegram := newTelegramFake(t)
277 telegram.stall = func(call TelegramFakeCall) bool {
278 if call.method != "sendMessage" || stalls == 0 {
287 bot, logs := telegramStartTestBot(t, telegram)
288 bot.api.sendTimeout = 50 * time.Millisecond
289 bot.deliver(t.Context(), TelegramPart{
293 require.Equal(t, []string{"hello"}, telegram.Sent())
294 require.Len(t, telegram.Calls(), 2)
295 require.Contains(t, logs.String(), "send failed: no answer in 50ms (retry in 1ms)\n")
296 require.Contains(t, logs.String(), "send back after ")
297 require.Equal(t, 2, strings.Count(logs.String(), "\n"))
298 require.NotContains(t, logs.String(), telegramTestToken)
301 func TestRepliesGoAsRichMessages(t *testing.T) {
302 reply := "**Done.** The table:\n\n| Name | Size |\n|:-----|-----:|\n| `a.go` | 10 |\n\n- one\n- two\n\n```go\nx := 1\n```"
303 server := newModelFake(t)
304 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
305 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem(reply)}, 100, 0, 5))
308 telegram := newTelegramFake(t)
309 bot, _ := telegramStartTestBot(t, telegram)
310 socket := daemonStartTestWithBot(t, server, bot)
311 terminal := terminalStartTestClient(t, socket)
312 terminal.WaitFor(t, "[connected to OneChat]")
314 terminal.Type("make **it** so")
315 telegramWaitForSent(t, telegram, "[terminal] make **it** so", reply)
317 telegram.Add(TelegramUpdate{
319 Message: telegramOwnerMessage(""),
322 telegramWaitForSent(t, telegram, telegramTextOnly)
324 var sends []TelegramFakeCall
325 for _, call := range telegram.Calls() {
326 if call.method == "sendMessage" || call.method == "sendRichMessage" {
327 sends = append(sends, call)
331 require.Equal(t, []TelegramFakeCall{
333 method: "sendMessage",
334 params: map[string]any{
335 "chat_id": float64(telegramTestOwner),
336 "text": "[terminal] make **it** so",
341 method: "sendRichMessage",
342 params: map[string]any{
343 "chat_id": float64(telegramTestOwner),
344 "rich_message": map[string]any{
351 method: "sendMessage",
352 params: map[string]any{
353 "chat_id": float64(telegramTestOwner),
354 "text": telegramTextOnly,
361 func TestRichMessageFailures(t *testing.T) {
362 testCases := []struct {
372 name: "bad request sends the reply once with entities",
373 status: http.StatusBadRequest,
375 methods: []string{"sendRichMessage", "sendMessage"},
376 sent: []string{"a secret word"},
378 logText: "reply sent with simple formatting",
381 name: "unknown method sends the reply once with entities",
382 status: http.StatusNotFound,
384 methods: []string{"sendRichMessage", "sendMessage"},
385 sent: []string{"a secret word"},
387 logText: "reply sent with simple formatting",
390 name: "rate limit tries the rich message again",
391 status: http.StatusTooManyRequests,
392 methods: []string{"sendRichMessage", "sendRichMessage"},
393 sent: []string{"a **secret** word"},
395 logText: "send failed: 429",
398 name: "server error tries the rich message again",
399 status: http.StatusInternalServerError,
400 methods: []string{"sendRichMessage", "sendRichMessage"},
401 sent: []string{"a **secret** word"},
403 logText: "send failed: 500",
406 name: "other refusal drops the reply",
407 status: http.StatusForbidden,
409 methods: []string{"sendRichMessage"},
411 logText: "reply lost: 403",
415 for _, tc := range testCases {
416 t.Run(tc.name, func(t *testing.T) {
417 telegram := newTelegramFake(t)
419 telegram.fail = func(call TelegramFakeCall) (int, string) {
420 if call.method != "sendRichMessage" || failed && !tc.always {
426 return tc.status, "refused for bot" + telegramTestToken
429 bot, logs := telegramStartTestBot(t, telegram)
430 bot.post(t.Context(), Event{
432 Text: "a **secret** word",
436 for _, call := range telegram.Calls() {
437 methods = append(methods, call.method)
440 require.Equal(t, tc.methods, methods)
441 require.Equal(t, tc.sent, telegram.Sent())
442 require.Equal(t, tc.lines, strings.Count(logs.String(), "\n"))
443 require.Contains(t, logs.String(), tc.logText)
444 require.NotContains(t, logs.String(), telegramTestToken)
445 require.NotContains(t, logs.String(), "secret word")
447 calls := telegram.Calls()
448 if calls[len(calls)-1].method == "sendMessage" {
449 require.NotNil(t, calls[len(calls)-1].params["entities"])
455 func TestLongReplyGoesAsRichMessagesSplitBetweenBlocks(t *testing.T) {
456 paragraphs := strings.Repeat("A paragraph of the reply.\n\n", telegramRichBytes/len("A paragraph of the reply.\n\n"))
457 table := "| a | b |\n|---|---|\n| 1 | 2 |\n"
458 reply := paragraphs + table
460 telegram := newTelegramFake(t)
461 bot, _ := telegramStartTestBot(t, telegram)
462 bot.post(t.Context(), Event{
467 require.Equal(t, []string{paragraphs, table}, telegram.Sent())
470 func TestReplyTagsGoAsText(t *testing.T) {
471 testCases := []struct {
477 name: "rich message escapes the tag outside code",
478 sent: []string{"a <details> & tag and `<b>`"},
481 name: "entity fallback shows the tag as written",
482 status: http.StatusBadRequest,
483 sent: []string{"a <details> & tag and <b>"},
487 for _, tc := range testCases {
488 t.Run(tc.name, func(t *testing.T) {
489 telegram := newTelegramFake(t)
490 telegram.fail = func(call TelegramFakeCall) (int, string) {
491 if call.method != "sendRichMessage" {
495 return tc.status, "Bad Request"
498 bot, _ := telegramStartTestBot(t, telegram)
499 bot.post(t.Context(), Event{
501 Text: "a <details> & tag and `<b>`",
504 require.Equal(t, tc.sent, telegram.Sent())
509 func TestLongReplySplitCountsTheEscapedTags(t *testing.T) {
510 paragraph := "A <b> tag.\n\n"
511 count := telegramRichBytes / len(paragraph)
512 reply := strings.Repeat(paragraph, count)
513 require.LessOrEqual(t, len(reply), telegramRichBytes)
515 telegram := newTelegramFake(t)
516 bot, _ := telegramStartTestBot(t, telegram)
517 bot.post(t.Context(), Event{
522 sent := telegram.Sent()
523 require.Len(t, sent, 2)
524 require.Equal(t, telegramEscapeTags(reply), strings.Join(sent, ""))
526 for _, markdown := range sent {
527 require.LessOrEqual(t, len(markdown), telegramRichBytes)
531 func TestRefusedRichPartGoesWithTheTextAsWritten(t *testing.T) {
532 paragraph := "A <b> & c.\n\n"
533 reply := strings.Repeat(paragraph, telegramRichBytes/len(paragraph))
535 telegram := newTelegramFake(t)
537 telegram.fail = func(call TelegramFakeCall) (int, string) {
538 if call.method != "sendRichMessage" || refused {
544 return http.StatusBadRequest, "Bad Request"
547 bot, _ := telegramStartTestBot(t, telegram)
548 bot.post(t.Context(), Event{
553 var fallback, rich []string
554 for _, call := range telegram.Calls() {
557 case call.method == "sendMessage":
558 fallback = append(fallback, call.params["text"].(string))
559 case call.method == "sendRichMessage":
560 rich = append(rich, call.params["rich_message"].(map[string]any)["markdown"].(string))
564 require.Greater(t, len(fallback), 1)
565 require.Len(t, rich, 1)
566 require.True(t, strings.HasPrefix(reply, strings.Join(fallback, "")), "fallback text is not the reply as written")
568 for _, text := range fallback {
569 require.LessOrEqual(t, telegramUTF16Units(text), telegramMessageUnits)
572 require.True(t, reply == strings.Join(fallback, "")+telegramUnescapeTags(rich[0]), "reply is not sent once and whole")
575 func TestRefusedEntitiesGoAgainAsPlainText(t *testing.T) {
576 telegram := newTelegramFake(t)
577 telegram.fail = func(call TelegramFakeCall) (int, string) {
578 if call.params["entities"] == nil {
582 return http.StatusBadRequest, "Bad Request: can't parse entities of bot" + telegramTestToken
585 bot, logs := telegramStartTestBot(t, telegram)
586 text, entities := telegramMarkdownEntities("a **secret** word")
587 bot.deliver(t.Context(), TelegramPart{
592 calls := telegram.Calls()
593 require.Len(t, calls, 2)
594 require.NotNil(t, calls[0].params["entities"])
595 require.Nil(t, calls[1].params["entities"])
596 require.Equal(t, []string{"a secret word"}, telegram.Sent())
597 require.Equal(t, 1, strings.Count(logs.String(), "\n"))
598 require.Contains(t, logs.String(), "reply sent as plain text")
599 require.NotContains(t, logs.String(), telegramTestToken)
600 require.NotContains(t, logs.String(), "secret word")
603 func TestBotLogsNoTokenWhenTheNetworkFails(t *testing.T) {
604 telegram := newTelegramFake(t)
607 bot, logs := telegramStartTestBot(t, telegram)
608 daemonStartTestWithBot(t, newModelFake(t), bot)
610 require.Eventually(t, func() bool {
611 return strings.Contains(logs.String(), "receive failed: no answer: ")
612 }, 10*time.Second, time.Millisecond)
613 require.NotContains(t, logs.String(), telegramTestToken)
616 func TestBotLogsAnOutageOnlyAtItsStartAndEnd(t *testing.T) {
617 server := newModelFake(t)
618 server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
619 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("ok")}, 100, 0, 5))
622 telegram := newTelegramFake(t)
624 telegram.fail = func(call TelegramFakeCall) (int, string) {
625 if call.method != "getUpdates" || failures == 0 {
631 return http.StatusBadGateway, "Bad Gateway"
634 bot, logs := telegramStartTestBot(t, telegram)
635 daemonStartTestWithBot(t, server, bot)
637 telegram.Add(TelegramUpdate{
639 Message: telegramOwnerMessage("a secret word"),
642 telegramWaitForSent(t, telegram, "ok")
643 lines := strings.Split(strings.TrimSuffix(logs.String(), "\n"), "\n")
644 require.Len(t, lines, 2)
645 require.Equal(t, "receive failed: 502 Bad Gateway (retry in 1ms)", lines[0])
646 require.Regexp(t, `^receive back after [0-9]+m?s$`, lines[1])
647 require.NotContains(t, logs.String(), "secret word")
648 require.NotContains(t, logs.String(), strconv.FormatInt(telegramTestOwner, 10))
651 func TestSplitMessage(t *testing.T) {
652 testCases := []struct {
659 name: "short text stays whole",
662 parts: []string{"one two"},
665 name: "cut after the last line break",
666 text: "first line\nsecond line",
668 parts: []string{"first line\n", "second line"},
671 name: "cut after the last space without a line break",
672 text: "aaaa bbbb cccc",
674 parts: []string{"aaaa bbbb ", "cccc"},
677 name: "hard cut of one long word",
680 parts: []string{"abcd", "efgh", "ij"},
683 name: "a character outside the BMP counts two units and is not cut",
684 text: "ab\U0001F600cd",
686 parts: []string{"ab", "\U0001F600c", "d"},
690 for _, tc := range testCases {
691 t.Run(tc.name, func(t *testing.T) {
692 require.Equal(t, tc.parts, telegramSplitMessage(tc.text, tc.limit))
696 long := strings.Repeat("word ", 2000)
697 parts := telegramSplitMessage(long, telegramMessageUnits)
698 require.Len(t, parts, 3)
699 require.Equal(t, long, strings.Join(parts, ""))
702 func TestSplitEntitiesCutsAnEntityAcrossASplit(t *testing.T) {
703 parts := telegramSplitEntities("aabbbbcc", []TelegramEntity{
716 require.Equal(t, []TelegramPart{
719 entities: []TelegramEntity{
729 entities: []TelegramEntity{
745 func TestSplitRich(t *testing.T) {
746 testCases := []struct {
753 name: "short reply stays whole",
754 markdown: "| a | b |",
756 parts: []string{"| a | b |"},
759 name: "cut after a blank line keeps a table whole",
760 markdown: "Intro.\n\n| a | b |\n|---|---|\n| 1 | 2 |\n",
762 parts: []string{"Intro.\n\n", "| a | b |\n|---|---|\n| 1 | 2 |\n"},
765 name: "blank line in a code block is not a cut",
766 markdown: "Text.\n\n```go\nx\n\ny\n```\n\nEnd.\n",
768 parts: []string{"Text.\n\n", "```go\nx\n\ny\n```\n\n", "End.\n"},
771 name: "paragraph with no blank line is cut after a line",
772 markdown: "aaa\nbbb\nccc\n",
774 parts: []string{"aaa\nbbb\n", "ccc\n"},
777 name: "long code block is closed and opened again",
778 markdown: "```sh\none\ntwo\nthree\n```\n",
780 parts: []string{"```sh\none\n```\n", "```sh\ntwo\n```\n", "```sh\nthree\n```\n"},
783 name: "long line is cut at the limit and not inside a character",
784 markdown: "ab\u00e9cd",
786 parts: []string{"ab", "\u00e9c", "d"},
789 name: "long line is not cut inside an entity",
790 markdown: "abc&def",
792 parts: []string{"abc", "&d", "ef"},
796 for _, tc := range testCases {
797 t.Run(tc.name, func(t *testing.T) {
798 require.Equal(t, tc.parts, telegramSplitRich(tc.markdown, tc.limit))
802 long := strings.Repeat("A paragraph of the reply.\n\n", 2000)
803 parts := telegramSplitRich(long, telegramRichBytes)
804 require.Len(t, parts, 2)
805 require.Len(t, parts[0], telegramRichBytes-telegramRichBytes%len("A paragraph of the reply.\n\n"))
806 require.Equal(t, long, strings.Join(parts, ""))
809 func TestLoadBotConfig(t *testing.T) {
810 testCases := []struct {
817 name: "owner file is read",
818 content: `{"token": "` + telegramTestToken + `", "owner": 4242}`,
822 name: "file that others can read is refused",
823 content: `{"token": "` + telegramTestToken + `", "owner": 4242}`,
825 errorText: "mode must be 0600",
828 name: "token with a path character is refused",
829 content: `{"token": "123:abc/../x", "owner": 4242}`,
831 errorText: "form of a bot token",
834 name: "missing owner is refused",
835 content: `{"token": "` + telegramTestToken + `"}`,
837 errorText: "numeric Telegram user ID",
840 name: "unknown field is refused",
841 content: `{"token": "` + telegramTestToken + `", "owner": 4242, "chat": 1}`,
843 errorText: "not valid JSON",
847 for _, tc := range testCases {
848 t.Run(tc.name, func(t *testing.T) {
850 path := filepath.Join(dir, telegramConfigFileName)
851 require.NoError(t, os.WriteFile(path, []byte(tc.content), tc.mode))
852 require.NoError(t, os.Chmod(path, tc.mode))
854 config, found, err := loadTelegramConfig(dir)
855 if tc.errorText != "" {
856 require.ErrorContains(t, err, tc.errorText)
857 require.NotContains(t, err.Error(), telegramTestToken)
862 require.NoError(t, err)
863 require.True(t, found)
864 require.Equal(t, TelegramConfig{
865 Token: telegramTestToken,
866 Owner: telegramTestOwner,
871 _, found, err := loadTelegramConfig(t.TempDir())
872 require.NoError(t, err)
873 require.False(t, found)
876 // telegramStartTestBot returns a bot of telegramTestOwner on server, with its
877 // offset file in a temporary directory and short waits, and the buffer of its
879 func telegramStartTestBot(t *testing.T, server *TelegramFake) (*TelegramBot, *SyncBuffer) {
882 logs := &SyncBuffer{}
883 bot := newTelegramBot(newTelegramAPI(server.URL, telegramTestToken), telegramTestOwner, filepath.Join(t.TempDir(), telegramOffsetFileName), log.New(logs, "", 0))
884 bot.minWait = time.Millisecond
885 bot.maxWait = 10 * time.Millisecond
890 // telegramWaitForSent waits until the bot sent the texts in order, and returns
892 func telegramWaitForSent(t *testing.T, server *TelegramFake, texts ...string) []string {
895 require.Eventually(t, func() bool {
896 sent := server.Sent()
897 return len(sent) >= len(texts) && slices.Equal(sent[len(sent)-len(texts):], texts)
898 }, 10*time.Second, time.Millisecond, "sent so far: %q", server.Sent())