import { describe, expect, test } from "bun:test"; import { createContext, type Context } from "@wrnexus/core"; import { captchaGuard, captchaPageGate } from "../src/middleware.ts"; import type { CaptchaEngine } from "../src/types.ts"; function fakeEngine(): CaptchaEngine { return { provider: "self-hosted", basePath: "/api/captcha", async create() { throw new Error("not used"); }, async verify() { throw new Error("not used"); }, async verifyResponseToken(input) { return input.responseToken === "verified-token" ? { success: true, provider: "self-hosted", action: input.action } : { success: false, provider: "self-hosted", action: input.action, code: "invalid-input" }; }, async renderAudio() { return undefined; }, async gc() {}, }; } function sessionFixture() { const values = new Map(); let sessionId = "session-1"; return { id(): string { return sessionId; }, get(key: string): T | undefined { return values.get(key) as T | undefined; }, getAll(): Record { return Object.fromEntries(values); }, set(key: string, value: unknown): void { values.set(key, value); }, delete(key: string): void { values.delete(key); }, regenerate(): void { sessionId = `${sessionId}-regenerated`; }, clear(): void { values.clear(); }, }; } describe("CAPTCHA request guard", () => { test("reuses an action-bound verified session grant for retryable requests", async () => { const session = sessionFixture(); const guard = captchaGuard({ action: "auth-login", engine: fakeEngine(), verifiedForMs: 5 * 60_000, }); const firstRequest = new Request("https://example.test/api/auth/login", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ "wrn-captcha-response": "verified-token" }), }); const firstContext: Context = { ...createContext(firstRequest, new URL(firstRequest.url)), session, ip: "127.0.0.1", }; const first = await guard( firstContext, () => new Response("credentials-invalid", { status: 401 }), ); expect(first.status).toBe(401); const retryRequest = new Request("https://example.test/api/auth/login", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({}), }); const retryContext: Context = { ...createContext(retryRequest, new URL(retryRequest.url)), session, ip: "127.0.0.1", }; const retry = await guard(retryContext, () => new Response("retry-allowed")); expect(await retry.text()).toBe("retry-allowed"); expect(retryContext.locals.captcha).toMatchObject({ success: true, action: "auth-login", }); expect(retryContext.locals.captchaVerified).toBe(true); }); test("rejects invalid verified-session durations", () => { expect(() => captchaGuard({ action: "auth-login", engine: fakeEngine(), verifiedForMs: Number.NaN, }), ).toThrow("verifiedForMs"); }); }); describe("CAPTCHA page gate", () => { test("accepts a verified form token and grants the protected route", async () => { const session = sessionFixture(); const gate = captchaPageGate({ action: "protected-page-access", engine: fakeEngine(), challengePath: "/captcha", policy: { mode: "session", verifiedForMs: 15 * 60_000, routeGroups: ["/protected"], }, }); const body = new URLSearchParams({ returnTo: "/protected", "wrn-captcha-response": "verified-token", }); const request = new Request("https://example.test/api/page-grant", { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body, }); const requestUrl = new URL(request.url); const context: Context = { ...createContext(request, requestUrl), session, ip: "127.0.0.1", }; const granted = await gate(context, () => Response.redirect("https://example.test/protected", 303), ); expect(granted.status).toBe(303); const protectedRequest = new Request("https://example.test/protected"); const protectedUrl = new URL(protectedRequest.url); const protectedContext: Context = { ...createContext(protectedRequest, protectedUrl), session, ip: "127.0.0.1", }; const allowed = await gate(protectedContext, () => new Response("unlocked")); expect(await allowed.text()).toBe("unlocked"); }); });