Cover timeline epoch reset behavior

This commit is contained in:
Mohamed Boudra
2026-05-29 09:55:40 +07:00
parent 65ace20880
commit 0ee76b2ea0
2 changed files with 49 additions and 0 deletions
@@ -388,6 +388,41 @@ describe("processTimelineResponse", () => {
expect(getUserTexts(result.tail)).toHaveLength(0);
});
it("treats a stale epoch reset as a replacement with the new epoch window", () => {
const oldTail: StreamItem[] = [makeAssistantItem("old epoch message", "old-assistant")];
const oldHead: StreamItem[] = [makeAssistantItem("old live head", "old-head")];
const result = processTimelineResponse({
...baseTimelineInput,
currentTail: oldTail,
currentHead: oldHead,
currentCursor: { epoch: "old-epoch", startSeq: 1, endSeq: 95 },
payload: {
...baseTimelineInput.payload,
reset: true,
epoch: "new-epoch",
startCursor: { seq: 101 },
endCursor: { seq: 105 },
hasOlder: true,
entries: [
makeTimelineEntry(101, "new one"),
makeTimelineEntry(102, "new two"),
makeTimelineEntry(103, "new three"),
makeTimelineEntry(104, "new four"),
makeTimelineEntry(105, "new five"),
],
},
});
expect(getAssistantTexts(result.tail)).toEqual(["new onenew twonew threenew fournew five"]);
expect(result.head).toEqual([]);
expect(result.cursor).toEqual({ epoch: "new-epoch", startSeq: 101, endSeq: 105 });
expect(result.sideEffects).not.toContainEqual({
type: "catch_up",
cursor: { epoch: "old-epoch", endSeq: 95 },
});
});
it("performs bootstrap tail init with catch-up side effect", () => {
const result = processTimelineResponse({
...baseTimelineInput,
@@ -2214,6 +2214,20 @@ test("fetchTimeline returns a bounded reset window when cursor epoch is stale",
expect(result.rows[0]?.seq).toBe(3);
expect(result.rows[result.rows.length - 1]?.seq).toBe(3);
expect(result.hasOlder).toBe(true);
const older = manager.fetchTimeline(snapshot.id, {
direction: "before",
cursor: {
epoch: result.epoch,
seq: result.rows[0]?.seq ?? 0,
},
limit: 1,
});
expect(older.reset).toBe(false);
expect(older.rows).toHaveLength(1);
expect(older.rows[0]?.seq).toBe(2);
expect(older.hasOlder).toBe(true);
});
test("getTimelineRows falls back to the in-memory timeline when no durable store is configured", async () => {