Files
2026-07-27 12:42:18 +05:30

71 lines
2.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { createCaptchaHttpHandlers } from "../src/http.ts";
import type { CaptchaEngine } from "../src/types.ts";
const engine: CaptchaEngine = {
provider: "self-hosted",
basePath: "/api/captcha",
async create(options) {
return {
id: "c1",
provider: "self-hosted",
type: "number",
presentation: "visual",
action: options.action,
prompt: "Enter 1",
createdAt: 1,
expiresAt: 2,
responseField: "wrn-captcha-response",
};
},
async verify(input) {
return {
success: input.answer === "1",
provider: "self-hosted",
action: input.action,
code: input.answer === "1" ? undefined : "incorrect-answer",
};
},
async verifyResponseToken(input) {
return { success: true, provider: "self-hosted", action: input.action };
},
async renderAudio() {
return { bytes: new Uint8Array([1, 2, 3]), contentType: "audio/wav" };
},
async gc() {},
};
describe("CAPTCHA HTTP handlers", () => {
test("creates and verifies same-origin challenges", async () => {
const handlers = createCaptchaHttpHandlers(engine);
const created = await handlers.handle(
new Request("https://example.test/api/captcha/challenge", {
method: "POST",
headers: { origin: "https://example.test", "content-type": "application/json" },
body: JSON.stringify({ action: "signup" }),
}),
);
expect(created?.status).toBe(201);
const verified = await handlers.handle(
new Request("https://example.test/api/captcha/verify", {
method: "POST",
headers: { origin: "https://example.test", "content-type": "application/json" },
body: JSON.stringify({ action: "signup", answer: "1" }),
}),
);
expect(verified?.status).toBe(200);
});
test("rejects cross-origin requests", async () => {
const handlers = createCaptchaHttpHandlers(engine);
const response = await handlers.create(
new Request("https://example.test/api/captcha/challenge", {
method: "POST",
headers: { origin: "https://evil.test", "content-type": "application/json" },
body: "{}",
}),
);
expect(response.status).toBe(403);
});
});