feat(monitor): add central PostgreSQL persistence API
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { test } from "bun:test";
|
||||
|
||||
import { createInMemoryMonitorCentralStore, createMonitorCentralService, MONITOR_CENTRAL_MIGRATIONS, MonitorCentralConfigurationError } from "./monitor-central-persistence";
|
||||
|
||||
function service() { return createMonitorCentralService({ store: createInMemoryMonitorCentralStore() }); }
|
||||
async function ingest(target: ReturnType<typeof service>, runId: string, updatedAt: string, scope: Record<string, string> = {}) {
|
||||
return target.ingest({ sentinelId: scope.sentinelId ?? "sentinel-a", node: scope.node ?? "NC01", lane: scope.lane ?? "v03", runId, updatedAt, status: "succeeded", payload: { runId, views: { summary: { state: "ready" } } }, findings: [{ id: `finding-${runId}` }], artifactLocators: [{ locator: `s3://evidence/${runId}`, sha256: "sha256:abc", owner: "runner", sizeBytes: 12, kind: "report" }], timeline: [{ phase: "done" }] });
|
||||
}
|
||||
|
||||
test("schema covers central persistence objects and does not store artifact blobs", () => {
|
||||
const schema = MONITOR_CENTRAL_MIGRATIONS.join("\n");
|
||||
for (const name of ["schema_migrations", "runners", "runs", "findings", "run_payloads", "artifact_locators", "timeline_events", "import_manifests"]) assert.match(schema, new RegExp(`monitor\\.${name}`));
|
||||
assert.doesNotMatch(schema, /blob|bytea/i);
|
||||
});
|
||||
|
||||
test("ingest is transactional in contract, idempotent by payload hash, and rejects conflicts", async () => {
|
||||
const target = service();
|
||||
const first = await ingest(target, "run-1", "2026-07-12T10:00:00.000Z");
|
||||
const repeated = await ingest(target, "run-1", "2026-07-12T10:00:00.000Z");
|
||||
assert.equal(first.disposition, "inserted");
|
||||
assert.equal(repeated.disposition, "idempotent");
|
||||
await assert.rejects(() => target.ingest({ sentinelId: "sentinel-a", node: "NC01", lane: "v03", runId: "run-1", payload: { changed: true } }), { code: "payload_hash_conflict" });
|
||||
});
|
||||
|
||||
test("global and scoped latest use updatedAt then runId descending", async () => {
|
||||
const target = service();
|
||||
await ingest(target, "run-a", "2026-07-12T10:00:00.000Z");
|
||||
await ingest(target, "run-b", "2026-07-12T10:00:00.000Z");
|
||||
await ingest(target, "run-c", "2026-07-12T09:00:00.000Z", { sentinelId: "sentinel-b" });
|
||||
const overview = await (await target.fetch(new Request("http://local/api/overview"))).json() as any;
|
||||
const scoped = await (await target.fetch(new Request("http://local/api/overview?sentinelId=sentinel-b"))).json() as any;
|
||||
assert.equal(overview.overview.latest.runId, "run-b");
|
||||
assert.equal(scoped.overview.latest.runId, "run-c");
|
||||
});
|
||||
|
||||
test("runs and findings are bounded, cursor-paged, and scope-isolated", async () => {
|
||||
const target = service();
|
||||
await ingest(target, "run-1", "2026-07-12T10:00:00.000Z");
|
||||
await ingest(target, "run-2", "2026-07-12T11:00:00.000Z");
|
||||
await ingest(target, "run-3", "2026-07-12T12:00:00.000Z", { lane: "v04" });
|
||||
const first = await (await target.fetch(new Request("http://local/api/runs?lane=v03&limit=1"))).json() as any;
|
||||
const second = await (await target.fetch(new Request(`http://local/api/runs?lane=v03&limit=1000&cursor=${first.nextCursor}`))).json() as any;
|
||||
const findings = await (await target.fetch(new Request("http://local/api/findings?lane=v03"))).json() as any;
|
||||
assert.equal(first.items.length, 1);
|
||||
assert.equal(first.items[0].runId, "run-2");
|
||||
assert.equal(second.items.length, 1);
|
||||
assert.equal(second.items[0].runId, "run-1");
|
||||
assert.equal(findings.items.length, 2);
|
||||
});
|
||||
|
||||
test("run detail returns payload, locator metadata, timeline, and no secret values", async () => {
|
||||
const target = service();
|
||||
await ingest(target, "run-detail", "2026-07-12T10:00:00.000Z");
|
||||
const response = await (await target.fetch(new Request("http://local/api/runs/run-detail"))).json() as any;
|
||||
assert.equal(response.run.payload.runId, "run-detail");
|
||||
assert.deepEqual(response.run.artifactLocators[0], { locator: "s3://evidence/run-detail", sha256: "sha256:abc", owner: "runner", sizeBytes: 12, kind: "report" });
|
||||
assert.equal(response.valuesRedacted, true);
|
||||
});
|
||||
|
||||
test("health is redacted and missing DATABASE_URL fails structurally", async () => {
|
||||
const target = service();
|
||||
const health = await target.health();
|
||||
assert.equal(health.ok, true);
|
||||
assert.equal(health.valuesRedacted, true);
|
||||
assert.throws(() => createMonitorCentralService({ databaseUrl: "" }), MonitorCentralConfigurationError);
|
||||
});
|
||||
|
||||
test("view contract reshapes an existing report payload without reading runner artifacts", async () => {
|
||||
const target = service();
|
||||
await ingest(target, "run-view", "2026-07-12T10:00:00.000Z");
|
||||
const response = await (await target.fetch(new Request("http://local/api/runs/run-view/views/summary"))).json() as any;
|
||||
assert.deepEqual(response.view, { runId: "run-view", name: "summary", value: { state: "ready" } });
|
||||
});
|
||||
|
||||
test("service entry exits with a structured redacted error when DATABASE_URL is absent", () => {
|
||||
const result = spawnSync("bun", ["scripts/monitor-central-service.ts"], { cwd: process.cwd(), encoding: "utf8", env: { PATH: process.env.PATH ?? "" } });
|
||||
assert.equal(result.status, 2);
|
||||
assert.deepEqual(JSON.parse(result.stdout), { ok: false, error: { code: "monitor_central_database_url_missing", message: "Monitor central service requires an explicit DATABASE_URL" }, valuesRedacted: true });
|
||||
});
|
||||
Reference in New Issue
Block a user