import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { SurrealAdapter } from "@suaveplan/surrealdb"; import { afterAll, beforeAll, describe, expect, it, } from "@suaveplan/testing/runner"; import { SurrealDBFixture } from "@suaveplan/testing-integration-surrealdb"; import { SurrealExportStore } from "../../src/store/surreal/surreal-store.js"; import type { ExportJob } from "../../src/types/types.js"; let fixture: SurrealDBFixture | null = null; let adapter: SurrealAdapter | null = null; let migration: string | null = null; const now = new Date("2026-05-19T00:00:00.000Z").toISOString(); function mk(id: string, overrides: Partial = {}): ExportJob { return { id, format: "csv", resource: "users", status: "completed", filters: undefined, filename: undefined, totalRows: null, exportedRows: null, fileSize: null, error: null, createdAt: now, completedAt: null, ...overrides, }; } beforeAll(async () => { fixture = new SurrealDBFixture(); const cfg = await fixture.start(); adapter = new SurrealAdapter({ url: cfg.url, namespace: cfg.namespace, database: cfg.database, username: cfg.username, password: cfg.password, }); await adapter.connect(); migration = await readFile( join(__dirname, "..", "..", "surql", "tables", "export_job.surql"), "utf8" ); await adapter.query(migration); }, 180_000); afterAll(async () => { if (adapter !== null) { await adapter.disconnect(); adapter = null; } if (fixture !== null) { await fixture.stop(); fixture = null; } }, 60_000); describe("SurrealExportStore — integration against real SurrealDB", () => { it("set + get round-trips a job through SurrealDB", { timeout: 60_000, }, async () => { if (adapter === null) throw new Error("fixture not ready"); const store = new SurrealExportStore({ db: adapter }); await store.clear(); const job = mk("rt1", { status: "pending" }); await store.set("rt1", job); const round = await store.get("rt1"); expect(round).not.toBeNull(); expect(round?.format).toBe("csv"); expect(round?.resource).toBe("users"); expect(round?.status).toBe("pending"); expect(round?.createdAt).toBe(now); }); it("has reflects set / delete against SurrealDB", { timeout: 60_000, }, async () => { if (adapter === null) throw new Error("fixture not ready"); const store = new SurrealExportStore({ db: adapter }); await store.clear(); await store.set("h1", mk("h1")); expect(await store.has("h1")).toBe(true); await store.delete("h1"); expect(await store.has("h1")).toBe(false); }); it("delete on a missing row is idempotent", { timeout: 60_000, }, async () => { if (adapter === null) throw new Error("fixture not ready"); const store = new SurrealExportStore({ db: adapter }); await store.clear(); await expect(store.delete("never-existed")).resolves.toBeUndefined(); }); it("list applies status filter and pagination through SurrealDB", { timeout: 60_000, }, async () => { if (adapter === null) throw new Error("fixture not ready"); const store = new SurrealExportStore({ db: adapter }); await store.clear(); await store.set("l1", mk("l1", { status: "completed" })); await store.set("l2", mk("l2", { status: "completed" })); await store.set("l3", mk("l3", { status: "failed" })); const page = await store.list({ status: "completed", limit: 10, offset: 0, }); expect(page.total).toBe(2); expect(page.items).toHaveLength(2); }); it("clear empties the SurrealDB table", { timeout: 60_000, }, async () => { if (adapter === null) throw new Error("fixture not ready"); const store = new SurrealExportStore({ db: adapter }); await store.set("c1", mk("c1")); await store.clear(); const page = await store.list({ limit: 10, offset: 0 }); expect(page.total).toBe(0); }); it("export_job migration is idempotent", { timeout: 60_000, }, async () => { if (adapter === null || migration === null) throw new Error("fixture not ready"); // Re-apply the migration; OVERWRITE semantics make it idempotent. await expect(adapter.query(migration)).resolves.toBeDefined(); await expect(adapter.query(migration)).resolves.toBeDefined(); }); });