Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247import { describe, it, beforeEach, afterEach } from "node:test";import assert from "node:assert/strict";import { PluginCache } from "/js/plugins/pluginCache.js";
class FakeCache { constructor() { this._store = new Map(); // url -> Response } async match(url) { return this._store.get(url) ?? undefined; } async put(url, response) { this._store.set(url, response); } async keys() { return [...this._store.keys()].map((url) => ({ url })); } async delete(request) { return this._store.delete(request.url); }}
class FakeCaches { constructor() { this._buckets = new Map(); } async open(name) { if (!this._buckets.has(name)) this._buckets.set(name, new FakeCache()); return this._buckets.get(name); }}
function makeResponse(body, { ok = true, status = 200 } = {}) { return { ok, status, _body: body, clone() { return makeResponse(body, { ok, status }); }, async text() { return body; }, async json() { return JSON.parse(body); }, };}
// Installs a stub for `fetch` on globalThis and window. Returns// `{ calls, restore }` so tests can inspect requests and clean up.function stubFetch(handler) { const calls = []; const fetchImpl = async (url, options) => { calls.push({ url, options }); return handler(url, options); }; const originalGlobal = globalThis.fetch; const originalWindow = globalThis.window.fetch; globalThis.fetch = fetchImpl; globalThis.window.fetch = fetchImpl; return { calls, restore() { globalThis.fetch = originalGlobal; globalThis.window.fetch = originalWindow; }, };}
// Installs a fresh FakeCaches on globalThis and window. Returns the fake plus// a restore function.function stubCaches() { const fakeCaches = new FakeCaches(); const originalGlobal = globalThis.caches; const originalWindow = globalThis.window.caches; globalThis.caches = fakeCaches; globalThis.window.caches = fakeCaches; return { caches: fakeCaches, restore() { globalThis.caches = originalGlobal; globalThis.window.caches = originalWindow; }, };}
describe("PluginCache.fetch", () => { let fetchStub; let cachesStub; beforeEach(() => { cachesStub = stubCaches(); }); afterEach(() => { fetchStub?.restore(); cachesStub.restore(); });
it("fetches on miss and stores in cache", async () => { fetchStub = stubFetch(async () => makeResponse("hello")); const cache = new PluginCache(); const response = await cache.fetch("https://example.test/a.js"); assert.deepEqual(await response.text(), "hello"); assert.deepEqual(fetchStub.calls.length, 1); const bucket = await cachesStub.caches.open("plugins-v1"); assert(await bucket.match("https://example.test/a.js")); });
it("reuses cached response on hit", async () => { fetchStub = stubFetch(async () => makeResponse("hello")); const cache = new PluginCache(); await cache.fetch("https://example.test/a.js"); await cache.fetch("https://example.test/a.js"); assert.deepEqual(fetchStub.calls.length, 1); });
it("throws on non-OK responses and does not cache them", async () => { fetchStub = stubFetch(async () => makeResponse("nope", { ok: false, status: 404 }), ); const cache = new PluginCache(); let threw = false; try { await cache.fetch("https://example.test/missing.js"); } catch (error) { threw = true; assert(error.message.includes("404")); } assert(threw); const bucket = await cachesStub.caches.open("plugins-v1"); assert.deepEqual((await bucket.keys()).length, 0); });
it("caches 404s with doCacheNotFound and skips refetching them", async () => { fetchStub = stubFetch(async () => makeResponse("nope", { ok: false, status: 404 }), ); const cache = new PluginCache(); const url = "https://example.test/styles.css"; await assert.rejects(cache.fetch(url, { doCacheNotFound: true }), /404/); const error = await cache .fetch(url, { doCacheNotFound: true }) .then(null, (thrown) => thrown); assert.deepEqual(error.status, 404); assert.deepEqual(fetchStub.calls.length, 1); });
it("caches errors a custom isNotFound predicate matches on the body", async () => { fetchStub = stubFetch(async () => makeResponse("failed to get blob", { ok: false, status: 500 }), ); const cache = new PluginCache(); const url = "https://example.test/styles.css"; const options = { doCacheNotFound: true, isNotFound: (status, body) => status === 500 && body === "failed to get blob", }; const error = await cache .fetch(url, options) .then(null, (thrown) => thrown); assert.deepEqual(error.status, 500); assert.deepEqual(error.body, "failed to get blob"); await assert.rejects(cache.fetch(url, options), /500/); assert.deepEqual(fetchStub.calls.length, 1); });
it("does not cache non-404 errors with the default isNotFound", async () => { fetchStub = stubFetch(async () => makeResponse("boom", { ok: false, status: 500 }), ); const cache = new PluginCache(); const url = "https://example.test/styles.css"; await assert.rejects(cache.fetch(url, { doCacheNotFound: true }), /500/); await assert.rejects(cache.fetch(url, { doCacheNotFound: true }), /500/); assert.deepEqual(fetchStub.calls.length, 2); });
it("does not cache not-found responses without doCacheNotFound", async () => { fetchStub = stubFetch(async () => makeResponse("nope", { ok: false, status: 404 }), ); const cache = new PluginCache(); const url = "https://example.test/styles.css"; await assert.rejects( cache.fetch(url, { isNotFound: (status) => status === 404 }), /404/, ); await assert.rejects( cache.fetch(url, { isNotFound: (status) => status === 404 }), /404/, ); assert.deepEqual(fetchStub.calls.length, 2); });
it("attaches the response body text to errors on non-OK responses", async () => { fetchStub = stubFetch(async () => makeResponse( '{"error":"InternalServerError","message":"failed to get blob"}', { ok: false, status: 500, }, ), ); const cache = new PluginCache(); const error = await cache .fetch("https://example.test/styles.css") .then(null, (thrown) => thrown); assert.deepEqual(error.status, 500); assert.deepEqual( error.body, '{"error":"InternalServerError","message":"failed to get blob"}', ); });});
describe("PluginCache.reconcile", () => { let cachesStub; beforeEach(() => { cachesStub = stubCaches(); }); afterEach(() => cachesStub.restore());
it("deletes entries not in the wanted set", async () => { const bucket = await cachesStub.caches.open("plugins-v1"); await bucket.put("https://x.test/keep.js", makeResponse("k")); await bucket.put("https://x.test/old.js", makeResponse("o")); const cache = new PluginCache(); await cache.reconcile(["https://x.test/keep.js"]); const remaining = (await bucket.keys()).map((request) => request.url); assert.deepEqual(remaining, ["https://x.test/keep.js"]); });
it("keeps wanted entries even if not all are present", async () => { const bucket = await cachesStub.caches.open("plugins-v1"); await bucket.put("https://x.test/keep.js", makeResponse("k")); const cache = new PluginCache(); await cache.reconcile([ "https://x.test/keep.js", "https://x.test/not-yet-fetched.js", ]); const remaining = (await bucket.keys()).map((request) => request.url); assert.deepEqual(remaining, ["https://x.test/keep.js"]); });});