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(), "a Telegram message is 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(), "answered 502")
270 require.Contains(t, logs.String(), "answered 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(), "cannot send replies to Telegram: no answer in 50ms (next try in 1ms)\n")
296 require.Contains(t, logs.String(), "replies go to Telegram again, 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: "Telegram refused its full 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: "Telegram refused its full 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: "cannot send replies to Telegram: Telegram answered 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: "cannot send replies to Telegram: Telegram answered 500",
406 name: "other refusal drops the reply",
407 status: http.StatusForbidden,
409 methods: []string{"sendRichMessage"},
411 logText: "a reply to Telegram is lost: Telegram answered 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(), "a reply goes as plain text, because Telegram refused its formatting")
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(), "cannot get messages from Telegram: 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, "cannot get messages from Telegram: Telegram answered 502 Bad Gateway (next try in 1ms)", lines[0])
646 require.Regexp(t, `^messages from Telegram come again, 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())