]> git.giacomolli.uk - onechat/blobdiff - telegram_test.go
Send the compaction view in block messages
[onechat] / telegram_test.go
index 44c9c8fe16de2bb285b58c976947a0be583e8a43..66a5f4749d1d17866731412dc8969c8784891792 100644 (file)
@@ -127,6 +127,74 @@ func TestStopFromTelegramInterruptsTheTurn(t *testing.T) {
        })
 
        telegramWaitForSent(t, telegram, "Error: interrupted", "No turn runs.")
+
+       telegram.Add(TelegramUpdate{
+               UpdateID: 4,
+               Message:  telegramOwnerMessage(stopCommand + " BlueFox"),
+       })
+
+       telegramWaitForSent(t, telegram, "No turn runs.", "No subagent BlueFox runs.")
+}
+
+func TestBotShowsOnlyTheStartAndTheEndOfASubagent(t *testing.T) {
+       telegram := newTelegramFake(t)
+       server := newModelFake(t)
+       server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
+               last := subagentTestLastItem(call)
+               switch {
+               case subagentTestCall(call):
+                       // The report comes after the reply of the turn of the spawn.
+                       require.Eventually(t, func() bool {
+                               return slices.Contains(telegram.Sent(), "Started.")
+                       }, 10*time.Second, time.Millisecond)
+                       modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("There are 3 files.")}, 100, 0, 5))
+               case last["type"] == "function_call_output":
+                       modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Started.")}, 100, 0, 5))
+               case strings.HasPrefix(subagentTestText(last), "["):
+                       modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("It counted 3 files.")}, 100, 0, 5))
+               default:
+                       modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "spawn", `{"tasks":["Count the files."],"effort":null}`)}, 100, 0, 5))
+               }
+       }
+
+       bot, _ := telegramStartTestBot(t, telegram)
+       daemonStartTestWithBot(t, server, bot)
+       telegram.Add(TelegramUpdate{
+               UpdateID: 1,
+               Message:  telegramOwnerMessage("Count the files in a subagent."),
+       })
+
+       require.Eventually(t, func() bool {
+               return slices.Contains(telegram.Sent(), "It counted 3 files.")
+       }, 10*time.Second, time.Millisecond)
+
+       sent := telegram.Sent()
+       name := strings.TrimSuffix(strings.TrimPrefix(sent[0], "["), "] started")
+       require.True(t, subagentNameValid(name), sent[0])
+       require.Equal(t, []string{
+               "[" + name + "] started",
+               "Started.",
+               "[" + name + "] finished",
+               "It counted 3 files.",
+       }, sent)
+}
+
+func TestBotSkipsTheWorkOfWhatsAppAndTheSchedules(t *testing.T) {
+       bot := &TelegramBot{
+               ready: make(chan struct{}, 1),
+       }
+
+       bot.Notify(Event{
+               Kind:   "work",
+               Source: whatsappSource,
+               Text:   "[WhatsApp] Ana Souza",
+       })
+       bot.Notify(Event{
+               Kind:   "work",
+               Source: scheduleSource,
+               Text:   "[Schedule 1] Sunday 2026-11-08 20:05 -03:00, once",
+       })
+       require.Empty(t, bot.queue)
 }
 
 func TestBotStartsAtTheStoredOffset(t *testing.T) {
@@ -390,6 +458,111 @@ func TestLongReplyGoesAsRichMessagesSplitBetweenBlocks(t *testing.T) {
        require.Equal(t, []string{paragraphs, table}, telegram.Sent())
 }
 
+func TestReplyTagsGoAsText(t *testing.T) {
+       testCases := []struct {
+               name   string
+               status int
+               sent   []string
+       }{
+               {
+                       name: "rich message escapes the tag outside code",
+                       sent: []string{"a &lt;details> &amp; tag and `<b>`"},
+               },
+               {
+                       name:   "entity fallback shows the tag as written",
+                       status: http.StatusBadRequest,
+                       sent:   []string{"a <details> & tag and <b>"},
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       telegram := newTelegramFake(t)
+                       telegram.fail = func(call TelegramFakeCall) (int, string) {
+                               if call.method != "sendRichMessage" {
+                                       return 0, ""
+                               }
+
+                               return tc.status, "Bad Request"
+                       }
+
+                       bot, _ := telegramStartTestBot(t, telegram)
+                       bot.post(t.Context(), Event{
+                               Kind: "reply",
+                               Text: "a <details> & tag and `<b>`",
+                       })
+
+                       require.Equal(t, tc.sent, telegram.Sent())
+               })
+       }
+}
+
+func TestLongReplySplitCountsTheEscapedTags(t *testing.T) {
+       paragraph := "A <b> tag.\n\n"
+       count := telegramRichBytes / len(paragraph)
+       reply := strings.Repeat(paragraph, count)
+       require.LessOrEqual(t, len(reply), telegramRichBytes)
+
+       telegram := newTelegramFake(t)
+       bot, _ := telegramStartTestBot(t, telegram)
+       bot.post(t.Context(), Event{
+               Kind: "reply",
+               Text: reply,
+       })
+
+       sent := telegram.Sent()
+       require.Len(t, sent, 2)
+       require.Equal(t, telegramEscapeTags(reply), strings.Join(sent, ""))
+
+       for _, markdown := range sent {
+               require.LessOrEqual(t, len(markdown), telegramRichBytes)
+       }
+}
+
+func TestRefusedRichPartGoesWithTheTextAsWritten(t *testing.T) {
+       paragraph := "A <b> & c.\n\n"
+       reply := strings.Repeat(paragraph, telegramRichBytes/len(paragraph))
+
+       telegram := newTelegramFake(t)
+       refused := false
+       telegram.fail = func(call TelegramFakeCall) (int, string) {
+               if call.method != "sendRichMessage" || refused {
+                       return 0, ""
+               }
+
+               refused = true
+
+               return http.StatusBadRequest, "Bad Request"
+       }
+
+       bot, _ := telegramStartTestBot(t, telegram)
+       bot.post(t.Context(), Event{
+               Kind: "reply",
+               Text: reply,
+       })
+
+       var fallback, rich []string
+       for _, call := range telegram.Calls() {
+               switch {
+               case !call.ok:
+               case call.method == "sendMessage":
+                       fallback = append(fallback, call.params["text"].(string))
+               case call.method == "sendRichMessage":
+                       rich = append(rich, call.params["rich_message"].(map[string]any)["markdown"].(string))
+               }
+       }
+
+       require.Greater(t, len(fallback), 1)
+       require.Len(t, rich, 1)
+       require.True(t, strings.HasPrefix(reply, strings.Join(fallback, "")), "fallback text is not the reply as written")
+
+       for _, text := range fallback {
+               require.LessOrEqual(t, telegramUTF16Units(text), telegramMessageUnits)
+       }
+
+       require.True(t, reply == strings.Join(fallback, "")+telegramUnescapeTags(rich[0]), "reply is not sent once and whole")
+}
+
 func TestRefusedEntitiesGoAgainAsPlainText(t *testing.T) {
        telegram := newTelegramFake(t)
        telegram.fail = func(call TelegramFakeCall) (int, string) {
@@ -568,6 +741,12 @@ func TestSplitRich(t *testing.T) {
                        limit:    3,
                        parts:    []string{"ab", "\u00e9c", "d"},
                },
+               {
+                       name:     "long line is not cut inside an entity",
+                       markdown: "abc&amp;def",
+                       limit:    6,
+                       parts:    []string{"abc", "&amp;d", "ef"},
+               },
        }
 
        for _, tc := range testCases {