owpengram-server/internal/rpc/channels_send_as_foreign_rpc_test.go

245 lines
10 KiB
Go

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)
}
}