From 80c9279825e8df031c0dae8d6d4522143e96dbc2 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 7 Oct 2026 03:43:06 -0500 Subject: [PATCH] diigo-cache: test that a cached page replays fetch-time order A hit must replay diigo's order as fetched (page_members.pos), not a column sort. The fake webapp's link_id and updated_at are scrambled so neither column sorts back into diigo's order; ordering the replay by updated or item_id fails the new test. Generated-by: Claude Opus 5.5 --- diigo-cache/tests/caches.test.ts | 17 +++++++++++++++++ diigo-cache/tests/fake-webapp.js | 9 ++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/diigo-cache/tests/caches.test.ts b/diigo-cache/tests/caches.test.ts index c65eabf..55523d0 100644 --- a/diigo-cache/tests/caches.test.ts +++ b/diigo-cache/tests/caches.test.ts @@ -79,6 +79,23 @@ describe("DiigoCache", () => { }); }); +describe("page replay order", () => { + it("a hit replays diigo's fetch-time order (page_members), not a column sort", async () => { + const stub = testEnv.CACHE_DO.get(testEnv.CACHE_DO.newUniqueId()); + await stub.setup({ webappId: WEBAPP_ID }); + const titles = (items: string[]) => parse(items).map((i) => i.title); + const expected = Array.from({ length: 24 }, (_, k) => `bookmark ${k + 1}`); + + const miss = await stub.page(1, 24); + expect(titles(miss.items)).toEqual(expected); + // page 2's etch upserts other rows; page 1's replay must not shift + await stub.page(2, 24); + const hit = await stub.page(1, 24); + expect(hit.cached).toBe(true); + expect(titles(hit.items)).toEqual(expected); + }); +}); + describe("worker routes", () => { const call = (path: string, init?: RequestInit) => worker.fetch(new Request(`https://diigo-cache.test${path}`, init), env as never); diff --git a/diigo-cache/tests/fake-webapp.js b/diigo-cache/tests/fake-webapp.js index ab7af3f..bab8c96 100644 --- a/diigo-cache/tests/fake-webapp.js +++ b/diigo-cache/tests/fake-webapp.js @@ -10,10 +10,13 @@ import { RpcTarget, WorkerEntrypoint } from "cloudflare:workers"; const KNOWN_WEBAPP = "fake-webapp-id"; const TOTAL = 30; -/** item n (1-based, updated-desc order): diigo's field names, made-up values */ +/** item n (1-based, in diigo's order): diigo's field names, made-up + * values. link_id and updated_at are scrambled (distinct; 7 and 11 are + * coprime to 31), so neither column sorts back into diigo's order — a + * cache that replays by column rather than by fetch order is caught. */ function item(n) { return { - link_id: 1000 + n, + link_id: 1000 + ((n * 7) % 31), url: `https://example.com/${n}`, title: `bookmark ${n}`, description: "", @@ -23,7 +26,7 @@ function item(n) { user_id: 4242, tags: "", created_at: 1700000000, - updated_at: 1700001000 - n, + updated_at: 1700001000 + ((n * 11) % 31), }; }