]> git.giacomolli.uk - onechat/blobdiff - telegram_test.go
Split long replies between Markdown blocks
[onechat] / telegram_test.go
index d18e7948e2b27f430b4fec071a6fe7a1391aa6ae..30b2fa1bb686f730963c2426bdb88e3761949d5b 100644 (file)
@@ -202,6 +202,106 @@ func TestBotTriesAgainAfterFailures(t *testing.T) {
        require.NotContains(t, logs.String(), testBotToken)
 }
 
        require.NotContains(t, logs.String(), testBotToken)
 }
 
+func TestDeliverTriesAgainAfterATimeout(t *testing.T) {
+       telegram := newFakeBot(t)
+       stalls := 1
+       telegram.stall = func(call fakeBotCall) bool {
+               if call.method != "sendMessage" || stalls == 0 {
+                       return false
+               }
+
+               stalls--
+
+               return true
+       }
+
+       bot, logs := startTestBot(t, telegram)
+       bot.api.sendTimeout = 50 * time.Millisecond
+       bot.deliver(t.Context(), botPart{
+               text: "hello",
+       })
+
+       require.Equal(t, []string{"hello"}, telegram.Sent())
+       require.Len(t, telegram.Calls(), 2)
+       require.Contains(t, logs.String(), "no valid answer")
+       require.NotContains(t, logs.String(), testBotToken)
+}
+
+func TestOnlyRepliesGetEntities(t *testing.T) {
+       server := newFakeResponses(t)
+       server.reply = func(w http.ResponseWriter, call fakeCall) {
+               writeEvents(w, completedEvent([]any{messageItem("**Done** with `x`.")}, 100, 0, 5))
+       }
+
+       telegram := newFakeBot(t)
+       bot, _ := startTestBot(t, telegram)
+       socket := startTestDaemonWithBot(t, server, bot)
+       terminal := startTestClient(t, socket)
+       terminal.WaitFor(t, "[connected to OneChat]")
+
+       terminal.Type("make **it** so")
+       waitForSent(t, telegram, "[terminal] make **it** so", "Done with x.")
+
+       telegram.Add(botUpdate{
+               UpdateID: 1,
+               Message:  ownerMessage(""),
+       })
+
+       waitForSent(t, telegram, textOnly)
+
+       entities := map[string]any{}
+       for _, call := range telegram.Calls() {
+               if call.method == "sendMessage" {
+                       entities[call.params["text"].(string)] = call.params["entities"]
+               }
+       }
+
+       require.Equal(t, map[string]any{
+               "[terminal] make **it** so": nil,
+               "Done with x.": []any{
+                       map[string]any{
+                               "type":   "bold",
+                               "offset": float64(0),
+                               "length": float64(4),
+                       },
+                       map[string]any{
+                               "type":   "code",
+                               "offset": float64(10),
+                               "length": float64(1),
+                       },
+               },
+               textOnly: nil,
+       }, entities)
+}
+
+func TestRefusedEntitiesGoAgainAsPlainText(t *testing.T) {
+       telegram := newFakeBot(t)
+       telegram.fail = func(call fakeBotCall) (int, string) {
+               if call.params["entities"] == nil {
+                       return 0, ""
+               }
+
+               return http.StatusBadRequest, "Bad Request: can't parse entities of bot" + testBotToken
+       }
+
+       bot, logs := startTestBot(t, telegram)
+       text, entities := markdownEntities("a **secret** word")
+       bot.deliver(t.Context(), botPart{
+               text:     text,
+               entities: entities,
+       })
+
+       calls := telegram.Calls()
+       require.Len(t, calls, 2)
+       require.NotNil(t, calls[0].params["entities"])
+       require.Nil(t, calls[1].params["entities"])
+       require.Equal(t, []string{"a secret word"}, telegram.Sent())
+       require.Equal(t, 1, strings.Count(logs.String(), "\n"))
+       require.Contains(t, logs.String(), "entities refused")
+       require.NotContains(t, logs.String(), testBotToken)
+       require.NotContains(t, logs.String(), "secret word")
+}
+
 func TestBotLogsNoTokenWhenTheNetworkFails(t *testing.T) {
        telegram := newFakeBot(t)
        telegram.Close()
 func TestBotLogsNoTokenWhenTheNetworkFails(t *testing.T) {
        telegram := newFakeBot(t)
        telegram.Close()
@@ -266,6 +366,107 @@ func TestSplitMessage(t *testing.T) {
        require.Equal(t, long, strings.Join(parts, ""))
 }
 
        require.Equal(t, long, strings.Join(parts, ""))
 }
 
+func TestSplitEntitiesCutsAnEntityAcrossASplit(t *testing.T) {
+       parts := splitEntities("aabbbbcc", []botEntity{
+               {
+                       Type:   "bold",
+                       Offset: 2,
+                       Length: 4,
+               },
+               {
+                       Type:   "italic",
+                       Offset: 6,
+                       Length: 2,
+               },
+       }, 5)
+
+       require.Equal(t, []botPart{
+               {
+                       text: "aabbb",
+                       entities: []botEntity{
+                               {
+                                       Type:   "bold",
+                                       Offset: 2,
+                                       Length: 3,
+                               },
+                       },
+               },
+               {
+                       text: "bcc",
+                       entities: []botEntity{
+                               {
+                                       Type:   "bold",
+                                       Offset: 0,
+                                       Length: 1,
+                               },
+                               {
+                                       Type:   "italic",
+                                       Offset: 1,
+                                       Length: 2,
+                               },
+                       },
+               },
+       }, parts)
+}
+
+func TestSplitRich(t *testing.T) {
+       testCases := []struct {
+               name     string
+               markdown string
+               limit    int
+               parts    []string
+       }{
+               {
+                       name:     "short reply stays whole",
+                       markdown: "| a | b |",
+                       limit:    100,
+                       parts:    []string{"| a | b |"},
+               },
+               {
+                       name:     "cut after a blank line keeps a table whole",
+                       markdown: "Intro.\n\n| a | b |\n|---|---|\n| 1 | 2 |\n",
+                       limit:    30,
+                       parts:    []string{"Intro.\n\n", "| a | b |\n|---|---|\n| 1 | 2 |\n"},
+               },
+               {
+                       name:     "blank line in a code block is not a cut",
+                       markdown: "Text.\n\n```go\nx\n\ny\n```\n\nEnd.\n",
+                       limit:    20,
+                       parts:    []string{"Text.\n\n", "```go\nx\n\ny\n```\n\n", "End.\n"},
+               },
+               {
+                       name:     "paragraph with no blank line is cut after a line",
+                       markdown: "aaa\nbbb\nccc\n",
+                       limit:    9,
+                       parts:    []string{"aaa\nbbb\n", "ccc\n"},
+               },
+               {
+                       name:     "long code block is closed and opened again",
+                       markdown: "```sh\none\ntwo\nthree\n```\n",
+                       limit:    16,
+                       parts:    []string{"```sh\none\n```\n", "```sh\ntwo\n```\n", "```sh\nthree\n```\n"},
+               },
+               {
+                       name:     "long line is cut at the limit and not inside a character",
+                       markdown: "ab\u00e9cd",
+                       limit:    3,
+                       parts:    []string{"ab", "\u00e9c", "d"},
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       require.Equal(t, tc.parts, splitRich(tc.markdown, tc.limit))
+               })
+       }
+
+       long := strings.Repeat("A paragraph of the reply.\n\n", 2000)
+       parts := splitRich(long, richMessageBytes)
+       require.Len(t, parts, 2)
+       require.Len(t, parts[0], richMessageBytes-richMessageBytes%len("A paragraph of the reply.\n\n"))
+       require.Equal(t, long, strings.Join(parts, ""))
+}
+
 func TestLoadBotConfig(t *testing.T) {
        testCases := []struct {
                name      string
 func TestLoadBotConfig(t *testing.T) {
        testCases := []struct {
                name      string