// e2e coverage for the S4 coverage-exhaustive gate (issue #157; issue #189). // // Boots a real Urban app with `{ coverage: true }` and proves the gate: // • the declared surfaces are derived from the app's OWN manifest + spec (operations from // the OpenAPI document, workers from `manifest.workers[].taskType`) — no second list; // • exercising a subset leaves the gate RED, naming exactly the un-exercised elements; // • exercising the rest turns it GREEN. // This is the Red/Green guard for "we forgot to test operation X" as a build failure. import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { bootTestApp } from "./boot-app.ts"; const ORDER_BPMN = ` `; const HANDLERS = `export const handlers = { "order.pack": async (job, app) => { await app.data.table("orders", "item").update(String(job.variables.item), { status: "packed" }); return { packed: true }; }, };`; const MIGRATION = `CREATE TABLE orders (id INTEGER PRIMARY KEY, item TEXT, status TEXT);`; const CREATE_ORDER = `export default async ({ body }, app) => { const item = String((body && body.item) || "").trim(); if (!item) return { status: 400, body: { error: "item is required" } }; await app.data.table("orders", "item").insert({ item, status: "active" }); await app.engine.createInstance({ processDefinitionId: "order", variables: { item } }); return { status: 202, body: { item } }; };`; const GET_ORDER = `export default async ({ params }, app) => { const row = await app.data.table("orders", "item").findOne({ item: params.item }); if (!row) return { status: 404, body: { error: "not found" } }; return { status: 200, body: row }; };`; const OPENAPI = { openapi: "3.0.3", info: { title: "Coverage Fixture", version: "1.0.0" }, paths: { "/orders": { post: { operationId: "createOrder", requestBody: { required: true, content: { "application/json": { schema: { type: "object", properties: { item: { type: "string" } }, required: ["item"] }, }, }, }, responses: { "202": { description: "started" } }, }, }, "/orders/{item}": { get: { operationId: "getOrder", parameters: [{ name: "item", in: "path", required: true, schema: { type: "string" } }], responses: { "200": { description: "ok" }, "404": { description: "missing" } }, }, }, }, }; /** A minimal app with two operations (`createOrder`, `getOrder`) and one worker (`order.pack`). */ async function makeFixture(): Promise { const dir = await mkdtemp(join(tmpdir(), "urban-testkit-coverage-")); await mkdir(join(dir, "processes"), { recursive: true }); await mkdir(join(dir, "workers"), { recursive: true }); await mkdir(join(dir, "operations"), { recursive: true }); await mkdir(join(dir, "db", "migrations"), { recursive: true }); await writeFile(join(dir, "processes", "order.bpmn"), ORDER_BPMN); await writeFile(join(dir, "workers", "handlers.ts"), HANDLERS); await writeFile(join(dir, "operations", "createOrder.ts"), CREATE_ORDER); await writeFile(join(dir, "operations", "getOrder.ts"), GET_ORDER); await writeFile(join(dir, "db", "migrations", "001_init.sql"), MIGRATION); await writeFile(join(dir, "openapi.json"), JSON.stringify(OPENAPI, null, 2)); const manifest = { schemaVersion: 1, id: "testkit-coverage-fixture", name: "Testkit Coverage Fixture", models: { processes: ["processes/*.bpmn"] }, data: { default: "app", sources: { app: { driver: "sqlite", url: "file:./db/app.db", migrations: "db/migrations" } }, }, workers: [{ taskType: "order.pack", handler: "workers/handlers.ts" }], api: { spec: "openapi.json", dir: "operations", validateResponses: "never" }, }; await writeFile(join(dir, "nano.app.json"), JSON.stringify(manifest, null, 2)); return dir; } /** A minimal app with a worker but NO `api` binding — a spec-less app has no operations surface. */ async function makeWorkerOnlyFixture(): Promise { const dir = await mkdtemp(join(tmpdir(), "urban-testkit-coverage-noapi-")); await mkdir(join(dir, "processes"), { recursive: true }); await mkdir(join(dir, "workers"), { recursive: true }); await mkdir(join(dir, "db", "migrations"), { recursive: true }); await writeFile(join(dir, "processes", "order.bpmn"), ORDER_BPMN); await writeFile(join(dir, "workers", "handlers.ts"), HANDLERS); await writeFile(join(dir, "db", "migrations", "001_init.sql"), MIGRATION); const manifest = { schemaVersion: 1, id: "testkit-coverage-noapi-fixture", name: "Testkit Coverage No-API Fixture", models: { processes: ["processes/*.bpmn"] }, data: { default: "app", sources: { app: { driver: "sqlite", url: "file:./db/app.db", migrations: "db/migrations" } }, }, workers: [{ taskType: "order.pack", handler: "workers/handlers.ts" }], }; await writeFile(join(dir, "nano.app.json"), JSON.stringify(manifest, null, 2)); return dir; } test("coverage: declares the app's surfaces from its own manifest + spec", async () => { const dir = await makeFixture(); const app = await bootTestApp(dir, { coverage: true }); try { assert.ok(app.coverage, "coverage:true exposes the gate"); const report = app.coverage.report(); const ops = report.surfaces.find((s) => s.surface === "operations"); const workers = report.surfaces.find((s) => s.surface === "workers"); assert.deepEqual(ops?.declared, ["createOrder", "getOrder"], "operations from the spec"); assert.deepEqual(workers?.declared, ["order.pack"], "workers from the manifest"); // Nothing exercised yet → the gate is red. assert.equal(report.complete, false); assert.throws(() => app.coverage?.assertFullCoverage(), /Coverage incomplete/); } finally { await app.stop(); await rm(dir, { recursive: true, force: true }); } }); test("coverage: exercising a subset leaves the gate RED naming the gap, then GREEN once all are driven", async () => { const dir = await makeFixture(); const app = await bootTestApp(dir, { coverage: true }); try { assert.ok(app.api && app.coverage); // Drive createOrder — which starts the `order` process, whose service task runs the // `order.pack` worker synchronously. So this single call exercises BOTH the createOrder // operation AND the order.pack worker, but NOT the getOrder operation. const created = await app.api.call<{ item: string }>("createOrder", { body: { item: "widget" } }); assert.equal(created.status, 202); const afterCreate = app.coverage.report(); const ops = afterCreate.surfaces.find((s) => s.surface === "operations"); const workers = afterCreate.surfaces.find((s) => s.surface === "workers"); assert.deepEqual(ops?.exercised, ["createOrder"]); assert.deepEqual(ops?.missing, ["getOrder"], "getOrder not yet driven"); assert.equal(workers?.complete, true, "the worker ran inside createInstance"); // The gate fails, naming exactly the un-exercised operation (and NOT the covered worker surface). assert.throws( () => app.coverage?.assertFullCoverage(), (err: unknown) => { assert.ok(err instanceof Error); assert.match(err.message, /operations: 1 un-exercised → getOrder/); assert.doesNotMatch(err.message, /workers:/); return true; }, ); // Drive the remaining operation → full coverage, gate passes. const fetched = await app.api.call<{ status: string }>("getOrder", { params: { item: "widget" } }); assert.equal(fetched.status, 200); assert.equal(app.coverage.report().complete, true); assert.doesNotThrow(() => app.coverage?.assertFullCoverage()); } finally { await app.stop(); await rm(dir, { recursive: true, force: true }); } }); test("coverage: an un-driven worker keeps the gate RED even when all operations are covered", async () => { const dir = await makeFixture(); const app = await bootTestApp(dir, { coverage: true }); try { assert.ok(app.api && app.coverage); // Call getOrder for a missing item (404): a read that touches neither the process nor the worker. const missing = await app.api.call("getOrder", { params: { item: "nope" } }); assert.equal(missing.status, 404); // createOrder still un-driven AND order.pack never ran → both surfaces incomplete. assert.throws( () => app.coverage?.assertFullCoverage(), (err: unknown) => { assert.ok(err instanceof Error); assert.match(err.message, /operations: 1 un-exercised → createOrder/); assert.match(err.message, /workers: 1 un-exercised → order\.pack/); return true; }, ); } finally { await app.stop(); await rm(dir, { recursive: true, force: true }); } }); test("coverage: off by default — app.coverage is undefined", async () => { const dir = await makeFixture(); const app = await bootTestApp(dir); try { assert.equal(app.coverage, undefined, "no coverage overhead unless opted in"); } finally { await app.stop(); await rm(dir, { recursive: true, force: true }); } }); test("coverage: a spec-less app declares no `operations` surface — only what it actually has", async () => { const dir = await makeWorkerOnlyFixture(); const app = await bootTestApp(dir, { coverage: true }); try { assert.ok(app.coverage); assert.equal(app.api, undefined, "no api binding → no operations driver"); // The gate must reflect the app's real surfaces: workers only, never a phantom `operations`. assert.deepEqual(app.coverage.surfaces(), ["workers"]); assert.equal( app.coverage.report().surfaces.find((s) => s.surface === "operations"), undefined, "no empty operations surface is fabricated", ); // Gating a surface the app doesn't declare is a test bug, not a silent pass. assert.throws( () => app.coverage?.assertFullCoverage({ surfaces: ["operations"] }), /unknown surface\(s\) operations/, ); } finally { await app.stop(); await rm(dir, { recursive: true, force: true }); } });