Files
captoys/diigo-cache/tests/fake-webapp.js
dckc 002f08b456 diigo-cache: date windows and search on the public page (1d)
/public/<username> takes days, after, before, in (year, month,
quarter, ISO week), by=updated|created, and q (words, "phrases",
#tags, -negation, gmail/Slack/GitHub date operators). JSON answers
echo the parsed filter; a malformed one is a 400.

Filters query the stored items in sqlite through the read-only
viewer instead of walking diigo pages: the public page keeps no
fetch authority, and a filled cache already holds every item.
Results say `complete` when every diigo page is stored; freshness
is 1f's head refresh. So the fetch budget and the O(log pages)
walk test go, and the fake deep account shrinks to 300.

Generated-by: Claude Opus 5.5
2026-10-09 01:17:03 -05:00

131 lines
4.5 KiB
JavaScript

/** @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 });
},
};