package main import ( "fmt" stdhtml "html" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-telegram/bot/models" maxbot "github.com/max-messenger/max-bot-api-client-go/v2" "github.com/max-messenger/max-bot-api-client-go/v2/model" ) func TestMaxRequiresWritePermissionForPublication(t *testing.T) { for _, tc := range []struct { name, membership string allowed bool }{ {"reader", `{"is_admin":true,"permissions":["read_all_messages"]}`, false}, {"writer", `{"is_admin":true,"permissions":["write"]}`, true}, {"legacy writer", `{"is_admin":true,"permissions":["post_edit_delete_message"]}`, true}, {"owner", `{"is_owner":true}`, true}, } { t.Run(tc.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, tc.membership) })) t.Cleanup(server.Close) api, err := maxbot.NewApi("token", maxbot.WithBaseURL(server.URL)) if err != nil { t.Fatal(err) } m := &maxClient{api: api} if err := m.checkAdmin(t.Context(), maxChat{id: -42, kind: model.ChatTypeChannel}); (err == nil) != tc.allowed { t.Fatalf("allowed=%t: %v", tc.allowed, err) } }) } } func TestRealGIFUsesMaxImageUpload(t *testing.T) { md, ok := mediaOf(&models.Message{Animation: &models.Animation{FileID: "gif", FileName: "animation.gif", MimeType: "image/gif"}}) if !ok || md.upload != model.UploadImage || md.attach != model.AttachImage { t.Fatalf("GIF mapped to %+v", md) } } func TestExpensiveLinkKeepsAddressAndFitsEveryPart(t *testing.T) { address := "https://example.org/?" + strings.Repeat("&", 1000) text := newRichText("🎤 Билет", []models.MessageEntity{{Type: models.MessageEntityTypeTextLink, Length: 8, URL: address}}) parts := text.split(maxTextLimit) var visible strings.Builder for _, part := range parts { if htmlLen(part) > maxTextLimit || strings.ContainsRune(part, '�') { t.Fatalf("invalid part length=%d", htmlLen(part)) } visible.WriteString(stdhtml.UnescapeString(tagRe.ReplaceAllString(part, ""))) } if !strings.Contains(visible.String(), address) || !strings.HasPrefix(visible.String(), "🎤 Билет") { t.Fatal("label or address lost") } } func TestMaxHeadingsHighlightsAndMentionsKeepUTF16Offsets(t *testing.T) { text := "🙂 Заголовок имя акцент" body := maxMessageBody{Text: text, Markup: []maxMarkup{ {Type: "heading", From: 3, Length: 9}, {Type: "user_mention", From: 13, Length: 3, UserLink: "@some_user"}, {Type: "highlighted", From: 17, Length: 6}, }} got := body.richText() if len(got.entities) != 3 { t.Fatalf("entities %+v", got.entities) } if got.entities[0].Type != models.MessageEntityTypeBold || got.entities[0].Offset != 3 || got.entities[1].URL != "https://max.ru/some_user" || got.entities[2].Type != models.MessageEntityTypeUnderline { t.Fatalf("entities %+v", got.entities) } // An ID belongs to MAX, not Telegram. Keep the name without fabricating // a Telegram user mention or converting an unsafe user_link to a URL. body.Markup = []maxMarkup{{Type: "user_mention", From: 13, Length: 3, UserLink: "user/../bad", URL: "https://example.org/pretend"}} got = body.richText() plain, _ := got.telegramPart(0, 4096) if len(got.entities) != 0 || plain.text != text { t.Fatal("invalid mention mapping") } } func TestNestedMarkupCannotProduceOversizedPart(t *testing.T) { var entities []models.MessageEntity for range 600 { entities = append(entities, entity(models.MessageEntityTypeBold, 0, 2)) } text := newRichText("🎤", entities) parts := text.split(maxTextLimit) if len(parts) != 1 || parts[0] != "🎤" { t.Fatalf("parts %q", parts) } if len(text.entities) != 600 { t.Fatal("split mutated the original entities") } } func TestLongBareURLDoesNotCreateInvalidMaxAnchor(t *testing.T) { address := "https://example.org/" + strings.Repeat("x", 2500) text := newRichText(address, []models.MessageEntity{{Type: models.MessageEntityTypeURL, Length: len(address)}}) parts := text.split(maxTextLimit) if len(parts) != 1 || parts[0] != address { t.Fatalf("long bare URL changed or wrapped in an invalid anchor: %q", parts) } }