Files
WRNexusJS/packages/auth/test/plugin.test.ts
T
Clintchiz a3ddd39b7b
Quality / quality (ubuntu-latest) (push) Failing after 14m38s
Quality / quality (windows-latest) (push) Canceled after 0s
feat: centralize application framework primitives
2026-08-22 23:07:46 +05:30

326 lines
10 KiB
TypeScript

import { expect, test } from "bun:test";
import type { Context } from "@wrnexus/core";
import { createPluginRunner } from "@wrnexus/plugin";
import { authPlugin, validateProductionAuthConfig } from "../src/plugin.ts";
import { AUTH_ROUTE_DEFINITIONS } from "../src/routes/definitions.ts";
import { readFileSync } from "node:fs";
import { createAuthEngine } from "../src/engine.ts";
import { MemoryAuthStore } from "../src/stores/memory.ts";
import { invokeAuthHandler } from "../src/routes/api.ts";
function routeContext(request: Request): Context {
const values = new Map<string, unknown>();
return {
req: request,
url: new URL(request.url),
params: {},
locals: {},
lang: "en",
t: (key: string) => key,
ip: "127.0.0.1",
user: null,
cookies: {
get: (name: string) => (name === "wrn-csrf" ? "plugin-test-csrf-token" : undefined),
} as Context["cookies"],
localStorage: {} as Context["localStorage"],
session: {
id: () => "plugin-test-session",
get: <T>(key: string) => values.get(key) as T | undefined,
getAll: () => Object.fromEntries(values),
set: (key: string, value: unknown) => {
values.set(key, value);
},
delete: (key: string) => {
values.delete(key);
},
regenerate: () => {},
clear: () => {
values.clear();
},
},
} as Context;
}
test("plugin contributes components, runtime, styles, migration, and toolbar", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(
authPlugin({ includeRoutes: true, includeMigrations: true, includeMiddleware: true }),
{
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
},
);
const contributions = await runner.contributions();
expect(contributions.componentDirs).toHaveLength(1);
expect(contributions.clientRuntimes[0]).toMatchObject({ id: "auth", singleton: true });
expect(contributions.styles[0]?.id).toBe("auth-components");
expect(contributions.migrations.map((migration) => migration.id)).toEqual([
"wrnexus-auth-001",
"wrnexus-auth-002-otp-purpose",
]);
expect(contributions.routes.length).toBeGreaterThanOrEqual(30);
expect(contributions.middleware).toHaveLength(1);
});
test("production auth validation rejects development secrets and origins", () => {
expect(() =>
validateProductionAuthConfig(
{ baseUrl: "http://localhost:3000" },
{ AUTH_SECRET: "change-me" },
),
).toThrow("AUTH_SECRET");
expect(() =>
validateProductionAuthConfig(
{ baseUrl: "http://app.example" },
{ AUTH_SECRET: "a-production-secret-that-is-longer-than-thirty-two-characters" },
),
).toThrow("HTTPS");
});
test("unconfigured automatic discovery fails closed for routes, middleware, and migrations", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
const contributions = await runner.contributions();
expect(contributions.routes).toHaveLength(0);
expect(contributions.middleware).toHaveLength(0);
expect(contributions.migrations).toHaveLength(0);
expect(contributions.componentDirs).toHaveLength(1);
expect(contributions.clientRuntimes).toHaveLength(1);
});
test("config.auth controls route groups and migrations without explicit plugin options", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
await runner.configure({
auth: {
engine: {} as never,
migrations: false,
routes: { enabled: true, registration: false, passkeys: false },
},
});
const contributions = await runner.contributions();
expect(contributions.migrations).toHaveLength(0);
expect(contributions.routes.some((route) => route.path === "/api/auth/register")).toBe(false);
expect(contributions.routes.some((route) => route.path.includes("/passkeys/"))).toBe(false);
expect(contributions.routes.some((route) => route.path === "/api/auth/login")).toBe(true);
});
test("a hook set on config.auth.engine fires when a request goes through the route layer", async () => {
const calls: string[] = [];
const engine = createAuthEngine({
store: new MemoryAuthStore(),
secret: "plugin-navigation-hooks-secret-longer-than-thirty-two-characters",
onSignedIn(ctx, returnTo) {
calls.push(`in:${returnTo}`);
return Response.redirect(new URL(returnTo ?? "/account", ctx.url), 303);
},
onSignedOut(ctx) {
calls.push("out");
return Response.redirect(new URL("/sign-in", ctx.url), 303);
},
});
await engine.register({
email: "plugin-navigation@example.com",
password: "StrongPassword123",
});
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata: new Map(),
warn() {},
});
await runner.configure({
auth: { engine, routes: true },
});
const loginResponse = await invokeAuthHandler(
"login",
routeContext(
new Request("https://example.test/api/auth/login", {
method: "POST",
headers: {
"content-type": "application/json",
"x-csrf-token": "plugin-test-csrf-token",
},
body: JSON.stringify({
identifier: "plugin-navigation@example.com",
password: "StrongPassword123",
returnTo: "/dashboard",
}),
}),
),
);
expect(loginResponse.status).toBe(303);
expect(loginResponse.headers.get("location")).toBe("https://example.test/dashboard");
const logoutResponse = await invokeAuthHandler(
"logout",
routeContext(
new Request("https://example.test/api/auth/logout", {
method: "POST",
headers: { "x-csrf-token": "plugin-test-csrf-token" },
}),
),
);
expect(logoutResponse.status).toBe(303);
expect(logoutResponse.headers.get("location")).toBe("https://example.test/sign-in");
expect(calls).toEqual(["in:/dashboard", "out"]);
});
test("auth runtime contains built-in browser schemas", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(authPlugin({ includeMigrations: false }), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
const contributions = await runner.contributions();
expect(contributions.clientRuntimes[0]?.source).toContain("auth-password-request");
expect(contributions.clientRuntimes[0]?.source).toContain("auth-register");
});
test("each package auth route uses a route-specific entry module", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(
authPlugin({
includeRoutes: true,
includeMigrations: false,
includeMiddleware: false,
}),
{
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
},
);
const contributions = await runner.contributions();
const entries = contributions.routes.map((route) => route.entry);
const passwordRequest = contributions.routes.find(
(route) => route.path === "/api/auth/password/request",
);
expect(new Set(entries).size).toBe(entries.length);
expect(
passwordRequest?.entry.replace(/\\/g, "/").endsWith("/src/routes/api/password-request.ts"),
).toBe(true);
for (const definition of AUTH_ROUTE_DEFINITIONS) {
const route = contributions.routes.find((item) => item.path === definition.path);
expect(route).toBeDefined();
const source = readFileSync(route!.entry, "utf8");
expect(source).toContain(`invokeAuthHandler("${definition.handler}"`);
expect(source.includes("dispatchAuthRoute")).toBe(false);
for (const method of definition.methods) {
expect(source).toContain(`export function ${method}`);
}
}
});
test("config.auth registers package routes", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
await runner.configure({
auth: {
engine: {} as never,
routes: true,
middleware: true,
migrations: false,
},
});
const contributions = await runner.contributions();
expect(contributions.routes.some((route) => route.path === "/api/auth/password/request")).toBe(
true,
);
expect(contributions.routes.some((route) => route.path === "/api/auth/register")).toBe(true);
expect(contributions.routes.some((route) => route.path === "/api/auth/login")).toBe(true);
expect(contributions.middleware).toHaveLength(1);
expect(contributions.migrations).toHaveLength(0);
});
test("config.auth contributes a reusable forward-auth verification route", async () => {
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata: new Map<string, unknown>(),
warn() {},
});
await runner.configure({
auth: {
engine: {} as never,
routes: true,
},
});
const contributions = await runner.contributions();
expect(contributions.routes.some((route) => route.path === "/api/auth/session/verify")).toBe(
true,
);
});
test("config.auth can disable route groups", async () => {
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(authPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
await runner.configure({
auth: {
engine: {} as never,
migrations: false,
routes: {
enabled: true,
password: true,
passkeys: false,
},
},
});
const contributions = await runner.contributions();
expect(contributions.routes.some((route) => route.path === "/api/auth/password/request")).toBe(
true,
);
expect(contributions.routes.some((route) => route.path.includes("/passkeys/"))).toBe(false);
});