import { OpenAPIHono } from "@hono/zod-openapi"; import { createOpenAPIRegistry } from "@suaveplan/api-openapi"; import { createPluginContext } from "@suaveplan/api-plugin"; import type { AuthService } from "@suaveplan/api-plugin-auth"; import { 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 { createVaultPlugin } from "../../src/plugin/plugin.js"; import { createMemoryDrive } from "../setup/memory-drive.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("full integration boot — auth + vault against real SurrealDB", () => { it("sign-up → POST → GET → PATCH → DELETE /vault/items roundtrip against real SurrealDB", { timeout: 180_000, }, async () => { if (client === null) throw new Error("client not ready"); const app = new OpenAPIHono(); const registry = createOpenAPIRegistry({ info: { title: "vault-integration", version: "0.1.0" }, }); const services = new Map(); services.set("app", app); services.set("openAPIRegistry", registry); services.set("db", client); // 1. Auth plugin — applies schema + registers authService + // installs session middleware on the app. 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 }, }, }); await authPlugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: authPlugin.defaults as never, services, }) ); // 2. Vault plugin — applies vault schema + registers vaultService // + mounts 5 routes. const drive = createMemoryDrive(); const vaultPlugin = createVaultPlugin({ storage: drive, applySchemaOnInit: true, }); await vaultPlugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: vaultPlugin.defaults as never, services, }) ); // Error-handler so VaultPluginError → JSON envelope w/ statusCode. app.onError((err, c) => { const status = (err as { context?: { statusCode?: number } }).context ?.statusCode ?? 500; return c.json( { success: false, error: { code: (err as { code?: string }).code ?? "X", message: (err as Error).message, timestamp: new Date().toISOString(), }, }, status as 400 | 401 | 403 | 404 | 413 | 500 ); }); // 3. Sign up via Better-Auth. const authService = services.get("authService") as AuthService; expect(authService).toBeDefined(); const auth = authService.getBetterAuthInstance(); const signUp = (await auth.api.signUpEmail({ body: { email: "vault-integration@example.test", password: "Passw0rd!2026", name: "Vault Integration", }, asResponse: true, })) as Response; expect(signUp.status).toBeLessThan(400); const cookie = signUp.headers.get("set-cookie") ?? ""; // 4. POST /vault/items const createRes = await app.request("/vault/items", { method: "POST", headers: { "X-Vault-Unlock": "MyVaultPwd-2026", "Content-Type": "application/json", Cookie: cookie.split(";")[0] ?? "", }, body: JSON.stringify({ title: "API key", kind: "credential", body: "secret-body-value", }), }); expect(createRes.status).toBe(201); const created = (await createRes.json()) as { data: { id: string; title: string; body: string }; }; expect(created.data.body).toBe("secret-body-value"); const itemId = created.data.id; // 5. GET /vault/items const listRes = await app.request("/vault/items", { headers: { "X-Vault-Unlock": "MyVaultPwd-2026", Cookie: cookie.split(";")[0] ?? "", }, }); expect(listRes.status).toBe(200); const list = (await listRes.json()) as { meta: { total: number }; }; expect(list.meta.total).toBe(1); // 6. PATCH /vault/items/:id const patchRes = await app.request(`/vault/items/${itemId}`, { method: "PATCH", headers: { "X-Vault-Unlock": "MyVaultPwd-2026", "Content-Type": "application/json", Cookie: cookie.split(";")[0] ?? "", }, body: JSON.stringify({ title: "Renamed" }), }); expect(patchRes.status).toBe(200); // 7. DELETE /vault/items/:id const delRes = await app.request(`/vault/items/${itemId}`, { method: "DELETE", headers: { "X-Vault-Unlock": "MyVaultPwd-2026", Cookie: cookie.split(";")[0] ?? "", }, }); expect(delRes.status).toBe(200); // 8. Final list should be empty. const finalRes = await app.request("/vault/items", { headers: { "X-Vault-Unlock": "MyVaultPwd-2026", Cookie: cookie.split(";")[0] ?? "", }, }); const final = (await finalRes.json()) as { meta: { total: number } }; expect(final.meta.total).toBe(0); }); });