owpengram-server/internal/store/postgres/contiguous_pts_integration_test.go
2026-06-04 01:37:39 +08:00

127 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package postgres
import (
"context"
"testing"
"telesrv/internal/domain"
)
// TestMaxContiguousPtsStopsAtHole 用真实 PG 验证 RecentUserPts 窗口查询 + 连续计算:
// 存在在途空洞时只报告最大连续 pts补洞后回升。
func TestMaxContiguousPtsStopsAtHole(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
owner, err := NewUserStore(pool).Create(ctx, domain.User{
AccessHash: 1,
Phone: "+1556" + suffix + "01",
FirstName: "Contig",
})
if err != nil {
t.Fatalf("create user: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM user_update_events WHERE user_id = $1", owner.ID)
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
})
events := NewUpdateEventStore(pool)
appendPts := func(pts int) {
if err := events.Append(ctx, owner.ID, domain.UpdateEvent{
Type: domain.UpdateEventNoop,
Pts: pts,
PtsCount: 1,
Date: 1700000000 + pts,
}); err != nil {
t.Fatalf("append pts=%d: %v", pts, err)
}
}
for _, p := range []int{1, 2, 3, 5, 6} { // pts=4 为在途空洞
appendPts(p)
}
if _, err := pool.Exec(ctx, "DELETE FROM user_update_watermarks WHERE user_id = $1", owner.ID); err != nil {
t.Fatalf("delete watermark fallback row: %v", err)
}
got, err := events.MaxContiguousPts(ctx, owner.ID)
if err != nil {
t.Fatalf("MaxContiguousPts: %v", err)
}
if got != 3 {
t.Fatalf("contiguous = %d, want 3止于 pts=4 空洞,最大已提交为 6", got)
}
appendPts(4) // 在途事务提交/补洞
got, err = events.MaxContiguousPts(ctx, owner.ID)
if err != nil {
t.Fatalf("MaxContiguousPts after fill: %v", err)
}
if got != 6 {
t.Fatalf("contiguous after fill = %d, want 6", got)
}
}
func TestAppendWithDispatchWritesEventAndOutboxAtomically(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
owner, err := NewUserStore(pool).Create(ctx, domain.User{
AccessHash: 2,
Phone: "+1557" + suffix + "01",
FirstName: "Dispatch",
})
if err != nil {
t.Fatalf("create user: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM dispatch_outbox WHERE target_user_id = $1", owner.ID)
_, _ = pool.Exec(ctx, "DELETE FROM user_update_events WHERE user_id = $1", owner.ID)
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
})
event := domain.UpdateEvent{
UserID: owner.ID,
Type: domain.UpdateEventDialogPinned,
Pts: 1,
PtsCount: 1,
Date: 1700000001,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 1000000002},
Settings: domain.PeerSettings{
ShareContact: true,
},
Bool: true,
}
var excludeAuthKeyID [8]byte
excludeAuthKeyID[0] = 9
if err := NewUpdateEventStore(pool).AppendWithDispatch(ctx, owner.ID, event, excludeAuthKeyID, 77); err != nil {
t.Fatalf("AppendWithDispatch: %v", err)
}
got, err := NewUpdateEventStore(pool).ListAfter(ctx, owner.ID, 0, 10)
if err != nil {
t.Fatalf("ListAfter: %v", err)
}
if len(got) != 1 || got[0].Type != event.Type || got[0].Peer != event.Peer || !got[0].Bool {
t.Fatalf("events = %+v, want dialog pinned event", got)
}
var outbox struct {
pts int
eventType string
excludeAuthKeyID int64
excludeSessionID int64
}
if err := pool.QueryRow(ctx, `
SELECT pts, event_type, exclude_auth_key_id, exclude_session_id
FROM dispatch_outbox
WHERE target_user_id = $1
`, owner.ID).Scan(&outbox.pts, &outbox.eventType, &outbox.excludeAuthKeyID, &outbox.excludeSessionID); err != nil {
t.Fatalf("query dispatch outbox: %v", err)
}
if outbox.pts != 1 || outbox.eventType != string(domain.UpdateEventDialogPinned) || outbox.excludeAuthKeyID != authKeyIDToInt64(excludeAuthKeyID) || outbox.excludeSessionID != 77 {
t.Fatalf("outbox = %+v, want event dispatch excluding current session", outbox)
}
}