(fix): finalize v2 contracts (tests): cover v2 migration (doc): prepare release guidance
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user