chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
245
internal/rpc/channels_send_as_foreign_rpc_test.go
Normal file
245
internal/rpc/channels_send_as_foreign_rpc_test.go
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gotd/td/clock"
|
||||
"github.com/gotd/td/tg"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
appchannels "telesrv/internal/app/channels"
|
||||
appusers "telesrv/internal/app/users"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
// createBroadcastChannelRPC creates a broadcast channel owned by ownerID and returns its tg.Channel.
|
||||
func createBroadcastChannelRPC(t *testing.T, r *Router, ownerID int64, title string) *tg.Channel {
|
||||
t.Helper()
|
||||
created, err := r.onChannelsCreateChannel(WithUserID(context.Background(), ownerID), &tg.ChannelsCreateChannelRequest{
|
||||
Title: title,
|
||||
About: "owned channel",
|
||||
Broadcast: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create broadcast %q: %v", title, err)
|
||||
}
|
||||
channel, ok := created.(*tg.Updates).Chats[0].(*tg.Channel)
|
||||
if !ok || !channel.Broadcast {
|
||||
t.Fatalf("create broadcast %q result = %#v, want broadcast channel", title, created)
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
func findSendAsChannel(peers []tg.SendAsPeer, channelID int64) (tg.SendAsPeer, bool) {
|
||||
for _, p := range peers {
|
||||
if ch, ok := p.Peer.(*tg.PeerChannel); ok && ch.ChannelID == channelID {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return tg.SendAsPeer{}, false
|
||||
}
|
||||
|
||||
func chatsContainChannel(chats []tg.ChatClass, channelID int64) bool {
|
||||
for _, c := range chats {
|
||||
switch ch := c.(type) {
|
||||
case *tg.Channel:
|
||||
if ch.ID == channelID {
|
||||
return true
|
||||
}
|
||||
case *tg.ChannelForbidden:
|
||||
if ch.ID == channelID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestChannelSendAsForeignChannelRPC verifies a premium user can post in another supergroup as one
|
||||
// of their own broadcast channels: getSendAs lists the owned channel (flagged premium_required), the
|
||||
// send path stamps from_id to that channel and ships it in Chats, a non-owner is rejected, and the
|
||||
// default round-trips into channelFull.default_send_as.
|
||||
func TestChannelSendAsForeignChannelRPC(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
userStore := memory.NewUserStore()
|
||||
premiumUntil := int(time.Now().Add(72 * time.Hour).Unix())
|
||||
owner, _ := userStore.Create(ctx, domain.User{AccessHash: 61, Phone: "15550002161", FirstName: "Owner", PremiumUntil: premiumUntil})
|
||||
friend, _ := userStore.Create(ctx, domain.User{AccessHash: 62, Phone: "15550002162", FirstName: "Friend", PremiumUntil: premiumUntil})
|
||||
channelStore := memory.NewChannelStore()
|
||||
r := New(Config{}, Deps{
|
||||
Users: appusers.NewService(userStore),
|
||||
Channels: appchannels.NewService(channelStore),
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
ownerCtx := WithUserID(ctx, owner.ID)
|
||||
friendCtx := WithUserID(ctx, friend.ID)
|
||||
|
||||
// Target supergroup G (owner creator, friend member) and an owned broadcast channel C.
|
||||
created, err := r.onMessagesCreateChat(ownerCtx, &tg.MessagesCreateChatRequest{
|
||||
Users: []tg.InputUserClass{&tg.InputUser{UserID: friend.ID, AccessHash: friend.AccessHash}},
|
||||
Title: "Foreign Send As Group",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create group: %v", err)
|
||||
}
|
||||
group := created.Updates.(*tg.Updates).Chats[0].(*tg.Channel)
|
||||
owned := createBroadcastChannelRPC(t, r, owner.ID, "Owner Channel")
|
||||
|
||||
// getSendAs(G) by owner: self + G + owned channel; owned channel carries premium_required and is in Chats.
|
||||
sendAs, err := r.onChannelsGetSendAs(ownerCtx, &tg.ChannelsGetSendAsRequest{Peer: inputPeerChannel(group)})
|
||||
if err != nil {
|
||||
t.Fatalf("owner get send as: %v", err)
|
||||
}
|
||||
if _, ok := findSendAsChannel(sendAs.Peers, group.ID); !ok {
|
||||
t.Fatalf("send as peers %+v missing current group %d", sendAs.Peers, group.ID)
|
||||
}
|
||||
ownedPeer, ok := findSendAsChannel(sendAs.Peers, owned.ID)
|
||||
if !ok {
|
||||
t.Fatalf("send as peers %+v missing owned channel %d", sendAs.Peers, owned.ID)
|
||||
}
|
||||
if !ownedPeer.PremiumRequired {
|
||||
t.Fatalf("owned foreign channel candidate premium_required = false, want true")
|
||||
}
|
||||
if grpPeer, _ := findSendAsChannel(sendAs.Peers, group.ID); grpPeer.PremiumRequired {
|
||||
t.Fatalf("current group candidate premium_required = true, want false")
|
||||
}
|
||||
if !chatsContainChannel(sendAs.Chats, owned.ID) {
|
||||
t.Fatalf("send as chats %+v missing owned channel object %d", sendAs.Chats, owned.ID)
|
||||
}
|
||||
|
||||
// Owner (premium) sends in G as the owned channel: from_id projected to the channel, channel in Chats.
|
||||
sent, err := r.onMessagesSendMessage(ownerCtx, &tg.MessagesSendMessageRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
Message: "as my channel",
|
||||
RandomID: 6101,
|
||||
SendAs: inputPeerChannel(owned),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send as owned foreign channel: %v", err)
|
||||
}
|
||||
sentUpdates := sent.(*tg.Updates)
|
||||
sentMsg := sentUpdates.Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message)
|
||||
if from, ok := sentMsg.FromID.(*tg.PeerChannel); !ok || from.ChannelID != owned.ID {
|
||||
t.Fatalf("send_as message from = %#v, want owned channel %d", sentMsg.FromID, owned.ID)
|
||||
}
|
||||
// The sender still sees their own send-as message as outgoing (out derives from SenderUserID, not from_id).
|
||||
if !sentMsg.Out {
|
||||
t.Fatalf("send_as message out = false for sender, want true")
|
||||
}
|
||||
if !chatsContainChannel(sentUpdates.Chats, owned.ID) {
|
||||
t.Fatalf("send_as echo chats %+v missing owned channel %d", sentUpdates.Chats, owned.ID)
|
||||
}
|
||||
|
||||
// A different member who does not own the channel cannot post as it.
|
||||
if _, err := r.onMessagesSendMessage(friendCtx, &tg.MessagesSendMessageRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
Message: "not my channel",
|
||||
RandomID: 6102,
|
||||
SendAs: inputPeerChannel(owned),
|
||||
}); err == nil || !strings.Contains(err.Error(), "SEND_AS_PEER_INVALID") {
|
||||
t.Fatalf("non-owner send_as err = %v, want SEND_AS_PEER_INVALID", err)
|
||||
}
|
||||
|
||||
// saveDefaultSendAs(G, ownedChannel) by owner round-trips into channelFull.default_send_as + Chats.
|
||||
if ok, err := r.onMessagesSaveDefaultSendAs(ownerCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
SendAs: inputPeerChannel(owned),
|
||||
}); err != nil || !ok {
|
||||
t.Fatalf("save default send as owned channel = ok %v err %v, want true", ok, err)
|
||||
}
|
||||
full, err := r.onChannelsGetFullChannel(ownerCtx, inputChannel(group))
|
||||
if err != nil {
|
||||
t.Fatalf("get full channel after default send as: %v", err)
|
||||
}
|
||||
channelFull := full.FullChat.(*tg.ChannelFull)
|
||||
def, ok := channelFull.GetDefaultSendAs()
|
||||
if !ok {
|
||||
t.Fatalf("channelFull default_send_as missing after saving owned channel")
|
||||
}
|
||||
if peer, ok := def.(*tg.PeerChannel); !ok || peer.ChannelID != owned.ID {
|
||||
t.Fatalf("channelFull default_send_as = %#v, want owned channel %d", def, owned.ID)
|
||||
}
|
||||
if !chatsContainChannel(full.Chats, owned.ID) {
|
||||
t.Fatalf("getFullChannel chats %+v missing owned default send-as channel %d", full.Chats, owned.ID)
|
||||
}
|
||||
|
||||
// With the default stored, a send carrying no explicit send_as posts as the owned channel.
|
||||
defaultSent, err := r.onMessagesSendMessage(ownerCtx, &tg.MessagesSendMessageRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
Message: "default channel",
|
||||
RandomID: 6103,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send with stored default send_as: %v", err)
|
||||
}
|
||||
defaultMsg := defaultSent.(*tg.Updates).Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message)
|
||||
if from, ok := defaultMsg.FromID.(*tg.PeerChannel); !ok || from.ChannelID != owned.ID {
|
||||
t.Fatalf("stored-default send_as from = %#v, want owned channel %d", defaultMsg.FromID, owned.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChannelSendAsForeignChannelPremiumGateRPC verifies that posting as an owned personal channel in
|
||||
// another supergroup is gated on Premium: a non-premium owner is rejected with SEND_AS_PEER_INVALID,
|
||||
// and the same owner succeeds once Premium is active.
|
||||
func TestChannelSendAsForeignChannelPremiumGateRPC(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
userStore := memory.NewUserStore()
|
||||
// creator of the group is premium so the group exists with a member; the send-as actor is non-premium.
|
||||
host, _ := userStore.Create(ctx, domain.User{AccessHash: 71, Phone: "15550002171", FirstName: "Host", PremiumUntil: int(time.Now().Add(72 * time.Hour).Unix())})
|
||||
actor, _ := userStore.Create(ctx, domain.User{AccessHash: 72, Phone: "15550002172", FirstName: "Actor"})
|
||||
channelStore := memory.NewChannelStore()
|
||||
r := New(Config{}, Deps{
|
||||
Users: appusers.NewService(userStore),
|
||||
Channels: appchannels.NewService(channelStore),
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
hostCtx := WithUserID(ctx, host.ID)
|
||||
actorCtx := WithUserID(ctx, actor.ID)
|
||||
|
||||
created, err := r.onMessagesCreateChat(hostCtx, &tg.MessagesCreateChatRequest{
|
||||
Users: []tg.InputUserClass{&tg.InputUser{UserID: actor.ID, AccessHash: actor.AccessHash}},
|
||||
Title: "Premium Gate Group",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create group: %v", err)
|
||||
}
|
||||
group := created.Updates.(*tg.Updates).Chats[0].(*tg.Channel)
|
||||
actorChannel := createBroadcastChannelRPC(t, r, actor.ID, "Actor Channel")
|
||||
|
||||
// Non-premium actor cannot post as their own channel in the group.
|
||||
if _, err := r.onMessagesSendMessage(actorCtx, &tg.MessagesSendMessageRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
Message: "non-premium send as",
|
||||
RandomID: 7201,
|
||||
SendAs: inputPeerChannel(actorChannel),
|
||||
}); err == nil || !strings.Contains(err.Error(), "SEND_AS_PEER_INVALID") {
|
||||
t.Fatalf("non-premium send_as err = %v, want SEND_AS_PEER_INVALID", err)
|
||||
}
|
||||
// And cannot persist it as the default.
|
||||
if _, err := r.onMessagesSaveDefaultSendAs(actorCtx, &tg.MessagesSaveDefaultSendAsRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
SendAs: inputPeerChannel(actorChannel),
|
||||
}); err == nil || !strings.Contains(err.Error(), "SEND_AS_PEER_INVALID") {
|
||||
t.Fatalf("non-premium save default send_as err = %v, want SEND_AS_PEER_INVALID", err)
|
||||
}
|
||||
|
||||
// Grant Premium and the same send succeeds.
|
||||
if _, err := userStore.SetPremiumUntil(ctx, actor.ID, int(time.Now().Add(72*time.Hour).Unix())); err != nil {
|
||||
t.Fatalf("grant premium: %v", err)
|
||||
}
|
||||
sent, err := r.onMessagesSendMessage(actorCtx, &tg.MessagesSendMessageRequest{
|
||||
Peer: inputPeerChannel(group),
|
||||
Message: "premium send as",
|
||||
RandomID: 7202,
|
||||
SendAs: inputPeerChannel(actorChannel),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("premium send_as: %v", err)
|
||||
}
|
||||
sentMsg := sent.(*tg.Updates).Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message)
|
||||
if from, ok := sentMsg.FromID.(*tg.PeerChannel); !ok || from.ChannelID != actorChannel.ID {
|
||||
t.Fatalf("premium send_as from = %#v, want actor channel %d", sentMsg.FromID, actorChannel.ID)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue