Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/verify-deploy-readiness.test.mjs
T

168 lines
8.6 KiB
JavaScript

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");
});
});