426 lines
13 KiB
Go
426 lines
13 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"telesrv/internal/domain"
|
|
"testing"
|
|
)
|
|
|
|
func TestChannelStoreListDialogsUsesDateAndOffset(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 35,
|
|
Phone: "+1777" + suffix + "03",
|
|
FirstName: "DialogPageOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) > 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
older, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Older Dialog " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000310,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create older channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, older.Channel.ID)
|
|
newer, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Newer Dialog " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000320,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create newer channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, newer.Channel.ID)
|
|
|
|
first, err := channels.ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{Limit: 1})
|
|
if err != nil {
|
|
t.Fatalf("list first channel dialogs: %v", err)
|
|
}
|
|
if len(first.Dialogs) != 1 || first.Dialogs[0].Peer.ID != newer.Channel.ID {
|
|
t.Fatalf("first page dialogs = %+v, want newer channel by top date", first.Dialogs)
|
|
}
|
|
|
|
next, err := channels.ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{
|
|
OffsetDate: first.Dialogs[0].TopMessageDate,
|
|
OffsetID: first.Dialogs[0].TopMessage,
|
|
HasOffsetPeer: true,
|
|
OffsetPeer: first.Dialogs[0].Peer,
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list next channel dialogs: %v", err)
|
|
}
|
|
if len(next.Dialogs) != 1 || next.Dialogs[0].Peer.ID != older.Channel.ID {
|
|
t.Fatalf("next page dialogs = %+v, want older channel without repeating offset peer", next.Dialogs)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListDialogsWarmsDialogCache(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 36,
|
|
Phone: "+1777" + suffix + "13",
|
|
FirstName: "DialogWarmOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
var channelID int64
|
|
t.Cleanup(func() {
|
|
if channelID != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
|
|
cache := NewChannelDialogCache(16)
|
|
channels := NewChannelStore(pool, WithChannelDialogCache(cache))
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Dialog Warm " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000325,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelID = created.Channel.ID
|
|
|
|
list, err := channels.ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{Limit: 10})
|
|
if err != nil {
|
|
t.Fatalf("list channel dialogs: %v", err)
|
|
}
|
|
if len(list.Dialogs) != 1 || list.Dialogs[0].Peer.ID != channelID {
|
|
t.Fatalf("dialogs = %+v, want created channel", list.Dialogs)
|
|
}
|
|
|
|
cached, ok := cache.get(owner.ID, channelID)
|
|
if !ok {
|
|
t.Fatalf("channel dialog cache was not warmed")
|
|
}
|
|
if cached.TopMessageID != list.Dialogs[0].TopMessage ||
|
|
cached.TopMessageDate != list.Dialogs[0].TopMessageDate ||
|
|
cached.ReadInboxMaxID != list.Dialogs[0].ReadInboxMaxID {
|
|
t.Fatalf("cached dialog = %+v, listed dialog = %+v", cached, list.Dialogs[0])
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListDialogsScansChannelWallpaper(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 38,
|
|
Phone: "+1777" + suffix + "14",
|
|
FirstName: "DialogWallpaperOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
var channelID int64
|
|
t.Cleanup(func() {
|
|
if channelID != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Dialog Wallpaper " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000326,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelID = created.Channel.ID
|
|
wallpaper := &domain.Wallpaper{
|
|
ID: 9914,
|
|
NoFile: true,
|
|
Settings: domain.WallpaperSettings{
|
|
HasBackgroundColor: true,
|
|
BackgroundColor: 0x8fd15b,
|
|
HasIntensity: true,
|
|
Intensity: 35,
|
|
},
|
|
}
|
|
if _, err := channels.SetChannelWallpaper(ctx, domain.SetChannelWallpaperRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
Wallpaper: wallpaper,
|
|
Date: 1700000327,
|
|
}); err != nil {
|
|
t.Fatalf("set channel wallpaper: %v", err)
|
|
}
|
|
|
|
list, err := channels.ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{Limit: 10})
|
|
if err != nil {
|
|
t.Fatalf("list channel dialogs: %v", err)
|
|
}
|
|
if len(list.Channels) != 1 || list.Channels[0].ID != channelID {
|
|
t.Fatalf("channels = %+v, want wallpaper channel", list.Channels)
|
|
}
|
|
if !domain.WallpaperEqual(list.Channels[0].Wallpaper, wallpaper) {
|
|
t.Fatalf("listed wallpaper = %+v, want %+v", list.Channels[0].Wallpaper, wallpaper)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListDialogsDerivesRecipientTopWithoutWriteFanout(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 41,
|
|
Phone: "+1777" + suffix + "31",
|
|
FirstName: "DialogTopOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
member, err := users.Create(ctx, domain.User{
|
|
AccessHash: 42,
|
|
Phone: "+1777" + suffix + "32",
|
|
FirstName: "DialogTopMember",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create member: %v", err)
|
|
}
|
|
var channelID int64
|
|
t.Cleanup(func() {
|
|
if channelID != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, member.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Dialog Top " + suffix,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{member.ID},
|
|
Date: 1700000330,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelID = created.Channel.ID
|
|
if _, err := channels.ReadChannelHistory(ctx, domain.ReadChannelHistoryRequest{
|
|
UserID: member.ID,
|
|
ChannelID: channelID,
|
|
MaxID: created.Message.ID,
|
|
Date: 1700000331,
|
|
}); err != nil {
|
|
t.Fatalf("read initial service message: %v", err)
|
|
}
|
|
sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
RandomID: 9331,
|
|
Message: "recipient top without write fanout",
|
|
Date: 1700000332,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("send channel message: %v", err)
|
|
}
|
|
|
|
list, err := channels.ListChannelDialogs(ctx, member.ID, domain.DialogFilter{Limit: 10})
|
|
if err != nil {
|
|
t.Fatalf("list recipient channel dialogs: %v", err)
|
|
}
|
|
if len(list.Dialogs) != 1 {
|
|
t.Fatalf("dialogs = %+v, want one channel dialog", list.Dialogs)
|
|
}
|
|
dialog := list.Dialogs[0]
|
|
if dialog.TopMessage != sent.Message.ID || dialog.TopMessageDate != sent.Message.Date || dialog.UnreadCount != 1 {
|
|
t.Fatalf("recipient dialog = %+v, want top sent message and unread=1", dialog)
|
|
}
|
|
if len(list.Messages) != 1 || list.Messages[0].ID != sent.Message.ID {
|
|
t.Fatalf("dialog messages = %+v, want sent top message", list.Messages)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListDialogsSeeksBeyondQueryWindow(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 36,
|
|
Phone: "+1777" + suffix + "04",
|
|
FirstName: "DialogSeekOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
count := channelDialogQueryLimit + 5
|
|
ids := make([]int64, count)
|
|
baseID := owner.ID * 1000
|
|
for i := range ids {
|
|
ids[i] = baseID + int64(i+1)
|
|
}
|
|
t.Cleanup(func() {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", ids)
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO channels (
|
|
id, access_hash, creator_user_id, title, broadcast, megagroup,
|
|
participants_count, admins_count, top_message_id, pts, date
|
|
)
|
|
SELECT id, id + 900000, $2, 'Bulk Dialog ' || ord, false, true, 1, 1, 1, 1, (1700000400 + ord)::int
|
|
FROM unnest($1::bigint[]) WITH ORDINALITY AS t(id, ord)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channels: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO channel_members (channel_id, user_id, role, status, joined_at)
|
|
SELECT id, $2, 'creator', 'active', 1700000400
|
|
FROM unnest($1::bigint[]) AS t(id)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channel members: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO user_channel_member_index (user_id, channel_id, status, megagroup, broadcast, deleted, role)
|
|
SELECT $2, id, 'active', true, false, false, 'creator'
|
|
FROM unnest($1::bigint[]) AS t(id)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channel member index: %v", err)
|
|
}
|
|
|
|
channels := NewChannelStore(pool)
|
|
var cursor domain.Dialog
|
|
var sixth domain.ChannelDialogList
|
|
for page := 0; page < 6; page++ {
|
|
filter := domain.DialogFilter{Limit: 100}
|
|
if page > 0 {
|
|
filter.OffsetDate = cursor.TopMessageDate
|
|
filter.OffsetID = cursor.TopMessage
|
|
filter.HasOffsetPeer = true
|
|
filter.OffsetPeer = cursor.Peer
|
|
}
|
|
got, err := channels.ListChannelDialogs(ctx, owner.ID, filter)
|
|
if err != nil {
|
|
t.Fatalf("list channel dialogs page %d: %v", page+1, err)
|
|
}
|
|
if len(got.Dialogs) == 0 {
|
|
t.Fatalf("page %d unexpectedly empty after cursor %+v", page+1, cursor)
|
|
}
|
|
cursor = got.Dialogs[len(got.Dialogs)-1]
|
|
if page == 5 {
|
|
sixth = got
|
|
}
|
|
}
|
|
if len(sixth.Dialogs) != 5 {
|
|
t.Fatalf("sixth page len = %d, want remaining 5 beyond query window", len(sixth.Dialogs))
|
|
}
|
|
if sixth.Dialogs[0].Peer.ID != ids[4] || sixth.Dialogs[4].Peer.ID != ids[0] {
|
|
t.Fatalf("sixth page dialogs = %+v, want oldest five descending by date", sixth.Dialogs)
|
|
}
|
|
|
|
included, err := channels.ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{
|
|
Folder: &domain.DialogFolder{
|
|
IncludePeers: []domain.DialogFolderPeer{{
|
|
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: ids[0]},
|
|
}},
|
|
},
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list included channel dialog beyond query window: %v", err)
|
|
}
|
|
if len(included.Dialogs) != 1 || included.Dialogs[0].Peer.ID != ids[0] {
|
|
t.Fatalf("included dialogs = %+v, want oldest included channel beyond query window", included.Dialogs)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListDialogsFolderFiltersBeforeQueryLimit(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 37,
|
|
Phone: "+1777" + suffix + "05",
|
|
FirstName: "DialogFolderOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
count := channelDialogQueryLimit + 5
|
|
ids := make([]int64, count)
|
|
baseID := owner.ID*1000 + 100000
|
|
for i := range ids {
|
|
ids[i] = baseID + int64(i+1)
|
|
}
|
|
archivedID := ids[0]
|
|
t.Cleanup(func() {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", ids)
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO channels (
|
|
id, access_hash, creator_user_id, title, broadcast, megagroup,
|
|
participants_count, admins_count, top_message_id, pts, date
|
|
)
|
|
SELECT id, id + 910000, $2, 'Folder Dialog ' || ord, false, true, 1, 1, 1, 1, (1700000500 + ord)::int
|
|
FROM unnest($1::bigint[]) WITH ORDINALITY AS t(id, ord)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channels: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO channel_members (channel_id, user_id, role, status, joined_at)
|
|
SELECT id, $2, 'creator', 'active', 1700000500
|
|
FROM unnest($1::bigint[]) AS t(id)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channel members: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO user_channel_member_index (user_id, channel_id, status, megagroup, broadcast, deleted, role)
|
|
SELECT $2, id, 'active', true, false, false, 'creator'
|
|
FROM unnest($1::bigint[]) AS t(id)`, ids, owner.ID); err != nil {
|
|
t.Fatalf("bulk insert channel member index: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO channel_dialogs (user_id, channel_id, folder_id, top_message_id, top_message_date)
|
|
VALUES ($1, $2, $3, 1, 1700000500)`, owner.ID, archivedID, domain.DialogArchiveFolderID); err != nil {
|
|
t.Fatalf("archive oldest channel dialog: %v", err)
|
|
}
|
|
|
|
archive, err := NewChannelStore(pool).ListChannelDialogs(ctx, owner.ID, domain.DialogFilter{
|
|
HasFolderID: true,
|
|
FolderID: domain.DialogArchiveFolderID,
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list archive channel dialogs: %v", err)
|
|
}
|
|
if len(archive.Dialogs) != 1 || archive.Dialogs[0].Peer.ID != archivedID {
|
|
t.Fatalf("archive dialogs = %+v, want archived channel beyond first query window", archive.Dialogs)
|
|
}
|
|
}
|