owpengram-server/internal/store/postgres/notify_settings_event_roundtrip_test.go
2026-09-07 09:31:06 +03:00

85 lines
3.5 KiB
Go

package postgres
import (
"context"
"testing"
"telesrv/internal/domain"
)
// TestNotifySettingsEventSurvivesPostgresRoundTrip is a regression test for a real production
// incident: RecordNotifySettings set domain.UpdateEvent.NotifyPeerSettings only in memory, but
// appendUserUpdateEvent/ListAfter never serialized that field to/from Postgres. The write
// itself succeeded, but every later read (outbox dispatch batching, updates.getDifference)
// got NotifyPeerSettings == nil back, so convert_updates.go's UpdateEventNotifySettings case
// silently produced no TL update ("non-noop outbox event produced no update"). Worse: the
// stuck outbox row blocked that account's entire dispatch lane, so unrelated updates (new
// messages) stopped reaching other sessions until a full resync. This test appends a real
// notify_settings event and reads it back through the same store, asserting the settings
// survive the round trip.
func TestNotifySettingsEventSurvivesPostgresRoundTrip(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
owner, err := users.Create(ctx, domain.User{AccessHash: 51, Phone: "+1668" + suffix + "01", FirstName: "Owner"})
if err != nil {
t.Fatalf("create owner: %v", err)
}
peerUser, err := users.Create(ctx, domain.User{AccessHash: 52, Phone: "+1668" + suffix + "02", FirstName: "Peer"})
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, peerUser.ID})
})
events := NewUpdateEventStore(pool)
muteUntil := 2000000000
showPreviews := false
appended, err := events.AppendAllocatedWithDispatch(ctx, owner.ID, domain.UpdateEvent{
Type: domain.UpdateEventNotifySettings,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: peerUser.ID},
NotifyPeerSettings: &domain.PeerNotifySettings{
MuteUntil: &muteUntil,
ShowPreviews: &showPreviews,
},
PtsCount: 1,
}, [8]byte{}, 0)
if err != nil {
t.Fatalf("AppendAllocatedWithDispatch: %v", err)
}
t.Logf("appended event = %+v", appended)
if appended.NotifyPeerSettings == nil {
t.Fatalf("appended.NotifyPeerSettings = nil immediately after append, want the settings we sent")
}
// Read it back exactly the way updates.getDifference does.
replayed, err := events.ListAfter(ctx, owner.ID, appended.Pts-1, 10)
if err != nil {
t.Fatalf("ListAfter: %v", err)
}
t.Logf("replayed events = %+v", replayed)
var found *domain.UpdateEvent
for i := range replayed {
if replayed[i].Type == domain.UpdateEventNotifySettings && replayed[i].Pts == appended.Pts {
found = &replayed[i]
}
}
if found == nil {
t.Fatalf("notify_settings event at pts=%d not found in ListAfter result", appended.Pts)
}
if found.NotifyPeerSettings == nil {
t.Fatalf("replayed event.NotifyPeerSettings = nil -- THE BUG: settings were lost across the Postgres round trip, so getDifference/outbox dispatch cannot build a TL update for this event")
}
if found.NotifyPeerSettings.MuteUntil == nil || *found.NotifyPeerSettings.MuteUntil != muteUntil {
t.Fatalf("replayed MuteUntil = %+v, want %d", found.NotifyPeerSettings.MuteUntil, muteUntil)
}
if found.NotifyPeerSettings.ShowPreviews == nil || *found.NotifyPeerSettings.ShowPreviews != showPreviews {
t.Fatalf("replayed ShowPreviews = %+v, want %v", found.NotifyPeerSettings.ShowPreviews, showPreviews)
}
if found.Peer != (domain.Peer{Type: domain.PeerTypeUser, ID: peerUser.ID}) {
t.Fatalf("replayed Peer = %+v, want peer %d", found.Peer, peerUser.ID)
}
}