Files
captoys/diigo-cache/tests/pm-save.test.ts
dckc 6f409fc83a diigo-cache: bookmark cache in Durable Objects (goal 1c)
The dump worker becomes a page cache: a hit is served from DO SQLite
storage with no diigo call; a miss asks the webapp instance in the
cache's wiring and etches the page.

- two independent caches, one behavior (PagedCache): DiigoCache keeps
  every item; PublicCache keeps shared items only, dropping private
  rows before they are stored; served at /public/<username> (HTML page
  form, or JSON)
- create flow on one port: /_connect relays webapp's mint form, /_mint
  proxies the mint, /_wire wires a private or public cache; /_publish
  adds a public cache later
- ids are webkeys: kept out of URLs (POST bodies), shown and logged
  only as a hash fingerprint; saved to the password manager under the
  holder's petname (never sent to the server)
- items keyed on diigo's link_id; username from u_name; unkeyable
  items and every failure path are logged; each page read logs hit/miss
- tests run the real worker + DOs in the vitest pool against a fake
  webapp (tests/fake-webapp.js)

Generated-by: opencode (GLM)
Generated-by: Claude Opus 5.5
2026-10-07 04:19:54 -05:00

63 lines
2.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { pmSave } from "../src/pm-save.js";
import type { PmPowers } from "../src/pm-save.js";
/** a fake console that records what was logged, by level */
function makeLog() {
const entries: { level: string; args: unknown[] }[] = [];
const at = (level: string) => (...args: unknown[]) => entries.push({ level, args });
return { log: { info: at("info"), warn: at("warn"), error: at("error") }, entries };
}
class FakeCredential {
constructor(readonly init: { id: string; password: string }) {}
}
function setup(store: (c: unknown) => Promise<unknown>, petname = "my cache") {
const { log, entries } = makeLog();
const stored: unknown[] = [];
const powers: PmPowers = {
petname: { value: petname },
credentials: { store: (c) => (stored.push(c), store(c)) },
PasswordCredential: FakeCredential,
log,
};
const btn = { textContent: "save to password manager", disabled: false };
return { powers, btn, entries, stored };
}
describe("pmSave", () => {
it("stores under the petname, and reports a spec-conformant resolve (undefined) as offered, not failed", async () => {
const { powers, btn, entries, stored } = setup(() => Promise.resolve(undefined));
await pmSave(btn, "the-key", powers);
expect(stored).toEqual([new FakeCredential({ id: "my cache", password: "the-key" })]);
expect(btn.textContent).not.toMatch(/not saved|fail/);
expect(btn.textContent).toMatch(/password manager/);
expect(btn.disabled).toBe(false);
expect(entries.map((e) => e.level)).toEqual(["info"]);
});
it("logs, never silently, when the browser has no credential store", async () => {
const { powers, btn, entries } = setup(() => Promise.resolve(undefined));
delete powers.PasswordCredential;
await pmSave(btn, "the-key", powers);
expect(btn.textContent).toMatch(/no credential store/);
expect(entries.map((e) => e.level)).toEqual(["warn"]);
});
it("logs the error when the store rejects, without the key", async () => {
const { powers, btn, entries } = setup(() => Promise.reject(new Error("NotAllowedError")));
await pmSave(btn, "the-key", powers);
expect(btn.textContent).toMatch(/save failed/);
expect(entries.map((e) => e.level)).toEqual(["error"]);
expect(JSON.stringify(entries)).not.toContain("the-key");
});
it("asks for a petname rather than saving under an empty username", async () => {
const { powers, btn, stored } = setup(() => Promise.resolve(undefined), " ");
await pmSave(btn, "the-key", powers);
expect(stored).toEqual([]);
expect(btn.textContent).toMatch(/petname/);
});
});