Follow up gramsrv#27 with the remaining peer-bearing messages.Messages boundaries and route-level wire regression coverage from the telesrv canonical implementation. Origin-PR: gramsrv#27 Origin-Head:f0bd6c9699Origin-Merge:7713847b2aCanonical: f1ba406015a2d5e37802b963757a5cac8ee3eb0e Co-authored-by: epilepticseizureee <business.egor.sg@gmail.com>
1368 lines
62 KiB
Go
1368 lines
62 KiB
Go
package rpc
|
||
|
||
import (
|
||
"github.com/iamxvbaba/td/tg"
|
||
"strings"
|
||
apptelemetry "telesrv/internal/app/clienttelemetry"
|
||
appmessages "telesrv/internal/app/messages"
|
||
appmoderation "telesrv/internal/app/moderation"
|
||
"telesrv/internal/domain"
|
||
"telesrv/internal/store/memory"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestTDesktopPassiveChannelStubs(t *testing.T) {
|
||
f := newRPCChannelFixture(t)
|
||
r := f.router
|
||
moderationReports := memory.NewModerationReportStore()
|
||
telemetryEvents := memory.NewClientTelemetryStore()
|
||
r.deps.ClientTelemetry = apptelemetry.NewService(telemetryEvents)
|
||
r.deps.Moderation = appmoderation.NewService(
|
||
moderationReports,
|
||
appmoderation.WithMessageReaders(nil, r.deps.Channels),
|
||
appmoderation.WithPeerReaders(r.deps.Users, r.deps.Channels),
|
||
)
|
||
owner := f.user(41, "15550002101", "Owner")
|
||
friend := f.user(42, "15550002102", "Friend")
|
||
invited := f.user(43, "15550002103", "Invited")
|
||
ownerCtx := f.userCtx(owner)
|
||
friendCtx := f.userCtx(friend)
|
||
channel := f.createLegacyMegagroup(owner, "RPC Passive Group", friend)
|
||
|
||
sendAs, err := r.onChannelsGetSendAs(ownerCtx, &tg.ChannelsGetSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("get send as: %v", err)
|
||
}
|
||
if len(sendAs.Peers) != 2 || len(sendAs.Users) != 1 || len(sendAs.Chats) != 1 {
|
||
t.Fatalf("send as = %+v, want self + current channel peers with current channel context", sendAs)
|
||
}
|
||
if peer, ok := sendAs.Peers[0].Peer.(*tg.PeerUser); !ok || peer.UserID != owner.ID {
|
||
t.Fatalf("send as peer = %#v, want owner user", sendAs.Peers[0].Peer)
|
||
}
|
||
if peer, ok := sendAs.Peers[1].Peer.(*tg.PeerChannel); !ok || peer.ChannelID != channel.ID {
|
||
t.Fatalf("send as peer[1] = %#v, want current channel", sendAs.Peers[1].Peer)
|
||
}
|
||
friendSendAs, err := r.onChannelsGetSendAs(friendCtx, &tg.ChannelsGetSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("friend get send as: %v", err)
|
||
}
|
||
if len(friendSendAs.Peers) != 1 {
|
||
t.Fatalf("friend send as peers = %+v, want only self", friendSendAs.Peers)
|
||
}
|
||
if ok, err := r.onMessagesSaveDefaultSendAs(ownerCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
SendAs: &tg.InputPeerSelf{},
|
||
}); err != nil || !ok {
|
||
t.Fatalf("save default send as self = ok %v err %v, want true", ok, err)
|
||
}
|
||
if ok, err := r.onMessagesSaveDefaultSendAs(ownerCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
SendAs: inputPeerChannel(channel),
|
||
}); err != nil || !ok {
|
||
t.Fatalf("save default send as channel = ok %v err %v, want true", ok, err)
|
||
}
|
||
fullWithDefault, err := r.onChannelsGetFullChannel(ownerCtx, inputChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("get full channel with default send as: %v", err)
|
||
}
|
||
channelFull := fullWithDefault.FullChat.(*tg.ChannelFull)
|
||
defaultSendAs, ok := channelFull.GetDefaultSendAs()
|
||
if !ok {
|
||
t.Fatalf("full channel default_send_as missing")
|
||
}
|
||
if peer, ok := defaultSendAs.(*tg.PeerChannel); !ok || peer.ChannelID != channel.ID {
|
||
t.Fatalf("full channel default_send_as = %#v, want current channel %d", defaultSendAs, channel.ID)
|
||
}
|
||
sentWithDefault, err := r.onMessagesSendMessage(ownerCtx, &tg.MessagesSendMessageRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Message: "default send as fallback",
|
||
RandomID: 80102,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("send message with default send as: %v", err)
|
||
}
|
||
defaultMsg := sentWithDefault.(*tg.Updates).Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message)
|
||
if from, ok := defaultMsg.FromID.(*tg.PeerChannel); !ok || from.ChannelID != channel.ID {
|
||
t.Fatalf("default send_as message from = %#v, want channel %d", defaultMsg.FromID, channel.ID)
|
||
}
|
||
forwardedWithDefault, err := r.onMessagesForwardMessages(ownerCtx, &tg.MessagesForwardMessagesRequest{
|
||
FromPeer: inputPeerChannel(channel),
|
||
ToPeer: inputPeerChannel(channel),
|
||
ID: []int{defaultMsg.ID},
|
||
RandomID: []int64{80103},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("forward with default send as: %v", err)
|
||
}
|
||
defaultForward := forwardedWithDefault.(*tg.Updates).Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message)
|
||
if from, ok := defaultForward.FromID.(*tg.PeerChannel); !ok || from.ChannelID != channel.ID {
|
||
t.Fatalf("default forward send_as from = %#v, want channel %d", defaultForward.FromID, channel.ID)
|
||
}
|
||
if ok, err := r.onMessagesSaveDefaultSendAs(ownerCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
SendAs: &tg.InputPeerSelf{},
|
||
}); err != nil || !ok {
|
||
t.Fatalf("clear default send as self = ok %v err %v, want true", ok, err)
|
||
}
|
||
fullAfterClear, err := r.onChannelsGetFullChannel(ownerCtx, inputChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("get full channel after clearing default send as: %v", err)
|
||
}
|
||
if _, ok := fullAfterClear.FullChat.(*tg.ChannelFull).GetDefaultSendAs(); ok {
|
||
t.Fatalf("full channel default_send_as still set after saving self")
|
||
}
|
||
if _, err := r.onMessagesSaveDefaultSendAs(ownerCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
SendAs: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
}); err == nil || !strings.Contains(err.Error(), "SEND_AS_PEER_INVALID") {
|
||
t.Fatalf("save default send as friend err = %v, want SEND_AS_PEER_INVALID", err)
|
||
}
|
||
|
||
sponsored, err := r.onMessagesGetSponsoredMessages(ownerCtx, &tg.MessagesGetSponsoredMessagesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("get sponsored messages: %v", err)
|
||
}
|
||
if _, ok := sponsored.(*tg.MessagesSponsoredMessagesEmpty); !ok {
|
||
t.Fatalf("sponsored = %T, want empty", sponsored)
|
||
}
|
||
historyTTL, err := r.onMessagesGetDefaultHistoryTTL(ownerCtx)
|
||
if err != nil {
|
||
t.Fatalf("get default history ttl: %v", err)
|
||
}
|
||
if historyTTL.Period != 0 {
|
||
t.Fatalf("default history ttl = %+v, want disabled", historyTTL)
|
||
}
|
||
accountTTL, err := r.onAccountGetAccountTTL(ownerCtx)
|
||
if err != nil {
|
||
t.Fatalf("get account ttl: %v", err)
|
||
}
|
||
if accountTTL.Days <= 0 {
|
||
t.Fatalf("account ttl = %+v, want positive days", accountTTL)
|
||
}
|
||
preview, err := r.onMessagesGetWebPagePreview(ownerCtx, &tg.MessagesGetWebPagePreviewRequest{
|
||
Message: "https://example.com",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getWebPagePreview: %v", err)
|
||
}
|
||
if _, ok := preview.Media.(*tg.MessageMediaEmpty); !ok || len(preview.Chats) != 0 || len(preview.Users) != 0 {
|
||
t.Fatalf("messages.getWebPagePreview = %+v, want empty media preview", preview)
|
||
}
|
||
if _, err := r.onMessagesGetWebPagePreview(ownerCtx, &tg.MessagesGetWebPagePreviewRequest{}); err == nil || !strings.Contains(err.Error(), "MESSAGE_EMPTY") {
|
||
t.Fatalf("messages.getWebPagePreview empty err = %v, want MESSAGE_EMPTY", err)
|
||
}
|
||
if media, err := r.onMessagesUploadMedia(ownerCtx, &tg.MessagesUploadMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Media: &tg.InputMediaEmpty{},
|
||
}); err != nil {
|
||
t.Fatalf("messages.uploadMedia empty: %v", err)
|
||
} else if _, ok := media.(*tg.MessageMediaEmpty); !ok {
|
||
t.Fatalf("messages.uploadMedia empty = %#v, want messageMediaEmpty", media)
|
||
}
|
||
if _, err := r.onMessagesUploadMedia(ownerCtx, &tg.MessagesUploadMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Media: &tg.InputMediaUploadedPhoto{},
|
||
}); err == nil || !strings.Contains(err.Error(), "MEDIA_INVALID") {
|
||
t.Fatalf("messages.uploadMedia unsupported err = %v, want MEDIA_INVALID", err)
|
||
}
|
||
if updates, err := r.onMessagesSendMedia(ownerCtx, &tg.MessagesSendMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Media: &tg.InputMediaWebPage{URL: "https://example.com"},
|
||
Message: "https://example.com",
|
||
RandomID: 91001,
|
||
}); err != nil {
|
||
t.Fatalf("messages.sendMedia webpage-as-text: %v", err)
|
||
} else if len(updates.(*tg.Updates).Updates) == 0 {
|
||
t.Fatalf("messages.sendMedia webpage-as-text = %+v, want channel message updates", updates)
|
||
}
|
||
if _, err := r.onMessagesSendMedia(ownerCtx, &tg.MessagesSendMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Media: &tg.InputMediaUploadedPhoto{},
|
||
Message: "photo",
|
||
RandomID: 91002,
|
||
}); err == nil || !strings.Contains(err.Error(), "MEDIA_INVALID") {
|
||
t.Fatalf("messages.sendMedia unsupported err = %v, want MEDIA_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesSendMultiMedia(ownerCtx, &tg.MessagesSendMultiMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MultiMedia: []tg.InputSingleMedia{{
|
||
Media: &tg.InputMediaUploadedPhoto{},
|
||
RandomID: 91003,
|
||
Message: "photo",
|
||
}},
|
||
}); err == nil || !strings.Contains(err.Error(), "MEDIA_INVALID") {
|
||
t.Fatalf("messages.sendMultiMedia unsupported err = %v, want MEDIA_INVALID", err)
|
||
}
|
||
savedHistoryReq := &tg.MessagesGetSavedHistoryRequest{
|
||
Peer: &tg.InputPeerSelf{},
|
||
Limit: 20,
|
||
}
|
||
savedHistoryReq.SetParentPeer(inputPeerChannel(channel))
|
||
savedHistory, err := r.onMessagesGetSavedHistory(ownerCtx, savedHistoryReq)
|
||
if err != nil {
|
||
t.Fatalf("messages.getSavedHistory: %v", err)
|
||
}
|
||
if len(savedHistory.(*tg.MessagesMessages).Messages) != 0 || len(savedHistory.(*tg.MessagesMessages).Chats) != 1 {
|
||
t.Fatalf("messages.getSavedHistory = %+v, want empty with parent channel context", savedHistory)
|
||
}
|
||
badSavedParent := &tg.MessagesGetSavedHistoryRequest{
|
||
Peer: &tg.InputPeerSelf{},
|
||
Limit: 20,
|
||
}
|
||
badSavedParent.SetParentPeer(inputPeerChannelWithHash(channel, channel.AccessHash+1))
|
||
if _, err := r.onMessagesGetSavedHistory(ownerCtx, badSavedParent); err == nil || !strings.Contains(err.Error(), "PARENT_PEER_INVALID") {
|
||
t.Fatalf("messages.getSavedHistory bad parent err = %v, want PARENT_PEER_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesGetSavedHistory(ownerCtx, &tg.MessagesGetSavedHistoryRequest{
|
||
Peer: inputPeerChannelWithHash(channel, channel.AccessHash+1),
|
||
Limit: 20,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_PRIVATE") {
|
||
t.Fatalf("messages.getSavedHistory bad peer err = %v, want CHANNEL_PRIVATE", err)
|
||
}
|
||
if _, err := r.onMessagesGetSavedHistory(ownerCtx, &tg.MessagesGetSavedHistoryRequest{
|
||
Peer: &tg.InputPeerSelf{},
|
||
Limit: maxSearchResultsLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.getSavedHistory limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
if ok, err := r.onMessagesReadSavedHistory(ownerCtx, &tg.MessagesReadSavedHistoryRequest{
|
||
ParentPeer: inputPeerChannel(channel),
|
||
Peer: &tg.InputPeerSelf{},
|
||
MaxID: 0,
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.readSavedHistory = ok %v err %v, want true", ok, err)
|
||
}
|
||
if _, err := r.onMessagesReadSavedHistory(ownerCtx, &tg.MessagesReadSavedHistoryRequest{
|
||
ParentPeer: &tg.InputPeerSelf{},
|
||
Peer: &tg.InputPeerSelf{},
|
||
}); err == nil || !strings.Contains(err.Error(), "PARENT_PEER_INVALID") {
|
||
t.Fatalf("messages.readSavedHistory bad parent err = %v, want PARENT_PEER_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesReadSavedHistory(ownerCtx, &tg.MessagesReadSavedHistoryRequest{
|
||
ParentPeer: inputPeerChannel(channel),
|
||
Peer: &tg.InputPeerSelf{},
|
||
MaxID: domain.MaxMessageBoxID + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.readSavedHistory max_id err = %v, want MESSAGE_ID_INVALID", err)
|
||
}
|
||
deleteSavedReq := &tg.MessagesDeleteSavedHistoryRequest{
|
||
Peer: &tg.InputPeerSelf{},
|
||
}
|
||
deleteSavedReq.SetParentPeer(inputPeerChannel(channel))
|
||
deletedSaved, err := r.onMessagesDeleteSavedHistory(ownerCtx, deleteSavedReq)
|
||
if err != nil {
|
||
t.Fatalf("messages.deleteSavedHistory: %v", err)
|
||
}
|
||
if deletedSaved.Offset != 0 || deletedSaved.PtsCount != 0 {
|
||
t.Fatalf("messages.deleteSavedHistory = %+v, want empty affected history", deletedSaved)
|
||
}
|
||
badDeleteDate := &tg.MessagesDeleteSavedHistoryRequest{
|
||
Peer: &tg.InputPeerSelf{},
|
||
}
|
||
badDeleteDate.SetMinDate(-1)
|
||
if _, err := r.onMessagesDeleteSavedHistory(ownerCtx, badDeleteDate); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.deleteSavedHistory min_date err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
scheduledHistory, err := r.onMessagesGetScheduledHistory(ownerCtx, &tg.MessagesGetScheduledHistoryRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getScheduledHistory: %v", err)
|
||
}
|
||
if len(scheduledHistory.(*tg.MessagesMessages).Messages) != 0 || len(scheduledHistory.(*tg.MessagesMessages).Chats) != 1 {
|
||
t.Fatalf("messages.getScheduledHistory = %+v, want empty with channel context", scheduledHistory)
|
||
}
|
||
if _, err := r.onMessagesGetScheduledHistory(ownerCtx, &tg.MessagesGetScheduledHistoryRequest{
|
||
Peer: inputPeerChannelWithHash(channel, channel.AccessHash+1),
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_PRIVATE") {
|
||
t.Fatalf("messages.getScheduledHistory bad hash err = %v, want CHANNEL_PRIVATE", err)
|
||
}
|
||
scheduled, err := r.onMessagesGetScheduledMessages(ownerCtx, &tg.MessagesGetScheduledMessagesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getScheduledMessages: %v", err)
|
||
}
|
||
if len(scheduled.(*tg.MessagesMessages).Messages) != 0 || len(scheduled.(*tg.MessagesMessages).Chats) != 1 {
|
||
t.Fatalf("messages.getScheduledMessages = %+v, want empty with channel context", scheduled)
|
||
}
|
||
if _, err := r.onMessagesGetScheduledMessages(ownerCtx, &tg.MessagesGetScheduledMessagesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: make([]int, maxGetMessagesIDs+1),
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.getScheduledMessages id cap err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
deletedScheduled, err := r.onMessagesDeleteScheduledMessages(ownerCtx, &tg.MessagesDeleteScheduledMessagesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{7, 8},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.deleteScheduledMessages: %v", err)
|
||
}
|
||
if got := deletedScheduled.(*tg.Updates).Updates; len(got) != 1 {
|
||
t.Fatalf("messages.deleteScheduledMessages updates = %+v, want one update", got)
|
||
} else if update, ok := got[0].(*tg.UpdateDeleteScheduledMessages); !ok || len(update.Messages) != 2 || update.Messages[0] != 7 {
|
||
t.Fatalf("messages.deleteScheduledMessages update = %#v, want requested ids", got[0])
|
||
}
|
||
if _, err := r.onMessagesSendScheduledMessages(ownerCtx, &tg.MessagesSendScheduledMessagesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{7},
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.sendScheduledMessages err = %v, want MESSAGE_ID_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesCreateForumTopic(ownerCtx, &tg.MessagesCreateForumTopicRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Title: "Topic",
|
||
RandomID: 91004,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.createForumTopic err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
if _, err := r.onMessagesCreateForumTopic(ownerCtx, &tg.MessagesCreateForumTopicRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
RandomID: 91005,
|
||
}); err == nil || !strings.Contains(err.Error(), "TOPIC_TITLE_EMPTY") {
|
||
t.Fatalf("messages.createForumTopic empty title err = %v, want TOPIC_TITLE_EMPTY", err)
|
||
}
|
||
if _, err := r.onMessagesEditForumTopic(ownerCtx, &tg.MessagesEditForumTopicRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
TopicID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.editForumTopic err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
if _, err := r.onMessagesUpdatePinnedForumTopic(ownerCtx, &tg.MessagesUpdatePinnedForumTopicRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
TopicID: 1,
|
||
Pinned: true,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.updatePinnedForumTopic err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
if _, err := r.onMessagesReorderPinnedForumTopics(ownerCtx, &tg.MessagesReorderPinnedForumTopicsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Order: []int{1, 2},
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.reorderPinnedForumTopics err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
if _, err := r.onMessagesDeleteTopicHistory(ownerCtx, &tg.MessagesDeleteTopicHistoryRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
TopMsgID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.deleteTopicHistory err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
chats, err := r.onMessagesGetChats(ownerCtx, []int64{channel.ID})
|
||
if err != nil {
|
||
t.Fatalf("messages.getChats legacy wrapper: %v", err)
|
||
}
|
||
if len(chats.(*tg.MessagesChats).Chats) != 1 {
|
||
t.Fatalf("messages.getChats = %+v, want current megagroup channel", chats)
|
||
}
|
||
migrated, err := r.onMessagesMigrateChat(ownerCtx, channel.ID)
|
||
if err != nil {
|
||
t.Fatalf("messages.migrateChat legacy mapping: %v", err)
|
||
}
|
||
migratedUpdates := migrated.(*tg.Updates)
|
||
if len(migratedUpdates.Chats) != 1 || len(migratedUpdates.Updates) != 1 {
|
||
t.Fatalf("messages.migrateChat = %+v, want updateChannel with current megagroup", migratedUpdates)
|
||
}
|
||
if _, err := r.onMessagesMigrateChat(friendCtx, channel.ID); err == nil {
|
||
t.Fatalf("messages.migrateChat by non-admin = nil err, want CHAT_ADMIN_REQUIRED")
|
||
}
|
||
full, err := r.onMessagesGetFullChat(ownerCtx, channel.ID)
|
||
if err != nil {
|
||
t.Fatalf("messages.getFullChat legacy wrapper: %v", err)
|
||
}
|
||
if got := full.FullChat.(*tg.ChannelFull).ID; got != channel.ID {
|
||
t.Fatalf("messages.getFullChat id = %d, want %d", got, channel.ID)
|
||
}
|
||
viewedID := defaultMsg.ID
|
||
views, err := r.onMessagesGetMessagesViews(ownerCtx, &tg.MessagesGetMessagesViewsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{viewedID},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessagesViews: %v", err)
|
||
}
|
||
if len(views.Views) != 1 || len(views.Chats) != 1 {
|
||
t.Fatalf("messages.getMessagesViews = %+v, want one view with channel context", views)
|
||
}
|
||
if got, ok := views.Views[0].GetViews(); !ok || got != 0 {
|
||
t.Fatalf("messages.getMessagesViews views = %d ok %v, want explicit zero before increment", got, ok)
|
||
}
|
||
incremented, err := r.onMessagesGetMessagesViews(ownerCtx, &tg.MessagesGetMessagesViewsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{viewedID},
|
||
Increment: true,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessagesViews increment: %v", err)
|
||
}
|
||
if got, ok := incrementalViewCount(incremented); !ok || got != 1 {
|
||
t.Fatalf("messages.getMessagesViews increment count = %d ok %v, want 1", got, ok)
|
||
}
|
||
repeated, err := r.onMessagesGetMessagesViews(ownerCtx, &tg.MessagesGetMessagesViewsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{viewedID},
|
||
Increment: true,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessagesViews repeat increment: %v", err)
|
||
}
|
||
if got, ok := incrementalViewCount(repeated); !ok || got != 1 {
|
||
t.Fatalf("messages.getMessagesViews repeated count = %d ok %v, want still 1", got, ok)
|
||
}
|
||
friendIncremented, err := r.onMessagesGetMessagesViews(friendCtx, &tg.MessagesGetMessagesViewsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{viewedID},
|
||
Increment: true,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessagesViews friend increment: %v", err)
|
||
}
|
||
if got, ok := incrementalViewCount(friendIncremented); !ok || got != 2 {
|
||
t.Fatalf("messages.getMessagesViews friend count = %d ok %v, want 2", got, ok)
|
||
}
|
||
if _, err := r.onMessagesReadMessageContents(ownerCtx, []int{1}); err != nil {
|
||
t.Fatalf("messages.readMessageContents: %v", err)
|
||
}
|
||
mentions, err := r.onMessagesGetUnreadMentions(ownerCtx, &tg.MessagesGetUnreadMentionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getUnreadMentions: %v", err)
|
||
}
|
||
_, mentionChats, _ := searchMessagesPayload(t, mentions)
|
||
if len(mentionChats) != 1 {
|
||
t.Fatalf("messages.getUnreadMentions = %+v, want channel context", mentions)
|
||
}
|
||
if _, err := r.onMessagesReadMentions(ownerCtx, &tg.MessagesReadMentionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}); err != nil {
|
||
t.Fatalf("messages.readMentions: %v", err)
|
||
}
|
||
if ok, err := r.onMessagesReportSpam(ownerCtx, inputPeerChannel(channel)); err != nil || !ok {
|
||
t.Fatalf("messages.reportSpam = ok %v err %v, want true nil", ok, err)
|
||
}
|
||
if _, err := r.onMessagesReport(ownerCtx, &tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_REQUIRED") {
|
||
t.Fatalf("desktop messages.report without ids err = %v, want MESSAGE_ID_REQUIRED", err)
|
||
}
|
||
androidReportOptions, err := r.onMessagesReport(
|
||
WithClientInfo(ownerCtx, ClientInfo{
|
||
Type: ClientTypeAndroid,
|
||
AppVersion: "12.9.0 (69669) pbeta",
|
||
}),
|
||
&tg.MessagesReportRequest{Peer: inputPeerChannel(channel)},
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("android messages.report initial options: %v", err)
|
||
}
|
||
if choices, ok := androidReportOptions.(*tg.ReportResultChooseOption); !ok || len(choices.Options) == 0 {
|
||
t.Fatalf("android messages.report initial options = %#v, want chooseOption", androidReportOptions)
|
||
}
|
||
if got := moderationReports.Reports(); len(got) != 1 {
|
||
t.Fatalf("android initial report option discovery persisted reports = %+v, want only earlier reportSpam", got)
|
||
}
|
||
if _, err := r.onMessagesReport(
|
||
WithClientInfo(ownerCtx, ClientInfo{Type: ClientTypeAndroid}),
|
||
&tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Option: []byte("spam"),
|
||
},
|
||
); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_REQUIRED") {
|
||
t.Fatalf("android selected report option without ids err = %v, want MESSAGE_ID_REQUIRED", err)
|
||
}
|
||
reportOptions, err := r.onMessagesReport(ownerCtx, &tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.report options: %v", err)
|
||
}
|
||
if choices, ok := reportOptions.(*tg.ReportResultChooseOption); !ok || len(choices.Options) == 0 {
|
||
t.Fatalf("messages.report options = %#v, want chooseOption", reportOptions)
|
||
}
|
||
reportComment, err := r.onMessagesReport(ownerCtx, &tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
Option: []byte("other"),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.report other: %v", err)
|
||
}
|
||
if _, ok := reportComment.(*tg.ReportResultAddComment); !ok {
|
||
t.Fatalf("messages.report other = %#v, want addComment", reportComment)
|
||
}
|
||
reported, err := r.onMessagesReport(ownerCtx, &tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
Option: []byte("spam"),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.report spam: %v", err)
|
||
}
|
||
if _, ok := reported.(*tg.ReportResultReported); !ok {
|
||
t.Fatalf("messages.report spam = %#v, want reported", reported)
|
||
}
|
||
if got := moderationReports.Reports(); len(got) != 2 {
|
||
t.Fatalf("moderation reports = %+v, want peer-spam and message reports", got)
|
||
}
|
||
if _, err := r.onMessagesReport(ownerCtx, &tg.MessagesReportRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
Option: []byte("bogus"),
|
||
}); err == nil || !strings.Contains(err.Error(), "OPTION_INVALID") {
|
||
t.Fatalf("messages.report invalid option err = %v, want OPTION_INVALID", err)
|
||
}
|
||
if ok, err := r.onMessagesReportMessagesDelivery(ownerCtx, &tg.MessagesReportMessagesDeliveryRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.reportMessagesDelivery = ok %v err %v, want true nil", ok, err)
|
||
}
|
||
if ok, err := r.onMessagesReportReadMetrics(ownerCtx, &tg.MessagesReportReadMetricsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Metrics: []tg.InputMessageReadMetric{{
|
||
MsgID: 1,
|
||
ViewID: 99,
|
||
TimeInViewMs: 10,
|
||
ActiveTimeInViewMs: 10,
|
||
HeightToViewportRatioPermille: 1000,
|
||
SeenRangeRatioPermille: 1000,
|
||
}},
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.reportReadMetrics = ok %v err %v, want true nil", ok, err)
|
||
}
|
||
if events := telemetryEvents.Events(); len(events) != 2 ||
|
||
events[0].Kind != domain.ClientTelemetryMessageDelivery ||
|
||
events[1].Kind != domain.ClientTelemetryReadMetrics ||
|
||
len(moderationReports.Reports()) != 2 {
|
||
t.Fatalf("telemetry=%+v moderation=%+v, want separate durable streams",
|
||
events, moderationReports.Reports())
|
||
}
|
||
if ok, err := r.onMessagesReportMusicListen(ownerCtx, &tg.MessagesReportMusicListenRequest{
|
||
ID: &tg.InputDocument{ID: 1, AccessHash: 2},
|
||
ListenedDuration: 1,
|
||
}); err == nil || ok || !strings.Contains(err.Error(), "DOCUMENT_INVALID") {
|
||
t.Fatalf("messages.reportMusicListen = ok %v err %v, want DOCUMENT_INVALID", ok, err)
|
||
}
|
||
if _, err := r.onMessagesReportSponsoredMessage(ownerCtx, &tg.MessagesReportSponsoredMessageRequest{
|
||
RandomID: []byte("unseen-ad"),
|
||
}); err == nil || !strings.Contains(err.Error(), "RANDOM_ID_INVALID") {
|
||
t.Fatalf("unseen sponsored report err=%v, want RANDOM_ID_INVALID", err)
|
||
}
|
||
impression, err := domain.NewSponsoredMessageImpression(
|
||
owner.ID, []byte("ad"),
|
||
domain.Peer{Type: domain.PeerTypeChannel, ID: channel.ID},
|
||
0, []byte(`{"schema_version":1,"text":"test sponsored message"}`),
|
||
time.Now().UTC(), time.Now().UTC().Add(time.Hour),
|
||
)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, _, err := moderationReports.CreateSponsoredMessageImpression(ownerCtx, impression); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
sponsoredOptions, err := r.onMessagesReportSponsoredMessage(ownerCtx, &tg.MessagesReportSponsoredMessageRequest{
|
||
RandomID: []byte("ad"),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.reportSponsoredMessage options: %v", err)
|
||
}
|
||
if choices, ok := sponsoredOptions.(*tg.ChannelsSponsoredMessageReportResultChooseOption); !ok ||
|
||
len(choices.Options) == 0 {
|
||
t.Fatalf("sponsored options=%#v, want chooseOption", sponsoredOptions)
|
||
}
|
||
sponsoredReport, err := r.onMessagesReportSponsoredMessage(ownerCtx, &tg.MessagesReportSponsoredMessageRequest{
|
||
RandomID: []byte("ad"),
|
||
Option: []byte("spam"),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.reportSponsoredMessage: %v", err)
|
||
}
|
||
if _, ok := sponsoredReport.(*tg.ChannelsSponsoredMessageReportResultReported); !ok {
|
||
t.Fatalf("messages.reportSponsoredMessage = %#v, want reported", sponsoredReport)
|
||
}
|
||
sendReactionReq := &tg.MessagesSendReactionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: viewedID,
|
||
Reaction: []tg.ReactionClass{&tg.ReactionEmoji{Emoticon: "\U0001f44d"}},
|
||
}
|
||
sendReactionReq.SetReaction(sendReactionReq.Reaction)
|
||
sendReactionReq.SetAddToRecent(true)
|
||
if updates, err := r.onMessagesSendReaction(ownerCtx, sendReactionReq); err != nil {
|
||
t.Fatalf("messages.sendReaction: %v", err)
|
||
} else if got := updates.(*tg.Updates).Updates; len(got) != 1 {
|
||
t.Fatalf("messages.sendReaction updates = %+v, want one reaction update", got)
|
||
} else if update, ok := got[0].(*tg.UpdateMessageReactions); !ok || update.MsgID != viewedID || len(update.Reactions.Results) != 1 || update.Reactions.Results[0].Count != 1 || update.Reactions.Results[0].ChosenOrder != 1 {
|
||
t.Fatalf("messages.sendReaction update = %#v, want one chosen reaction", got[0])
|
||
}
|
||
tooManyReactionsReq := &tg.MessagesSendReactionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: viewedID,
|
||
Reaction: make([]tg.ReactionClass, maxReactionVector+1),
|
||
}
|
||
tooManyReactionsReq.SetReaction(tooManyReactionsReq.Reaction)
|
||
if _, err := r.onMessagesSendReaction(ownerCtx, tooManyReactionsReq); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.sendReaction huge reaction vector err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
reactionUpdates, err := r.onMessagesGetMessagesReactions(ownerCtx, &tg.MessagesGetMessagesReactionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{viewedID},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessagesReactions: %v", err)
|
||
}
|
||
if got := reactionUpdates.(*tg.Updates).Updates; len(got) != 1 {
|
||
t.Fatalf("messages.getMessagesReactions updates = %+v, want one reaction update", got)
|
||
} else if update, ok := got[0].(*tg.UpdateMessageReactions); !ok || update.MsgID != viewedID || len(update.Reactions.Results) != 1 || update.Reactions.Results[0].Count != 1 {
|
||
t.Fatalf("messages.getMessagesReactions update = %#v, want one reaction", got[0])
|
||
}
|
||
reactionList, err := r.onMessagesGetMessageReactionsList(ownerCtx, &tg.MessagesGetMessageReactionsListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: viewedID,
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getMessageReactionsList: %v", err)
|
||
}
|
||
if reactionList.Count != 1 || len(reactionList.Reactions) != 1 || len(reactionList.Chats) != 1 || len(reactionList.Users) != 1 {
|
||
t.Fatalf("messages.getMessageReactionsList = %+v, want one reaction with channel context", reactionList)
|
||
}
|
||
if peer, ok := reactionList.Reactions[0].PeerID.(*tg.PeerUser); !ok || peer.UserID != owner.ID || !reactionList.Reactions[0].My {
|
||
t.Fatalf("messages.getMessageReactionsList reaction = %+v, want current user reaction", reactionList.Reactions[0])
|
||
}
|
||
friendReactionReq := &tg.MessagesSendReactionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: viewedID,
|
||
Reaction: []tg.ReactionClass{&tg.ReactionEmoji{Emoticon: "\U0001f44d"}},
|
||
}
|
||
friendReactionReq.SetReaction(friendReactionReq.Reaction)
|
||
if _, err := r.onMessagesSendReaction(friendCtx, friendReactionReq); err != nil {
|
||
t.Fatalf("messages.sendReaction by friend: %v", err)
|
||
}
|
||
if ok, err := r.onMessagesReportReaction(ownerCtx, &tg.MessagesReportReactionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: viewedID,
|
||
ReactionPeer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.reportReaction = ok %v err %v, want true nil", ok, err)
|
||
}
|
||
unreadReactions, err := r.onMessagesGetUnreadReactions(ownerCtx, &tg.MessagesGetUnreadReactionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getUnreadReactions: %v", err)
|
||
}
|
||
unreadMessages, unreadChats, unreadUsers := searchMessagesPayload(t, unreadReactions)
|
||
if len(unreadMessages) != 1 || len(unreadChats) != 1 || len(unreadUsers) == 0 {
|
||
t.Fatalf("messages.getUnreadReactions = %+v, want one unread reaction with channel context", unreadReactions)
|
||
}
|
||
unreadMessage, ok := unreadMessages[0].(*tg.Message)
|
||
if !ok || unreadMessage.ID != viewedID {
|
||
t.Fatalf("messages.getUnreadReactions message = %#v, want message %d", unreadMessages[0], viewedID)
|
||
}
|
||
reactions, ok := unreadMessage.GetReactions()
|
||
hasUnreadRecent := false
|
||
for _, recent := range reactions.RecentReactions {
|
||
if recent.Unread {
|
||
hasUnreadRecent = true
|
||
break
|
||
}
|
||
}
|
||
if !ok || !hasUnreadRecent {
|
||
t.Fatalf("messages.getUnreadReactions reactions = %+v ok %v, want unread recent reaction", reactions, ok)
|
||
}
|
||
if _, err := r.onMessagesReadReactions(ownerCtx, &tg.MessagesReadReactionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}); err != nil {
|
||
t.Fatalf("messages.readReactions: %v", err)
|
||
}
|
||
unreadAfterRead, err := r.onMessagesGetUnreadReactions(ownerCtx, &tg.MessagesGetUnreadReactionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getUnreadReactions after read: %v", err)
|
||
}
|
||
unreadAfterReadMessages, _, _ := searchMessagesPayload(t, unreadAfterRead)
|
||
if len(unreadAfterReadMessages) != 0 {
|
||
t.Fatalf("messages.getUnreadReactions after read = %+v, want empty messages", unreadAfterRead)
|
||
}
|
||
common, err := r.onMessagesGetCommonChats(ownerCtx, &tg.MessagesGetCommonChatsRequest{
|
||
UserID: &tg.InputUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Limit: 40,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getCommonChats: %v", err)
|
||
}
|
||
commonChats := common.(*tg.MessagesChats).Chats
|
||
if len(commonChats) != 1 || commonChats[0].(*tg.Channel).ID != channel.ID {
|
||
t.Fatalf("messages.getCommonChats = %+v, want current shared megagroup", common)
|
||
}
|
||
commonNext, err := r.onMessagesGetCommonChats(ownerCtx, &tg.MessagesGetCommonChatsRequest{
|
||
UserID: &tg.InputUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
MaxID: channel.ID,
|
||
Limit: 40,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getCommonChats next page: %v", err)
|
||
}
|
||
if len(commonNext.(*tg.MessagesChats).Chats) != 0 {
|
||
t.Fatalf("messages.getCommonChats next page = %+v, want empty after max id", commonNext)
|
||
}
|
||
fullFriend, err := r.onUsersGetFullUser(ownerCtx, &tg.InputUser{UserID: friend.ID, AccessHash: friend.AccessHash})
|
||
if err != nil {
|
||
t.Fatalf("users.getFullUser friend: %v", err)
|
||
}
|
||
if fullFriend.FullUser.CommonChatsCount != 1 {
|
||
t.Fatalf("users.getFullUser commonChatsCount = %d, want 1", fullFriend.FullUser.CommonChatsCount)
|
||
}
|
||
if _, err := r.onMessagesGetCommonChats(ownerCtx, &tg.MessagesGetCommonChatsRequest{
|
||
UserID: &tg.InputUserSelf{},
|
||
Limit: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "USER_ID_INVALID") {
|
||
t.Fatalf("messages.getCommonChats self err = %v, want USER_ID_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesGetAttachedStickers(ownerCtx, nil); err == nil || !strings.Contains(err.Error(), "MEDIA_EMPTY") {
|
||
t.Fatalf("messages.getAttachedStickers nil err = %v, want MEDIA_EMPTY", err)
|
||
}
|
||
attached, err := r.onMessagesGetAttachedStickers(ownerCtx, &tg.InputStickeredMediaDocument{
|
||
ID: &tg.InputDocument{ID: 1, AccessHash: 2},
|
||
})
|
||
if err != nil || len(attached) != 0 {
|
||
t.Fatalf("messages.getAttachedStickers = %+v err=%v, want empty", attached, err)
|
||
}
|
||
customEmojiDocs, err := r.onMessagesGetCustomEmojiDocuments(ownerCtx, []int64{1, 2})
|
||
if err != nil || len(customEmojiDocs) != 0 {
|
||
t.Fatalf("messages.getCustomEmojiDocuments = %+v err=%v, want empty", customEmojiDocs, err)
|
||
}
|
||
if _, err := r.onMessagesGetCustomEmojiDocuments(ownerCtx, make([]int64, maxEmojiDocuments+1)); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.getCustomEmojiDocuments too many err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
stickerSets, err := r.onMessagesSearchStickerSets(ownerCtx, &tg.MessagesSearchStickerSetsRequest{Q: "cat"})
|
||
if err != nil {
|
||
t.Fatalf("messages.searchStickerSets: %v", err)
|
||
}
|
||
if found, ok := stickerSets.(*tg.MessagesFoundStickerSets); !ok || len(found.Sets) != 0 {
|
||
t.Fatalf("messages.searchStickerSets = %#v, want empty found sets", stickerSets)
|
||
}
|
||
stickers, err := r.onMessagesSearchStickers(ownerCtx, &tg.MessagesSearchStickersRequest{
|
||
Q: "cat",
|
||
LangCode: []string{"en"},
|
||
Limit: 50,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.searchStickers: %v", err)
|
||
}
|
||
if found, ok := stickers.(*tg.MessagesFoundStickers); !ok || len(found.Stickers) != 0 {
|
||
t.Fatalf("messages.searchStickers = %#v, want empty found stickers", stickers)
|
||
}
|
||
if _, err := r.onMessagesSearchStickers(ownerCtx, &tg.MessagesSearchStickersRequest{
|
||
Limit: maxSearchResultsLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.searchStickers huge limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
// emoji 关键词已从官方 seed(catalog),返回真实词典而非空 stub。
|
||
keywords, err := r.onMessagesGetEmojiKeywords(ownerCtx, "en")
|
||
if err != nil || keywords.LangCode != "en" || keywords.Version == 0 || len(keywords.Keywords) == 0 {
|
||
t.Fatalf("messages.getEmojiKeywords lang=%q version=%d count=%d err=%v, want seeded en keywords",
|
||
keywords.LangCode, keywords.Version, len(keywords.Keywords), err)
|
||
}
|
||
// 客户端版本(7)落后于固化版本 → 下发全量,Version 推进到固化版本。
|
||
keywordsDiff, err := r.onMessagesGetEmojiKeywordsDifference(ownerCtx, &tg.MessagesGetEmojiKeywordsDifferenceRequest{
|
||
LangCode: "en",
|
||
FromVersion: 7,
|
||
})
|
||
if err != nil || keywordsDiff.FromVersion != 7 || keywordsDiff.Version <= 7 || len(keywordsDiff.Keywords) == 0 {
|
||
t.Fatalf("messages.getEmojiKeywordsDifference from=%d version=%d count=%d err=%v, want full diff to seeded version",
|
||
keywordsDiff.FromVersion, keywordsDiff.Version, len(keywordsDiff.Keywords), err)
|
||
}
|
||
extended, err := r.onMessagesGetExtendedMedia(ownerCtx, &tg.MessagesGetExtendedMediaRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: []int{1},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getExtendedMedia: %v", err)
|
||
}
|
||
if len(extended.(*tg.Updates).Updates) != 0 {
|
||
t.Fatalf("messages.getExtendedMedia = %+v, want empty updates", extended)
|
||
}
|
||
topReactions, err := r.onMessagesGetTopReactions(ownerCtx, &tg.MessagesGetTopReactionsRequest{Limit: 3})
|
||
if err != nil {
|
||
t.Fatalf("messages.getTopReactions: %v", err)
|
||
}
|
||
topPage, ok := topReactions.(*tg.MessagesReactions)
|
||
if !ok || topPage.Hash == 0 || len(topPage.Reactions) != 3 {
|
||
t.Fatalf("messages.getTopReactions = %#v, want three hashable reactions", topReactions)
|
||
}
|
||
if emoji, ok := topPage.Reactions[0].(*tg.ReactionEmoji); !ok || emoji.Emoticon != "\U0001f44d" {
|
||
t.Fatalf("messages.getTopReactions first reaction = %#v, want account top thumb", topPage.Reactions[0])
|
||
}
|
||
if emoji, ok := topPage.Reactions[1].(*tg.ReactionEmoji); !ok || emoji.Emoticon != "\u2764\ufe0f" {
|
||
t.Fatalf("messages.getTopReactions fallback reaction = %#v, want catalog heart", topPage.Reactions[1])
|
||
}
|
||
topNotModified, err := r.onMessagesGetTopReactions(ownerCtx, &tg.MessagesGetTopReactionsRequest{Limit: 3, Hash: topPage.Hash})
|
||
if err != nil {
|
||
t.Fatalf("messages.getTopReactions hash: %v", err)
|
||
}
|
||
if _, ok := topNotModified.(*tg.MessagesReactionsNotModified); !ok {
|
||
t.Fatalf("messages.getTopReactions hash = %#v, want notModified", topNotModified)
|
||
}
|
||
recentReactions, err := r.onMessagesGetRecentReactions(ownerCtx, &tg.MessagesGetRecentReactionsRequest{Limit: 40})
|
||
if err != nil {
|
||
t.Fatalf("messages.getRecentReactions: %v", err)
|
||
}
|
||
recentPage, ok := recentReactions.(*tg.MessagesReactions)
|
||
if !ok || recentPage.Hash == 0 || len(recentPage.Reactions) != 1 {
|
||
t.Fatalf("messages.getRecentReactions = %#v, want one reaction with non-zero hash", recentReactions)
|
||
}
|
||
if emoji, ok := recentPage.Reactions[0].(*tg.ReactionEmoji); !ok || emoji.Emoticon != "\U0001f44d" {
|
||
t.Fatalf("messages.getRecentReactions reaction = %#v, want thumb emoji", recentPage.Reactions[0])
|
||
}
|
||
recentNotModified, err := r.onMessagesGetRecentReactions(ownerCtx, &tg.MessagesGetRecentReactionsRequest{Limit: 40, Hash: recentPage.Hash})
|
||
if err != nil {
|
||
t.Fatalf("messages.getRecentReactions hash: %v", err)
|
||
}
|
||
if _, ok := recentNotModified.(*tg.MessagesReactionsNotModified); !ok {
|
||
t.Fatalf("messages.getRecentReactions hash = %#v, want notModified", recentNotModified)
|
||
}
|
||
if ok, err := r.onMessagesClearRecentReactions(ownerCtx); err != nil || !ok {
|
||
t.Fatalf("messages.clearRecentReactions = ok %v err %v, want true nil", ok, err)
|
||
}
|
||
recentAfterClear, err := r.onMessagesGetRecentReactions(ownerCtx, &tg.MessagesGetRecentReactionsRequest{Limit: 40, Hash: recentPage.Hash})
|
||
if err != nil {
|
||
t.Fatalf("messages.getRecentReactions after clear: %v", err)
|
||
}
|
||
clearedPage, ok := recentAfterClear.(*tg.MessagesReactions)
|
||
if !ok || clearedPage.Hash != 0 || len(clearedPage.Reactions) != 0 {
|
||
t.Fatalf("messages.getRecentReactions after clear = %#v, want empty page hash 0", recentAfterClear)
|
||
}
|
||
savedTagsReq := &tg.MessagesGetSavedReactionTagsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}
|
||
savedTagsReq.SetPeer(savedTagsReq.Peer)
|
||
savedTags, err := r.onMessagesGetSavedReactionTags(ownerCtx, savedTagsReq)
|
||
if err != nil {
|
||
t.Fatalf("messages.getSavedReactionTags: %v", err)
|
||
}
|
||
if got := savedTags.(*tg.MessagesSavedReactionTags).Tags; len(got) != 0 {
|
||
t.Fatalf("messages.getSavedReactionTags = %+v, want empty", got)
|
||
}
|
||
staleEmptyTags, err := r.onMessagesGetSavedReactionTags(ownerCtx, &tg.MessagesGetSavedReactionTagsRequest{Hash: 123})
|
||
if err != nil {
|
||
t.Fatalf("messages.getSavedReactionTags stale empty hash: %v", err)
|
||
}
|
||
staleEmptyPage, ok := staleEmptyTags.(*tg.MessagesSavedReactionTags)
|
||
if !ok || staleEmptyPage.Hash != 0 || len(staleEmptyPage.Tags) != 0 {
|
||
t.Fatalf("messages.getSavedReactionTags stale empty hash = %#v, want empty page hash 0", staleEmptyTags)
|
||
}
|
||
r.deps.Messages = appmessages.NewService(memory.NewMessageStore(), nil)
|
||
if _, err := f.users.SetPremiumUntil(ownerCtx, owner.ID, int(time.Now().Add(time.Hour).Unix())); err != nil {
|
||
t.Fatalf("grant owner premium for saved tag rename: %v", err)
|
||
}
|
||
updateTagReq := &tg.MessagesUpdateSavedReactionTagRequest{Reaction: &tg.ReactionEmoji{Emoticon: "ok"}}
|
||
updateTagReq.SetTitle("Work")
|
||
if _, err := r.onMessagesUpdateSavedReactionTag(ownerCtx, updateTagReq); err == nil || !strings.Contains(err.Error(), "REACTION_INVALID") {
|
||
t.Fatalf("messages.updateSavedReactionTag unassigned err = %v, want REACTION_INVALID", err)
|
||
}
|
||
globalTags, err := r.onMessagesGetSavedReactionTags(ownerCtx, &tg.MessagesGetSavedReactionTagsRequest{})
|
||
if err != nil {
|
||
t.Fatalf("messages.getSavedReactionTags global: %v", err)
|
||
}
|
||
globalPage, ok := globalTags.(*tg.MessagesSavedReactionTags)
|
||
if !ok || globalPage.Hash != 0 || len(globalPage.Tags) != 0 {
|
||
t.Fatalf("messages.getSavedReactionTags global = %#v, want empty", globalTags)
|
||
}
|
||
peerTagsAfterUpdate, err := r.onMessagesGetSavedReactionTags(ownerCtx, savedTagsReq)
|
||
if err != nil {
|
||
t.Fatalf("messages.getSavedReactionTags peer after update: %v", err)
|
||
}
|
||
if got := peerTagsAfterUpdate.(*tg.MessagesSavedReactionTags).Tags; len(got) != 0 {
|
||
t.Fatalf("messages.getSavedReactionTags peer after update = %+v, want empty until saved-message tag store exists", got)
|
||
}
|
||
longTagReq := &tg.MessagesUpdateSavedReactionTagRequest{Reaction: &tg.ReactionEmoji{Emoticon: "ok"}}
|
||
longTagReq.SetTitle("abcdefghijklmnop")
|
||
if _, err := r.onMessagesUpdateSavedReactionTag(ownerCtx, longTagReq); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.updateSavedReactionTag long title err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesUpdateSavedReactionTag(ownerCtx, &tg.MessagesUpdateSavedReactionTagRequest{
|
||
Reaction: &tg.ReactionCustomEmoji{DocumentID: 1},
|
||
}); err == nil || !strings.Contains(err.Error(), "REACTION_INVALID") {
|
||
t.Fatalf("messages.updateSavedReactionTag custom emoji err = %v, want REACTION_INVALID", err)
|
||
}
|
||
tagReactions, err := r.onMessagesGetDefaultTagReactions(ownerCtx, 0)
|
||
if err != nil {
|
||
t.Fatalf("messages.getDefaultTagReactions: %v", err)
|
||
}
|
||
defaultPage, ok := tagReactions.(*tg.MessagesReactions)
|
||
if !ok || defaultPage.Hash == 0 || len(defaultPage.Reactions) == 0 {
|
||
t.Fatalf("messages.getDefaultTagReactions = %#v, want non-empty hashable catalog", tagReactions)
|
||
}
|
||
r.deps.Messages = nil
|
||
// poll 链路已是真实现:对非 poll 消息一律 MESSAGE_ID_INVALID(与官方一致)。
|
||
if _, err := r.onMessagesSendVote(ownerCtx, &tg.MessagesSendVoteRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Options: [][]byte{[]byte("a")},
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.sendVote err = %v, want MESSAGE_ID_INVALID for non-poll message", err)
|
||
}
|
||
if _, err := r.onMessagesSendVote(ownerCtx, &tg.MessagesSendVoteRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Options: make([][]byte, maxPollVoteOptions+1),
|
||
}); err == nil || !strings.Contains(err.Error(), "OPTIONS_TOO_MUCH") {
|
||
t.Fatalf("messages.sendVote too many options err = %v, want OPTIONS_TOO_MUCH", err)
|
||
}
|
||
if _, err := r.onMessagesGetPollResults(ownerCtx, &tg.MessagesGetPollResultsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.getPollResults err = %v, want MESSAGE_ID_INVALID for non-poll message", err)
|
||
}
|
||
pollVotesReq := &tg.MessagesGetPollVotesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: 1,
|
||
Option: []byte("a"),
|
||
Limit: 10,
|
||
}
|
||
pollVotesReq.SetOption(pollVotesReq.Option)
|
||
if _, err := r.onMessagesGetPollVotes(ownerCtx, pollVotesReq); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.getPollVotes err = %v, want MESSAGE_ID_INVALID for non-poll message", err)
|
||
}
|
||
if _, err := r.onMessagesGetPollVotes(ownerCtx, &tg.MessagesGetPollVotesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
ID: 1,
|
||
Limit: maxSearchResultsLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.getPollVotes huge limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesAddPollAnswer(ownerCtx, &tg.MessagesAddPollAnswerRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Answer: &tg.InputPollAnswer{Text: tg.TextWithEntities{Text: "extra"}},
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.addPollAnswer err = %v, want MESSAGE_ID_INVALID without poll store", err)
|
||
}
|
||
if _, err := r.onMessagesDeletePollAnswer(ownerCtx, &tg.MessagesDeletePollAnswerRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Option: []byte("a"),
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.deletePollAnswer err = %v, want MESSAGE_ID_INVALID without poll store", err)
|
||
}
|
||
verify := newFakeBotVerifications()
|
||
r.deps.BotVerifications = verify
|
||
const unreadPollIcon = int64(8800024)
|
||
unreadPollPeer := domain.Peer{Type: domain.PeerTypeChannel, ID: channel.ID}
|
||
verify.marks[unreadPollPeer] = domain.CustomVerification{
|
||
VerifierBotID: 777000123,
|
||
Peer: unreadPollPeer,
|
||
IconDocumentID: unreadPollIcon,
|
||
Description: "Verified poll peer",
|
||
}
|
||
unreadPollVotes, err := r.onMessagesGetUnreadPollVotes(ownerCtx, &tg.MessagesGetUnreadPollVotesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getUnreadPollVotes: %v", err)
|
||
}
|
||
assertMessagesEnvelopeBotVerificationIcon(t, unreadPollVotes, unreadPollPeer, unreadPollIcon)
|
||
if len(unreadPollVotes.(*tg.MessagesMessages).Messages) != 0 {
|
||
t.Fatalf("messages.getUnreadPollVotes = %+v, want empty messages", unreadPollVotes)
|
||
}
|
||
if _, err := r.onMessagesReadPollVotes(ownerCtx, &tg.MessagesReadPollVotesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}); err != nil {
|
||
t.Fatalf("messages.readPollVotes: %v", err)
|
||
}
|
||
if _, err := r.onMessagesAppendTodoList(ownerCtx, &tg.MessagesAppendTodoListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "TODO_NOT_MODIFIED") {
|
||
t.Fatalf("messages.appendTodoList empty err = %v, want TODO_NOT_MODIFIED", err)
|
||
}
|
||
if _, err := r.onMessagesAppendTodoList(ownerCtx, &tg.MessagesAppendTodoListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
List: []tg.TodoItem{{Title: tg.TextWithEntities{Text: "item"}}},
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.appendTodoList err = %v, want MESSAGE_ID_INVALID without todo store", err)
|
||
}
|
||
if _, err := r.onMessagesToggleTodoCompleted(ownerCtx, &tg.MessagesToggleTodoCompletedRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "TODO_NOT_MODIFIED") {
|
||
t.Fatalf("messages.toggleTodoCompleted empty err = %v, want TODO_NOT_MODIFIED", err)
|
||
}
|
||
if _, err := r.onMessagesToggleTodoCompleted(ownerCtx, &tg.MessagesToggleTodoCompletedRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Completed: []int{1},
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.toggleTodoCompleted err = %v, want MESSAGE_ID_INVALID without todo store", err)
|
||
}
|
||
counters, err := r.onMessagesGetSearchCounters(ownerCtx, &tg.MessagesGetSearchCountersRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Filters: []tg.MessagesFilterClass{&tg.InputMessagesFilterEmpty{}},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getSearchCounters: %v", err)
|
||
}
|
||
if len(counters) != 1 || counters[0].Count != 0 {
|
||
t.Fatalf("messages.getSearchCounters = %+v, want one zero counter", counters)
|
||
}
|
||
calendar, err := r.onMessagesGetSearchResultsCalendar(ownerCtx, &tg.MessagesGetSearchResultsCalendarRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Filter: &tg.InputMessagesFilterPhotos{},
|
||
OffsetID: 7,
|
||
OffsetDate: 1700000000,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getSearchResultsCalendar: %v", err)
|
||
}
|
||
if calendar.Count != 0 || calendar.MinDate != 1700000000 || calendar.MinMsgID != 7 || len(calendar.Messages) != 0 {
|
||
t.Fatalf("messages.getSearchResultsCalendar = %+v, want empty stable-offset result", calendar)
|
||
}
|
||
positions, err := r.onMessagesGetSearchResultsPositions(ownerCtx, &tg.MessagesGetSearchResultsPositionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Filter: &tg.InputMessagesFilterPhotos{},
|
||
OffsetID: 7,
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getSearchResultsPositions: %v", err)
|
||
}
|
||
if positions.Count != 0 || len(positions.Positions) != 0 {
|
||
t.Fatalf("messages.getSearchResultsPositions = %+v, want empty positions", positions)
|
||
}
|
||
if _, err := r.onMessagesGetSearchResultsPositions(ownerCtx, &tg.MessagesGetSearchResultsPositionsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Filter: &tg.InputMessagesFilterPhotos{},
|
||
OffsetID: 7,
|
||
Limit: maxSearchResultsLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("messages.getSearchResultsPositions huge limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
replies, err := r.onMessagesGetReplies(ownerCtx, &tg.MessagesGetRepliesRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getReplies: %v", err)
|
||
}
|
||
_, replyChats, _ := searchMessagesPayload(t, replies)
|
||
if len(replyChats) != 1 {
|
||
t.Fatalf("messages.getReplies = %+v, want channel context", replies)
|
||
}
|
||
discussion, err := r.onMessagesGetDiscussionMessage(ownerCtx, &tg.MessagesGetDiscussionMessageRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.getDiscussionMessage: %v", err)
|
||
}
|
||
if len(discussion.Messages) != 1 || len(discussion.Chats) != 1 || discussion.UnreadCount != 0 {
|
||
t.Fatalf("messages.getDiscussionMessage = %+v, want root message with channel context", discussion)
|
||
}
|
||
if _, ok := discussion.Messages[0].(*tg.MessageService); !ok {
|
||
t.Fatalf("messages.getDiscussionMessage message = %T, want service root", discussion.Messages[0])
|
||
}
|
||
if _, err := r.onMessagesReadDiscussion(ownerCtx, &tg.MessagesReadDiscussionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
ReadMaxID: 1,
|
||
}); err != nil {
|
||
t.Fatalf("messages.readDiscussion err = %v, want nil", err)
|
||
}
|
||
if _, err := r.onMessagesGetDiscussionMessage(ownerCtx, &tg.MessagesGetDiscussionMessageRequest{
|
||
Peer: inputPeerChannelWithHash(channel, channel.AccessHash+1),
|
||
MsgID: 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_PRIVATE") {
|
||
t.Fatalf("messages.getDiscussionMessage bad hash err = %v, want CHANNEL_PRIVATE", err)
|
||
}
|
||
if _, err := r.onMessagesReadDiscussion(ownerCtx, &tg.MessagesReadDiscussionRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
MsgID: 1,
|
||
ReadMaxID: domain.MaxMessageBoxID + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "MESSAGE_ID_INVALID") {
|
||
t.Fatalf("messages.readDiscussion huge read_max_id err = %v, want MESSAGE_ID_INVALID", err)
|
||
}
|
||
if _, err := r.onMessagesGetForumTopics(ownerCtx, &tg.MessagesGetForumTopicsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.getForumTopics non-forum err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
if _, err := r.onMessagesGetForumTopicsByID(ownerCtx, &tg.MessagesGetForumTopicsByIDRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Topics: []int{1},
|
||
}); err == nil || !strings.Contains(err.Error(), "CHANNEL_FORUM_MISSING") {
|
||
t.Fatalf("messages.getForumTopicsByID non-forum err = %v, want CHANNEL_FORUM_MISSING", err)
|
||
}
|
||
onlines, err := r.onMessagesGetOnlines(ownerCtx, inputPeerChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("messages.getOnlines: %v", err)
|
||
}
|
||
if onlines.Onlines != 1 {
|
||
t.Fatalf("messages.getOnlines = %+v, want count 1", onlines)
|
||
}
|
||
stats, err := r.onStatsGetMegagroupStats(ownerCtx, &tg.StatsGetMegagroupStatsRequest{
|
||
Channel: inputChannel(channel),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("stats.getMegagroupStats: %v", err)
|
||
}
|
||
if stats.GrowthGraph == nil || stats.MembersGraph == nil {
|
||
t.Fatalf("megagroup stats = %+v, want non-nil graph stubs", stats)
|
||
}
|
||
if _, err := r.onStatsGetBroadcastStats(ownerCtx, &tg.StatsGetBroadcastStatsRequest{
|
||
Channel: inputChannel(channel),
|
||
}); err == nil || !strings.Contains(err.Error(), "BROADCAST_REQUIRED") {
|
||
t.Fatalf("stats.getBroadcastStats on megagroup err = %v, want BROADCAST_REQUIRED", err)
|
||
}
|
||
messageStats, err := r.onStatsGetMessageStats(ownerCtx, &tg.StatsGetMessageStatsRequest{
|
||
Channel: inputChannel(channel),
|
||
MsgID: 1,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("stats.getMessageStats: %v", err)
|
||
}
|
||
if messageStats.ViewsGraph == nil || messageStats.ReactionsByEmotionGraph == nil {
|
||
t.Fatalf("message stats = %+v, want non-nil graph stubs", messageStats)
|
||
}
|
||
forwards, err := r.onStatsGetMessagePublicForwards(ownerCtx, &tg.StatsGetMessagePublicForwardsRequest{
|
||
Channel: inputChannel(channel),
|
||
MsgID: 1,
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("stats.getMessagePublicForwards: %v", err)
|
||
}
|
||
if forwards.Count != 0 || len(forwards.Forwards) != 0 {
|
||
t.Fatalf("message public forwards = %+v, want empty", forwards)
|
||
}
|
||
if _, err := r.onStatsGetMessagePublicForwards(ownerCtx, &tg.StatsGetMessagePublicForwardsRequest{
|
||
Channel: inputChannel(channel),
|
||
MsgID: 1,
|
||
Limit: maxStatsPublicForwardsLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("stats.getMessagePublicForwards huge limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
graph, err := r.onStatsLoadAsyncGraph(ownerCtx, &tg.StatsLoadAsyncGraphRequest{Token: "stale"})
|
||
if err != nil {
|
||
t.Fatalf("stats.loadAsyncGraph: %v", err)
|
||
}
|
||
if _, ok := graph.(*tg.StatsGraphError); !ok {
|
||
t.Fatalf("stats.loadAsyncGraph = %T, want statsGraphError", graph)
|
||
}
|
||
boostStatus, err := r.onPremiumGetBoostsStatus(ownerCtx, inputPeerChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("premium.getBoostsStatus: %v", err)
|
||
}
|
||
if boostStatus.Level != 0 || boostStatus.CurrentLevelBoosts != 0 || boostStatus.Boosts != 0 {
|
||
t.Fatalf("premium.getBoostsStatus = level %d current %d boosts %d, want empty real state", boostStatus.Level, boostStatus.CurrentLevelBoosts, boostStatus.Boosts)
|
||
}
|
||
if boostStatus.BoostURL == "" {
|
||
t.Fatalf("premium.getBoostsStatus missing boost_url")
|
||
}
|
||
if next, ok := boostStatus.GetNextLevelBoosts(); !ok || next <= boostStatus.CurrentLevelBoosts {
|
||
t.Fatalf("premium.getBoostsStatus real NextLevelBoosts set=%v val=%d, want > CurrentLevelBoosts %d", ok, next, boostStatus.CurrentLevelBoosts)
|
||
}
|
||
boostStatusTDesktop, err := r.onPremiumGetBoostsStatus(WithClientInfo(ownerCtx, ClientInfo{
|
||
Type: ClientTypeTDesktop,
|
||
AppVersion: "6.8.4 x64",
|
||
}), inputPeerChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("premium.getBoostsStatus TDesktop: %v", err)
|
||
}
|
||
if boostStatusTDesktop.Level != boostStatus.Level || boostStatusTDesktop.CurrentLevelBoosts != boostStatus.CurrentLevelBoosts || boostStatusTDesktop.Boosts != boostStatus.Boosts {
|
||
t.Fatalf("premium.getBoostsStatus TDesktop = %+v, want same real status as generic client %+v", boostStatusTDesktop, boostStatus)
|
||
}
|
||
boosts, err := r.onPremiumGetBoostsList(ownerCtx, &tg.PremiumGetBoostsListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("premium.getBoostsList: %v", err)
|
||
}
|
||
if boosts.Count != 0 || len(boosts.Boosts) != 0 || len(boosts.Users) != 0 {
|
||
t.Fatalf("premium.getBoostsList = %+v, want empty", boosts)
|
||
}
|
||
if _, err := r.onPremiumGetBoostsList(ownerCtx, &tg.PremiumGetBoostsListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: domain.MaxPremiumBoostsListLimit + 1,
|
||
}); err == nil || !strings.Contains(err.Error(), "LIMIT_INVALID") {
|
||
t.Fatalf("premium.getBoostsList huge limit err = %v, want LIMIT_INVALID", err)
|
||
}
|
||
if _, err := f.users.SetPremiumUntil(f.ctx, owner.ID, int(time.Now().Add(time.Hour).Unix())); err != nil {
|
||
t.Fatalf("grant owner premium: %v", err)
|
||
}
|
||
myBoosts, err := r.onPremiumGetMyBoosts(ownerCtx)
|
||
if err != nil {
|
||
t.Fatalf("premium.getMyBoosts: %v", err)
|
||
}
|
||
if len(myBoosts.MyBoosts) != 1 || myBoosts.MyBoosts[0].Slot != domain.DefaultPremiumBoostSlotID || myBoosts.MyBoosts[0].Date != 0 || myBoosts.MyBoosts[0].Expires != 0 {
|
||
t.Fatalf("premium.getMyBoosts = %+v, want one free base slot", myBoosts)
|
||
}
|
||
if _, ok := myBoosts.MyBoosts[0].GetPeer(); ok || len(myBoosts.Chats) != 0 || len(myBoosts.Users) != 0 {
|
||
t.Fatalf("premium.getMyBoosts free slot refs = %+v, want no peer refs", myBoosts)
|
||
}
|
||
applyReq := &tg.PremiumApplyBoostRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}
|
||
applyReq.SetSlots([]int{1})
|
||
applied, err := r.onPremiumApplyBoost(ownerCtx, applyReq)
|
||
if err != nil {
|
||
t.Fatalf("premium.applyBoost: %v", err)
|
||
}
|
||
if len(applied.MyBoosts) != 1 {
|
||
t.Fatalf("premium.applyBoost = %+v, want assigned base slot", applied)
|
||
}
|
||
appliedPeer, ok := applied.MyBoosts[0].GetPeer()
|
||
if !ok {
|
||
t.Fatalf("premium.applyBoost assigned slot missing peer: %+v", applied.MyBoosts[0])
|
||
}
|
||
if peer, ok := appliedPeer.(*tg.PeerChannel); !ok || peer.ChannelID != channel.ID {
|
||
t.Fatalf("premium.applyBoost peer = %#v, want channel %d", appliedPeer, channel.ID)
|
||
}
|
||
boostStatusAfterApply, err := r.onPremiumGetBoostsStatus(ownerCtx, inputPeerChannel(channel))
|
||
if err != nil {
|
||
t.Fatalf("premium.getBoostsStatus after apply: %v", err)
|
||
}
|
||
if boostStatusAfterApply.Boosts != 1 || !boostStatusAfterApply.MyBoost {
|
||
t.Fatalf("premium.getBoostsStatus after apply = %+v, want one self boost", boostStatusAfterApply)
|
||
}
|
||
if _, err := r.onPremiumApplyBoost(ownerCtx, applyReq); err == nil || !strings.Contains(err.Error(), "BOOST_NOT_MODIFIED") {
|
||
t.Fatalf("premium.applyBoost same peer err = %v, want BOOST_NOT_MODIFIED", err)
|
||
}
|
||
if _, err := r.onPremiumApplyBoost(ownerCtx, &tg.PremiumApplyBoostRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
}); err == nil || !strings.Contains(err.Error(), "BOOSTS_EMPTY") {
|
||
t.Fatalf("premium.applyBoost missing slots err = %v, want BOOSTS_EMPTY", err)
|
||
}
|
||
badSlotReq := &tg.PremiumApplyBoostRequest{Peer: inputPeerChannel(channel)}
|
||
badSlotReq.SetSlots([]int{2})
|
||
if _, err := r.onPremiumApplyBoost(ownerCtx, badSlotReq); err == nil || !strings.Contains(err.Error(), "SLOTS_INVALID") {
|
||
t.Fatalf("premium.applyBoost unsupported slot err = %v, want SLOTS_INVALID", err)
|
||
}
|
||
userBoosts, err := r.onPremiumGetUserBoosts(ownerCtx, &tg.PremiumGetUserBoostsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
UserID: &tg.InputUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("premium.getUserBoosts: %v", err)
|
||
}
|
||
if userBoosts.Count != 0 || len(userBoosts.Boosts) != 0 {
|
||
t.Fatalf("premium.getUserBoosts invited = %+v, want empty", userBoosts)
|
||
}
|
||
ownerBoosts, err := r.onPremiumGetUserBoosts(ownerCtx, &tg.PremiumGetUserBoostsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
UserID: inputUser(owner),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("premium.getUserBoosts owner: %v", err)
|
||
}
|
||
if ownerBoosts.Count != 1 || len(ownerBoosts.Boosts) != 1 {
|
||
t.Fatalf("premium.getUserBoosts owner = %+v, want one boost", ownerBoosts)
|
||
}
|
||
if boostedList, err := r.onPremiumGetBoostsList(ownerCtx, &tg.PremiumGetBoostsListRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
Limit: 10,
|
||
}); err != nil {
|
||
t.Fatalf("premium.getBoostsList after apply: %v", err)
|
||
} else if boostedList.Count != 1 || len(boostedList.Boosts) != 1 || len(boostedList.Users) != 1 {
|
||
t.Fatalf("premium.getBoostsList after apply = %+v, want one boost and user", boostedList)
|
||
}
|
||
if _, err := r.onMessagesAddChatUser(ownerCtx, &tg.MessagesAddChatUserRequest{
|
||
ChatID: channel.ID,
|
||
UserID: &tg.InputUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
}); err != nil {
|
||
t.Fatalf("messages.addChatUser legacy wrapper: %v", err)
|
||
}
|
||
seedPhoto := domain.Photo{ID: 7701, AccessHash: 7702, DCID: 2}
|
||
if _, err := f.channels.SetChannelPhoto(f.ctx, owner.ID, channel.ID, &seedPhoto, 1700007701); err != nil {
|
||
t.Fatalf("seed channel photo before legacy clear: %v", err)
|
||
}
|
||
if _, err := r.onMessagesEditChatPhoto(ownerCtx, &tg.MessagesEditChatPhotoRequest{
|
||
ChatID: channel.ID,
|
||
Photo: &tg.InputChatPhotoEmpty{},
|
||
}); err != nil {
|
||
t.Fatalf("messages.editChatPhoto legacy wrapper: %v", err)
|
||
}
|
||
if _, err := r.onChannelsEditPhoto(ownerCtx, &tg.ChannelsEditPhotoRequest{
|
||
Channel: inputChannel(channel),
|
||
Photo: &tg.InputChatUploadedPhoto{},
|
||
}); err == nil || !strings.Contains(err.Error(), "PHOTO_INVALID") {
|
||
t.Fatalf("channels.editPhoto uploaded photo err = %v, want PHOTO_INVALID", err)
|
||
}
|
||
if _, err := r.onChannelsEditPhoto(ownerCtx, &tg.ChannelsEditPhotoRequest{
|
||
Channel: inputChannel(channel),
|
||
Photo: &tg.InputChatPhoto{ID: &tg.InputPhoto{ID: 1, AccessHash: 2, FileReference: []byte{3}}},
|
||
}); err == nil || !strings.Contains(err.Error(), "PHOTO_INVALID") {
|
||
t.Fatalf("channels.editPhoto existing photo err = %v, want PHOTO_INVALID", err)
|
||
}
|
||
if ok, err := r.onMessagesEditChatAdmin(ownerCtx, &tg.MessagesEditChatAdminRequest{
|
||
ChatID: channel.ID,
|
||
UserID: &tg.InputUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
IsAdmin: true,
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.editChatAdmin legacy wrapper = %v, %v", ok, err)
|
||
}
|
||
if _, err := r.onMessagesEditChatParticipantRank(ownerCtx, &tg.MessagesEditChatParticipantRankRequest{
|
||
Peer: &tg.InputPeerChat{ChatID: channel.ID},
|
||
Participant: &tg.InputPeerUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
Rank: "ops",
|
||
}); err != nil {
|
||
t.Fatalf("messages.editChatParticipantRank legacy wrapper: %v", err)
|
||
}
|
||
if ok, err := r.onMessagesEditChatAbout(ownerCtx, &tg.MessagesEditChatAboutRequest{
|
||
Peer: &tg.InputPeerChat{ChatID: channel.ID},
|
||
About: "legacy about",
|
||
}); err != nil || !ok {
|
||
t.Fatalf("messages.editChatAbout legacy wrapper = %v, %v", ok, err)
|
||
}
|
||
fullAfterAbout, err := r.onMessagesGetFullChat(ownerCtx, channel.ID)
|
||
if err != nil {
|
||
t.Fatalf("messages.getFullChat after editChatAbout: %v", err)
|
||
}
|
||
if got := fullAfterAbout.FullChat.(*tg.ChannelFull).About; got != "legacy about" {
|
||
t.Fatalf("full chat about = %q, want legacy about", got)
|
||
}
|
||
if _, err := r.onMessagesEditChatCreator(ownerCtx, &tg.MessagesEditChatCreatorRequest{
|
||
Peer: &tg.InputPeerChat{ChatID: channel.ID},
|
||
UserID: &tg.InputUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
}); err == nil {
|
||
t.Fatalf("messages.editChatCreator = nil error, want explicit password/2FA error")
|
||
}
|
||
permUpdates, err := r.onMessagesEditChatDefaultBannedRights(ownerCtx, &tg.MessagesEditChatDefaultBannedRightsRequest{
|
||
Peer: inputPeerChannel(channel),
|
||
BannedRights: tg.ChatBannedRights{
|
||
SendMessages: true,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("messages.editChatDefaultBannedRights: %v", err)
|
||
}
|
||
if len(permUpdates.(*tg.Updates).Updates) == 0 {
|
||
t.Fatalf("messages.editChatDefaultBannedRights updates = %+v, want updateChannel", permUpdates)
|
||
}
|
||
if _, err := r.onMessagesDeleteChatUser(ownerCtx, &tg.MessagesDeleteChatUserRequest{
|
||
ChatID: channel.ID,
|
||
UserID: &tg.InputUser{UserID: invited.ID, AccessHash: invited.AccessHash},
|
||
}); err != nil {
|
||
t.Fatalf("messages.deleteChatUser legacy wrapper: %v", err)
|
||
}
|
||
}
|