merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
332
internal/rpc/welcome_messages_rpc_test.go
Normal file
332
internal/rpc/welcome_messages_rpc_test.go
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/iamxvbaba/td/bin"
|
||||
"github.com/iamxvbaba/td/clock"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"github.com/iamxvbaba/td/tgerr"
|
||||
"github.com/iamxvbaba/td/tlprofile"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
type welcomeRPCService struct {
|
||||
messages []domain.WelcomeMessage
|
||||
hash int64
|
||||
hasAny bool
|
||||
authorizeErr error
|
||||
}
|
||||
|
||||
func (s *welcomeRPCService) Authorize(context.Context, int64, domain.Peer) error {
|
||||
return s.authorizeErr
|
||||
}
|
||||
|
||||
func (s *welcomeRPCService) Create(_ context.Context, userID int64, peer domain.Peer, randomID int64, content domain.WelcomeMessageContent) (domain.WelcomeMessage, bool, error) {
|
||||
s.hash++
|
||||
message := domain.WelcomeMessage{
|
||||
ID: len(s.messages) + 1, Peer: peer, CreatorUserID: userID, Date: 1700000100,
|
||||
RandomID: randomID, Content: content, CreateFingerprint: [32]byte{1}, Version: 1,
|
||||
}
|
||||
s.messages = append(s.messages, message)
|
||||
s.hasAny = true
|
||||
return message, true, nil
|
||||
}
|
||||
func (s *welcomeRPCService) Edit(_ context.Context, _ int64, _ domain.Peer, id int, fields domain.WelcomeMessageEditFields) (domain.WelcomeMessage, error) {
|
||||
for index := range s.messages {
|
||||
if s.messages[index].ID == id {
|
||||
content, err := fields.Apply(s.messages[index].Content)
|
||||
if err != nil {
|
||||
return domain.WelcomeMessage{}, err
|
||||
}
|
||||
s.messages[index].Content = content
|
||||
s.messages[index].EditDate = 1700000101
|
||||
s.messages[index].Version++
|
||||
s.hash++
|
||||
return s.messages[index], nil
|
||||
}
|
||||
}
|
||||
return domain.WelcomeMessage{}, domain.ErrWelcomeMessageNotFound
|
||||
}
|
||||
func (s *welcomeRPCService) List(_ context.Context, _ int64, _ domain.Peer, hash int64) (domain.WelcomeMessageList, error) {
|
||||
if hash == s.hash {
|
||||
return domain.WelcomeMessageList{Hash: s.hash, NotModified: true}, nil
|
||||
}
|
||||
return domain.WelcomeMessageList{Hash: s.hash, Messages: append([]domain.WelcomeMessage(nil), s.messages...)}, nil
|
||||
}
|
||||
func (s *welcomeRPCService) Delete(_ context.Context, _ int64, _ domain.Peer, id int) (bool, error) {
|
||||
for index := range s.messages {
|
||||
if s.messages[index].ID == id {
|
||||
s.messages = append(s.messages[:index], s.messages[index+1:]...)
|
||||
s.hash++
|
||||
s.hasAny = len(s.messages) != 0
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func (s *welcomeRPCService) DeleteAll(context.Context, int64, domain.Peer) (bool, error) {
|
||||
if len(s.messages) != 0 {
|
||||
s.messages = nil
|
||||
s.hash++
|
||||
}
|
||||
s.hasAny = false
|
||||
return true, nil
|
||||
}
|
||||
func (s *welcomeRPCService) HasAny(context.Context, domain.Peer) (bool, error) {
|
||||
return s.hasAny, nil
|
||||
}
|
||||
|
||||
type welcomeRPCChannels struct {
|
||||
ChannelsService
|
||||
view domain.ChannelView
|
||||
}
|
||||
|
||||
func (s *welcomeRPCChannels) ResolveChannel(context.Context, int64, int64) (domain.ChannelView, error) {
|
||||
return s.view, nil
|
||||
}
|
||||
func (s *welcomeRPCChannels) GetChannel(context.Context, int64, int64) (domain.ChannelView, error) {
|
||||
return s.view, nil
|
||||
}
|
||||
|
||||
type welcomeRPCUsers struct {
|
||||
user domain.User
|
||||
}
|
||||
|
||||
func (s *welcomeRPCUsers) Self(context.Context, int64) (domain.User, error) {
|
||||
return s.user, nil
|
||||
}
|
||||
func (s *welcomeRPCUsers) ByID(context.Context, int64, int64) (domain.User, bool, error) {
|
||||
return s.user, true, nil
|
||||
}
|
||||
func (s *welcomeRPCUsers) ByIDs(context.Context, int64, []int64) ([]domain.User, error) {
|
||||
return []domain.User{s.user}, nil
|
||||
}
|
||||
|
||||
func newWelcomeRPCRouter(t *testing.T) (*Router, *welcomeRPCService, context.Context, *tg.InputPeerChannel) {
|
||||
t.Helper()
|
||||
const userID, channelID, accessHash = int64(9), int64(77), int64(88)
|
||||
service := &welcomeRPCService{hash: domain.InitialWelcomeRevision}
|
||||
channels := &welcomeRPCChannels{view: domain.ChannelView{
|
||||
Channel: domain.Channel{ID: channelID, AccessHash: accessHash, Title: "Welcome", Megagroup: true, Pts: 1},
|
||||
Self: domain.ChannelMember{
|
||||
ChannelID: channelID, UserID: userID, Role: domain.ChannelRoleCreator, Status: domain.ChannelMemberActive,
|
||||
},
|
||||
}}
|
||||
users := &welcomeRPCUsers{user: domain.User{ID: userID, AccessHash: 99, FirstName: "Owner"}}
|
||||
router := New(Config{DC: 2}, Deps{WelcomeMessages: service, Channels: channels, Users: users}, zaptest.NewLogger(t), clock.System)
|
||||
return router, service, WithUserID(context.Background(), userID), &tg.InputPeerChannel{ChannelID: channelID, AccessHash: accessHash}
|
||||
}
|
||||
|
||||
func TestWelcomeMessageRPCTextMediaRichAndCRUD(t *testing.T) {
|
||||
router, service, ctx, peer := newWelcomeRPCRouter(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
request *tg.EphemeralSendMessageRequest
|
||||
assert func(*testing.T, tg.EphemeralMessage)
|
||||
}{
|
||||
{
|
||||
name: "text",
|
||||
request: &tg.EphemeralSendMessageRequest{
|
||||
Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, Message: "Hello", RandomID: 1001,
|
||||
},
|
||||
assert: func(t *testing.T, message tg.EphemeralMessage) {
|
||||
if message.Message != "Hello" || message.Media != nil || !message.RichMessage.Zero() {
|
||||
t.Fatalf("text welcome = %+v", message)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
request: &tg.EphemeralSendMessageRequest{
|
||||
Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, Message: "Contact", RandomID: 1002,
|
||||
Media: &tg.InputMediaContact{PhoneNumber: "+10000000000", FirstName: "Guest"},
|
||||
},
|
||||
assert: func(t *testing.T, message tg.EphemeralMessage) {
|
||||
if message.Media == nil {
|
||||
t.Fatalf("media welcome = %+v", message)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rich",
|
||||
request: &tg.EphemeralSendMessageRequest{
|
||||
Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, RandomID: 1003,
|
||||
RichMessage: &tg.InputRichMessage{Blocks: []tg.PageBlockClass{
|
||||
&tg.PageBlockParagraph{Text: &tg.TextPlain{Text: "Rich welcome"}},
|
||||
}},
|
||||
},
|
||||
assert: func(t *testing.T, message tg.EphemeralMessage) {
|
||||
if message.RichMessage.Zero() || len(message.RichMessage.Blocks) != 1 {
|
||||
t.Fatalf("rich welcome = %+v", message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
updates, err := router.onEphemeralSendMessage(ctx, test.request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, ok := updates.(*tg.Updates)
|
||||
if !ok || len(result.Updates) != 1 || result.Seq != 0 {
|
||||
t.Fatalf("send updates = %#v", updates)
|
||||
}
|
||||
created, ok := result.Updates[0].(*tg.UpdateNewEphemeralMessage)
|
||||
if !ok || !created.Message.WelcomeTemplate || created.Message.PeerID == nil ||
|
||||
created.Message.ReceiverID != 0 || !created.Message.Out {
|
||||
t.Fatalf("new welcome update = %#v", result.Updates[0])
|
||||
}
|
||||
test.assert(t, created.Message)
|
||||
})
|
||||
}
|
||||
|
||||
edit := &tg.EphemeralEditMessageRequest{
|
||||
Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, ID: 1,
|
||||
}
|
||||
edit.SetMessage("Edited")
|
||||
editedUpdates, err := router.onEphemeralEditWelcomeMessage(ctx, edit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
edited := editedUpdates.(*tg.Updates).Updates[0].(*tg.UpdateEditEphemeralMessage).Message
|
||||
if edited.Message != "Edited" || !edited.WelcomeTemplate {
|
||||
t.Fatalf("edited welcome = %+v", edited)
|
||||
}
|
||||
|
||||
listed, err := router.onEphemeralGetWelcomeMessages(ctx, &tg.EphemeralGetWelcomeMessagesRequest{Peer: peer, Hash: 0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
modified, ok := listed.(*tg.EphemeralWelcomeMessages)
|
||||
if !ok || len(modified.Messages) != 3 || modified.Hash != service.hash {
|
||||
t.Fatalf("listed welcomes = %#v", listed)
|
||||
}
|
||||
if same, err := router.onEphemeralGetWelcomeMessages(ctx, &tg.EphemeralGetWelcomeMessagesRequest{Peer: peer, Hash: service.hash}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, ok := same.(*tg.EphemeralWelcomeMessagesNotModified); !ok {
|
||||
t.Fatalf("same hash = %#v", same)
|
||||
}
|
||||
if ok, err := router.onEphemeralDeleteWelcomeMessage(ctx, &tg.EphemeralDeleteWelcomeMessageRequest{Peer: peer, ID: 1}); err != nil || !ok {
|
||||
t.Fatalf("delete = %v,%v", ok, err)
|
||||
}
|
||||
if ok, err := router.onEphemeralDeleteAllWelcomeMessages(ctx, &tg.EphemeralDeleteAllWelcomeMessagesRequest{Peer: peer}); err != nil || !ok {
|
||||
t.Fatalf("delete all = %v,%v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWelcomeMessageFullChatProjectionAndAdminRight(t *testing.T) {
|
||||
router, service, ctx, _ := newWelcomeRPCRouter(t)
|
||||
channelFull := &tg.ChannelFull{}
|
||||
chatFull := &tg.ChatFull{}
|
||||
service.hasAny = true
|
||||
if err := router.applyWelcomeMessagesToFullChat(ctx, 77, channelFull); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := router.applyWelcomeMessagesToFullChat(ctx, 77, chatFull); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !channelFull.HasWelcomeMessages || !chatFull.HasWelcomeMessages {
|
||||
t.Fatalf("full projections channel=%v chat=%v", channelFull.HasWelcomeMessages, chatFull.HasWelcomeMessages)
|
||||
}
|
||||
service.hasAny = false
|
||||
if err := router.applyWelcomeMessagesToFullChat(ctx, 77, channelFull); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if channelFull.HasWelcomeMessages {
|
||||
t.Fatal("deleted templates left stale channelFull.has_welcome_messages")
|
||||
}
|
||||
rights := tgChatAdminRights(domain.ChannelAdminRights{ManageWelcomeMessages: true})
|
||||
if !rights.ManageWelcomeMessages || !domainChannelAdminRights(rights).ManageWelcomeMessages {
|
||||
t.Fatalf("manage_welcome_messages conversion = %+v", rights)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWelcomeMessageAuthorizationPrecedesRichMaterialization(t *testing.T) {
|
||||
router, service, ctx, peer := newWelcomeRPCRouter(t)
|
||||
service.authorizeErr = domain.ErrWelcomeMessageForbidden
|
||||
request := &tg.EphemeralSendMessageRequest{
|
||||
Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, RandomID: 1001,
|
||||
// This rich payload is deliberately invalid. CHAT_ADMIN_REQUIRED proves
|
||||
// the permission gate ran before rich parsing or media resolution.
|
||||
RichMessage: &tg.InputRichMessage{},
|
||||
}
|
||||
if _, err := router.onEphemeralSendMessage(ctx, request); err == nil || !tgerr.Is(err, "CHAT_ADMIN_REQUIRED") {
|
||||
t.Fatalf("unauthorized invalid rich send err=%v, want CHAT_ADMIN_REQUIRED", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWelcomeMessagesExactLayer229Only(t *testing.T) {
|
||||
router := New(Config{DC: 2}, Deps{}, zaptest.NewLogger(t), clock.System)
|
||||
peer := &tg.InputPeerChannel{ChannelID: 77, AccessHash: 88}
|
||||
methods := []struct {
|
||||
name string
|
||||
request bin.Object
|
||||
semantic tlprofile.SemanticID
|
||||
}{
|
||||
{"send", &tg.EphemeralSendMessageRequest{Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, Message: "Hello", RandomID: 1001}, tlprofile.SemanticMethodEphemeralSendMessage},
|
||||
{"edit", &tg.EphemeralEditMessageRequest{Welcome: true, Peer: peer, ReceiverID: &tg.InputUserEmpty{}, ID: 1}, tlprofile.SemanticMethodEphemeralEditMessage},
|
||||
{"delete", &tg.EphemeralDeleteWelcomeMessageRequest{Peer: peer, ID: 1}, tlprofile.SemanticMethodEphemeralDeleteWelcomeMessage},
|
||||
{"delete-all", &tg.EphemeralDeleteAllWelcomeMessagesRequest{Peer: peer}, tlprofile.SemanticMethodEphemeralDeleteAllWelcomeMessages},
|
||||
{"get", &tg.EphemeralGetWelcomeMessagesRequest{Peer: peer, Hash: 0}, tlprofile.SemanticMethodEphemeralGetWelcomeMessages},
|
||||
}
|
||||
var getAdmission tlprofile.Admission
|
||||
hasGetAdmission := false
|
||||
for _, method := range methods {
|
||||
t.Run(method.name, func(t *testing.T) {
|
||||
body := encodeExactLayerRPC(t, tlprofile.Profile229, method.request)
|
||||
raw := body.Copy()
|
||||
admission, err := router.AdmitLayer(tlprofile.Profile229, &body, tlprofile.Limits{})
|
||||
if err != nil || admission.Call().Method() != method.semantic || body.Len() != 0 {
|
||||
t.Fatalf("Layer 229 admission method=%#x want=%#x remaining=%d err=%v", admission.Call().Method(), method.semantic, body.Len(), err)
|
||||
}
|
||||
if method.semantic == tlprofile.SemanticMethodEphemeralGetWelcomeMessages {
|
||||
getAdmission = admission
|
||||
hasGetAdmission = true
|
||||
}
|
||||
for _, profile := range []tlprofile.Profile{tlprofile.Profile225, tlprofile.Profile226, tlprofile.Profile227, tlprofile.Profile228} {
|
||||
older := bin.Buffer{Buf: append([]byte(nil), raw...)}
|
||||
if _, err := router.AdmitLayer(profile, &older, tlprofile.Limits{}); err == nil {
|
||||
t.Fatalf("exact Layer %d admitted Layer 229 %s RPC", profile, method.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if !hasGetAdmission {
|
||||
t.Fatal("missing Layer 229 getWelcomeMessages admission")
|
||||
}
|
||||
|
||||
message := domain.WelcomeMessage{
|
||||
ID: 1, Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 77}, CreatorUserID: 9,
|
||||
Date: 1700000100, RandomID: 1001, Content: domain.WelcomeMessageContent{Message: "Hello"},
|
||||
CreateFingerprint: [32]byte{1}, Version: 1,
|
||||
}
|
||||
wire, err := tgWelcomeMessage(message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := &tg.EphemeralWelcomeMessages{Hash: 2, Messages: []tg.EphemeralMessage{wire}}
|
||||
var encoded229 bin.Buffer
|
||||
if err := getAdmission.Call().EncodeResult(result, &encoded229); err != nil {
|
||||
t.Fatalf("encode Layer 229 welcome result: %v", err)
|
||||
}
|
||||
for _, profile := range []tlprofile.Profile{tlprofile.Profile225, tlprofile.Profile226, tlprofile.Profile227, tlprofile.Profile228} {
|
||||
var older bin.Buffer
|
||||
if err := tlprofile.EncodeObject(profile, result, &older); err == nil {
|
||||
t.Fatalf("exact Layer %d encoded Layer 229 welcome result", profile)
|
||||
}
|
||||
for _, update := range []tg.UpdateClass{
|
||||
&tg.UpdateNewEphemeralMessage{Message: wire},
|
||||
&tg.UpdateEditEphemeralMessage{Message: wire},
|
||||
} {
|
||||
var olderUpdate bin.Buffer
|
||||
if err := tlprofile.EncodeObject(profile, update, &olderUpdate); err == nil {
|
||||
t.Fatalf("exact Layer %d encoded Layer 229 welcome update %T", profile, update)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue