X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/97729fc59a2fbd73e3f8a424cae4c825f5ba885b..84fc815feb485b35eec53a8c7863c6770fc65e02:/telegram_test.go diff --git a/telegram_test.go b/telegram_test.go index 30b2fa1..df35e0d 100644 --- a/telegram_test.go +++ b/telegram_test.go @@ -171,8 +171,8 @@ func TestBotTriesAgainAfterFailures(t *testing.T) { telegram := newFakeBot(t) failures := map[string]int{ - "getUpdates": 2, - "sendMessage": 1, + "getUpdates": 2, + "sendRichMessage": 1, } telegram.fail = func(call fakeBotCall) (int, string) { @@ -181,7 +181,7 @@ func TestBotTriesAgainAfterFailures(t *testing.T) { } failures[call.method]-- - if call.method == "sendMessage" { + if call.method == "sendRichMessage" { return http.StatusTooManyRequests, "Too Many Requests: retry after 1" } @@ -227,10 +227,11 @@ func TestDeliverTriesAgainAfterATimeout(t *testing.T) { require.NotContains(t, logs.String(), testBotToken) } -func TestOnlyRepliesGetEntities(t *testing.T) { +func TestRepliesGoAsRichMessages(t *testing.T) { + reply := "**Done.** The table:\n\n| Name | Size |\n|:-----|-----:|\n| `a.go` | 10 |\n\n- one\n- two\n\n```go\nx := 1\n```" server := newFakeResponses(t) server.reply = func(w http.ResponseWriter, call fakeCall) { - writeEvents(w, completedEvent([]any{messageItem("**Done** with `x`.")}, 100, 0, 5)) + writeEvents(w, completedEvent([]any{messageItem(reply)}, 100, 0, 5)) } telegram := newFakeBot(t) @@ -240,7 +241,7 @@ func TestOnlyRepliesGetEntities(t *testing.T) { terminal.WaitFor(t, "[connected to OneChat]") terminal.Type("make **it** so") - waitForSent(t, telegram, "[terminal] make **it** so", "Done with x.") + waitForSent(t, telegram, "[terminal] make **it** so", reply) telegram.Add(botUpdate{ UpdateID: 1, @@ -249,29 +250,144 @@ func TestOnlyRepliesGetEntities(t *testing.T) { waitForSent(t, telegram, textOnly) - entities := map[string]any{} + var sends []fakeBotCall for _, call := range telegram.Calls() { - if call.method == "sendMessage" { - entities[call.params["text"].(string)] = call.params["entities"] + if call.method == "sendMessage" || call.method == "sendRichMessage" { + sends = append(sends, call) } } - 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), + require.Equal(t, []fakeBotCall{ + { + method: "sendMessage", + params: map[string]any{ + "chat_id": float64(testOwner), + "text": "[terminal] make **it** so", }, - map[string]any{ - "type": "code", - "offset": float64(10), - "length": float64(1), + ok: true, + }, + { + method: "sendRichMessage", + params: map[string]any{ + "chat_id": float64(testOwner), + "rich_message": map[string]any{ + "markdown": reply, + }, }, + ok: true, + }, + { + method: "sendMessage", + params: map[string]any{ + "chat_id": float64(testOwner), + "text": textOnly, + }, + ok: true, + }, + }, sends) +} + +func TestRichMessageFailures(t *testing.T) { + testCases := []struct { + name string + status int + always bool + methods []string + sent []string + logText string + }{ + { + name: "bad request sends the reply once with entities", + status: http.StatusBadRequest, + always: true, + methods: []string{"sendRichMessage", "sendMessage"}, + sent: []string{"a secret word"}, + logText: "rich message refused", + }, + { + name: "unknown method sends the reply once with entities", + status: http.StatusNotFound, + always: true, + methods: []string{"sendRichMessage", "sendMessage"}, + sent: []string{"a secret word"}, + logText: "rich message refused", + }, + { + name: "rate limit tries the rich message again", + status: http.StatusTooManyRequests, + methods: []string{"sendRichMessage", "sendRichMessage"}, + sent: []string{"a **secret** word"}, + logText: "answered 429", }, - textOnly: nil, - }, entities) + { + name: "server error tries the rich message again", + status: http.StatusInternalServerError, + methods: []string{"sendRichMessage", "sendRichMessage"}, + sent: []string{"a **secret** word"}, + logText: "answered 500", + }, + { + name: "other refusal drops the reply", + status: http.StatusForbidden, + always: true, + methods: []string{"sendRichMessage"}, + logText: "message dropped", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + telegram := newFakeBot(t) + failed := false + telegram.fail = func(call fakeBotCall) (int, string) { + if call.method != "sendRichMessage" || failed && !tc.always { + return 0, "" + } + + failed = true + + return tc.status, "refused for bot" + testBotToken + } + + bot, logs := startTestBot(t, telegram) + bot.post(t.Context(), Event{ + Kind: "reply", + Text: "a **secret** word", + }) + + var methods []string + for _, call := range telegram.Calls() { + methods = append(methods, call.method) + } + + require.Equal(t, tc.methods, methods) + require.Equal(t, tc.sent, telegram.Sent()) + require.Equal(t, 1, strings.Count(logs.String(), "\n")) + require.Contains(t, logs.String(), tc.logText) + require.NotContains(t, logs.String(), testBotToken) + require.NotContains(t, logs.String(), "secret word") + + calls := telegram.Calls() + if calls[len(calls)-1].method == "sendMessage" { + require.NotNil(t, calls[len(calls)-1].params["entities"]) + } + }) + } +} + +func TestLongReplyGoesAsRichMessagesSplitBetweenBlocks(t *testing.T) { + paragraphs := strings.Repeat("A paragraph of the reply.\n\n", richMessageBytes/len("A paragraph of the reply.\n\n")) + table := "| a | b |\n|---|---|\n| 1 | 2 |\n" + reply := paragraphs + table + + telegram := newFakeBot(t) + bot, _ := startTestBot(t, telegram) + bot.post(t.Context(), Event{ + Kind: "reply", + Text: reply, + }) + + require.Equal(t, []string{paragraphs, table}, telegram.Sent()) } func TestRefusedEntitiesGoAgainAsPlainText(t *testing.T) {