/** * Schema discovery integration test — verifies the filesystem-based * `discoverSchemas` pipeline against the live SurrealDB v3.0.4 engine. * * Spec: `openspec/changes/surrealdb-integration-tests/specs/database-surrealdb-integration/spec.md` * Requirement "Schema discovery verified against the real engine" * * WHEN `DEFINE TABLE a SCHEMAFULL FIELD x TYPE string` and * `DEFINE TABLE b SCHEMAFULL FIELD y TYPE int` have been executed * THEN discovery returns both `a` and `b`, with `a.x: string` and * `b.y: int`. * * Strategy: write two `.surql` files into a tmpdir, run `discoverSchemas` to * enumerate them, apply each file's contents via the adapter against an * isolated namespace/database, then introspect the engine via `INFO FOR DB` * and `INFO FOR TABLE` to assert the tables and field types are present. * * @module */ import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { discoverSchemas, readSchemaContent, } from "../../src/schema/discovery.js"; import { connectAdapter } from "./helpers/connect.js"; import { randomNamespace } from "./helpers/random-ns.js"; import { surrealUnavailable } from "./integration-setup.js"; describe.skipIf(surrealUnavailable)( "SurrealDB schema discovery (real SurrealDB v3.0.4)", () => { it("lists every defined table and field with the expected type", async () => { const dir = await mkdtemp(join(tmpdir(), "surreal-discovery-int-")); const ns = randomNamespace(); const { adapter } = await connectAdapter({ namespaceOverride: ns, databaseOverride: ns, }); try { await writeFile( join(dir, "schema.surql"), "DEFINE TABLE a SCHEMAFULL;\nDEFINE FIELD x ON a TYPE string;" ); await writeFile( join(dir, "tables.surql"), "DEFINE TABLE b SCHEMAFULL;\nDEFINE FIELD y ON b TYPE int;" ); const files = await discoverSchemas(dir); expect(files.map((f) => f.name)).toEqual(["schema", "tables"]); for (const file of files) { const content = await readSchemaContent(file); for (const stmt of content .split(";") .map((s) => s.trim()) .filter((s) => s.length > 0)) { await adapter.execute(stmt); } } const dbInfo = await adapter.query>("INFO FOR DB"); expect(dbInfo.length).toBeGreaterThan(0); const tables = (dbInfo[0]?.tables ?? {}) as Record< string, unknown >; expect(tables.a).toBeDefined(); expect(tables.b).toBeDefined(); const tableA = await adapter.query>( "INFO FOR TABLE a" ); const fieldsA = (tableA[0]?.fields ?? {}) as Record< string, string >; expect(fieldsA.x).toBeDefined(); expect(fieldsA.x).toMatch(/string/i); const tableB = await adapter.query>( "INFO FOR TABLE b" ); const fieldsB = (tableB[0]?.fields ?? {}) as Record< string, string >; expect(fieldsB.y).toBeDefined(); expect(fieldsB.y).toMatch(/int/i); } finally { await adapter.disconnect(); await rm(dir, { recursive: true, force: true }); } }, 30000); } );