owpengram-server/internal/rpc/chatlists_rpc_test.go

307 lines
11 KiB
Go

package rpc
import (
"context"
"testing"
"github.com/iamxvbaba/td/bin"
"github.com/iamxvbaba/td/clock"
"github.com/iamxvbaba/td/tg"
"go.uber.org/zap"
"telesrv/internal/domain"
)
func TestChatlistsExportInviteRPCShape(t *testing.T) {
fake := &fakeChatlistsService{
exportFolder: domain.DialogFolder{
ID: 2,
Title: "Team",
IsChatlist: true,
HasMyInvites: true,
IncludePeers: []domain.DialogFolderPeer{{Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 42}, AccessHash: 4200}},
},
exportInvite: domain.ChatlistInvite{
OwnerUserID: 1001,
FilterID: 2,
Slug: "slug-rpc",
Title: "Main link",
Peers: []domain.DialogFolderPeer{{Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 42}, AccessHash: 4200}},
Date: 100,
},
}
r := New(Config{PublicBaseURL: "http://127.0.0.1:2401"}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
got, err := r.onChatlistsExportChatlistInvite(WithUserID(context.Background(), 1001), &tg.ChatlistsExportChatlistInviteRequest{
Chatlist: tg.InputChatlistDialogFilter{FilterID: 2},
Title: "Main link",
Peers: []tg.InputPeerClass{&tg.InputPeerChannel{ChannelID: 42, AccessHash: 4200}},
})
if err != nil {
t.Fatalf("export rpc: %v", err)
}
filter, ok := got.Filter.(*tg.DialogFilterChatlist)
if !ok || !filter.HasMyInvites || filter.ID != 2 {
t.Fatalf("filter = %#v, want dialogFilterChatlist with has_my_invites", got.Filter)
}
if got.Invite.URL != "http://127.0.0.1:2401/addlist/slug-rpc" || len(got.Invite.Peers) != 1 {
t.Fatalf("invite = %+v, want url and one peer", got.Invite)
}
if fake.exportPeers[0].Peer.Type != domain.PeerTypeChannel || fake.exportPeers[0].Peer.ID != 42 || fake.exportPeers[0].AccessHash != 4200 {
t.Fatalf("service peers = %+v, want parsed input peer", fake.exportPeers)
}
}
func TestChatlistsExportInvitePublicBaseURLIsRouterScoped(t *testing.T) {
fake := &fakeChatlistsService{
exportFolder: domain.DialogFolder{ID: 2, Title: "Team", IsChatlist: true, HasMyInvites: true},
exportInvite: domain.ChatlistInvite{
OwnerUserID: 1001,
FilterID: 2,
Slug: "slug-rpc",
Title: "Main link",
},
}
local := New(Config{PublicBaseURL: "http://127.0.0.1:2401"}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
_ = New(Config{PublicBaseURL: "https://telesrv.net"}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
got, err := local.onChatlistsExportChatlistInvite(WithUserID(context.Background(), 1001), &tg.ChatlistsExportChatlistInviteRequest{
Chatlist: tg.InputChatlistDialogFilter{FilterID: 2},
Title: "Main link",
})
if err != nil {
t.Fatalf("export rpc: %v", err)
}
if got.Invite.URL != "http://127.0.0.1:2401/addlist/slug-rpc" {
t.Fatalf("invite url = %q, want local router base URL", got.Invite.URL)
}
}
func TestChatlistsJoinInviteReturnsDialogFilterUpdate(t *testing.T) {
fake := &fakeChatlistsService{
joinResult: domain.ChatlistJoinResult{
Folder: domain.DialogFolder{
ID: 3,
Title: "Team",
IsChatlist: true,
IncludePeers: []domain.DialogFolderPeer{
{Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 42}, AccessHash: 4200},
},
},
ChannelResults: []domain.CreateChannelResult{
{
Channel: domain.Channel{
ID: 42,
AccessHash: 4200,
Title: "Team channel",
Megagroup: true,
Pts: 9,
},
Members: []domain.ChannelMember{
{ChannelID: 42, UserID: 2002, Status: domain.ChannelMemberActive},
},
},
},
Date: 100,
},
}
r := New(Config{}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
got, err := r.onChatlistsJoinChatlistInvite(WithUserID(context.Background(), 2002), &tg.ChatlistsJoinChatlistInviteRequest{
Slug: "slug-rpc",
Peers: []tg.InputPeerClass{&tg.InputPeerChannel{ChannelID: 42, AccessHash: 4200}},
})
if err != nil {
t.Fatalf("join rpc: %v", err)
}
updates, ok := got.(*tg.Updates)
if !ok || len(updates.Updates) == 0 {
t.Fatalf("updates = %#v, want updates with updateDialogFilter", got)
}
update, ok := updates.Updates[0].(*tg.UpdateDialogFilter)
if !ok || update.ID != 3 {
t.Fatalf("first update = %#v, want updateDialogFilter id=3", updates.Updates[0])
}
filter, ok := update.Filter.(*tg.DialogFilterChatlist)
if !ok || filter.ID != 3 || len(filter.IncludePeers) != 1 {
t.Fatalf("update filter = %#v, want joined chatlist filter", update.Filter)
}
if fake.joinPeers[0].Peer.Type != domain.PeerTypeChannel || fake.joinPeers[0].Peer.ID != 42 || fake.joinPeers[0].AccessHash != 4200 {
t.Fatalf("service join peers = %+v, want parsed input peer", fake.joinPeers)
}
var gotChannelNudge bool
for _, u := range updates.Updates {
nudge, ok := u.(*tg.UpdateChannelTooLong)
if !ok || nudge.ChannelID != 42 {
continue
}
pts, ok := nudge.GetPts()
if !ok || pts != 9 {
t.Fatalf("channel nudge pts = %d ok %v, want 9 true", pts, ok)
}
gotChannelNudge = true
}
if !gotChannelNudge {
t.Fatalf("updates = %#v, want UpdateChannelTooLong for joined channel", updates.Updates)
}
}
func TestChatlistsJoinedChannelNudgesCarryPts(t *testing.T) {
sessions := &captureSessions{}
r := New(Config{}, Deps{Sessions: sessions}, zap.NewNop(), clock.System)
r.pushChatlistJoinedChannelNudges(context.Background(), 2002, []domain.Channel{
{ID: 42, Pts: 9},
{ID: 43},
})
updates, ok := sessions.lastUserPush().(*tg.Updates)
if !ok || len(updates.Updates) != 1 {
t.Fatalf("pushed updates = %#v, want one UpdateChannelTooLong", sessions.lastUserPush())
}
nudge, ok := updates.Updates[0].(*tg.UpdateChannelTooLong)
if !ok || nudge.ChannelID != 42 {
t.Fatalf("pushed update = %#v, want channel 42 nudge", updates.Updates[0])
}
pts, ok := nudge.GetPts()
if !ok || pts != 9 {
t.Fatalf("pushed nudge pts = %d ok %v, want 9 true", pts, ok)
}
sessions.clearMessages()
r.pushChatlistJoinedChannelNudges(WithSessionID(context.Background(), 77), 2002, []domain.Channel{{ID: 44, Pts: 11}})
snap := sessions.snapshot()
currentUpdates, ok := snap.message.(*tg.Updates)
if !ok || len(currentUpdates.Updates) != 1 {
t.Fatalf("current session push = %#v, want one UpdateChannelTooLong", snap.message)
}
if ids := sessions.pushedUserIDs(); len(ids) != 0 {
t.Fatalf("current session nudge pushed to user ids = %v, want none", ids)
}
}
func TestChatlistsCheckInviteRPCAlreadyAndFreshShapes(t *testing.T) {
peer := domain.DialogFolderPeer{Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 42}, AccessHash: 4200}
fake := &fakeChatlistsService{
checkPreview: domain.ChatlistInvitePreview{
Invite: domain.ChatlistInvite{Slug: "slug-rpc", Peers: []domain.DialogFolderPeer{peer}},
OwnerFolder: domain.DialogFolder{
ID: 2,
Title: "Team",
HasEmoticon: true,
Emoticon: ":)",
IncludePeers: []domain.DialogFolderPeer{peer},
},
Missing: []domain.DialogFolderPeer{peer},
},
}
r := New(Config{}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
fresh, err := r.onChatlistsCheckChatlistInvite(WithUserID(context.Background(), 2002), "slug-rpc")
if err != nil {
t.Fatalf("check fresh rpc: %v", err)
}
invite, ok := fresh.(*tg.ChatlistsChatlistInvite)
if !ok || invite.Title.Text != "Team" || len(invite.Peers) != 1 {
t.Fatalf("fresh = %#v, want chatlistInvite with title and peer", fresh)
}
if emoticon, ok := invite.GetEmoticon(); !ok || emoticon != ":)" {
t.Fatalf("fresh emoticon = %q ok %v, want folder emoji", emoticon, ok)
}
local := domain.DialogFolder{ID: 5, Title: "Team", IsChatlist: true, IncludePeers: []domain.DialogFolderPeer{peer}}
fake.checkPreview.LocalFolder = &local
fake.checkPreview.Membership = &domain.ChatlistMembership{UserID: 2002, LocalFilterID: 5, Slug: "slug-rpc"}
fake.checkPreview.Missing = nil
fake.checkPreview.Already = []domain.DialogFolderPeer{peer}
already, err := r.onChatlistsCheckChatlistInvite(WithUserID(context.Background(), 2002), "slug-rpc")
if err != nil {
t.Fatalf("check already rpc: %v", err)
}
gotAlready, ok := already.(*tg.ChatlistsChatlistInviteAlready)
if !ok || gotAlready.FilterID != 5 || len(gotAlready.AlreadyPeers) != 1 {
t.Fatalf("already = %#v, want already filter id and peer", already)
}
}
func TestChatlistsEditInviteRevokedFlagRoundTrips(t *testing.T) {
fake := &fakeChatlistsService{
exportInvite: domain.ChatlistInvite{
OwnerUserID: 1001,
FilterID: 2,
Slug: "slug-rpc",
Title: "Main link",
Revoked: true,
},
}
r := New(Config{PublicBaseURL: "https://telesrv.net"}, Deps{Chatlists: fake}, zap.NewNop(), clock.System)
var flags bin.Fields
flags.Set(0)
got, err := r.onChatlistsEditExportedInvite(WithUserID(context.Background(), 1001), &tg.ChatlistsEditExportedInviteRequest{
Flags: flags,
Chatlist: tg.InputChatlistDialogFilter{FilterID: 2},
Slug: "slug-rpc",
})
if err != nil {
t.Fatalf("edit rpc: %v", err)
}
if !fake.editRevoke {
t.Fatalf("service revoke = false, want true")
}
if !got.Flags.Has(0) {
t.Fatalf("edited invite flags = %v, want revoked bit", got.Flags)
}
}
type fakeChatlistsService struct {
exportFolder domain.DialogFolder
exportInvite domain.ChatlistInvite
exportPeers []domain.DialogFolderPeer
checkPreview domain.ChatlistInvitePreview
joinResult domain.ChatlistJoinResult
joinPeers []domain.DialogFolderPeer
updates domain.ChatlistUpdates
joinUpdFolder domain.DialogFolder
editRevoke bool
}
func (f *fakeChatlistsService) ExportInvite(_ context.Context, _ int64, _ int, _ string, peers []domain.DialogFolderPeer, _ int) (domain.DialogFolder, domain.ChatlistInvite, error) {
f.exportPeers = append([]domain.DialogFolderPeer(nil), peers...)
return f.exportFolder, f.exportInvite, nil
}
func (f *fakeChatlistsService) ListInvites(context.Context, int64, int) ([]domain.ChatlistInvite, error) {
return []domain.ChatlistInvite{f.exportInvite}, nil
}
func (f *fakeChatlistsService) EditInvite(_ context.Context, _ int64, _ int, _ string, _ *string, _ *[]domain.DialogFolderPeer, revoke bool) (domain.ChatlistInvite, error) {
f.editRevoke = revoke
return f.exportInvite, nil
}
func (f *fakeChatlistsService) DeleteInvite(context.Context, int64, int, string) (domain.DialogFolder, bool, error) {
return domain.DialogFolder{}, false, nil
}
func (f *fakeChatlistsService) CheckInvite(context.Context, int64, string) (domain.ChatlistInvitePreview, error) {
return f.checkPreview, nil
}
func (f *fakeChatlistsService) JoinInvite(_ context.Context, _ int64, _ string, peers []domain.DialogFolderPeer, _ int) (domain.ChatlistJoinResult, error) {
f.joinPeers = append([]domain.DialogFolderPeer(nil), peers...)
return f.joinResult, nil
}
func (f *fakeChatlistsService) GetUpdates(context.Context, int64, int) (domain.ChatlistUpdates, error) {
return f.updates, nil
}
func (f *fakeChatlistsService) JoinUpdates(context.Context, int64, int, []domain.DialogFolderPeer, int) (domain.ChatlistJoinResult, error) {
return domain.ChatlistJoinResult{Folder: f.joinUpdFolder}, nil
}
func (f *fakeChatlistsService) HideUpdates(context.Context, int64, int) error {
return nil
}
func (f *fakeChatlistsService) Leave(context.Context, int64, int, []domain.DialogFolderPeer, int) (domain.ChatlistLeaveResult, error) {
return domain.ChatlistLeaveResult{FilterID: 3}, nil
}
func (f *fakeChatlistsService) LeaveSuggestions(context.Context, int64, int) ([]domain.DialogFolderPeer, error) {
return f.exportInvite.Peers, nil
}