Files
WRNexusJS/packages/captcha/test/validation-plugin.test.ts
T

72 lines
2.8 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { createContext } from "@wrnexus/core";
import { v } from "@wrnexus/validation";
import {
captchaPlugin,
defineCaptchaProvider,
parseWithCaptcha,
type CaptchaProvider,
} from "../src/index.ts";
import type { PluginContext, TransformContext } from "@wrnexus/plugin";
describe("CAPTCHA validation and development audit", () => {
test("combines schema and action-bound provider verification failures", async () => {
const requests: unknown[] = [];
const provider: CaptchaProvider = defineCaptchaProvider({
name: "fixture",
client: { responseField: "captcha-response" },
async verify(input) {
requests.push(input);
return { success: false, provider: "fixture", action: input.action, message: "Try again" };
},
});
const ctx = createContext(
new Request("https://app.test/signup"),
new URL("https://app.test/signup"),
);
const result = await parseWithCaptcha(
v.object({ email: v.string().email() }),
{ email: "invalid", "captcha-response": "token" },
ctx,
{ action: "signup", provider, bindIp: true },
);
expect(result.ok).toBe(false);
expect(result.errors.email).toBeDefined();
expect(result.errors["captcha-response"]).toBe("Try again");
expect(requests[0]).toEqual(
expect.objectContaining({ action: "signup", hostname: "app.test", providerToken: "token" }),
);
});
test("rejects incomplete custom providers", () => {
expect(() =>
defineCaptchaProvider({ name: "", client: { responseField: "x" } } as never),
).toThrow("stable name");
expect(() =>
defineCaptchaProvider({ name: "x", client: { responseField: "" } } as never),
).toThrow("responseField");
expect(() =>
defineCaptchaProvider({ name: "x", client: { responseField: "token" } } as never),
).toThrow("verify()");
});
test("flags exposed secrets, missing provider keys, and inaccessible hard challenges", async () => {
const metadata = new Map<string, unknown>();
const plugin = captchaPlugin();
const context = {
mode: "development",
file: "signup.wrn",
metadata,
} as TransformContext;
await plugin.transformCode?.(
`<Captcha provider="turnstile" secretKey="leaked" action="signup" disturbance="90" showAudio="false" />`,
context,
);
const panels = await plugin.devToolbarPanels?.({ metadata } as PluginContext);
const ids = panels?.[0]?.issues?.map((issue) => String((issue as { id: string }).id)) ?? [];
expect(ids.some((id) => id.startsWith("client-secret:"))).toBe(true);
expect(ids.some((id) => id.startsWith("missing-site-key:"))).toBe(true);
expect(ids.some((id) => id.startsWith("hard-without-audio:"))).toBe(true);
});
});