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>
295 lines
11 KiB
Go
295 lines
11 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/iamxvbaba/td/clock"
|
|
"github.com/iamxvbaba/td/tg"
|
|
"go.uber.org/zap/zaptest"
|
|
|
|
accountapp "telesrv/internal/app/account"
|
|
appusers "telesrv/internal/app/users"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store/memory"
|
|
)
|
|
|
|
func TestQuickReplyRPCSaveListAndDeleteMessage(t *testing.T) {
|
|
const userID int64 = domain.UserIDSequenceBase
|
|
ctx := WithSessionID(WithAuthKeyID(WithUserID(context.Background(), userID), [8]byte{1}), 77)
|
|
r, updates := newChatAutomationTestRouter(t)
|
|
verify := newFakeBotVerifications()
|
|
r.deps.BotVerifications = verify
|
|
userStore := memory.NewUserStore()
|
|
self, err := userStore.Create(context.Background(), domain.User{
|
|
AccessHash: 7000001,
|
|
Phone: "15550007001",
|
|
FirstName: "Quick",
|
|
})
|
|
if err != nil || self.ID != userID {
|
|
t.Fatalf("create quick-reply user = %+v err %v, want id %d", self, err, userID)
|
|
}
|
|
r.deps.Users = appusers.NewService(userStore)
|
|
const quickReplyIcon = int64(8800023)
|
|
quickReplyPeer := domain.Peer{Type: domain.PeerTypeUser, ID: userID}
|
|
verify.marks[quickReplyPeer] = domain.CustomVerification{
|
|
VerifierBotID: 777000123,
|
|
Peer: quickReplyPeer,
|
|
IconDocumentID: quickReplyIcon,
|
|
Description: "Verified quick-reply peer",
|
|
}
|
|
|
|
got, err := r.onMessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
|
|
Peer: &tg.InputPeerSelf{},
|
|
Message: "Saved template",
|
|
RandomID: 12345,
|
|
QuickReplyShortcut: &tg.InputQuickReplyShortcut{Shortcut: "hello"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesSendMessage quick reply: %v", err)
|
|
}
|
|
result, ok := got.(*tg.Updates)
|
|
if !ok {
|
|
t.Fatalf("result type = %T, want *tg.Updates", got)
|
|
}
|
|
var messageID int
|
|
var shortcutID int
|
|
var sawNew bool
|
|
var sawMessageID bool
|
|
for _, update := range result.Updates {
|
|
switch u := update.(type) {
|
|
case *tg.UpdateMessageID:
|
|
sawMessageID = true
|
|
messageID = u.ID
|
|
if u.RandomID != 12345 {
|
|
t.Fatalf("UpdateMessageID random_id = %d", u.RandomID)
|
|
}
|
|
case *tg.UpdateNewQuickReply:
|
|
sawNew = true
|
|
shortcutID = u.QuickReply.ShortcutID
|
|
if u.QuickReply.Shortcut != "hello" {
|
|
t.Fatalf("UpdateNewQuickReply shortcut = %q", u.QuickReply.Shortcut)
|
|
}
|
|
}
|
|
}
|
|
if !sawMessageID || !sawNew || messageID == 0 || shortcutID == 0 {
|
|
t.Fatalf("updates = %#v, want updateMessageID and updateNewQuickReply", result.Updates)
|
|
}
|
|
if len(updates.events) != 1 || updates.events[0].Type != domain.UpdateEventNewQuickReply {
|
|
t.Fatalf("recorded events = %+v", updates.events)
|
|
}
|
|
|
|
list, err := r.onMessagesGetQuickReplies(ctx, 0)
|
|
if err != nil {
|
|
t.Fatalf("onMessagesGetQuickReplies: %v", err)
|
|
}
|
|
replies, ok := list.(*tg.MessagesQuickReplies)
|
|
if !ok || len(replies.QuickReplies) != 1 || len(replies.Messages) != 1 {
|
|
t.Fatalf("quick replies = %#v", list)
|
|
}
|
|
|
|
quickReplyMessages, err := r.onMessagesGetQuickReplyMessages(ctx, &tg.MessagesGetQuickReplyMessagesRequest{
|
|
ShortcutID: shortcutID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesGetQuickReplyMessages: %v", err)
|
|
}
|
|
assertMessagesEnvelopeBotVerificationIcon(t, quickReplyMessages, quickReplyPeer, quickReplyIcon)
|
|
if verify.batchCalls != 0 || verify.peerCalls != 1 {
|
|
t.Fatalf("quick-reply verification reads = batch %d peer %d, want 0/1 for one peer", verify.batchCalls, verify.peerCalls)
|
|
}
|
|
|
|
deleted, err := r.onMessagesDeleteQuickReplyMessages(ctx, &tg.MessagesDeleteQuickReplyMessagesRequest{
|
|
ShortcutID: shortcutID,
|
|
ID: []int{messageID},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesDeleteQuickReplyMessages: %v", err)
|
|
}
|
|
deleteUpdates, ok := deleted.(*tg.Updates)
|
|
if !ok {
|
|
t.Fatalf("delete result type = %T", deleted)
|
|
}
|
|
var sawDelete bool
|
|
for _, update := range deleteUpdates.Updates {
|
|
if u, ok := update.(*tg.UpdateDeleteQuickReplyMessages); ok {
|
|
sawDelete = true
|
|
if u.ShortcutID != shortcutID || len(u.Messages) != 1 || u.Messages[0] != messageID {
|
|
t.Fatalf("delete update = %+v", u)
|
|
}
|
|
}
|
|
}
|
|
if !sawDelete {
|
|
t.Fatalf("delete updates = %#v, want updateDeleteQuickReplyMessages", deleteUpdates.Updates)
|
|
}
|
|
}
|
|
|
|
func TestBusinessChatLinkRPCs(t *testing.T) {
|
|
const userID int64 = 1000000002
|
|
ctx := WithUserID(context.Background(), userID)
|
|
r, _ := newChatAutomationTestRouter(t)
|
|
|
|
created, err := r.onAccountCreateBusinessChatLink(ctx, tg.InputBusinessChatLink{
|
|
Message: "Prefilled message",
|
|
Title: "Support",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("onAccountCreateBusinessChatLink: %v", err)
|
|
}
|
|
if created.Link == "" || created.Message != "Prefilled message" {
|
|
t.Fatalf("created link = %+v", created)
|
|
}
|
|
slug := strings.TrimPrefix(created.Link, "https://telesrv.net/m/")
|
|
if slug == created.Link || slug == "" {
|
|
t.Fatalf("created link URL = %q, want telesrv.net/m slug", created.Link)
|
|
}
|
|
list, err := r.onAccountGetBusinessChatLinks(ctx)
|
|
if err != nil || len(list.Links) != 1 {
|
|
t.Fatalf("onAccountGetBusinessChatLinks len=%d err=%v", len(list.Links), err)
|
|
}
|
|
resolved, err := r.onAccountResolveBusinessChatLink(ctx, slug)
|
|
if err != nil {
|
|
t.Fatalf("onAccountResolveBusinessChatLink: %v", err)
|
|
}
|
|
peer, ok := resolved.Peer.(*tg.PeerUser)
|
|
if !ok || peer.UserID != userID || resolved.Message != "Prefilled message" {
|
|
t.Fatalf("resolved = %+v", resolved)
|
|
}
|
|
list, err = r.onAccountGetBusinessChatLinks(ctx)
|
|
if err != nil || len(list.Links) != 1 || list.Links[0].Views != 1 {
|
|
t.Fatalf("post-resolve links = %+v err=%v", list, err)
|
|
}
|
|
if deleted, err := r.onAccountDeleteBusinessChatLink(ctx, slug); err != nil || !deleted {
|
|
t.Fatalf("onAccountDeleteBusinessChatLink deleted=%v err=%v", deleted, err)
|
|
}
|
|
}
|
|
|
|
func TestConnectedBusinessBotRPCFlow(t *testing.T) {
|
|
const ownerID int64 = 1000000010
|
|
const peerID int64 = 1000000011
|
|
const botID int64 = 1000000012
|
|
ctx := WithSessionID(WithAuthKeyID(WithUserID(context.Background(), ownerID), [8]byte{2}), 88)
|
|
store := memory.NewPasswordStore()
|
|
updates := &captureUpdates{state: domain.UpdateState{Pts: 20, Date: 1700000000}}
|
|
users := mapUsersService{users: map[int64]domain.User{
|
|
ownerID: {ID: ownerID, AccessHash: 101, FirstName: "Bob"},
|
|
peerID: {ID: peerID, AccessHash: 102, FirstName: "Alice"},
|
|
botID: {ID: botID, AccessHash: 103, FirstName: "Echo", Username: "echo_test_bot", Bot: true, BotInfoVersion: 1},
|
|
}}
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
|
|
Account: accountapp.NewService(store, accountapp.WithBusinessAutomation(store)),
|
|
Users: users,
|
|
Updates: updates,
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
|
|
updateReq := &tg.AccountUpdateConnectedBotRequest{
|
|
Bot: &tg.InputUser{UserID: botID, AccessHash: 103},
|
|
Recipients: tg.InputBusinessBotRecipients{ExcludeSelected: true},
|
|
}
|
|
updateReq.SetRights(tg.BusinessBotRights{Reply: true})
|
|
if _, err := r.onAccountUpdateConnectedBot(ctx, updateReq); err != nil {
|
|
t.Fatalf("onAccountUpdateConnectedBot: %v", err)
|
|
}
|
|
connected, err := r.onAccountGetConnectedBots(ctx)
|
|
if err != nil {
|
|
t.Fatalf("onAccountGetConnectedBots: %v", err)
|
|
}
|
|
if len(connected.ConnectedBots) != 1 || connected.ConnectedBots[0].BotID != botID || !connected.ConnectedBots[0].Rights.Reply {
|
|
t.Fatalf("connected bots = %+v", connected.ConnectedBots)
|
|
}
|
|
botUser, ok := connected.Users[0].(*tg.User)
|
|
if !ok || !botUser.Bot || !botUser.BotBusiness {
|
|
t.Fatalf("connected bot user = %#v, want bot_business", connected.Users[0])
|
|
}
|
|
|
|
peerSettings, err := r.onMessagesGetPeerSettings(ctx, &tg.InputPeerUser{UserID: peerID, AccessHash: 102})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesGetPeerSettings: %v", err)
|
|
}
|
|
if peerSettings.Settings.BusinessBotID != botID || !peerSettings.Settings.BusinessBotCanReply || peerSettings.Settings.BusinessBotPaused {
|
|
t.Fatalf("peer settings before pause = %+v", peerSettings.Settings)
|
|
}
|
|
|
|
if ok, err := r.onAccountToggleConnectedBotPaused(ctx, &tg.AccountToggleConnectedBotPausedRequest{
|
|
Peer: &tg.InputPeerUser{UserID: peerID, AccessHash: 102},
|
|
Paused: true,
|
|
}); err != nil || !ok {
|
|
t.Fatalf("onAccountToggleConnectedBotPaused = %v,%v", ok, err)
|
|
}
|
|
peerSettings, err = r.onMessagesGetPeerSettings(ctx, &tg.InputPeerUser{UserID: peerID, AccessHash: 102})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesGetPeerSettings paused: %v", err)
|
|
}
|
|
if peerSettings.Settings.BusinessBotID != botID || !peerSettings.Settings.BusinessBotPaused || peerSettings.Settings.BusinessBotCanReply {
|
|
t.Fatalf("peer settings paused = %+v", peerSettings.Settings)
|
|
}
|
|
|
|
if ok, err := r.onAccountDisablePeerConnectedBot(ctx, &tg.InputPeerUser{UserID: peerID, AccessHash: 102}); err != nil || !ok {
|
|
t.Fatalf("onAccountDisablePeerConnectedBot = %v,%v", ok, err)
|
|
}
|
|
peerSettings, err = r.onMessagesGetPeerSettings(ctx, &tg.InputPeerUser{UserID: peerID, AccessHash: 102})
|
|
if err != nil {
|
|
t.Fatalf("onMessagesGetPeerSettings disabled: %v", err)
|
|
}
|
|
if peerSettings.Settings.BusinessBotID != 0 || peerSettings.Settings.BusinessBotCanReply || peerSettings.Settings.BusinessBotPaused {
|
|
t.Fatalf("peer settings disabled = %+v", peerSettings.Settings)
|
|
}
|
|
}
|
|
|
|
func TestConnectedBusinessBotDefaultsMissingRightsToReply(t *testing.T) {
|
|
const ownerID int64 = 1000000110
|
|
const peerID int64 = 1000000111
|
|
const botID int64 = 1000000112
|
|
ctx := WithSessionID(WithAuthKeyID(WithUserID(context.Background(), ownerID), [8]byte{3}), 89)
|
|
store := memory.NewPasswordStore()
|
|
users := mapUsersService{users: map[int64]domain.User{
|
|
ownerID: {ID: ownerID, AccessHash: 101, FirstName: "Bob"},
|
|
peerID: {ID: peerID, AccessHash: 102, FirstName: "Alice"},
|
|
botID: {ID: botID, AccessHash: 103, FirstName: "Echo", Username: "echo_default_bot", Bot: true, BotInfoVersion: 1},
|
|
}}
|
|
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
|
|
Account: accountapp.NewService(store, accountapp.WithBusinessAutomation(store)),
|
|
Users: users,
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
|
|
if _, err := r.onAccountUpdateConnectedBot(ctx, &tg.AccountUpdateConnectedBotRequest{
|
|
Bot: &tg.InputUser{UserID: botID, AccessHash: 103},
|
|
Recipients: tg.InputBusinessBotRecipients{ExcludeSelected: true},
|
|
}); err != nil {
|
|
t.Fatalf("onAccountUpdateConnectedBot missing rights: %v", err)
|
|
}
|
|
connected, err := r.onAccountGetConnectedBots(ctx)
|
|
if err != nil {
|
|
t.Fatalf("onAccountGetConnectedBots: %v", err)
|
|
}
|
|
if len(connected.ConnectedBots) != 1 || !connected.ConnectedBots[0].Rights.Reply {
|
|
t.Fatalf("missing rights connected bots = %+v, want reply default", connected.ConnectedBots)
|
|
}
|
|
|
|
explicitEmpty := &tg.AccountUpdateConnectedBotRequest{
|
|
Bot: &tg.InputUser{UserID: botID, AccessHash: 103},
|
|
Recipients: tg.InputBusinessBotRecipients{ExcludeSelected: true},
|
|
}
|
|
explicitEmpty.SetRights(tg.BusinessBotRights{})
|
|
if _, err := r.onAccountUpdateConnectedBot(ctx, explicitEmpty); err != nil {
|
|
t.Fatalf("onAccountUpdateConnectedBot explicit empty rights: %v", err)
|
|
}
|
|
connected, err = r.onAccountGetConnectedBots(ctx)
|
|
if err != nil {
|
|
t.Fatalf("onAccountGetConnectedBots explicit empty: %v", err)
|
|
}
|
|
if len(connected.ConnectedBots) != 1 || connected.ConnectedBots[0].Rights.Reply {
|
|
t.Fatalf("explicit empty rights connected bots = %+v, want reply disabled", connected.ConnectedBots)
|
|
}
|
|
}
|
|
|
|
func newChatAutomationTestRouter(t *testing.T) (*Router, *captureUpdates) {
|
|
t.Helper()
|
|
store := memory.NewPasswordStore()
|
|
updates := &captureUpdates{state: domain.UpdateState{Pts: 10, Date: 1700000000}}
|
|
return New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
|
|
Account: accountapp.NewService(store, accountapp.WithBusinessAutomation(store)),
|
|
Updates: updates,
|
|
}, zaptest.NewLogger(t), clock.System), updates
|
|
}
|