import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { afterEach, describe, expect, it } from "vitest"; import { verifyDeployReadiness } from "./verify-deploy-readiness.mjs"; const fixtures = []; function writeFixtureFile(rootDir, relativePath, content) { const absolutePath = path.join(rootDir, relativePath); fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); fs.writeFileSync(absolutePath, content); } function createFixture(overrides = {}) { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "deploy-readiness-")); fixtures.push(rootDir); for (const relativePath of [ "package.json", ".gitea/workflows/deploy.yml", ".gitea/workflows/ci.yml", "docs/deployment.md", "docs/improvement-plan/OPERATOR-CHECKLIST.md", "docs/worker.md", "docs/ops/worker-queues.md", "docs/deployments/boiler-saas-ctrl-com.md", ]) { writeFixtureFile(rootDir, relativePath, overrides[relativePath] ?? fs.readFileSync(path.join(process.cwd(), relativePath), "utf8")); } return rootDir; } function packageJsonWithoutWorkflowOutboxTarget(target) { const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8")); packageJson.scripts["verify:workflow-outbox-native"] = packageJson.scripts["verify:workflow-outbox-native"] .split(/\s+/) .filter((part) => part !== target) .join(" "); return `${JSON.stringify(packageJson, null, 2)}\n`; } afterEach(() => { while (fixtures.length > 0) { fs.rmSync(fixtures.pop(), { recursive: true, force: true }); } }); describe("verify-deploy-readiness", () => { it("accepts the repository deploy readiness contract", () => { const result = spawnSync(process.execPath, ["scripts/verify-deploy-readiness.mjs"], { cwd: process.cwd(), encoding: "utf8", }); expect(result.status).toBe(0); expect(result.stdout).toContain("Deploy readiness contract verified."); }); it("checks worker runbook matrix rows against the Coolify provisioner contract", () => { const result = verifyDeployReadiness(createFixture()); expect(result.failures).toEqual([]); expect(result.passes).toContain("docs/worker.md documents worker-plugin as plugin on health 9105 and admin 9205"); expect(result.passes).toContain("docs/ops/worker-queues.md documents worker-mail as mail on health 9102 and admin 9202"); expect(result.passes).toContain("docs/deployments/boiler-saas-ctrl-com.md documents worker-mail as mail on health 9102 and admin 9202"); expect(result.passes).toContain("Coolify worker provisioner sets worker-general OUTBOX_DISPATCHER_REQUIRED=true"); expect(result.passes).toContain("Coolify worker provisioner sets worker-general WORKFLOW_TRIGGER_OUTBOX_REQUIRED=true"); expect(result.passes).toContain("Coolify worker provisioner detects missing worker-general API_ACCESS_TOKEN"); expect(result.passes).toContain("Coolify worker provisioner detects missing worker-general WORKFLOW_TRIGGER_EXECUTOR_URL"); }); it("fails when Gitea CI stops running the workflow outbox native gate", () => { const ciWorkflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/ci.yml"), "utf8") .replace(" - name: Verify workflow outbox native readiness\n run: pnpm run verify:workflow-outbox-native\n", ""); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/ci.yml": ciWorkflow })); expect(result.failures).toContain(".gitea CI workflow missing pnpm run verify:workflow-outbox-native"); }); it("fails when Gitea CI stops running the deploy readiness gate", () => { const ciWorkflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/ci.yml"), "utf8") .replace(" - name: Verify deploy readiness\n run: pnpm run verify:deploy-readiness\n", ""); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/ci.yml": ciWorkflow })); expect(result.failures).toContain(".gitea CI workflow missing pnpm run verify:deploy-readiness"); }); it("fails when web workflow producer inventory coverage falls out of the workflow outbox native gate", () => { const target = "apps/web/app/modules/workflowEngine/routes/workflow-engine/workflow-producer-inventory.test.ts"; const result = verifyDeployReadiness(createFixture({ "package.json": packageJsonWithoutWorkflowOutboxTarget(target) })); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${target}`); }); it("fails when worker workflow consumer or bootstrap coverage falls out of the workflow outbox native gate", () => { const consumerTarget = "apps/worker/src/jobs/workflow-trigger.test.ts"; const bootstrapTarget = "apps/worker/src/jobs/workflow-trigger-bootstrap.test.ts"; const boundaryTarget = "apps/worker/src/jobs/workflow-trigger-boundary.test.ts"; const storeTarget = "apps/worker/src/jobs/workflow-trigger-store.test.ts"; const healthTarget = "apps/worker/src/health.test.ts"; const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8")); packageJson.scripts["verify:workflow-outbox-native"] = packageJson.scripts["verify:workflow-outbox-native"] .split(/\s+/) .filter((part) => ![consumerTarget, bootstrapTarget, boundaryTarget, storeTarget, healthTarget].includes(part)) .join(" "); const result = verifyDeployReadiness(createFixture({ "package.json": `${JSON.stringify(packageJson, null, 2)}\n` })); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${consumerTarget}`); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${bootstrapTarget}`); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${boundaryTarget}`); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${storeTarget}`); expect(result.failures).toContain(`package.json verify:workflow-outbox-native missing ${healthTarget}`); }); it("fails when the worker runbook drifts from managed Coolify ports", () => { const workerDocs = fs .readFileSync(path.join(process.cwd(), "docs/worker.md"), "utf8") .replace( "| `worker-mail` | `WORKER_QUEUE_GROUP=mail` | none | `9102` | `9202` |", "| `worker-mail` | `WORKER_QUEUE_GROUP=mail` | none | `9101` | `9202` |" ); const result = verifyDeployReadiness(createFixture({ "docs/worker.md": workerDocs })); expect(result.failures).toContain("docs/worker.md missing worker-mail row with WORKER_QUEUE_GROUP=mail, health port 9102, and admin port 9202"); }); it("fails when the deployment-specific Coolify worker matrix drifts from managed ports", () => { const deploymentDocs = fs .readFileSync(path.join(process.cwd(), "docs/deployments/boiler-saas-ctrl-com.md"), "utf8") .replace( "| `worker-mail` | `WORKER_QUEUE_GROUP=mail` | `9102` | `9202` |", "| `worker-mail` | `WORKER_QUEUE_GROUP=mail` | `9101` | `9202` |" ); const result = verifyDeployReadiness(createFixture({ "docs/deployments/boiler-saas-ctrl-com.md": deploymentDocs })); expect(result.failures).toContain( "docs/deployments/boiler-saas-ctrl-com.md missing worker-mail row with WORKER_QUEUE_GROUP=mail, health port 9102, and admin port 9202" ); }); it("fails when canonical worker fail-loud readiness guidance is removed", () => { const workerQueueDocs = fs .readFileSync(path.join(process.cwd(), "docs/ops/worker-queues.md"), "utf8") .replace(/`TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=true`/g, "`TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=false`"); const result = verifyDeployReadiness(createFixture({ "docs/ops/worker-queues.md": workerQueueDocs })); expect(result.failures).toContain("canonical worker queue docs missing TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=true"); }); it("fails when operator-owned deploy/backlog status markers drift", () => { const operatorChecklist = fs .readFileSync(path.join(process.cwd(), "docs/improvement-plan/OPERATOR-CHECKLIST.md"), "utf8") .replace("B-008 is closed", "B-008 pending") .replace("A7/#51 is backlogged", "A7/#51 is completed"); const result = verifyDeployReadiness(createFixture({ "docs/improvement-plan/OPERATOR-CHECKLIST.md": operatorChecklist })); expect(result.failures).toContain("operator checklist missing B-008 is closed"); expect(result.failures).toContain("operator checklist missing A7/#51 is backlogged"); }); });