fix(localdb): oplog store review fixes — ack clamping, age-based pruning, monotonicity tests

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
Joseph Doherty
2026-07-17 21:55:25 -04:00
parent 4f9982c53f
commit 3cf8cabaf9
2 changed files with 89 additions and 15 deletions
@@ -90,6 +90,38 @@ public sealed class OplogStoreTests : IDisposable
Assert.Equal(third.Seq, peer.LastAckedSeq);
}
[Fact]
public async Task RecordPeerAck_OutOfOrderAck_DoesNotRegressWatermark()
{
using var db = await NewOrdersDb();
await WriteRows(db, 5);
var store = new OplogStore(db, new ReplicationOptions());
var batch = await store.ReadBatchAboveAsync(0, 100, default);
await store.RecordPeerAckAsync(batch[4].Seq, default);
await store.RecordPeerAckAsync(batch[1].Seq, default);
var peer = await store.GetPeerStateAsync(default);
Assert.Equal(batch[4].Seq, peer.LastAckedSeq);
// A late duplicate/out-of-order ack of an already-acked seq is benign, not a violation.
Assert.False(peer.NeedsSnapshot);
}
[Fact]
public async Task RecordPeerAck_AckBeyondMaxSeq_ClampsAndFlagsSnapshot()
{
using var db = await NewOrdersDb();
await WriteRows(db, 3);
var store = new OplogStore(db, new ReplicationOptions());
var maxSeq = await store.GetMaxSeqAsync(default);
await store.RecordPeerAckAsync(maxSeq + 100, default);
var peer = await store.GetPeerStateAsync(default);
Assert.Equal(maxSeq, peer.LastAckedSeq);
Assert.True(peer.NeedsSnapshot);
}
[Fact]
public async Task Prune_DropsExpiredTombstoneRowVersions_KeepsLive()
{
@@ -137,12 +169,12 @@ public sealed class OplogStoreTests : IDisposable
Assert.True(flagged);
var count = await db.QueryAsync("SELECT COUNT(*) FROM __localdb_oplog", r => r.GetInt64(0));
Assert.True(count[0] <= 3, $"oplog should be pruned to <= 3, was {count[0]}");
Assert.Equal(3, count[0]);
Assert.True((await store.GetPeerStateAsync(default)).NeedsSnapshot);
}
[Fact]
public async Task CapExceeded_ByAge_SetsNeedsSnapshot()
public async Task CapExceeded_ByAge_SetsNeedsSnapshot_AndPrunesExpired()
{
using var db = await NewOrdersDb();
await db.ExecuteAsync("INSERT INTO orders (id, sku, qty) VALUES (1,'A',1),(2,'B',2)");
@@ -154,6 +186,9 @@ public sealed class OplogStoreTests : IDisposable
Assert.True(flagged);
Assert.True((await store.GetPeerStateAsync(default)).NeedsSnapshot);
// Age-only trigger must actually prune: both entries are past the age cutoff.
var count = await db.QueryAsync("SELECT COUNT(*) FROM __localdb_oplog", r => r.GetInt64(0));
Assert.Equal(0, count[0]);
}
[Fact]