import { OpenAPIHono } from "@hono/zod-openapi"; import { createOpenAPIRegistry } from "@suaveplan/api-openapi"; import { createPluginContext } from "@suaveplan/api-plugin"; import { type AuthPluginConfig, createAuthPlugin, } from "@suaveplan/api-plugin-auth"; 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 { createUserPlugin } from "../../src/plugin/plugin.js"; import type { UserPluginConfig } 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("user plugin full-boot integration", () => { it("sign-up → /users/me → PATCH → DELETE roundtrip against real SurrealDB", { timeout: 120_000, }, async () => { if (client === null) throw new Error("client not ready"); const app = new OpenAPIHono(); const registry = createOpenAPIRegistry({ info: { title: "user-test", version: "0.1.0" }, }); // Mount auth plugin first. const authPlugin = createAuthPlugin({ betterAuth: { database: betterAuthSurrealAdapter({ client }), secret: "test-secret-at-least-32-chars-long-for-better-auth", baseURL: "http://localhost:3000/auth", emailAndPassword: { enabled: true }, user: { deleteUser: { enabled: true } }, }, }); const services = new Map(); services.set("app", app); services.set("openAPIRegistry", registry); services.set("db", client); await authPlugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: authPlugin.defaults as AuthPluginConfig, services, }) ); // Mount user plugin. const userPlugin = createUserPlugin({}); await userPlugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: userPlugin.defaults as UserPluginConfig, services, }) ); registry.applyTo(app, "/openapi.json"); // Sign up via Better-Auth API. const authServiceSlot = services.get("authService") as { getBetterAuthInstance: () => { api: { signUpEmail: (a: { body: { email: string; password: string; name: string }; }) => Promise<{ user: { id: string } }>; }; }; }; const auth = authServiceSlot.getBetterAuthInstance(); const signUp = await auth.api.signUpEmail({ body: { email: "user-plugin@example.test", password: "Passw0rd!2026", name: "Test User", }, }); expect(signUp.user.id.length).toBeGreaterThan(0); // userService.getById round-trips against the live row. const userService = services.get("userService") as { getById: (id: string) => Promise; updateProfile: ( id: string, patch: Readonly> ) => Promise<{ displayName?: string }>; }; const profile = await userService.getById(signUp.user.id); expect(profile).not.toBeNull(); // PATCH applies and refresh returns updated value. const updated = await userService.updateProfile(signUp.user.id, { displayName: "Test Updated", }); expect(updated.displayName).toBe("Test Updated"); // OpenAPI doc lists /users/me three times (get, patch, delete). const openapiRes = await app.request("/openapi.json"); const doc = (await openapiRes.json()) as { paths: Record>; }; const meMethods = Object.keys(doc.paths?.["/users/me"] ?? {}); expect(meMethods.sort()).toEqual(["delete", "get", "patch"]); }); });