owpengram-server/internal/loadharness/snapshot_test.go
2026-09-01 12:06:31 +03:00

189 lines
6.9 KiB
Go

package loadharness
import (
"os"
"path/filepath"
"testing"
"github.com/iamxvbaba/td/tg"
)
func TestMergeDialogPageCapturesChannelCursorAndOffset(t *testing.T) {
user := &tg.User{ID: 11}
user.SetAccessHash(111)
channel := &tg.Channel{ID: 22, Title: "group", Megagroup: true}
channel.SetAccessHash(222)
userDialog := &tg.Dialog{Peer: &tg.PeerUser{UserID: 11}, TopMessage: 7}
channelDialog := &tg.Dialog{Peer: &tg.PeerChannel{ChannelID: 22}, TopMessage: 8}
channelDialog.SetPts(12)
destination := make(map[clientPeerKey]ClientDialogState)
last, err := mergeDialogPage(destination,
[]tg.DialogClass{userDialog, channelDialog},
[]tg.MessageClass{
&tg.Message{ID: 7, PeerID: &tg.PeerUser{UserID: 11}, Date: 101},
&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102},
},
[]tg.ChatClass{channel}, []tg.UserClass{user}, false,
)
if err != nil {
t.Fatal(err)
}
if len(destination) != 2 || last.PeerType != "channel" || last.PeerID != 22 || last.Pts != 12 || !last.HasPts || last.TopMessageDate != 102 {
t.Fatalf("merged dialogs = %+v, last = %+v", destination, last)
}
if _, err := mergeDialogPage(destination,
[]tg.DialogClass{channelDialog},
[]tg.MessageClass{&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102}},
[]tg.ChatClass{channel}, nil, false,
); err == nil {
t.Fatal("duplicate dialog page passed validation")
}
overlapDestination := make(map[clientPeerKey]ClientDialogState)
pinnedDialog := *channelDialog
pinnedDialog.Pinned = true
if _, err := mergeDialogPage(overlapDestination,
[]tg.DialogClass{&pinnedDialog},
[]tg.MessageClass{&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102}},
[]tg.ChatClass{channel}, nil, true,
); err != nil {
t.Fatal(err)
}
allowed := map[clientPeerKey]struct{}{{typ: "channel", id: 22}: {}}
if _, overlaps, err := mergeDialogPageKnownOverlap(overlapDestination,
[]tg.DialogClass{&pinnedDialog},
[]tg.MessageClass{&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102}},
[]tg.ChatClass{channel}, nil, false, allowed,
); err != nil || overlaps != 1 {
t.Fatalf("known pinned overlap count=%d err=%v", overlaps, err)
}
if _, _, err := mergeDialogPageKnownOverlap(overlapDestination,
[]tg.DialogClass{&pinnedDialog},
[]tg.MessageClass{&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102}},
[]tg.ChatClass{channel}, nil, false, allowed,
); err == nil {
t.Fatal("second copy of a consumed pinned overlap passed validation")
}
channelWithoutPts := &tg.Dialog{Peer: &tg.PeerChannel{ChannelID: 22}, TopMessage: 8}
if _, err := mergeDialogPage(make(map[clientPeerKey]ClientDialogState),
[]tg.DialogClass{channelWithoutPts},
[]tg.MessageClass{&tg.Message{ID: 8, PeerID: &tg.PeerChannel{ChannelID: 22}, Date: 102}},
[]tg.ChatClass{channel}, nil, false,
); err == nil {
t.Fatal("channel dialog without pts passed validation")
}
}
func TestClientStateRoundTripLocksSeededChannelIdentity(t *testing.T) {
dataset, seedState, targets := snapshotFixture(t)
seedIdentity, err := seedIdentitySHA256(seedState)
if err != nil {
t.Fatal(err)
}
state := &ClientState{
Version: ClientStateVersion, DatasetSHA256: dataset.PlanSHA256, SeedIdentitySHA: seedIdentity,
Accounts: make([]ClientAccountState, dataset.Config.Accounts),
}
for account := range state.Accounts {
state.Accounts[account] = ClientAccountState{
AccountIndex: account, UserID: targets[account].UserID,
State: ClientUpdateState{Pts: account + 1, Date: 100},
}
expected := expectedDatasetPeers(dataset, seedState, targets, account)
for peer := range expected {
dialog := ClientDialogState{
PeerType: peer.typ, PeerID: peer.id, AccessHash: 99,
TopMessage: 1, TopMessageDate: 100, DatasetExpected: true,
}
if peer.typ == "channel" {
dialog.HasPts, dialog.Pts = true, 5
}
state.Accounts[account].Dialogs = append(state.Accounts[account].Dialogs, dialog)
}
}
if err := state.Validate(dataset, seedState, targets); err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "client-state.json")
if err := WriteClientState(path, state); err != nil {
t.Fatal(err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("client state mode = %o, want 600", info.Mode().Perm())
}
loaded, err := LoadClientState(path)
if err != nil {
t.Fatal(err)
}
if err := loaded.Validate(dataset, seedState, targets); err != nil {
t.Fatal(err)
}
seedState.Groups[0].ChannelID++
if err := loaded.Validate(dataset, seedState, targets); err == nil {
t.Fatal("client state accepted different seeded channel identity")
}
}
func TestClientStateRejectsMissingExpectedPeer(t *testing.T) {
dataset, seedState, targets := snapshotFixture(t)
expected := expectedDatasetPeers(dataset, seedState, targets, 0)
account := ClientAccountState{AccountIndex: 0, UserID: targets[0].UserID, State: ClientUpdateState{Date: 100}}
for peer := range expected {
dialog := ClientDialogState{PeerType: peer.typ, PeerID: peer.id, AccessHash: 1, TopMessage: 1, TopMessageDate: 1, DatasetExpected: true}
if peer.typ == "channel" {
dialog.HasPts, dialog.Pts = true, 1
}
account.Dialogs = append(account.Dialogs, dialog)
}
account.Dialogs = account.Dialogs[1:]
if err := validateExpectedDatasetPeers(dataset, seedState, targets, &account); err == nil {
t.Fatal("account with missing expected peer passed validation")
}
}
func TestDialogsPaginationOnlyFinishesOnFullOrEmptyResponse(t *testing.T) {
if dialogsPaginationDone(false, 100) {
t.Fatal("non-empty dialogsSlice was treated as final when the client requested a larger page")
}
if !dialogsPaginationDone(false, 0) {
t.Fatal("empty dialogsSlice did not finish pagination")
}
if !dialogsPaginationDone(true, 100) {
t.Fatal("messages.dialogs full constructor did not finish pagination")
}
}
func snapshotFixture(t *testing.T) (*Dataset, *DatasetSeedState, []SessionRecord) {
t.Helper()
cfg := DatasetConfig{
Accounts: 4, Seed: 7, PrivateFanout: 1,
HotGroups: 1, HotMembers: 4, HotHistory: 1,
}
dataset, err := PlanDataset(cfg)
if err != nil {
t.Fatal(err)
}
seedState, err := NewDatasetSeedState(dataset)
if err != nil {
t.Fatal(err)
}
// This fixture models a pre-rich-state journal unless an individual test
// explicitly opts into the newer phase.
seedState.RichStateByAccount = nil
for account := 0; account < cfg.Accounts; account++ {
seedState.PrivateSentByAccount[account] = cfg.PrivateFanout
}
seedState.Groups[0].ChannelID = 500
seedState.Groups[0].AccessHash = 600
seedState.Groups[0].InviteCursor = 3
seedState.Groups[0].InvitePendingEnd = 3
seedState.HistorySentByAccount[dataset.Groups[0].MemberAccounts[0]] = 1
targets := make([]SessionRecord, cfg.Accounts)
for account := range targets {
targets[account] = SessionRecord{AccountIndex: account, UserID: int64(100 + account), AccessHash: int64(200 + account), SessionFile: "session"}
}
return dataset, seedState, targets
}