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
This commit is contained in:
1 parent
efb54ad4cc
commit
002f08b456
9 files changed
+418
-120
No files matched your search
@@ -3,7 +3,6 @@ import { evictDurableObject, runInDurableObject } from "cloudflare:test";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import worker from "../src/index.js";
|
||||
import { idFromWebkey, toWebkey } from "../src/webkeys.js";
|
||||
import { FETCH_BUDGET } from "../src/query.js";
|
||||
import { PAGE_SIZE } from "../src/cache.js";
|
||||
|
||||
// matches tests/fake-webapp.js: 30 items, every third private
|
||||
@@ -283,30 +282,34 @@ function isoWeek(d: Date): string {
|
||||
return `${year}-W${String(week).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
describe.skip("public page filters (goal 1d)", () => {
|
||||
describe("public page filters (goal 1d)", () => {
|
||||
// the fake's dated account: "dated n" updated n-1 days + 1h ago;
|
||||
// #weekly on every 7th; every 10th private; every 5th created 100
|
||||
// days before it was updated
|
||||
// days before it was updated. The public page only reads, so each
|
||||
// test stores the pages it needs on the owner's side first.
|
||||
const call = (path: string, init?: RequestInit) =>
|
||||
worker.fetch(new Request(`https://diigo-cache.test${path}`, init), env as never);
|
||||
const wireDated = async () => {
|
||||
/** wire a public cache to `webappId` and store diigo pages 1..`pages` */
|
||||
const wireStored = async (webappId: string, pages: number) => {
|
||||
const res = await call("/_wire", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ webappId: "fake-dated-webapp", mode: "public" }),
|
||||
body: JSON.stringify({ webappId, mode: "public" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
return "/public/dated_dan";
|
||||
const { username, publicId } = (await res.json()) as { username: string; publicId: string };
|
||||
const pub = testEnv.PUBLIC_DO.get(testEnv.PUBLIC_DO.idFromString(idFromWebkey(publicId)));
|
||||
for (let n = 1; n <= pages; n += 1) await pub.page(n, PAGE_SIZE);
|
||||
return { page: `/public/${username}`, pub };
|
||||
};
|
||||
const wireDated = async () => (await wireStored("fake-dated-webapp", 1)).page;
|
||||
const titlesIn = (html: string) => [...html.matchAll(/>(dated \d+)<\/a>/g)].map((m) => m[1]);
|
||||
const dated = (...ns: number[]) => ns.map((n) => `dated ${n}`);
|
||||
|
||||
it("?days=7 lists the last week, walking only the diigo pages it needs", async () => {
|
||||
it("?days=7 lists the last week", async () => {
|
||||
const page = await wireDated();
|
||||
const before = await testEnv.WEBAPP.calls();
|
||||
const html = await (await call(`${page}?days=7`)).text();
|
||||
expect(titlesIn(html)).toEqual(dated(1, 2, 3, 4, 5, 6, 7));
|
||||
expect((await testEnv.WEBAPP.calls()) - before).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("?by=created windows, lists, and dates by when it was bookmarked", async () => {
|
||||
@@ -327,13 +330,7 @@ describe.skip("public page filters (goal 1d)", () => {
|
||||
});
|
||||
|
||||
it("?days=300 pages the matches PAGE_SIZE at a time; links keep the filter", async () => {
|
||||
const res = await call("/_wire", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ webappId: "fake-deep-webapp", mode: "public" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const page = "/public/deep_dee";
|
||||
const { page } = await wireStored("fake-deep-webapp", 3);
|
||||
// deep_dee: one a day; days=300 holds 1..300, minus every 10th (private)
|
||||
const want = Array.from({ length: 300 }, (_, k) => k + 1).filter((n) => n % 10 !== 0);
|
||||
const first = await (await call(`${page}?days=300`)).text();
|
||||
@@ -354,6 +351,15 @@ describe.skip("public page filters (goal 1d)", () => {
|
||||
expect(titlesIn(html)).toEqual(dated(11, 12));
|
||||
});
|
||||
|
||||
it("?in= names an ISO week", async () => {
|
||||
const { page } = await wireStored("fake-deep-webapp", 3);
|
||||
const week = isoWeek(new Date(Date.now() - 100 * 24 * 60 * 60 * 1000));
|
||||
const html = await (await call(`${page}?in=${week}`)).text();
|
||||
// one a day; every 10th is private
|
||||
expect(titlesIn(html).length).toBeGreaterThanOrEqual(6);
|
||||
expect(titlesIn(html).length).toBeLessThanOrEqual(7);
|
||||
});
|
||||
|
||||
it("?q=#weekly&days=60 filters by tag; private items stay out", async () => {
|
||||
const page = await wireDated();
|
||||
const html = await (await call(`${page}?days=60&q=${encodeURIComponent("#weekly")}`)).text();
|
||||
@@ -366,22 +372,21 @@ describe.skip("public page filters (goal 1d)", () => {
|
||||
expect(titlesIn(html)).toEqual(dated(7, 14, 21, 28, 35, 42, 49, 56));
|
||||
});
|
||||
|
||||
it("a request spends at most FETCH_BUDGET diigo fetches; incomplete results say so, and a reload continues", async () => {
|
||||
const res = await call("/_wire", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ webappId: "fake-deep-webapp", mode: "public" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
// deep_dee: 60 diigo pages of 100; a search matching nothing must walk them all
|
||||
it("a filter reads only stored items, never diigo; results say whether storage is complete", async () => {
|
||||
const { page, pub } = await wireStored("fake-deep-webapp", 1);
|
||||
const json = async () =>
|
||||
(await call(`${page}?days=300`, { headers: { accept: "application/json" } })).json() as Promise<{
|
||||
matched: number;
|
||||
complete: boolean;
|
||||
}>;
|
||||
const before = await testEnv.WEBAPP.calls();
|
||||
const first = await call("/public/deep_dee?q=nomatch", { headers: { accept: "application/json" } });
|
||||
expect(await first.json()).toMatchObject({ complete: false, items: [] });
|
||||
expect((await testEnv.WEBAPP.calls()) - before).toBeLessThanOrEqual(FETCH_BUDGET + 1);
|
||||
const html = await (await call("/public/deep_dee?q=nomatch")).text();
|
||||
expect(html).toMatch(/complete|no matches/);
|
||||
const again = await call("/public/deep_dee?q=nomatch", { headers: { accept: "application/json" } });
|
||||
expect(await again.json()).toMatchObject({ complete: true, items: [] });
|
||||
// page 1 holds 1..100, minus 10 private
|
||||
expect(await json()).toMatchObject({ matched: 90, complete: false });
|
||||
expect(await (await call(`${page}?days=300`)).text()).toMatch(/some matches may be missing/);
|
||||
expect(await testEnv.WEBAPP.calls()).toBe(before);
|
||||
await pub.page(2, PAGE_SIZE);
|
||||
await pub.page(3, PAGE_SIZE);
|
||||
expect(await json()).toMatchObject({ matched: 270, complete: true });
|
||||
});
|
||||
|
||||
it("JSON answers carry the canonical filter, so devs see what was understood", async () => {
|
||||
@@ -393,25 +398,6 @@ describe.skip("public page filters (goal 1d)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("?in= an old week costs O(log pages) diigo fetches with by=updated, not a walk from page 1", async () => {
|
||||
const res = await call("/_wire", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ webappId: "fake-deep-webapp", mode: "public" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
// deep_dee: 6000 bookmarks, one a day, 60 diigo pages of 100; a week
|
||||
// ~14 years back sits near page 51 — a walk from page 1 would cost ~51
|
||||
const longAgo = new Date(Date.now() - 5100 * 24 * 60 * 60 * 1000);
|
||||
const week = isoWeek(longAgo);
|
||||
const before = await testEnv.WEBAPP.calls();
|
||||
const html = await (await call(`/public/deep_dee?in=${week}`)).text();
|
||||
// one a day; every 10th is private
|
||||
expect(titlesIn(html).length).toBeGreaterThanOrEqual(6);
|
||||
expect(titlesIn(html).length).toBeLessThanOrEqual(7);
|
||||
expect((await testEnv.WEBAPP.calls()) - before).toBeLessThanOrEqual(12);
|
||||
});
|
||||
|
||||
it("a malformed filter is a 400 with a message, not an empty list", async () => {
|
||||
const page = await wireDated();
|
||||
const res = await call(`${page}?days=7x`);
|
||||
|
||||
@@ -18,9 +18,9 @@ const OVERSTATEMENT = 60;
|
||||
* 5th an old bookmark edited recently (created 100 days before updated) */
|
||||
const DATED_WEBAPP = "fake-dated-webapp";
|
||||
const DATED_TOTAL = 60;
|
||||
/** the same shape, 6000 deep (~16 years, 60 pages of 100): for walk cost */
|
||||
/** the same shape, 300 deep (3 pages of 100): for paging matches */
|
||||
const DEEP_WEBAPP = "fake-deep-webapp";
|
||||
const DEEP_TOTAL = 6000;
|
||||
const DEEP_TOTAL = 300;
|
||||
const HOUR = 60 * 60 * 1000;
|
||||
|
||||
function datedItem(n, now, user = "dated_dan") {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { matches, parseQuery, toQuery } from "../src/query.js";
|
||||
import type { FilterSpec, Query, QueryError } from "../src/query.js";
|
||||
|
||||
// goal 1d (capsite checklist): the design as tests; unskip as it lands
|
||||
// goal 1d (capsite checklist)
|
||||
|
||||
const NOW = new Date("2026-10-07T12:00:00.000Z");
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
@@ -21,7 +21,7 @@ const refused = (q: Query | QueryError) => {
|
||||
const day = (iso: string) => new Date(`${iso}T00:00:00.000Z`);
|
||||
const range = (q: Query) => [q.after?.toISOString().slice(0, 10), q.before?.toISOString().slice(0, 10)];
|
||||
|
||||
describe.skip("filters: one spec, two spellings (goal 1d)", () => {
|
||||
describe("filters: one spec, two spellings (goal 1d)", () => {
|
||||
it("devs write { days: 7 }; URLs write ?days=7; same query", () => {
|
||||
expect(spec({ days: 7 })).toEqual(url("days=7"));
|
||||
expect(spec({ days: 7 }).after).toEqual(new Date(NOW.getTime() - 7 * DAY));
|
||||
@@ -42,7 +42,7 @@ describe.skip("filters: one spec, two spellings (goal 1d)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip("windows: UTC, half-open (goal 1d)", () => {
|
||||
describe("windows: UTC, half-open (goal 1d)", () => {
|
||||
it("after/before are UTC days: after inclusive from 00:00Z, before exclusive", () => {
|
||||
expect(range(url("after=2026-01-01&before=2026-02-01"))).toEqual(["2026-01-01", "2026-02-01"]);
|
||||
expect(url("after=2026/01/01").after).toEqual(day("2026-01-01"));
|
||||
@@ -88,7 +88,7 @@ describe.skip("windows: UTC, half-open (goal 1d)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip("q: terms and operators (goal 1d)", () => {
|
||||
describe("q: terms and operators (goal 1d)", () => {
|
||||
const q = (text: string) => url(`q=${encodeURIComponent(text)}`);
|
||||
|
||||
it("words, phrases, #tags, tag:, and - negation; case-insensitive", () => {
|
||||
@@ -160,7 +160,7 @@ const item = (iso: string, extra: Record<string, unknown> = {}) => ({
|
||||
});
|
||||
const query = (q: Partial<Query>): Query => ({ by: "updated", words: [], tags: [], ...q });
|
||||
|
||||
describe.skip("matches (goal 1d)", () => {
|
||||
describe("matches (goal 1d)", () => {
|
||||
it("the window is half-open: after inclusive, before exclusive", () => {
|
||||
const q = query({ after: day("2026-01-01"), before: day("2026-02-01") });
|
||||
expect(matches(item("2026-01-01T00:00:00.000Z"), q)).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user