feat(store): seen-set read state with floor baseline and compaction
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package store
|
||||
|
||||
import "testing"
|
||||
|
||||
func backlogAccount(t *testing.T, s *Store, backlog bool) {
|
||||
t.Helper()
|
||||
a := sampleAccount()
|
||||
a.ProcessBacklog = backlog
|
||||
if _, err := s.AddAccount(a); err != nil {
|
||||
t.Fatalf("AddAccount: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaselineIgnoresHistoryByDefault(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
backlogAccount(t, s, false)
|
||||
if err := s.EnsureFolderBaseline("work", "INBOX", 1, 100); err != nil {
|
||||
t.Fatalf("baseline: %v", err)
|
||||
}
|
||||
// Existing mail (uid <= 100) is not new; 101 is.
|
||||
for _, tc := range []struct {
|
||||
uid uint32
|
||||
want bool
|
||||
}{{50, false}, {100, false}, {101, true}} {
|
||||
got, _ := s.IsNew("work", "INBOX", tc.uid)
|
||||
if got != tc.want {
|
||||
t.Fatalf("IsNew(%d)=%v want %v", tc.uid, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaselineBacklogProcessesAll(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
backlogAccount(t, s, true)
|
||||
_ = s.EnsureFolderBaseline("work", "INBOX", 1, 100)
|
||||
if n, _ := s.IsNew("work", "INBOX", 1); !n {
|
||||
t.Fatal("with backlog, uid 1 must be new")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAckRemovesFromNewAndIsOutOfOrderSafe(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
backlogAccount(t, s, false)
|
||||
_ = s.EnsureFolderBaseline("work", "INBOX", 1, 100)
|
||||
// New mail arrives: 101..105. Ack out of order: 103 then 101.
|
||||
_ = s.Ack("work", "INBOX", 1, 103)
|
||||
if n, _ := s.IsNew("work", "INBOX", 103); n {
|
||||
t.Fatal("103 acked, must not be new")
|
||||
}
|
||||
if n, _ := s.IsNew("work", "INBOX", 101); !n {
|
||||
t.Fatal("101 not acked yet, must still be new")
|
||||
}
|
||||
_ = s.Ack("work", "INBOX", 1, 101, 102)
|
||||
got, _ := s.FilterNew("work", "INBOX", []uint32{101, 102, 103, 104, 105})
|
||||
want := []uint32{104, 105}
|
||||
if len(got) != 2 || got[0] != want[0] || got[1] != want[1] {
|
||||
t.Fatalf("FilterNew got %v want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactionAdvancesFloor(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
backlogAccount(t, s, false)
|
||||
_ = s.EnsureFolderBaseline("work", "INBOX", 1, 100)
|
||||
// Ack a contiguous run just above the floor: 101,102,103.
|
||||
_ = s.Ack("work", "INBOX", 1, 102, 101, 103)
|
||||
f, _ := s.floor("work", "INBOX")
|
||||
if f != 103 {
|
||||
t.Fatalf("floor should advance to 103, got %d", f)
|
||||
}
|
||||
// The acked rows for the collapsed run are gone.
|
||||
var n int
|
||||
_ = s.db.QueryRow("SELECT COUNT(*) FROM acked").Scan(&n)
|
||||
if n != 0 {
|
||||
t.Fatalf("acked rows should be compacted away, got %d", n)
|
||||
}
|
||||
// A hole remains uncompacted: ack 105 (gap at 104).
|
||||
_ = s.Ack("work", "INBOX", 1, 105)
|
||||
f, _ = s.floor("work", "INBOX")
|
||||
if f != 103 {
|
||||
t.Fatalf("floor must stay at 103 while 104 is a hole, got %d", f)
|
||||
}
|
||||
_ = s.db.QueryRow("SELECT COUNT(*) FROM acked").Scan(&n)
|
||||
if n != 1 {
|
||||
t.Fatalf("105 should remain in acked, got %d rows", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIDValidityChangeResets(t *testing.T) {
|
||||
s := openTemp(t)
|
||||
backlogAccount(t, s, false)
|
||||
_ = s.EnsureFolderBaseline("work", "INBOX", 1, 100)
|
||||
_ = s.Ack("work", "INBOX", 1, 105)
|
||||
// Server reports a new UIDVALIDITY: state resets, re-baselines at new max.
|
||||
if err := s.EnsureFolderBaseline("work", "INBOX", 2, 10); err != nil {
|
||||
t.Fatalf("rebaseline: %v", err)
|
||||
}
|
||||
f, _ := s.floor("work", "INBOX")
|
||||
if f != 10 {
|
||||
t.Fatalf("floor should re-baseline to 10, got %d", f)
|
||||
}
|
||||
var n int
|
||||
_ = s.db.QueryRow("SELECT COUNT(*) FROM acked").Scan(&n)
|
||||
if n != 0 {
|
||||
t.Fatalf("acked must reset on uidvalidity change, got %d", n)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user