Initial Commit
This commit is contained in:
201
packages/api-stack/api/api-plugin-auth/src/plugin/plugin.test.ts
Normal file
201
packages/api-stack/api/api-plugin-auth/src/plugin/plugin.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||
import { createOpenAPIRegistry } from "@suaveplan/api-openapi";
|
||||
import { createPluginContext } from "@suaveplan/api-plugin";
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
import { afterEach, describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import type { Auth } from "better-auth";
|
||||
import { AuthError } from "../error-codes/error-codes.js";
|
||||
import type { AuthPluginConfig } from "../types/types.js";
|
||||
import { __setBetterAuthFactory, createAuthPlugin } from "./plugin.js";
|
||||
|
||||
const noopLogger = {
|
||||
debug: () => undefined,
|
||||
info: () => undefined,
|
||||
warn: () => undefined,
|
||||
error: () => undefined,
|
||||
};
|
||||
|
||||
function makeAuthStub(): Auth {
|
||||
return {
|
||||
handler: async () => new Response("ok", { status: 200 }),
|
||||
api: { getSession: async () => null },
|
||||
} as unknown as Auth;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
__setBetterAuthFactory(undefined);
|
||||
});
|
||||
|
||||
describe("createAuthPlugin", () => {
|
||||
it("createAuthPlugin returns valid PluginDescriptor", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const plugin = createAuthPlugin({ betterAuth: {} });
|
||||
expect(plugin.name).toBe("auth");
|
||||
expect(plugin.version).toBe("0.1.0");
|
||||
expect(typeof plugin.onInit).toBe("function");
|
||||
expect(typeof plugin.onClose).toBe("function");
|
||||
});
|
||||
|
||||
it("invalid options raise AUTH_INVALID_INPUT", { timeout: 5000 }, () => {
|
||||
let caught: AuthError | undefined;
|
||||
try {
|
||||
createAuthPlugin({} as never);
|
||||
} catch (err) {
|
||||
caught = err as AuthError;
|
||||
}
|
||||
expect(caught?.code).toBe("AUTH.INVALID_INPUT");
|
||||
expect(caught?.cause).toBeDefined();
|
||||
});
|
||||
|
||||
it("descriptor exposes resolved defaults", { timeout: 5000 }, () => {
|
||||
const plugin = createAuthPlugin({
|
||||
betterAuth: {},
|
||||
routePrefix: "/v2/auth",
|
||||
});
|
||||
expect((plugin.defaults as AuthPluginConfig).routePrefix).toBe(
|
||||
"/v2/auth"
|
||||
);
|
||||
expect((plugin.defaults as AuthPluginConfig).applySchemaOnInit).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("onClose clears internal state without error", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const plugin = createAuthPlugin({ betterAuth: {} });
|
||||
const services = new Map<string, unknown>();
|
||||
const ctrl = new AbortController();
|
||||
const ctx = createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: ctrl.signal,
|
||||
config: plugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
});
|
||||
await plugin.onClose?.(ctx);
|
||||
});
|
||||
|
||||
it("onInit raises AUTH_SERVICE_NOT_AVAILABLE when app missing", {
|
||||
timeout: 10_000,
|
||||
}, async () => {
|
||||
__setBetterAuthFactory(() => makeAuthStub());
|
||||
const plugin = createAuthPlugin({
|
||||
betterAuth: {},
|
||||
applySchemaOnInit: false,
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
const ctrl = new AbortController();
|
||||
const ctx = createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: ctrl.signal,
|
||||
config: plugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
});
|
||||
let caught: AuthError | undefined;
|
||||
try {
|
||||
await plugin.onInit?.(ctx);
|
||||
} catch (err) {
|
||||
caught = err as AuthError;
|
||||
}
|
||||
expect(caught?.code).toBe("AUTH.SERVICE_NOT_AVAILABLE");
|
||||
});
|
||||
|
||||
it("onInit completes with applySchemaOnInit: false + stubbed auth", {
|
||||
timeout: 15_000,
|
||||
}, async () => {
|
||||
__setBetterAuthFactory(() => makeAuthStub());
|
||||
const plugin = createAuthPlugin({
|
||||
betterAuth: {},
|
||||
applySchemaOnInit: false,
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
const app = new OpenAPIHono();
|
||||
const registry = createOpenAPIRegistry({
|
||||
info: { title: "t", version: "0.1.0" },
|
||||
});
|
||||
services.set("app", app);
|
||||
services.set("openAPIRegistry", registry);
|
||||
services.set("logger", noopLogger);
|
||||
services.set("eventBus", new EventBus());
|
||||
services.set("clock", () => Date.now());
|
||||
const ctrl = new AbortController();
|
||||
const ctx = createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: ctrl.signal,
|
||||
config: plugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
});
|
||||
await plugin.onInit?.(ctx);
|
||||
expect(services.get("authService")).toBeDefined();
|
||||
expect(registry.getRoutesByPlugin("auth")).toHaveLength(9);
|
||||
});
|
||||
|
||||
it("default factory delegates to real betterAuth", {
|
||||
timeout: 10_000,
|
||||
}, async () => {
|
||||
// Restore the default factory and exercise the real `betterAuth`
|
||||
// import — invoked with deliberately bad input so the test
|
||||
// surfaces the BETTER_AUTH_INIT_FAILED translation rather than
|
||||
// standing up a real Better-Auth instance.
|
||||
__setBetterAuthFactory(undefined);
|
||||
const plugin = createAuthPlugin({
|
||||
betterAuth: { __invalid__: Symbol("nope") as unknown as string },
|
||||
applySchemaOnInit: false,
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
services.set("app", new OpenAPIHono());
|
||||
const ctx = createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: new AbortController().signal,
|
||||
config: plugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
});
|
||||
let caught: AuthError | undefined;
|
||||
try {
|
||||
await plugin.onInit?.(ctx);
|
||||
} catch (err) {
|
||||
caught = err as AuthError;
|
||||
}
|
||||
// Real betterAuth may succeed with default in-memory config
|
||||
// depending on version; both outcomes count as covering the
|
||||
// default factory path. We only assert NO exception other than
|
||||
// an AuthError reaches here.
|
||||
if (caught !== undefined) {
|
||||
expect(caught).toBeInstanceOf(AuthError);
|
||||
}
|
||||
});
|
||||
|
||||
it("onInit raises AUTH_BETTER_AUTH_INIT_FAILED when betterAuth throws", {
|
||||
timeout: 10_000,
|
||||
}, async () => {
|
||||
__setBetterAuthFactory(() => {
|
||||
throw new Error("better-auth: bad config");
|
||||
});
|
||||
const plugin = createAuthPlugin({
|
||||
betterAuth: {},
|
||||
applySchemaOnInit: false,
|
||||
});
|
||||
const services = new Map<string, unknown>();
|
||||
services.set("app", new OpenAPIHono());
|
||||
const ctrl = new AbortController();
|
||||
const ctx = createPluginContext<AuthPluginConfig>({
|
||||
logger: noopLogger,
|
||||
eventBus: new EventBus(),
|
||||
signal: ctrl.signal,
|
||||
config: plugin.defaults as AuthPluginConfig,
|
||||
services,
|
||||
});
|
||||
let caught: AuthError | undefined;
|
||||
try {
|
||||
await plugin.onInit?.(ctx);
|
||||
} catch (err) {
|
||||
caught = err as AuthError;
|
||||
}
|
||||
expect(caught?.code).toBe("AUTH.BETTER_AUTH_INIT_FAILED");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user