release: WRNexusJS 0.5.0
This commit is contained in:
@@ -19,10 +19,14 @@ test("Captcha browser runtime is valid JavaScript and exposes the expected lifec
|
||||
expect(source).toContain("captchaRandomizeStyle");
|
||||
expect(source).toContain("captchaResolvedImageStyle");
|
||||
expect(source).toContain("captchaShowListen");
|
||||
expect(source).toContain("captchaResetOnError");
|
||||
expect(source).toContain("normalizeSize");
|
||||
expect(source).toContain("verifyNotRobot");
|
||||
expect(source).toContain("stopImmediatePropagation");
|
||||
expect(source).toContain('addEventListener("submit"');
|
||||
expect(source).toContain('addEventListener("wire:error"');
|
||||
expect(source).toContain("state.config.resetOnError || captchaFailed");
|
||||
expect(source).toContain("Verified response tokens are single-use");
|
||||
expect(source).toContain("MutationObserver");
|
||||
expect(source).toContain("new CustomEvent(name");
|
||||
});
|
||||
@@ -42,6 +46,7 @@ test("Captcha component delegates native browser work to the packaged runtime",
|
||||
expect(source).toContain("data-captcha-randomize-style");
|
||||
expect(source).toContain("data-captcha-show-listen");
|
||||
expect(source).toContain("data-captcha-not-robot-button");
|
||||
expect(source).toContain("data-captcha-reset-on-error");
|
||||
expect(source).not.toContain("lifecycle {");
|
||||
expect(source).not.toContain("async function");
|
||||
expect(source).not.toContain("await ");
|
||||
|
||||
@@ -26,6 +26,7 @@ test("Captcha.wrn parses and exposes the full public contract", async () => {
|
||||
"color",
|
||||
"class",
|
||||
"showListen",
|
||||
"resetOnError",
|
||||
]) {
|
||||
expect(source).toContain(`${prop} =`);
|
||||
}
|
||||
@@ -50,6 +51,7 @@ test("Captcha.wrn parses and exposes the full public contract", async () => {
|
||||
expect(source).toContain("data-captcha-excluded-styles='{excludedStyles}'");
|
||||
expect(source).toContain("data-captcha-randomize-style='{randomizeStyle}'");
|
||||
expect(source).toContain("data-captcha-show-listen='{showListen}'");
|
||||
expect(source).toContain("data-captcha-reset-on-error='{resetOnError}'");
|
||||
expect(source).toContain("data-captcha-not-robot-button");
|
||||
expect(source).toContain("data-[captcha-size=compact]");
|
||||
expect(source).toContain("data-[captcha-size=compact]:max-w-xs");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createContext, type Context } from "@wrnexus/core";
|
||||
import { captchaPageGate } from "../src/middleware.ts";
|
||||
import { captchaGuard, captchaPageGate } from "../src/middleware.ts";
|
||||
import type { CaptchaEngine } from "../src/types.ts";
|
||||
|
||||
function fakeEngine(): CaptchaEngine {
|
||||
@@ -54,6 +54,61 @@ function sessionFixture() {
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user