FILE / ScuroNeko/Laniakea

tgapi/rich_103_test.go

Исходный файл и его история в репозитории.
FILE d78526242bc7aa184ce5e030b95d8afe2d1243c4
Files
Laniakea/tgapi/rich_103_test.go
T
ScuroNeko d78526242b
Golang lint / lint (push) Failing after 1m37s
(new): support Bot API 10.3
(fix): finalize v2 contracts
(tests): cover v2 migration
(doc): prepare release guidance
2026-09-08 23:21:38 +03:00

157 lines
5.1 KiB
Go

package tgapi
import (
"encoding/json"
"io"
"mime"
"mime/multipart"
"reflect"
"strings"
"testing"
)
func TestRich103RoundTrip(t *testing.T) {
for _, tc := range []struct {
name, data string
want any
}{
{"inline button", `{"blocks":[{"type":"paragraph","text":{"type":"button","button":{"text":["Go",{"type":"custom_emoji","custom_emoji_id":"123","alternative_text":"!"}],"switch_inline_query":""}}}]}`, RichBlockWrap{}},
{"button row", `{"blocks":[{"type":"buttons","align":"right","buttons":[{"text":"Go","switch_inline_query_current_chat":""},{"text":"Choose","switch_inline_query_chosen_chat":{"query":"x","allow_user_chats":true}}]}]}`, RichBlockButtons{}},
{"expandable quote", `{"blocks":[{"type":"expandable_blockquote","text":{"type":"bold","text":"quote"},"credit":"source"}]}`, RichBlockExpandableBlockQuotation{}},
{"document", `{"blocks":[{"type":"document","document":{"file_id":"file","file_unique_id":"unique","file_name":"notes.txt"},"caption":{"text":"Notes","credit":"Author"}}]}`, RichBlockDocument{}},
{"compact table", `{"blocks":[{"type":"table","cells":[],"is_compact":true}]}`, RichBlockTable{}},
} {
t.Run(tc.name, func(t *testing.T) {
msg, err := UnmarshalRichMessage([]byte(tc.data))
if err != nil {
t.Fatal(err)
}
if reflect.TypeOf(msg.Blocks[0]) != reflect.TypeOf(tc.want) {
t.Fatalf("got %T", msg.Blocks[0])
}
data, err := json.Marshal(msg)
if err != nil {
t.Fatal(err)
}
// Compare the second decode to avoid unrelated omitted zero-value media fields.
again, err := UnmarshalRichMessage(data)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(msg, again) {
t.Fatalf("round trip lost data: %s", data)
}
if strings.Contains(tc.data, `"switch_inline_query":""`) && !strings.Contains(string(data), `"switch_inline_query":""`) {
t.Fatalf("lost empty query: %s", data)
}
})
}
}
func TestRichButtonActionsRoundTrip(t *testing.T) {
for _, action := range []string{
`"url":"https://example.com"`, `"callback_data":"callback","style":"link"`,
`"web_app":{"url":"https://example.com"}`, `"login_url":{"url":"https://example.com","forward_text":"Forward","request_write_access":true}`,
`"switch_inline_query":""`, `"switch_inline_query_current_chat":""`,
`"switch_inline_query_chosen_chat":{"allow_user_chats":true}`, `"copy_text":{"text":"Copy"}`, `"disabled":{}`,
} {
raw := `{"text":"Button",` + action + `}`
var button RichMessageButton
if err := json.Unmarshal([]byte(raw), &button); err != nil {
t.Fatal(err)
}
encoded, err := json.Marshal(button)
if err != nil {
t.Fatal(err)
}
var expected, actual any
if err := json.Unmarshal([]byte(raw), &expected); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(encoded, &actual); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("wire fields changed: %s -> %s", raw, encoded)
}
var again RichMessageButton
if err := json.Unmarshal(encoded, &again); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(button, again) {
t.Fatalf("action lost: %s", encoded)
}
if button.Text != RichTextPlain("Button") {
t.Fatalf("label lost: %#v", button)
}
}
}
func TestRich103Malformed(t *testing.T) {
for _, raw := range []string{
`{"type":"button","button":{"text":null,"disabled":{}}}`,
} {
if _, err := UnmarshalRichText([]byte(raw)); err == nil {
t.Fatalf("accepted %s", raw)
}
}
for _, raw := range []string{
`{"type":"buttons","buttons":[{"text":null,"disabled":{}}]}`,
`{"type":"document","document":42}`,
} {
if _, err := UnmarshalRichBlock([]byte(raw)); err == nil {
t.Fatalf("accepted %s", raw)
}
}
raw := `"x"`
for range 70 {
raw = `{"type":"button","button":{"text":` + raw + `,"disabled":{}}}`
}
if _, err := UnmarshalRichText([]byte(raw)); err == nil {
t.Fatal("accepted excessive nesting")
}
}
func TestRichDocumentMultipart(t *testing.T) {
params := SendRichMessage{ChatID: 42, RichMessage: InputRichMessage{
HTML: `<tg-document src="tg://document?id=notes"></tg-document>`,
Media: []InputRichMessageMedia{{ID: "notes", Media: InputMedia{Type: InputMediaTypeDocument, Media: "attach://notes"}}},
}}
body, contentType := prepareMultipartStream([]UploaderFile{NewUploaderFile("notes.txt", []byte("hello")).SetAttachName("notes")}, params)
defer func() { _ = body.Close() }()
_, attrs, err := mime.ParseMediaType(contentType)
if err != nil {
t.Fatal(err)
}
reader := multipart.NewReader(body, attrs["boundary"])
gotFile, gotMessage := false, false
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
data, err := io.ReadAll(part)
if err != nil {
t.Fatal(err)
}
switch part.FormName() {
case "notes":
gotFile = true
if part.FileName() != "notes.txt" || string(data) != "hello" {
t.Fatalf("bad file part: %s", data)
}
case "rich_message":
gotMessage = true
if !strings.Contains(string(data), `"media":"attach://notes"`) || !strings.Contains(string(data), `"type":"document"`) {
t.Fatalf("bad rich payload: %s", data)
}
}
}
if !gotFile || !gotMessage {
t.Fatal("missing multipart parts")
}
}