import { env, exports } from "cloudflare:workers"; import { beforeAll, describe, expect, it } from "vitest"; import { makeAuth } from "@/worker/auth"; import { ISSUER, signInForCookie, testHeaders } from "./helpers"; const SELF = exports.default; const credential = { email: "signout-tester@example.com", password: "correct-horse-battery-staple", name: "Sign-out Tester", }; describe("sign-out clears the session cookie and invalidates the DB row", () => { beforeAll(async () => { const auth = makeAuth(env, { baseURL: ISSUER, issuer: ISSUER }); await auth.api.signUpEmail({ body: credential, asResponse: false }); }); it("Set-Cookie on /api/auth/sign-out has maxAge=0 and the session is gone afterward", async () => { // 1. Sign in to get a real session cookie. const signedInCookie = await signInForCookie(credential.email, credential.password, "10.40.0.1"); // 2. The cookie is good — get-session returns the user. const beforeSignOut = await SELF.fetch(`${ISSUER}/api/auth/get-session`, { headers: { cookie: signedInCookie }, }); expect(beforeSignOut.status).toBe(200); const beforeBody = (await beforeSignOut.json()) as { user?: { email?: string } } | null; expect(beforeBody?.user?.email).toBe(credential.email); // 3. POST /api/auth/sign-out. The response MUST clear the cookie via // `Max-Age=0` (or `Expires` in the past). If this header is absent // or carries `Secure` on an HTTP origin the browser will silently // ignore it — which is exactly the failure mode I'm hunting. const signOutRes = await SELF.fetch(`${ISSUER}/api/auth/sign-out`, { method: "POST", headers: testHeaders({ cookie: signedInCookie }), }); expect(signOutRes.status).toBe(200); const signOutSetCookie = signOutRes.headers.get("set-cookie") ?? ""; expect(signOutSetCookie).toMatch(/better-auth\.session_token=/); expect(signOutSetCookie.toLowerCase()).toMatch(/max-age=0|expires=/); // 4. Replay the original cookie value: the server must report no // session even though the cookie string is still in our jar. (Real // browsers honour Max-Age=0 by dropping the cookie entirely; we // re-send it explicitly to assert the *server* side is dead too.) const afterSignOut = await SELF.fetch(`${ISSUER}/api/auth/get-session`, { headers: { cookie: signedInCookie }, }); expect(afterSignOut.status).toBe(200); const afterText = await afterSignOut.text(); // Better Auth returns `null` (literal) when the session is gone. expect(afterText.trim()).toBe("null"); }); });