/** @file a stand-in for webapp's `WebAppFactory`, serving diigo-shaped pages to tests. */ import { RpcTarget, WorkerEntrypoint } from "cloudflare:workers"; const KNOWN_WEBAPP = "fake-webapp-id"; const TOTAL = 250; /** the plain account, capped below PAGE_SIZE (as diigo caps at 100) */ const CAPPED_WEBAPP = "fake-capped-webapp"; const CAP = 64; /** the plain account, but its `total` claims 60 more items than it has * (hypothetical: what looked like this on 2026-10-07 was the 0-based * page_num, but the guard stays) */ const OVERSTATED_WEBAPP = "fake-overstated-webapp"; const OVERSTATEMENT = 60; /** a second account, for date filters (goal 1d): 60 bookmarks, item n * updated n-1 days + 1 hour before the call, in diigo's updated-desc * order; tagged ocap, plus weekly every 7th; every 10th private; every * 5th an old bookmark edited recently (created 100 days before updated) */ const DATED_WEBAPP = "fake-dated-webapp"; const DATED_TOTAL = 60; /** the same shape, 300 deep (3 pages of 100): for paging matches */ const DEEP_WEBAPP = "fake-deep-webapp"; const DEEP_TOTAL = 300; const HOUR = 60 * 60 * 1000; function datedItem(n, now, user = "dated_dan") { return { link_id: 5000 + n, url: `https://dated.example/${n}`, title: `dated ${n}`, description: n % 7 === 0 ? "a week of fragments" : "", private: n % 10 === 0, u_name: user, user_id: 777, tags: n % 7 === 0 ? "ocap,weekly" : "ocap", created_at: Math.floor((now - (n - 1) * 24 * HOUR - HOUR - (n % 5 === 0 ? 100 * 24 * HOUR : 0)) / 1000), updated_at: Math.floor((now - (n - 1) * 24 * HOUR - HOUR) / 1000), }; } /** 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 307, a prime above TOTAL), 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 * 7) % 307), url: `https://example.com/${n}`, title: `bookmark ${n}`, description: "", private: n % 3 === 0, u_name: "alice_99", first_by: "someone-else", user_id: 4242, tags: "", created_at: 1700000000, updated_at: 1700001000 + ((n * 11) % 307), }; } let calls = 0; class FakeGet extends RpcTarget { #path; #params; /** null (the plain account), "capped", "overstated", "dated", or "deep" */ #dated; constructor(path, params, dated) { super(); this.#path = path; this.#params = params; this.#dated = dated; } pathjoin(ref) { return new FakeGet(ref, this.#params, this.#dated); } query(params) { return new FakeGet(this.#path, { ...this.#params, ...params }, this.#dated); } async get() { calls += 1; if (this.#path !== "load_user_items") throw new Error(`fake webapp: no route ${this.#path}`); const count = Math.min(Number(this.#params.count), this.#dated === "capped" ? CAP : Infinity); const page = Number(this.#params.page_num); const dated = this.#dated === "dated" || this.#dated === "deep"; const total = this.#dated === "deep" ? DEEP_TOTAL : dated ? DATED_TOTAL : TOTAL; const now = Date.now(); const items = []; // diigo's page_num counts from 0 (observed 2026-10-07: page_num=1 // skipped the newest page) for (let n = page * count + 1; n <= Math.min((page + 1) * count, total); n += 1) { items.push(dated ? datedItem(n, now, this.#dated === "deep" ? "deep_dee" : "dated_dan") : item(n)); } // past the end, diigo answers no items and a total of 0 (observed // 2026-10-07: the page after the last reported total ≤ 100) if (items.length === 0) return JSON.stringify({ total: 0, items }); return JSON.stringify({ total: this.#dated === "overstated" ? total + OVERSTATEMENT : total, items }); } } /** * webapp's `WebAppFactory` entrypoint, faked: an auxiliary worker (plain * JS; the vitest pool doesn't transform aux workers) serving * `load_user_items` pages for a few known webapp ids. Counts its diigo * calls, so tests can tell a cache hit (no call) from a miss. */ export class WebAppFactory extends WorkerEntrypoint { async at(id) { const kind = { [KNOWN_WEBAPP]: null, [CAPPED_WEBAPP]: "capped", [OVERSTATED_WEBAPP]: "overstated", [DATED_WEBAPP]: "dated", [DEEP_WEBAPP]: "deep", }[id]; if (kind === undefined) throw new Error("fake webapp: unknown id"); return new FakeGet("", {}, kind); } /** how many diigo calls so far (test hook) */ async calls() { return calls; } } export default { fetch() { return new Response("fake webapp", { status: 404 }); }, };