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); async function bootOnce(c: SurrealAdapter): Promise { const app = new OpenAPIHono(); const registry = createOpenAPIRegistry({ info: { title: "auth-test", version: "0.1.0" }, }); const plugin = createAuthPlugin({ betterAuth: { database: betterAuthSurrealAdapter({ client: c }), 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", c); await plugin.onInit?.( createPluginContext({ logger: noopLogger, eventBus: new EventBus(), signal: new AbortController().signal, config: plugin.defaults as AuthPluginConfig, services, }) ); } describe("schema apply idempotency", () => { it("re-apply succeeds after a prior boot wrote rows", { timeout: 180_000, }, async () => { if (client === null) throw new Error("client not ready"); await bootOnce(client); // Write a row so the second apply has data in scope. await client.execute( "CREATE user:integration_idem SET email = 'idem@example.test';" ); // Re-apply. await bootOnce(client); const rows = await client.query<{ email: string }>( "SELECT * FROM user WHERE email = 'idem@example.test';" ); expect(rows.length).toBe(1); }); });