owpengram-server/internal/rpc/messages_forward_rpc_test.go

350 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"github.com/gotd/td/bin"
"github.com/gotd/td/clock"
"github.com/gotd/td/tg"
"go.uber.org/zap/zaptest"
"strings"
appchannels "telesrv/internal/app/channels"
appusers "telesrv/internal/app/users"
"telesrv/internal/domain"
"telesrv/internal/store/memory"
"testing"
)
func TestMessagesForwardMessagesRecordsRequestAndReturnsUpdates(t *testing.T) {
const (
ownerID = int64(1000000001)
fromID = int64(1000000002)
toID = int64(1000000003)
)
// 私聊→私聊转发统一经 forwardSources 取源GetMessages再逐条 SendPrivateText
// 以便在 RPC 层对原作者做 PrivacyKeyForwards 降级,故必须提供源消息。
messages := &captureMessages{list: domain.MessageList{Messages: []domain.Message{
{ID: 3, OwnerUserID: ownerID, Peer: domain.Peer{Type: domain.PeerTypeUser, ID: fromID}, From: domain.Peer{Type: domain.PeerTypeUser, ID: fromID}, Date: 1700000000, Body: "first"},
{ID: 4, OwnerUserID: ownerID, Peer: domain.Peer{Type: domain.PeerTypeUser, ID: fromID}, From: domain.Peer{Type: domain.PeerTypeUser, ID: fromID}, Date: 1700000001, Body: "second"},
}}}
r := New(Config{}, Deps{
Messages: messages,
Users: mapUsersService{users: map[int64]domain.User{
ownerID: {ID: ownerID, FirstName: "Owner"},
fromID: {ID: fromID, FirstName: "From"},
toID: {ID: toID, FirstName: "To"},
}},
}, zaptest.NewLogger(t), clock.System)
req := &tg.MessagesForwardMessagesRequest{
FromPeer: &tg.InputPeerUser{UserID: fromID},
ToPeer: &tg.InputPeerUser{UserID: toID},
ID: []int{3, 4},
RandomID: []int64{1001, 1002},
Silent: true,
Noforwards: true,
}
replyTo := &tg.InputReplyToMessage{ReplyToMsgID: 9}
replyTo.SetQuoteText("target")
req.SetReplyTo(replyTo)
var in bin.Buffer
if err := req.Encode(&in); err != nil {
t.Fatalf("encode request: %v", err)
}
enc, err := r.Dispatch(WithUserID(context.Background(), ownerID), [8]byte{}, 99, &in)
if err != nil {
t.Fatalf("dispatch: %v", err)
}
if messages.sendUserID != ownerID || messages.sendReq.SenderUserID != ownerID || messages.sendReq.RecipientUserID != toID || messages.sendReq.OriginSessionID != 99 {
t.Fatalf("forward send = user %d %+v, want owner/to/session", messages.sendUserID, messages.sendReq)
}
if messages.sendReq.ReplyTo == nil || messages.sendReq.ReplyTo.MessageID != 9 || messages.sendReq.ReplyTo.Peer.ID != toID || messages.sendReq.ReplyTo.QuoteText != "target" {
t.Fatalf("forward reply = %+v, want target peer reply metadata", messages.sendReq.ReplyTo)
}
box, ok := enc.(*tg.UpdatesBox)
if !ok {
t.Fatalf("response = %T, want *tg.UpdatesBox", enc)
}
got := box.Updates.(*tg.Updates)
if len(got.Updates) != 4 {
t.Fatalf("updates = %+v, want two message ids and two new messages", got.Updates)
}
if id, ok := got.Updates[0].(*tg.UpdateMessageID); !ok || id.RandomID != 1001 {
t.Fatalf("first update = %#v, want updateMessageID random 1001", got.Updates[0])
}
newMsg := got.Updates[1].(*tg.UpdateNewMessage)
msg := newMsg.Message.(*tg.Message)
if msg.FwdFrom.Date == 0 || !msg.Silent || !msg.Noforwards {
t.Fatalf("forwarded message = %#v, want fwd header and flags", msg)
}
if header, ok := msg.ReplyTo.(*tg.MessageReplyHeader); !ok || header.ReplyToMsgID != 9 {
t.Fatalf("forwarded reply = %#v, want reply header id=9", msg.ReplyTo)
}
hasForwardAuthor := false
for _, user := range got.Users {
if u, ok := user.(*tg.User); ok && u.ID == fromID {
hasForwardAuthor = true
break
}
}
if !hasForwardAuthor {
t.Fatalf("forward users = %+v, want original author %d for fwd_from", got.Users, fromID)
}
}
func TestMessagesForwardMessagesLoadsPrivateSourcesInSingleBatch(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, err := userStore.Create(ctx, domain.User{
AccessHash: 41,
Phone: "15550004001",
FirstName: "ForwardOwner",
})
if err != nil {
t.Fatalf("create owner: %v", err)
}
sourceUser, err := userStore.Create(ctx, domain.User{
AccessHash: 42,
Phone: "15550004002",
FirstName: "ForwardSource",
})
if err != nil {
t.Fatalf("create source: %v", err)
}
channelSvc := appchannels.NewService(memory.NewChannelStore())
created, err := channelSvc.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
CreatorUserID: owner.ID,
Title: "Forward Batch",
Megagroup: true,
Date: 1700001200,
})
if err != nil {
t.Fatalf("create target channel: %v", err)
}
messages := &captureMessages{
getMessagesListed: true,
list: domain.MessageList{Messages: []domain.Message{
{
ID: 5,
OwnerUserID: owner.ID,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: sourceUser.ID},
From: domain.Peer{Type: domain.PeerTypeUser, ID: sourceUser.ID},
Date: 1700001195,
Body: "source five",
},
{
ID: 7,
OwnerUserID: owner.ID,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: sourceUser.ID},
From: domain.Peer{Type: domain.PeerTypeUser, ID: sourceUser.ID},
Date: 1700001197,
Body: "source seven",
},
}},
}
r := New(Config{}, Deps{
Users: appusers.NewService(userStore),
Messages: messages,
Channels: channelSvc,
}, zaptest.NewLogger(t), clock.System)
updatesClass, err := r.onMessagesForwardMessages(WithUserID(ctx, owner.ID), &tg.MessagesForwardMessagesRequest{
FromPeer: &tg.InputPeerUser{UserID: sourceUser.ID, AccessHash: sourceUser.AccessHash},
ToPeer: &tg.InputPeerChannel{
ChannelID: created.Channel.ID,
AccessHash: created.Channel.AccessHash,
},
ID: []int{7, 5},
RandomID: []int64{7001, 5001},
})
if err != nil {
t.Fatalf("forward private sources to channel: %v", err)
}
if messages.getMessagesCalls != 1 {
t.Fatalf("GetMessages calls = %d, want one batched source load", messages.getMessagesCalls)
}
if len(messages.getMessagesIDs) != 1 || len(messages.getMessagesIDs[0]) != 2 || messages.getMessagesIDs[0][0] != 7 || messages.getMessagesIDs[0][1] != 5 {
t.Fatalf("GetMessages ids = %+v, want [[7 5]]", messages.getMessagesIDs)
}
updates, ok := updatesClass.(*tg.Updates)
if !ok || len(updates.Updates) != 4 {
t.Fatalf("forward updates = %T %+v, want 4 updates", updatesClass, updatesClass)
}
firstID, ok := updates.Updates[0].(*tg.UpdateMessageID)
if !ok || firstID.RandomID != 7001 {
t.Fatalf("first update = %#v, want random 7001", updates.Updates[0])
}
firstNew, ok := updates.Updates[1].(*tg.UpdateNewChannelMessage)
if !ok {
t.Fatalf("second update = %#v, want updateNewChannelMessage", updates.Updates[1])
}
firstMsg, ok := firstNew.Message.(*tg.Message)
if !ok || firstMsg.Message != "source seven" {
t.Fatalf("first forwarded message = %#v, want source seven", firstNew.Message)
}
secondID, ok := updates.Updates[2].(*tg.UpdateMessageID)
if !ok || secondID.RandomID != 5001 {
t.Fatalf("third update = %#v, want random 5001", updates.Updates[2])
}
secondNew, ok := updates.Updates[3].(*tg.UpdateNewChannelMessage)
if !ok {
t.Fatalf("fourth update = %#v, want updateNewChannelMessage", updates.Updates[3])
}
secondMsg, ok := secondNew.Message.(*tg.Message)
if !ok || secondMsg.Message != "source five" {
t.Fatalf("second forwarded message = %#v, want source five", secondNew.Message)
}
}
func TestChatsForMessageUpdatesUsesBatchChannelProjection(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, err := userStore.Create(ctx, domain.User{
AccessHash: 51,
Phone: "15550005001",
FirstName: "BatchOwner",
})
if err != nil {
t.Fatalf("create owner: %v", err)
}
channelStore := memory.NewChannelStore()
channelService := appchannels.NewService(channelStore)
first, err := channelService.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
CreatorUserID: owner.ID,
Title: "Message Ref One",
Broadcast: true,
Date: 1700001500,
})
if err != nil {
t.Fatalf("create first channel: %v", err)
}
second, err := channelService.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
CreatorUserID: owner.ID,
Title: "Message Ref Two",
Megagroup: true,
Date: 1700001510,
})
if err != nil {
t.Fatalf("create second channel: %v", err)
}
counting := &countingChannelsService{Service: channelService}
r := New(Config{}, Deps{
Users: appusers.NewService(userStore),
Channels: counting,
}, zaptest.NewLogger(t), clock.System)
chats := r.chatsForMessageUpdates(ctx, owner.ID, []domain.Message{
{
From: domain.Peer{Type: domain.PeerTypeChannel, ID: first.Channel.ID},
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
Forward: &domain.MessageForward{
From: domain.Peer{Type: domain.PeerTypeChannel, ID: second.Channel.ID},
},
},
{
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: first.Channel.ID},
ReplyTo: &domain.MessageReply{
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: second.Channel.ID},
},
},
})
if len(chats) != 2 {
t.Fatalf("chats = %d, want two unique channel refs", len(chats))
}
firstChat, ok := chats[0].(*tg.Channel)
if !ok || firstChat.ID != first.Channel.ID {
t.Fatalf("first chat = %#v, want first channel", chats[0])
}
secondChat, ok := chats[1].(*tg.Channel)
if !ok || secondChat.ID != second.Channel.ID {
t.Fatalf("second chat = %#v, want second channel", chats[1])
}
if counting.getChannelsCalls != 1 || counting.getChannelCalls != 0 {
t.Fatalf("channel service calls: GetChannels=%d GetChannel=%d, want one batch call only", counting.getChannelsCalls, counting.getChannelCalls)
}
}
func TestMessagesForwardMessagesUnsupportedOptionErrors(t *testing.T) {
const ownerID = int64(1000000001)
ctx := WithUserID(context.Background(), ownerID)
r := New(Config{}, Deps{}, zaptest.NewLogger(t), clock.System)
base := func() *tg.MessagesForwardMessagesRequest {
return &tg.MessagesForwardMessagesRequest{
FromPeer: &tg.InputPeerUser{UserID: 1000000002},
ToPeer: &tg.InputPeerUser{UserID: 1000000003},
ID: []int{3},
RandomID: []int64{1001},
}
}
suggested := func() tg.SuggestedPost {
post := tg.SuggestedPost{}
post.SetAccepted(true)
return post
}
cases := []struct {
name string
req *tg.MessagesForwardMessagesRequest
want string
}{
{
name: "quick reply",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetQuickReplyShortcut(&tg.InputQuickReplyShortcut{Shortcut: "hello"})
return req
}(),
want: "SHORTCUT_INVALID",
},
{
name: "effect",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetEffect(1)
return req
}(),
want: "EFFECT_ID_INVALID",
},
{
name: "video timestamp without media model",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetVideoTimestamp(10)
return req
}(),
want: "MEDIA_INVALID",
},
{
name: "negative paid stars",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetAllowPaidStars(-1)
return req
}(),
want: "STARS_AMOUNT_INVALID",
},
{
name: "paid floodskip",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetAllowPaidFloodskip(true)
return req
}(),
want: "PAYMENT_UNSUPPORTED",
},
{
name: "suggested post",
req: func() *tg.MessagesForwardMessagesRequest {
req := base()
req.SetSuggestedPost(suggested())
return req
}(),
want: "SUGGESTED_POST_PEER_INVALID",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if _, err := r.onMessagesForwardMessages(ctx, tc.req); err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("forward err = %v, want %s", err, tc.want)
}
})
}
}