FILE / ScuroNeko/Laniakea
tgapi/rich_103_test.go
Исходный файл и его история в репозитории.
Golang lint / lint (push) Failing after 1m37s
(fix): finalize v2 contracts (tests): cover v2 migration (doc): prepare release guidance
157 lines
5.1 KiB
Go
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")
|
|
}
|
|
}
|