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/ops/asset-cdn.md", "docs/observability/sentry-selfhost.md", "docs/worker.md", "docs/ops/worker-queues.md", "scripts/coolify-ensure-worker-groups.mjs", "apps/worker/scripts/build.mjs", "apps/worker/src/deploy-health.ts", "apps/mcp/scripts/build.mjs", "apps/mcp/src/deploy-health.ts", "Dockerfile", "start.sh", "scripts/verify-migrator-role.mjs", ]) { 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("fails when the MCP stage and promote gates stop consuming readiness", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replace('if [ "$RESOURCE_KIND" = "mcp" ]; then', 'if [ "$RESOURCE_KIND" = "api" ]; then'); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain('.gitea deploy workflow requires if [ "$RESOURCE_KIND" = "mcp" ]; then at least 2 times (found 1)'); }); it("fails when the exact deployment revision is no longer checked out", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replaceAll('git checkout --detach "$IMAGE_TAG"', "git checkout --detach main"); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain('.gitea deploy workflow missing git checkout --detach "$IMAGE_TAG"'); }); it("fails when staging builds bypass the isolated deploy builder", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replace('docker buildx build --builder "$DEPLOY_BUILDER_NAME" --push --target', "docker buildx build --push --target"); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain( '.gitea deploy workflow missing docker buildx build --builder "$DEPLOY_BUILDER_NAME" --push --target' ); }); it("fails when either Hocuspocus rollout stops consuming Docker readiness", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replace('if [ "$RESOURCE_KIND" = "hocuspocus" ]; then', 'if [ "$RESOURCE_KIND" = "hocuspocus-disabled" ]; then'); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain('.gitea deploy workflow requires if [ "$RESOURCE_KIND" = "hocuspocus" ]; then at least 2 times (found 1)'); }); it("fails when the migration entrypoint skips the semantic role probe", () => { const entrypoint = fs.readFileSync(path.join(process.cwd(), "start.sh"), "utf8").replace(" node /myapp/scripts/verify-migrator-role.mjs\n", ""); const result = verifyDeployReadiness(createFixture({ "start.sh": entrypoint })); expect(result.failures).toContain("migration entrypoint missing node /myapp/scripts/verify-migrator-role.mjs"); }); it("fails when the migration entrypoint skips reviewed Prisma baseline adoption", () => { const entrypoint = fs .readFileSync(path.join(process.cwd(), "start.sh"), "utf8") .replace(" node /myapp/scripts/adopt-prisma-baseline.mjs --adopt\n", ""); const result = verifyDeployReadiness(createFixture({ "start.sh": entrypoint })); expect(result.failures).toContain("migration entrypoint missing node /myapp/scripts/adopt-prisma-baseline.mjs --adopt"); }); it("fails when the migration entrypoint skips post-migration Prisma schema verification", () => { const entrypoint = fs .readFileSync(path.join(process.cwd(), "start.sh"), "utf8") .replace(" node /myapp/scripts/adopt-prisma-baseline.mjs --verify-current-schema\n", ""); const result = verifyDeployReadiness(createFixture({ "start.sh": entrypoint })); expect(result.failures).toContain("migration entrypoint missing node /myapp/scripts/adopt-prisma-baseline.mjs --verify-current-schema"); }); it("fails when the migration entrypoint skips the post-migration permission catalog sync", () => { const entrypoint = fs .readFileSync(path.join(process.cwd(), "start.sh"), "utf8") .replace(" node /myapp/packages/db/dist/deploy-permission-catalog-sync.server.js\n", ""); const result = verifyDeployReadiness(createFixture({ "start.sh": entrypoint })); expect(result.failures).toContain( "migration entrypoint missing node /myapp/packages/db/dist/deploy-permission-catalog-sync.server.js" ); }); it("fails when the migrator role probe stops rejecting superusers", () => { const probe = fs .readFileSync(path.join(process.cwd(), "scripts/verify-migrator-role.mjs"), "utf8") .replace("roles[0].isSuperuser !== false", "roles[0].isSuperuser !== true"); const result = verifyDeployReadiness(createFixture({ "scripts/verify-migrator-role.mjs": probe })); expect(result.failures).toContain("migrator role probe missing roles[0].isSuperuser !== false"); }); 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("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 sets worker-general DATABASE_RUNTIME_ROLE=hivegrid_worker"); expect(result.passes).toContain("Coolify worker provisioner sets worker-general RLS_RUNTIME_MODE=required"); expect(result.passes).toContain("Coolify worker provisioner rejects privileged worker env DIRECT_URL"); expect(result.passes).toContain("Coolify worker provisioner rejects a non-worker DATABASE_URL role"); 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"); expect(result.passes).toContain("Coolify worker provisioner binds worker deployment health to the immutable image SHA"); expect(result.passes).toContain("Coolify worker provisioner accepts enabled SHA-bound cmd health configuration"); expect(result.passes).toContain("Coolify worker provisioner detects stale worker health_check_enabled"); expect(result.passes).toContain("Coolify worker provisioner detects stale worker health_check_type"); expect(result.passes).toContain("Coolify worker provisioner detects stale worker health_check_command"); }); it("fails when the worker provisioner validates an unused legacy env source", () => { const provisioner = fs .readFileSync(path.join(process.cwd(), "scripts/coolify-ensure-worker-groups.mjs"), "utf8") .replace( 'const workerEnvSourceUuid = needsProvisioningOrRepair ? resolveWorkerEnvSourceUuid(apps, allowed) : "";', "const workerEnvSourceUuid = resolveWorkerEnvSourceUuid(apps, allowed);" ); const result = verifyDeployReadiness(createFixture({ "scripts/coolify-ensure-worker-groups.mjs": provisioner })); expect(result.failures).toContain( 'Coolify worker provisioner missing const workerEnvSourceUuid = needsProvisioningOrRepair ? resolveWorkerEnvSourceUuid(apps, allowed) : "";' ); }); it("fails when either stage or promotion stops enforcing the SHA-bound worker command", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replace( 'EXPECTED_HEALTH_COMMAND="node apps/worker/dist/deploy-health.js $IMAGE_TAG $EXPECTED_WORKER_GROUP"', 'EXPECTED_HEALTH_COMMAND="node apps/worker/dist/health.js"' ); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain( '.gitea deploy workflow requires EXPECTED_HEALTH_COMMAND="node apps/worker/dist/deploy-health.js $IMAGE_TAG $EXPECTED_WORKER_GROUP" at least 2 times (found 1)' ); }); it("fails when the worker build omits the deployment health entrypoint", () => { const workerBuild = fs.readFileSync(path.join(process.cwd(), "apps/worker/scripts/build.mjs"), "utf8").replace(', "src/deploy-health.ts"', ""); const result = verifyDeployReadiness(createFixture({ "apps/worker/scripts/build.mjs": workerBuild })); expect(result.failures).toContain('worker build missing "src/deploy-health.ts"'); }); it("fails when worker deployment health stops checking the exact image revision", () => { const deployHealth = fs .readFileSync(path.join(process.cwd(), "apps/worker/src/deploy-health.ts"), "utf8") .replace("imageRevision !== expectedRevision || env.APP_REVISION !== expectedRevision", "!env.APP_REVISION"); const result = verifyDeployReadiness(createFixture({ "apps/worker/src/deploy-health.ts": deployHealth })); expect(result.failures).toContain( "worker deployment health entrypoint missing imageRevision !== expectedRevision || env.APP_REVISION !== expectedRevision" ); }); it("fails when MCP command health stops being bound to the staged revision", () => { const workflow = fs .readFileSync(path.join(process.cwd(), ".gitea/workflows/deploy.yml"), "utf8") .replace( 'EXPECTED_MCP_HEALTH_COMMAND="node apps/mcp/dist/deploy-health.js $IMAGE_TAG"', 'EXPECTED_MCP_HEALTH_COMMAND="node apps/mcp/dist/deploy-health.js staging"' ); const result = verifyDeployReadiness(createFixture({ ".gitea/workflows/deploy.yml": workflow })); expect(result.failures).toContain( '.gitea deploy workflow requires EXPECTED_MCP_HEALTH_COMMAND="node apps/mcp/dist/deploy-health.js $IMAGE_TAG" at least 2 times (found 1)' ); }); it("fails when image-baked revision proof is removed from a runtime target", () => { const dockerfile = fs .readFileSync(path.join(process.cwd(), "Dockerfile"), "utf8") .replace("RUN printf '%s\\n' \"$APP_REVISION\" > /myapp/.image-revision", ""); const result = verifyDeployReadiness(createFixture({ Dockerfile: dockerfile })); expect(result.failures).toContain("Dockerfile requires RUN printf '%s\\n' \"$APP_REVISION\" > /myapp/.image-revision at least 3 times (found 2)"); }); 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 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 deploymentDocs = fs .readFileSync(path.join(process.cwd(), "docs/deployment.md"), "utf8") .replace("B-012 is not a repository code blocker", "B-012 is a repository blocker"); const assetCdnRunbook = fs.readFileSync(path.join(process.cwd(), "docs/ops/asset-cdn.md"), "utf8").replace("remain parked", "are active"); const sentryRunbook = fs .readFileSync(path.join(process.cwd(), "docs/observability/sentry-selfhost.md"), "utf8") .replace("B-008 is closed", "B-008 pending"); const result = verifyDeployReadiness( createFixture({ "docs/deployment.md": deploymentDocs, "docs/ops/asset-cdn.md": assetCdnRunbook, "docs/observability/sentry-selfhost.md": sentryRunbook, }) ); expect(result.failures).toContain("deployment docs missing B-012 is not a repository code blocker"); expect(result.failures).toContain("asset CDN runbook missing remain parked"); expect(result.failures).toContain("Sentry self-host runbook missing B-008 is closed"); }); });