From c8edf2cb4dc675444b2cba285f9c642f069bc81c Mon Sep 17 00:00:00 2001 From: Vencislav Atanasov Date: Wed, 22 Nov 2023 23:52:16 +0200 Subject: [PATCH 03/10] Add tests for replying to UTF-8 messages, use runes to substring by character offset instead of byte offset (#418) Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Co-authored-by: Tim Zabel Signed-off-by: Alexander Miroshnichenko --- internal/handlers/telegram/handler.go | 4 +- internal/handlers/telegram/handler_test.go | 124 ++++++++++++++++----- internal/handlers/telegram/helpers.go | 6 +- internal/handlers/telegram/helpers_test.go | 63 +++++++++-- 4 files changed, 156 insertions(+), 41 deletions(-) diff --git a/internal/handlers/telegram/handler.go b/internal/handlers/telegram/handler.go index 12404273adf4..2600b1dc42ef 100644 --- a/internal/handlers/telegram/handler.go +++ b/internal/handlers/telegram/handler.go @@ -97,8 +97,8 @@ func replyHandler(tg *Client, u tgbotapi.Update) { replyUser := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.ReplyToMessage.From) // Only show a portion of the reply text - if len(replyText) > tg.Settings.ReplyLength { - replyText = replyText[0:tg.Settings.ReplyLength] + "…" + if replyTextAsRunes := []rune(replyText); len(replyTextAsRunes) > tg.Settings.ReplyLength { + replyText = string(replyTextAsRunes[:tg.Settings.ReplyLength]) + "…" } formatted := fmt.Sprintf("%s%s%s %sRe %s: %s%s %s", diff --git a/internal/handlers/telegram/handler_test.go b/internal/handlers/telegram/handler_test.go index a2413c70d616..e356c5b54433 100644 --- a/internal/handlers/telegram/handler_test.go +++ b/internal/handlers/telegram/handler_test.go @@ -762,39 +762,107 @@ func TestMessageReply(t *testing.T) { testChat := &tgbotapi.Chat{ ID: 100, } - initMessage := &tgbotapi.Message{ - From: testUser, - Text: "Initial Text", - Chat: testChat, - } - correct := " [Re test: Initial Text] Response Text" - updateObj := tgbotapi.Update{ - Message: &tgbotapi.Message{ - From: replyUser, - Text: "Response Text", - Chat: testChat, - ReplyToMessage: initMessage, + tests := []struct { + name string + updateFn func() tgbotapi.Update + expected string + }{ + { + name: "ascii", + updateFn: func() tgbotapi.Update { + return tgbotapi.Update{ + Message: &tgbotapi.Message{ + From: replyUser, + Text: "Response Text", + Chat: testChat, + ReplyToMessage: &tgbotapi.Message{ + From: testUser, + Text: "Initial Text", + Chat: testChat, + }, + }, + } + }, + expected: " [Re test: Initial Text] Response Text", }, - } - clientObj := &Client{ - Settings: &internal.TelegramSettings{ - Prefix: "<", - Suffix: ">", - ReplyPrefix: "[", - ReplySuffix: "]", - ReplyLength: 15, - ChatID: 100, + { + name: "cyrillic-short", + updateFn: func() tgbotapi.Update { + return tgbotapi.Update{ + Message: &tgbotapi.Message{ + From: replyUser, + Text: "Response Text", + Chat: testChat, + ReplyToMessage: &tgbotapi.Message{ + From: testUser, + Text: "Тест", + Chat: testChat, + }, + }, + } + }, + expected: " [Re test: Тест] Response Text", }, - IRCSettings: &internal.IRCSettings{ - ShowZWSP: false, + { + name: "cyrillic-long", + updateFn: func() tgbotapi.Update { + return tgbotapi.Update{ + Message: &tgbotapi.Message{ + From: replyUser, + Text: "Response Text", + Chat: testChat, + ReplyToMessage: &tgbotapi.Message{ + From: testUser, + Text: "Уикипедия е свободна енциклопедия", + Chat: testChat, + }, + }, + } + }, + expected: " [Re test: Уикипедия е сво…] Response Text", }, - sendToIrc: func(s string) { - assert.Equal(t, correct, s) + { + name: "japanese-long", + updateFn: func() tgbotapi.Update { + return tgbotapi.Update{ + Message: &tgbotapi.Message{ + From: replyUser, + Text: "Response Text", + Chat: testChat, + ReplyToMessage: &tgbotapi.Message{ + From: testUser, + Text: "1234567テストテストテスト", + Chat: testChat, + }, + }, + } + }, + expected: " [Re test: 1234567テストテストテス…] Response Text", }, } - messageHandler(clientObj, updateObj) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + clientObj := &Client{ + Settings: &internal.TelegramSettings{ + Prefix: "<", + Suffix: ">", + ReplyPrefix: "[", + ReplySuffix: "]", + ReplyLength: 15, + ChatID: 100, + }, + IRCSettings: &internal.IRCSettings{ + ShowZWSP: false, + }, + sendToIrc: func(actual string) { + assert.Equal(t, test.expected, actual) + }, + } + messageHandler(clientObj, test.updateFn()) + }) + } } func TestMessageReplyZwsp(t *testing.T) { @@ -891,8 +959,8 @@ func TestLocationHandlerWithLocationEnabled(t *testing.T) { LastName: "123", } - // https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api#Location - location := &tgbotapi.Location{ + // https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api#Location + location := &tgbotapi.Location{ Latitude: 43.0845274, Longitude: -77.6781174, } diff --git a/internal/handlers/telegram/helpers.go b/internal/handlers/telegram/helpers.go index f092979a9ffd..19b65ab722e3 100644 --- a/internal/handlers/telegram/helpers.go +++ b/internal/handlers/telegram/helpers.go @@ -37,7 +37,8 @@ Adds ZWSP to username to prevent username pinging across platform. func GetFullUserZwsp(u *tgbotapi.User) string { // Add ZWSP to prevent pinging across platforms // See https://github.com/42wim/matterbridge/issues/175 - return u.FirstName + " (@" + u.UserName[:1] + "\u200b" + u.UserName[1:] + ")" + userNameAsRunes := []rune(u.UserName) + return u.FirstName + " (@" + string(userNameAsRunes[:1]) + "\u200b" + string(userNameAsRunes[1:]) + ")" } /* @@ -47,7 +48,8 @@ username. func ZwspUsername(u *tgbotapi.User) string { // Add ZWSP to prevent pinging across platforms // See https://github.com/42wim/matterbridge/issues/175 - return u.UserName[:1] + "\u200b" + u.UserName[1:] + userNameAsRunes := []rune(u.UserName) + return string(userNameAsRunes[:1]) + "\u200b" + string(userNameAsRunes[1:]) } /* diff --git a/internal/handlers/telegram/helpers_test.go b/internal/handlers/telegram/helpers_test.go index 52eaa475c409..710f5a2c3ad7 100644 --- a/internal/handlers/telegram/helpers_test.go +++ b/internal/handlers/telegram/helpers_test.go @@ -16,12 +16,34 @@ func TestGetFullUsername(t *testing.T) { } func TestGetFullUserZwsp(t *testing.T) { - user := &tgbotapi.User{ID: 1, FirstName: "John", UserName: "jsmith"} - correct := user.FirstName + " (@" + user.UserName[:1] + - "\u200b" + user.UserName[1:] + ")" - name := GetFullUsername(true, user) + tests := []struct { + name string + user *tgbotapi.User + expected string + }{ + { + name: "ascii", + user: &tgbotapi.User{ID: 1, FirstName: "John", UserName: "jsmith"}, + expected: "John (@j\u200bsmith)", + }, + { + name: "cyrillic", + user: &tgbotapi.User{ID: 1, FirstName: "Иван", UserName: "иван"}, + expected: "Иван (@и\u200bван)", + }, + { + name: "japanese", + user: &tgbotapi.User{ID: 1, FirstName: "まこと", UserName: "まこと"}, + expected: "まこと (@ま\u200bこと)", + }, + } - assert.Equal(t, correct, name) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := GetFullUsername(true, test.user) + assert.Equal(t, test.expected, actual) + }) + } } func TestGetFullNoUsername(t *testing.T) { @@ -49,9 +71,32 @@ func TestGetUsername(t *testing.T) { } func TestZwspUsername(t *testing.T) { - user := &tgbotapi.User{ID: 1, FirstName: "John", UserName: "jsmith"} - correct := "j" + "\u200b" + "smith" - name := GetUsername(true, user) + tests := []struct { + name string + user *tgbotapi.User + expected string + }{ + { + name: "ascii", + user: &tgbotapi.User{ID: 1, FirstName: "John", UserName: "jsmith"}, + expected: "j\u200bsmith", + }, + { + name: "cyrillic", + user: &tgbotapi.User{ID: 1, FirstName: "Иван", UserName: "иван"}, + expected: "и\u200bван", + }, + { + name: "japanese", + user: &tgbotapi.User{ID: 1, FirstName: "まこと", UserName: "まこと"}, + expected: "ま\u200bこと", + }, + } - assert.Equal(t, correct, name) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := GetUsername(true, test.user) + assert.Equal(t, test.expected, actual) + }) + } } -- 2.41.0