owpengram-server/internal/store/postgres/channel_dialog_integration_test.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)
}
}