import { assert, assertStringIncludes } from "@std/assert"; import { expect } from "@std/expect"; import { afterEach, describe, it as test } from "@std/testing/bdd"; import { parseCookies } from "#routes/url.ts"; import { isSecureMode, resetEffectiveDomain, seedEffectiveDomainHost, setEffectiveDomainForTest, } from "#shared/config.ts"; import { buildFlashCookie, buildSessionCookie, clearFlashCookie, clearSessionCookie, getSessionCookieName, parseFlashValue, } from "#shared/cookies.ts"; import { expectIncludesAll } from "#test-utils/assertions.ts"; /** Assert common cookie attributes for dev (localhost) mode */ const expectDevCookieAttributes = (cookie: string) => { assertStringIncludes(cookie, "HttpOnly"); assert(!cookie.includes("; Secure;")); expectIncludesAll(cookie, ["SameSite=Strict", "Path=/"]); }; /** Assert common cookie attributes for secure (non-localhost) mode */ const expectSecureCookieAttributes = (cookie: string) => { expectIncludesAll(cookie, [ "HttpOnly", "; Secure;", "SameSite=Strict", "Path=/", ]); }; describe("isSecureMode", () => { afterEach(() => resetEffectiveDomain()); test("returns true for non-localhost domains", () => { setEffectiveDomainForTest("example.com"); expect(isSecureMode()).toBe(true); }); test("returns false for localhost", () => { setEffectiveDomainForTest("localhost"); expect(isSecureMode()).toBe(false); }); test("returns false for loopback IP hosts", () => { setEffectiveDomainForTest("127.0.0.1"); expect(isSecureMode()).toBe(false); setEffectiveDomainForTest("[::1]"); expect(isSecureMode()).toBe(false); }); }); describe("getSessionCookieName", () => { afterEach(() => resetEffectiveDomain()); test("returns __Host-session in secure mode", () => { setEffectiveDomainForTest("example.com"); expect(getSessionCookieName()).toBe("__Host-session"); }); test("returns 'session' in dev mode", () => { setEffectiveDomainForTest("localhost"); expect(getSessionCookieName()).toBe("session"); }); }); describe("buildSessionCookie", () => { afterEach(() => resetEffectiveDomain()); test("includes __Host-session in secure mode with all required attributes", () => { setEffectiveDomainForTest("example.com"); const cookie = buildSessionCookie("test-token"); assertStringIncludes(cookie, "__Host-session=test-token"); expectSecureCookieAttributes(cookie); assertStringIncludes(cookie, "Max-Age=86400"); }); test("includes 'session' in dev mode without Secure flag", () => { setEffectiveDomainForTest("localhost"); const cookie = buildSessionCookie("test-token"); assertStringIncludes(cookie, "session=test-token"); expectDevCookieAttributes(cookie); assertStringIncludes(cookie, "Max-Age=86400"); }); test("keeps loopback request hosts in dev cookie mode", () => { seedEffectiveDomainHost(new URL("http://127.0.0.1:38123/admin/")); const cookie = buildSessionCookie("test-token"); assertStringIncludes(cookie, "session=test-token"); expectDevCookieAttributes(cookie); }); test("respects custom maxAge", () => { setEffectiveDomainForTest("example.com"); const cookie = buildSessionCookie("test-token", { maxAge: 3600 }); assertStringIncludes(cookie, "Max-Age=3600"); }); }); describe("clearSessionCookie", () => { afterEach(() => resetEffectiveDomain()); test("includes __Host-session in secure mode with Max-Age=0", () => { setEffectiveDomainForTest("example.com"); const cookie = clearSessionCookie(); assertStringIncludes(cookie, "__Host-session="); expectSecureCookieAttributes(cookie); assertStringIncludes(cookie, "Max-Age=0"); }); test("includes 'session' in dev mode without Secure flag", () => { setEffectiveDomainForTest("localhost"); const cookie = clearSessionCookie(); assertStringIncludes(cookie, "session="); expectDevCookieAttributes(cookie); assertStringIncludes(cookie, "Max-Age=0"); }); }); describe("buildFlashCookie", () => { afterEach(() => resetEffectiveDomain()); test("keys cookie name by flash ID", () => { setEffectiveDomainForTest("localhost"); const cookie = buildFlashCookie("abc123", "Saved", true); expect(cookie).toMatch(/^flash_abc123=/); }); test("encodes success message as JSON", () => { setEffectiveDomainForTest("localhost"); const cookie = buildFlashCookie("abc123", "Saved", true); assertStringIncludes( cookie, encodeURIComponent(JSON.stringify({ m: "Saved", t: "s" })), ); }); test("encodes error message as JSON", () => { setEffectiveDomainForTest("localhost"); const cookie = buildFlashCookie("abc123", "Failed", false); assertStringIncludes( cookie, encodeURIComponent(JSON.stringify({ m: "Failed", t: "e" })), ); }); test("sets short Max-Age", () => { setEffectiveDomainForTest("localhost"); const cookie = buildFlashCookie("abc123", "Saved", true); assertStringIncludes(cookie, "Max-Age=10"); }); test("embeds the form re-fill token when provided", () => { setEffectiveDomainForTest("localhost"); const cookie = buildFlashCookie( "abc123", "Saved", true, undefined, undefined, "stash-token", ); assertStringIncludes( cookie, encodeURIComponent( JSON.stringify({ f: "stash-token", m: "Saved", t: "s" }), ), ); }); }); describe("clearFlashCookie", () => { afterEach(() => resetEffectiveDomain()); test("clears the keyed cookie with Max-Age=0", () => { setEffectiveDomainForTest("localhost"); const cookie = clearFlashCookie("abc123"); expect(cookie).toMatch(/^flash_abc123=/); assertStringIncludes(cookie, "Max-Age=0"); }); }); describe("parseFlashValue", () => { test("parses success flash value", () => { const encoded = JSON.stringify({ m: "Listing created", t: "s" }); expect(parseFlashValue(encoded)).toEqual({ error: undefined, formToken: undefined, info: undefined, result: undefined, success: "Listing created", }); }); test("parses error flash value", () => { const encoded = JSON.stringify({ m: "Something went wrong", t: "e" }); expect(parseFlashValue(encoded)).toEqual({ error: "Something went wrong", formToken: undefined, info: undefined, result: undefined, success: undefined, }); }); test("parses info flash value", () => { const encoded = JSON.stringify({ m: "You've unsubscribed", t: "i" }); expect(parseFlashValue(encoded)).toEqual({ error: undefined, formToken: undefined, info: "You've unsubscribed", result: undefined, success: undefined, }); }); test("decodes URL-encoded values", () => { const encoded = encodeURIComponent( JSON.stringify({ m: "Hello world", t: "s" }), ); expect(parseFlashValue(encoded)).toEqual({ error: undefined, formToken: undefined, info: undefined, result: undefined, success: "Hello world", }); }); test("parses flash with result", () => { const encoded = JSON.stringify({ m: "Created", r: "abc123", t: "s" }); expect(parseFlashValue(encoded)).toEqual({ error: undefined, formToken: undefined, info: undefined, result: "abc123", success: "Created", }); }); test("surfaces the form re-fill token when present", () => { const encoded = JSON.stringify({ f: "stash-token", m: "Failed", t: "e" }); expect(parseFlashValue(encoded).formToken).toBe("stash-token"); }); test("throws for invalid format", () => { expect(() => parseFlashValue("invalid")).toThrow(); }); }); describe("parseCookies", () => { const reqWithCookies = (cookie: string) => new Request("http://localhost/", { headers: { cookie } }); test("parses simple key=value pairs", () => { const cookies = parseCookies(reqWithCookies("session=abc; lang=en")); expect(cookies.get("session")).toBe("abc"); expect(cookies.get("lang")).toBe("en"); }); test("returns empty map for no cookie header", () => { const cookies = parseCookies(new Request("http://localhost/")); expect(cookies.size).toBe(0); }); test("preserves equals signs in cookie values", () => { const cookies = parseCookies( reqWithCookies("token=eyJhbGci.payload.sig=="), ); expect(cookies.get("token")).toBe("eyJhbGci.payload.sig=="); }); test("handles URI-encoded flash cookie values", () => { const payload = encodeURIComponent(JSON.stringify({ m: "Saved", t: "s" })); const cookies = parseCookies(reqWithCookies(`flash_abc=${payload}`)); expect(cookies.get("flash_abc")).toBe(payload); }); });