212 lines
6.7 KiB
TypeScript
212 lines
6.7 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { createPluginRunner } from "@wrnexus/plugin";
|
|
import { authPlugin } from "../src/plugin.ts";
|
|
import { AUTH_ROUTE_DEFINITIONS } from "../src/routes/definitions.ts";
|
|
import { readFileSync } from "node:fs";
|
|
import { getDefaultAuthRouteOptions } from "../src/runtime.ts";
|
|
|
|
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("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("config.auth resolves navigation hooks from the configured engine", async () => {
|
|
const onSignedIn = () => new Response(null, { status: 204 });
|
|
const onSignedOut = () => new Response(null, { status: 204 });
|
|
const runner = createPluginRunner(authPlugin(), {
|
|
root: process.cwd(),
|
|
mode: "development",
|
|
command: "dev",
|
|
metadata: new Map(),
|
|
warn() {},
|
|
});
|
|
|
|
await runner.configure({
|
|
auth: {
|
|
engine: { onSignedIn, onSignedOut } as never,
|
|
routes: true,
|
|
},
|
|
});
|
|
|
|
expect(getDefaultAuthRouteOptions().onSignedIn).toBe(onSignedIn);
|
|
expect(getDefaultAuthRouteOptions().onSignedOut).toBe(onSignedOut);
|
|
});
|
|
|
|
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 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);
|
|
});
|