Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
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> = {}): 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();
});
});

View File

@@ -0,0 +1,32 @@
/**
* Test setup for @suaveplan/api-plugin-export.
*
* Installs a real in-memory OpenTelemetry MeterProvider BEFORE any source
* module imports so the meters resolved on first call bind to it.
*/
import { metrics } from "@opentelemetry/api";
import {
AggregationTemporality,
InMemoryMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
export const testInMemoryMetricExporter = new InMemoryMetricExporter(
AggregationTemporality.CUMULATIVE
);
const reader = new PeriodicExportingMetricReader({
exporter: testInMemoryMetricExporter,
exportIntervalMillis: 60_000,
});
export const testMeterProvider = new MeterProvider({ readers: [reader] });
metrics.setGlobalMeterProvider(testMeterProvider);
export async function flushTestMetrics() {
await testMeterProvider.forceFlush();
const collected = testInMemoryMetricExporter.getMetrics();
testInMemoryMetricExporter.reset();
return collected;
}