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 }; 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(); }); const fresh = () => testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.newUniqueId()); /** `init` as stored */ const storedInit = (stub: DurableObjectStub) => runInDurableObject(stub, (_obj, state) => state.storage.kv.get("init")); describe("cookie rotation", () => { it("mint keeps the cookie out of init; sends carry it", async () => { const seen = spyCookies(); const stub = fresh(); await stub.configure({ url: URL_, headers: { Cookie: "session=a", accept: "application/json" } }); expect(await storedInit(stub)).toEqual({ url: URL_, headers: { accept: "application/json" } }); using app = await stub.handle(); await app.get(); expect(seen).toEqual(["session=a"]); }); it("a rotation reaches handles already out", async () => { const seen = spyCookies(); const stub = fresh(); await stub.configure({ url: URL_, headers: { cookie: "session=a" } }); using app = await stub.handle(); await app.pathjoin("x").get(); using cookies = await stub.cookies(); await cookies.set("cookie", "session=b"); await app.pathjoin("x").get(); expect(seen).toEqual(["session=a", "session=b"]); }); it("an instance minted before rotation moves its cookie to the store on first use", async () => { const seen = spyCookies(); const stub = fresh(); // the shape configure() stored until the cookie store await runInDurableObject(stub, (_obj, state) => state.storage.put("init", { url: URL_, headers: { cookie: "session=old" }, msPerCall: 0 }), ); using app = await stub.handle(); await app.get(); expect(await storedInit(stub)).toEqual({ url: URL_, headers: {}, msPerCall: 0 }); using cookies = await stub.cookies(); expect(await cookies.get("cookie")).toBe("session=old"); await cookies.set("cookie", "session=new"); using again = await stub.handle(); await again.get(); expect(seen).toEqual(["session=old", "session=new"]); }); it("POST /at//cookie rotates, by the same id that drives", async () => { const seen = spyCookies(); const id = testEnv.WEBAPP_DO.newUniqueId(); const stub = testEnv.WEBAPP_DO.get(id); await stub.configure({ url: URL_ }); const rotate = (cookie: string) => worker.fetch( new Request(`https://webapp.example/at/${id}/cookie`, { method: "POST", body: JSON.stringify({ cookie }), headers: { "content-type": "application/json" }, }), testEnv, ); expect(await (await rotate("session=first")).json()).toEqual({ ok: true }); expect(await (await rotate("session=second")).json()).toEqual({ ok: true }); using app = await stub.handle(); await app.get(); expect(seen).toEqual(["session=second"]); }); it("a fixed header contradicting the stored cookie fails at send, naming no values", async () => { spyCookies(); const stub = fresh(); await stub.configure({ url: URL_, headers: { cookie: "session=a" } }); using app = await stub.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="); }); });