owpengram-server/internal/store/postgres/dialog_snapshot_hash_integration_test.go
2026-09-01 12:06:31 +03:00

190 lines
6.4 KiB
Go

package postgres
import (
"context"
"testing"
"telesrv/internal/domain"
)
func TestChannelDialogSnapshotHashTracksDependenciesWithoutWideHeaders(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
owner, err := NewUserStore(pool).Create(ctx, domain.User{
AccessHash: 51,
Phone: "+1778" + suffix + "01",
FirstName: "SnapshotHashOwner",
})
if err != nil {
t.Fatalf("create owner: %v", err)
}
created, err := NewChannelStore(pool).CreateChannel(ctx, domain.CreateChannelRequest{
CreatorUserID: owner.ID,
Title: "Snapshot Hash " + suffix,
Megagroup: true,
Date: 1700000600,
})
if err != nil {
t.Fatalf("create channel: %v", err)
}
channelID := created.Channel.ID
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
})
channels := NewChannelStore(pool)
load := func() domain.ChannelDialogList {
t.Helper()
list, err := channels.ListChannelDialogSnapshotHeaders(ctx, owner.ID, domain.DialogFilter{})
if err != nil {
t.Fatalf("list snapshot headers: %v", err)
}
if len(list.Dialogs) != 1 || list.Hash == 0 {
t.Fatalf("snapshot = %+v, want one dialog and non-zero hash", list)
}
dialog := list.Dialogs[0]
if dialog.Peer.ID != channelID || dialog.TopMessage == 0 || dialog.TopMessageDate == 0 {
t.Fatalf("ordering header = %+v, want peer/top/date", dialog)
}
if dialog.ReadInboxMaxID != 0 || dialog.ReadOutboxMaxID != 0 || dialog.UnreadCount != 0 ||
dialog.UnreadMentions != 0 || dialog.UnreadReactions != 0 || dialog.Pts != 0 {
t.Fatalf("snapshot header retained mutable hydration fields: %+v", dialog)
}
return list
}
before := load()
changed, err := channels.SetChannelDialogUnreadMark(ctx, owner.ID, channelID, true)
if err != nil || !changed {
t.Fatalf("set unread mark = %v, %v", changed, err)
}
afterOwnerState := load()
if afterOwnerState.Hash == before.Hash {
t.Fatalf("owner-local dependency hash stayed %d after unread mark", before.Hash)
}
if afterOwnerState.Dialogs[0].TopMessage != before.Dialogs[0].TopMessage ||
afterOwnerState.Dialogs[0].TopMessageDate != before.Dialogs[0].TopMessageDate {
t.Fatalf("unread mark changed ordering header: before=%+v after=%+v", before.Dialogs[0], afterOwnerState.Dialogs[0])
}
if _, err := pool.Exec(ctx, "UPDATE channels SET title = title || ' changed' WHERE id = $1", channelID); err != nil {
t.Fatalf("update channel title: %v", err)
}
afterSharedState := load()
if afterSharedState.Hash == afterOwnerState.Hash {
t.Fatalf("shared channel dependency hash stayed %d after channel-base change", afterOwnerState.Hash)
}
}
func TestPrivateDialogSnapshotHashTracksDraftDependency(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
owner, err := users.Create(ctx, domain.User{
AccessHash: 52,
Phone: "+1778" + suffix + "02",
FirstName: "PrivateSnapshotOwner",
})
if err != nil {
t.Fatalf("create owner: %v", err)
}
peer, err := users.Create(ctx, domain.User{
AccessHash: 53,
Phone: "+1778" + suffix + "03",
FirstName: "PrivateSnapshotPeer",
})
if err != nil {
t.Fatalf("create peer: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, peer.ID})
})
dialogs := NewDialogStore(pool)
dialogPeer := domain.Peer{Type: domain.PeerTypeUser, ID: peer.ID}
if err := dialogs.Upsert(ctx, owner.ID, domain.Dialog{
Peer: dialogPeer,
TopMessage: 7,
TopMessageDate: 1700000610,
}); err != nil {
t.Fatalf("upsert dialog: %v", err)
}
before, err := dialogs.ListDialogSnapshotHeaders(ctx, owner.ID, domain.DialogFilter{})
if err != nil {
t.Fatalf("list snapshot before draft: %v", err)
}
if len(before.Dialogs) != 1 || before.Hash == 0 {
t.Fatalf("snapshot before draft = %+v", before)
}
if err := dialogs.SaveDraft(ctx, owner.ID, domain.DialogDraft{
Peer: dialogPeer,
Date: 1700000611,
Message: "draft changes hash without changing ordering",
}); err != nil {
t.Fatalf("save draft: %v", err)
}
after, err := dialogs.ListDialogSnapshotHeaders(ctx, owner.ID, domain.DialogFilter{})
if err != nil {
t.Fatalf("list snapshot after draft: %v", err)
}
if after.Hash == before.Hash {
t.Fatalf("private snapshot hash stayed %d after draft change", before.Hash)
}
if after.Dialogs[0].TopMessage != before.Dialogs[0].TopMessage || after.Dialogs[0].TopMessageDate != before.Dialogs[0].TopMessageDate {
t.Fatalf("draft changed ordering header: before=%+v after=%+v", before.Dialogs[0], after.Dialogs[0])
}
}
func TestPrivateDialogAllBuiltinSnapshotIncludesMainAndArchive(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
owner, err := users.Create(ctx, domain.User{AccessHash: 61, Phone: "+1778" + suffix + "11", FirstName: "AllFolderOwner"})
if err != nil {
t.Fatal(err)
}
mainPeer, err := users.Create(ctx, domain.User{AccessHash: 62, Phone: "+1778" + suffix + "12", FirstName: "MainPeer"})
if err != nil {
t.Fatal(err)
}
archivePeer, err := users.Create(ctx, domain.User{AccessHash: 63, Phone: "+1778" + suffix + "13", FirstName: "ArchivePeer"})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, mainPeer.ID, archivePeer.ID})
})
dialogs := NewDialogStore(pool)
for index, peer := range []int64{mainPeer.ID, archivePeer.ID} {
if err := dialogs.Upsert(ctx, owner.ID, domain.Dialog{
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: peer},
TopMessage: 10 + index, TopMessageDate: 1700000700 + index,
}); err != nil {
t.Fatal(err)
}
}
if err := dialogs.EditPeerFolders(ctx, owner.ID, []domain.FolderPeerUpdate{{
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: archivePeer.ID}, FolderID: domain.DialogArchiveFolderID,
}}); err != nil {
t.Fatal(err)
}
all, err := dialogs.ListAllBuiltinDialogSnapshotHeaders(ctx, owner.ID)
if err != nil {
t.Fatal(err)
}
if len(all.Dialogs) != 2 {
t.Fatalf("all built-in private dialogs = %+v", all.Dialogs)
}
folders := map[int64]int{}
for _, dialog := range all.Dialogs {
folders[dialog.Peer.ID] = dialog.FolderID
}
if folders[mainPeer.ID] != domain.DialogMainFolderID || folders[archivePeer.ID] != domain.DialogArchiveFolderID {
t.Fatalf("all built-in private folders = %#v", folders)
}
}