chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,350 @@
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)
}
})
}
}