import { OpenAPIHono } from "@hono/zod-openapi"; import { createOpenAPIRegistry } from "@suaveplan/api-openapi"; import { createPluginContext } from "@suaveplan/api-plugin"; import { betterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb"; import { EventBus } from "@suaveplan/events"; import { SurrealAdapter } from "@suaveplan/surrealdb"; import { afterAll, beforeAll, describe, expect, it, } from "@suaveplan/testing/runner"; import { SurrealDBFixture } from "@suaveplan/testing-integration-surrealdb"; import { createAuthPlugin } from "../../src/plugin/plugin.js"; import type { AuthPluginConfig } from "../../src/types/types.js"; const noopLogger = { debug: () => undefined, info: () => undefined, warn: () => undefined, error: () => undefined, }; let fixture: SurrealDBFixture | null = null; let client: SurrealAdapter | null = null; beforeAll(async () => { fixture = new SurrealDBFixture(); const cfg = await fixture.start(); client = new SurrealAdapter({ url: cfg.url, namespace: cfg.namespace, database: cfg.database, username: cfg.username, password: cfg.password, }); await client.connect(); await client.execute("DEFINE TABLE OVERWRITE user SCHEMAFULL;"); await client.execute( "DEFINE FIELD OVERWRITE email ON user TYPE option;" ); await client.execute( "DEFINE FIELD OVERWRITE phoneNumber ON user TYPE option;" ); await client.execute( "DEFINE FIELD OVERWRITE createdAt ON user TYPE option DEFAULT time::now() VALUE $before OR time::now();" ); await client.execute( "DEFINE FIELD OVERWRITE updatedAt ON user TYPE option DEFAULT time::now() VALUE time::now();" ); }, 180_000); afterAll(async () => { if (client) { await client.disconnect(); client = null; } if (fixture) { await fixture.stop(); fixture = null; } }, 60_000); describe("openapi-doc", () => { it("served /openapi.json includes auth routes with Zod-typed schemas", { timeout: 120_000, }, async () => { if (client === null) throw new Error("client not ready"); const app = new OpenAPIHono(); const registry = createOpenAPIRegistry({ info: { title: "auth-test", version: "0.1.0" }, }); const plugin = createAuthPlugin({ betterAuth: { database: betterAuthSurrealAdapter({ client }), secret: "test-secret-at-least-32-chars-long-for-better-auth", baseURL: "http://localhost:3000/auth", emailAndPassword: { enabled: true }, }, }); const services = new Map(); services.set("app", app); services.set("openAPIRegistry", registry); services.set("db", client); await plugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: plugin.defaults as AuthPluginConfig, services, }) ); registry.applyTo(app, "/openapi.json"); const res = await app.request("/openapi.json"); const doc = (await res.json()) as { paths: Record>; }; for (const path of [ "/auth/sign-up", "/auth/sign-in", "/auth/sign-out", "/auth/session", "/auth/forgot-password", "/auth/reset-password", "/auth/verify-email", ]) { expect(doc.paths[path]).toBeDefined(); } }); it("served doc lists all 9 auth routes", { timeout: 120_000 }, async () => { if (client === null) throw new Error("client not ready"); const app = new OpenAPIHono(); const registry = createOpenAPIRegistry({ info: { title: "auth-test", version: "0.1.0" }, }); const plugin = createAuthPlugin({ betterAuth: { database: betterAuthSurrealAdapter({ client }), secret: "test-secret-at-least-32-chars-long-for-better-auth", baseURL: "http://localhost:3000/auth", emailAndPassword: { enabled: true }, }, }); const services = new Map(); services.set("app", app); services.set("openAPIRegistry", registry); services.set("db", client); await plugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: plugin.defaults as AuthPluginConfig, services, }) ); const routes = registry.getRoutesByPlugin("auth"); expect(routes.length).toBe(9); }); });