import { env } from "cloudflare:workers"; import { runInDurableObject } from "cloudflare:test"; import { afterEach, describe, expect, it, vi } from "vitest"; import worker, { type WebAppInit } from "../src/index.js"; type TestEnv = { WEBAPP_DO: DurableObjectNamespace; MAP_STORE_DO: DurableObjectNamespace; }; const testEnv = env as unknown as TestEnv; const URL_ = "https://diigo.example/api/"; /** the cookie header of each outbound fetch */ function spyCookies(): (string | null)[] { const seen: (string | null)[] = []; vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => { seen.push(new Headers(init?.headers).get("cookie")); return new Response("ok"); }); return seen; } afterEach(() => { vi.restoreAllMocks(); }); /** the worker's HTTP surface, as a JSON client */ const call = (path: string, method: string, body?: unknown) => worker.fetch( new Request(`https://webapp.example${path}`, { method, body: body === undefined ? undefined : JSON.stringify(body), headers: { "content-type": "application/json" }, }), testEnv, ); const mint = async (init: WebAppInit) => { const { id, storeId } = (await (await call("/", "POST", init)).json()) as { id: string; storeId: string }; const instance = testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.idFromString(id)); return { id, storeId, instance }; }; /** the failure of a call: its error status, or what it threw; "" if it succeeds */ async function errorOf(response: Promise): Promise { try { const res = await response; return res.ok ? "" : `${res.status} ${await res.text()}`; } catch (err) { return String(err); } } /** `init` as stored */ const storedInit = (stub: DurableObjectStub) => runInDurableObject(stub, (_obj, state) => state.storage.kv.get("init")); describe("cookie rotation", () => { it("mint puts the cookie in the header store, not init; sends carry it", async () => { const seen = spyCookies(); const { storeId, instance } = await mint({ url: URL_, headers: { Cookie: "session=a", accept: "application/json" }, }); expect(await storedInit(instance)).toEqual({ url: URL_, headers: { accept: "application/json" }, headerStore: storeId, }); using app = await instance.handle(); await app.get(); expect(seen).toEqual(["session=a"]); }); it("the store id rotates under handles already out, and revokes", async () => { const seen = spyCookies(); const { storeId, instance } = await mint({ url: URL_, headers: { cookie: "session=a" } }); using app = await instance.handle(); await app.pathjoin("x").get(); expect(await (await call(`/store/${storeId}/cookie`, "PUT", "session=b")).json()).toEqual({ ok: true }); await app.pathjoin("x").get(); expect(await (await call(`/store/${storeId}/cookie`, "DELETE")).json()).toEqual({ ok: true }); await app.get(); expect(seen).toEqual(["session=a", "session=b", null]); }); it("the instance id reaches no store: its holder cannot swap in another session", async () => { const seen = spyCookies(); const { id, instance } = await mint({ url: URL_, headers: { cookie: "session=alice" } }); expect(await errorOf(call(`/store/${id}/cookie`, "PUT", "session=mallory"))).not.toBe(""); expect((await call(`/at/${id}/cookie`, "POST", { cookie: "session=mallory" })).status).toBe(404); using app = await instance.handle(); await app.get(); expect(seen).toEqual(["session=alice"]); }); it("the store's shapes admit only the cookie; errors carry no values", async () => { const { storeId } = await mint({ url: URL_ }); const err = await errorOf(call(`/store/${storeId}/authorization`, "PUT", "Bearer s3kr1t")); expect(err).toContain('cookie: key: must match \\"cookie\\"'); expect(err).not.toContain("s3kr1t"); expect(await errorOf(call(`/store/${storeId}/cookie`, "PUT", 42))).toContain("must match M.string()"); }); it("an instance minted before header stores keeps its cookie in init, untouched", async () => { const seen = spyCookies(); const instance = testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.newUniqueId()); const old = { url: URL_, headers: { cookie: "session=old" }, msPerCall: 0 }; await runInDurableObject(instance, (_obj, state) => state.storage.put("init", old)); using app = await instance.handle(); await app.get(); expect(seen).toEqual(["session=old"]); expect(await storedInit(instance)).toEqual(old); }); it("a fixed header contradicting the stored cookie fails at send, naming no values", async () => { spyCookies(); const { instance } = await mint({ url: URL_, headers: { cookie: "session=a" } }); using app = await instance.handle(); const err = await app .withHeader("cookie", "session=mine") .get() .then( () => "", (e: unknown) => String(e), ); expect(err).toContain("headers: contradicts on cookie"); expect(err).not.toContain("session="); }); });