owpengram-server/internal/rpc/convert_markup_test.go
Astra 7fef8438c5 deps: bump github.com/iamxvbaba/td to v1.3.2
Matches upstream owpengram/owpengram-server's version. v1.3.2 collapses the
old per-kind KeyboardButtonClass sum type (KeyboardButton, KeyboardButtonURL,
KeyboardButtonCallback, KeyboardButtonRequestPeer, ...) into two unified
structs mirroring Telegram's actual current MTProto layer: KeyboardButton
(reply keyboards) and KeyboardInlineButton (inline keyboards), each carrying
a Text/Style pair plus a Type field (ButtonTypeClass / InlineButtonTypeClass)
that now holds what used to be the concrete Go type.

Migrated the two call sites (internal/rpc/convert_markup.go,
internal/rpc/bots_longtail.go) and their tests to the new shape. Behavior is
unchanged -- every button kind (callback, url, url_auth, web_view,
switch_inline, copy, request_phone, request_geo_location, request_poll,
request_peer, simple_web_view) still round-trips the same domain fields,
just read from/written to Type instead of the button's own concrete type.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 11:28:16 +01:00

196 lines
8.2 KiB
Go

package rpc
import (
"testing"
"github.com/iamxvbaba/td/tg"
"telesrv/internal/domain"
)
func TestReplyKeyboardTLDomainRoundTrip(t *testing.T) {
statusButton := tg.KeyboardButton{Text: "Status"}
style := tg.KeyboardButtonStyle{}
style.SetBgPrimary(true)
style.SetIcon(123456)
statusButton.SetStyle(style)
in := &tg.ReplyKeyboardMarkup{
Resize: true,
SingleUse: true,
Selective: true,
Persistent: true,
Placeholder: "Choose",
Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButton{
{Text: "Help"},
statusButton,
}}},
}
got, err := domainOutgoingReplyMarkupForSender(in, true)
if err != nil {
t.Fatalf("domainOutgoingReplyMarkupForSender: %v", err)
}
if got == nil || got.Kind() != domain.MessageReplyMarkupKeyboard || len(got.Keyboard) != 1 ||
len(got.Keyboard[0]) != 2 || got.Keyboard[0][0].Text != "Help" || !got.Resize ||
!got.SingleUse || !got.Selective || !got.Persistent || got.Placeholder != "Choose" {
t.Fatalf("domain markup = %#v", got)
}
if got.Keyboard[0][1].Style != domain.MarkupButtonStylePrimary || got.Keyboard[0][1].IconCustomEmojiID != 123456 {
t.Fatalf("second button decoration = %#v", got.Keyboard[0][1])
}
wire, ok := tgReplyMarkup(got).(*tg.ReplyKeyboardMarkup)
if !ok || len(wire.Rows) != 1 || len(wire.Rows[0].Buttons) != 2 {
t.Fatalf("wire markup = %#v", wire)
}
button := wire.Rows[0].Buttons[1]
if button.Text != "Status" {
t.Fatalf("second button = %#v", button)
} else if style, ok := button.GetStyle(); !ok || !style.GetBgPrimary() || style.Icon != 123456 {
t.Fatalf("second button style = %#v ok=%v", style, ok)
}
if !wire.Resize || !wire.SingleUse || !wire.Selective || !wire.Persistent || wire.Placeholder != "Choose" {
t.Fatalf("wire flags = %#v", wire)
}
}
func TestInlineButtonStyleTLDomainRoundTrip(t *testing.T) {
button := tg.KeyboardInlineButton{Text: "Delete", Type: &tg.InlineButtonTypeCallback{Data: []byte("delete")}}
style := tg.KeyboardButtonStyle{}
style.SetBgDanger(true)
button.SetStyle(style)
got, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{Buttons: []tg.KeyboardInlineButton{button}}}}, true)
if err != nil {
t.Fatalf("domainReplyMarkupForSender: %v", err)
}
if got.Inline[0][0].Style != domain.MarkupButtonStyleDanger {
t.Fatalf("domain style = %#v", got.Inline[0][0])
}
wire := tgReplyMarkup(got).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0]
if roundTrip, ok := wire.GetStyle(); !ok || !roundTrip.GetBgDanger() {
t.Fatalf("wire style = %#v ok=%v", roundTrip, ok)
}
if _, ok := wire.Type.(*tg.InlineButtonTypeCallback); !ok {
t.Fatalf("wire type = %#v", wire.Type)
}
}
func TestLoginURLButtonTLDomainProjection(t *testing.T) {
buttonType := &tg.InputInlineButtonTypeURLAuth{URL: "https://example.com/login", Bot: &tg.InputUser{UserID: 9001, AccessHash: 77}}
buttonType.SetRequestWriteAccess(true)
buttonType.SetFwdText("Open login")
button := tg.KeyboardInlineButton{Text: "Log in", Type: buttonType}
markup, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{Buttons: []tg.KeyboardInlineButton{button}}}}, true)
if err != nil {
t.Fatal(err)
}
got := markup.Inline[0][0]
if got.Type != domain.MarkupButtonLoginURL || got.LoginBotUserID != 9001 || !got.RequestWriteAccess || got.ForwardText != "Open login" || got.ButtonID != 0 {
t.Fatalf("domain login_url = %#v", got)
}
wire := tgReplyMarkup(markup).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0]
wireType, ok := wire.Type.(*tg.InlineButtonTypeURLAuth)
if !ok || wire.Text != "Log in" || wireType.URL != "https://example.com/login" || wireType.ButtonID != 0 || wireType.FwdText != "Open login" {
t.Fatalf("wire login_url = %#v", wire)
}
}
func TestReplyKeyboardHideAndForceReplyTLDomainRoundTrip(t *testing.T) {
hide, err := domainOutgoingReplyMarkupForSender(&tg.ReplyKeyboardHide{Selective: true}, true)
if err != nil {
t.Fatalf("hide parse: %v", err)
}
if wire, ok := tgReplyMarkup(hide).(*tg.ReplyKeyboardHide); !ok || !wire.Selective {
t.Fatalf("hide wire = %#v", wire)
}
force, err := domainOutgoingReplyMarkupForSender(&tg.ReplyKeyboardForceReply{
SingleUse: true, Selective: true, Placeholder: "Answer",
}, true)
if err != nil {
t.Fatalf("force parse: %v", err)
}
if wire, ok := tgReplyMarkup(force).(*tg.ReplyKeyboardForceReply); !ok || !wire.SingleUse || !wire.Selective || wire.Placeholder != "Answer" {
t.Fatalf("force wire = %#v", wire)
}
}
func TestReplyKeyboardRequestPhoneTLDomainRoundTrip(t *testing.T) {
markup, err := domainOutgoingReplyMarkupForSender(&tg.ReplyKeyboardMarkup{Rows: []tg.KeyboardButtonRow{{
Buttons: []tg.KeyboardButton{{Text: "Share phone", Type: &tg.ButtonTypeRequestPhone{}}},
}}}, true)
if err != nil || markup == nil || len(markup.Keyboard) != 1 || len(markup.Keyboard[0]) != 1 ||
markup.Keyboard[0][0].Type != domain.MarkupButtonRequestPhone {
t.Fatalf("request_phone markup = %#v err=%v", markup, err)
}
wire, ok := tgReplyMarkup(markup).(*tg.ReplyKeyboardMarkup)
if !ok || len(wire.Rows) != 1 || len(wire.Rows[0].Buttons) != 1 {
t.Fatalf("request_phone wire = %#v", wire)
}
if _, ok := wire.Rows[0].Buttons[0].Type.(*tg.ButtonTypeRequestPhone); !ok {
t.Fatalf("request_phone button = %#v", wire.Rows[0].Buttons[0])
}
if _, err := domainReplyMarkupForSender(&tg.ReplyKeyboardHide{}, true); err == nil {
t.Fatal("inline-only edit/result parser must reject reply-keyboard constructors")
}
}
func TestReplyKeyboardRequestPeerFiltersTLDomainRoundTrip(t *testing.T) {
userType := &tg.RequestPeerTypeUser{}
userType.SetBot(false)
userType.SetPremium(true)
chatType := &tg.RequestPeerTypeChat{Creator: true, BotParticipant: true}
chatType.SetHasUsername(false)
chatType.SetForum(true)
chatType.SetUserAdminRights(tg.ChatAdminRights{DeleteMessages: true, ManageTopics: true})
in := &tg.ReplyKeyboardMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButton{
{Text: "Premium person", Type: &tg.ButtonTypeRequestPeer{ButtonID: 1, PeerType: userType, MaxQuantity: 2}},
{Text: "Forum", Type: &tg.ButtonTypeRequestPeer{ButtonID: 2, PeerType: chatType, MaxQuantity: 1}},
}}}}
markup, err := domainOutgoingReplyMarkupForSender(in, true)
if err != nil {
t.Fatalf("parse request peer filters: %v", err)
}
userFilter := markup.Keyboard[0][0].RequestPeerFilter
chatFilter := markup.Keyboard[0][1].RequestPeerFilter
if userFilter == nil || !userFilter.UserIsBotSet || userFilter.UserIsBot || !userFilter.UserIsPremiumSet || !userFilter.UserIsPremium {
t.Fatalf("user filter = %#v", userFilter)
}
if chatFilter == nil || !chatFilter.ChatIsCreated || !chatFilter.BotIsMember || !chatFilter.ChatHasUsernameSet ||
chatFilter.ChatHasUsername || !chatFilter.ChatIsForumSet || !chatFilter.ChatIsForum ||
chatFilter.UserAdminRights == nil || !chatFilter.UserAdminRights.DeleteMessages || !chatFilter.UserAdminRights.ManageTopics {
t.Fatalf("chat filter = %#v", chatFilter)
}
wire := tgReplyMarkup(markup).(*tg.ReplyKeyboardMarkup)
wireUser := wire.Rows[0].Buttons[0].Type.(*tg.ButtonTypeRequestPeer).PeerType.(*tg.RequestPeerTypeUser)
if bot, ok := wireUser.GetBot(); !ok || bot {
t.Fatalf("wire user bot=%v ok=%v", bot, ok)
}
if premium, ok := wireUser.GetPremium(); !ok || !premium {
t.Fatalf("wire user premium=%v ok=%v", premium, ok)
}
wireChat := wire.Rows[0].Buttons[1].Type.(*tg.ButtonTypeRequestPeer).PeerType.(*tg.RequestPeerTypeChat)
if !wireChat.Creator || !wireChat.BotParticipant {
t.Fatalf("wire chat = %#v", wireChat)
}
if hasUsername, ok := wireChat.GetHasUsername(); !ok || hasUsername {
t.Fatalf("wire has_username=%v ok=%v", hasUsername, ok)
}
if rights, ok := wireChat.GetUserAdminRights(); !ok || !rights.DeleteMessages || !rights.ManageTopics {
t.Fatalf("wire rights=%#v ok=%v", rights, ok)
}
}
func TestInputRequestPeerButtonPreservesRequestedMetadata(t *testing.T) {
button := tg.KeyboardButton{
Text: "Share",
Type: &tg.InputButtonTypeRequestPeer{
NameRequested: true, UsernameRequested: true, PhotoRequested: true,
ButtonID: 99, PeerType: &tg.RequestPeerTypeUser{}, MaxQuantity: 3,
},
}
got, err := domainRequestedButtonFromTG(1001, nil, button)
if err != nil {
t.Fatal(err)
}
if !got.NameRequested || !got.UsernameRequested || !got.PhotoRequested || got.MaxQuantity != 3 {
t.Fatalf("requested button=%#v", got)
}
}