Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-gitea-ci-workflow.test.mjs
T

1601 lines
75 KiB
JavaScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { afterEach, describe, it } from "vitest";
import { formatGiteaCiWorkflowResult, verifyGiteaCiWorkflow } from "./verify-gitea-ci-workflow.mjs";
const VALID_WORKFLOW = `name: CI
on:
pull_request:
workflow_dispatch:
concurrency:
group: \${{ github.workflow }}-\${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
verify-1:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify Gitea CI workflow
run: pnpm run verify:gitea-ci-workflow
- name: Verify deploy workflow
run: pnpm run verify:deploy-workflow
- name: Verify deploy readiness
run: pnpm run verify:deploy-readiness
- name: Verify production dependency licenses
run: pnpm run verify:licenses
- name: Audit production dependencies
run: pnpm audit --prod --audit-level high
- name: Verify CI baseline
run: pnpm run verify:ci-baseline
- name: Verify source health
run: pnpm run verify:source-health
- name: Verify ESLint baseline
run: pnpm run verify:eslint-baseline
- name: Verify env drift
run: pnpm run verify:env-drift
- name: Verify health baseline
run: pnpm run verify:health-baseline
- name: Verify API inventory
run: pnpm run verify:api-inventory
- name: Verify UI mock static baseline
run: pnpm run verify:ui-visual
- name: Test UI hot-swap
run: pnpm run test:hot-swap
verify-2:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify tenant isolation report
run: pnpm run verify:tenant-isolation-report
- name: Verify sidebar routes
run: pnpm run verify:sidebar-routes
- name: Verify i18n
run: pnpm run verify:i18n
- name: Verify Tenant Auth baseline gates
run: pnpm run verify:tenant-auth-baseline
- name: Verify Tenant Auth onboarding flow
run: pnpm run verify:onboarding-flow
- name: Verify Phase C boundaries
run: pnpm run verify:phase-c-boundaries
- name: Verify removed modules
run: pnpm run verify:removed-modules
- name: Verify accessibility coverage
run: pnpm run verify:a11y-coverage
- name: Test accessibility smoke
run: pnpm run test:a11y
- name: Verify docs links
run: pnpm run verify:docs-links
typecheck-test:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run parallel quality groups
run: bash scripts/ci/run-quality-groups.sh
build:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify Feature Manifest core without plugins
run: pnpm run verify:core-build-without-plugins-smoke
- name: Build
run: pnpm run build:ci
- name: Verify web runtime dependency closure
run: pnpm run verify:runtime-closures -- --runtime=web
- name: Verify web Sentry sourcemaps
run: test -n "$(find apps/web/build-sourcemaps/core -type f -name '*.map' -print -quit)" && test -n "$(find apps/web/build-sourcemaps/portal -type f -name '*.map' -print -quit)"
- name: Upload web Sentry sourcemaps
uses: actions/upload-artifact@v4
with:
name: web-client-sourcemaps
path: apps/web/build-sourcemaps
if-no-files-found: error
retention-days: 7
- name: Verify production seed artifact
run: pnpm run verify:production-seed-artifact
api:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify OpenAPI security
run: pnpm run verify:openapi-security
- name: Typecheck API
run: pnpm --filter @hivegrid/api typecheck
- name: Test API
run: pnpm exec vitest run apps/api/src
- name: Build API
run: pnpm --filter @hivegrid/api build
- name: Verify API runtime dependency closure
run: pnpm run verify:runtime-closures -- --runtime=api
mcp:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck MCP
run: pnpm --filter @hivegrid/mcp typecheck
- name: Test MCP
run: pnpm --filter @hivegrid/mcp test
- name: Build MCP
run: pnpm --filter @hivegrid/mcp build
- name: Verify MCP runtime dependency closure
run: pnpm run verify:runtime-closures -- --runtime=mcp
hocuspocus:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck Hocuspocus
run: pnpm --filter @hivegrid/hocuspocus typecheck
- name: Test Hocuspocus
run: pnpm --filter @hivegrid/hocuspocus test
- name: Build Hocuspocus
run: pnpm --filter @hivegrid/hocuspocus build
- name: Verify Hocuspocus runtime dependency closure
run: pnpm run verify:runtime-closures -- --runtime=hocuspocus
worker:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck Worker
run: pnpm --filter @hivegrid/worker typecheck
- name: Test Worker
run: pnpm --filter @hivegrid/worker exec vitest run
- name: Verify workflow outbox native readiness
run: pnpm run verify:workflow-outbox-native
- name: Build Worker
run: pnpm --filter @hivegrid/worker build
- name: Verify worker runtime dependency closure
run: pnpm run verify:runtime-closures -- --runtime=worker
- name: Health Check Worker
run: pnpm --filter @hivegrid/worker health
database:
runs-on: toolctrl-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Pin pnpm
run: corepack prepare pnpm@11.12.0 --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate database client
run: pnpm run db:generate
- name: Verify migrations schema parity
run: pnpm run verify:migrations-parity
- name: Verify Prisma baseline equivalence
run: pnpm run verify:prisma-baseline-equivalence
- name: Verify tenant policy contract
run: pnpm run verify:tenant-policy-contract
- name: Verify RLS release readiness
run: pnpm run verify:rls-release-readiness
- name: Verify RLS against fresh PostgreSQL
run: pnpm run verify:rls-live
env:
HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY: "1"
- name: Verify database migration status
run: pnpm run verify:db-migrate-status:staging
env:
COOLIFY_STAGING_SSH_HOST: \${{ vars.COOLIFY_STAGING_SSH_HOST }}
COOLIFY_STAGING_SSH_USER: \${{ vars.COOLIFY_STAGING_SSH_USER }}
COOLIFY_STAGING_WEB_RESOURCE_UUID: \${{ vars.COOLIFY_STAGING_WEB_RESOURCE_UUID }}
COOLIFY_STAGING_SSH_KEY: \${{ secrets.COOLIFY_STAGING_SSH_KEY }}
docker-smoke:
runs-on: toolctrl-ci
timeout-minutes: 45
needs: [verify-1, verify-2, typecheck-test, build, api, worker, database]
env:
DOCKER_BUILDKIT: "1"
DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS: "90"
DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS: "180"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Docker smoke targets
shell: bash
run: |
set -euo pipefail
(
set -euo pipefail
docker build --target web --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --build-arg VITE_HOCUSPOCUS_URL=wss://collab.invalid --tag hivegrid-web:smoke .
docker build --target api --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-api:smoke .
docker build --target mcp --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-mcp:smoke .
docker build --target worker --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-worker:smoke .
docker build --target portal-domain-controller --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-portal-domain-controller:smoke .
) &
root_targets_pid=$!
docker build --file apps/hocuspocus/Dockerfile --target hocuspocus --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-hocuspocus:smoke . &
hocuspocus_pid=$!
status=0
wait "$root_targets_pid" || status=$?
wait "$hocuspocus_pid" || status=$?
exit "$status"
- name: Validate web Docker artifacts
run: docker run --rm --entrypoint /bin/sh hivegrid-web:smoke -lc 'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/apps/web/build-portal/server/index.js && test -d /myapp/apps/web/build-portal/client && test -f /myapp/build/server-runtime/server.js'
- name: Validate web Docker runtime
run: bash scripts/docker-web-runtime-smoke.sh hivegrid-web:smoke
- name: Validate API Docker runtime
run: bash scripts/docker-api-runtime-smoke.sh hivegrid-api:smoke
env:
DOCKER_API_SMOKE_TRANSPORT: exec
- name: Validate MCP Docker runtime
run: bash scripts/docker-mcp-runtime-smoke.sh hivegrid-mcp:smoke
- name: Validate worker Docker runtime
run: bash scripts/docker-worker-runtime-smoke.sh hivegrid-worker:smoke
- name: Validate Hocuspocus Docker runtime
run: bash scripts/docker-hocuspocus-runtime-smoke.sh hivegrid-hocuspocus:smoke
- name: Validate Docker Compose config
run: docker compose config
`;
const ISOLATED_DOCKER_WORKFLOW = fs.readFileSync(path.resolve(".gitea/workflows/ci.yml"), "utf8");
const ISOLATED_DOCKER_SUITE = fs.readFileSync(path.resolve("scripts/ci/docker-smoke-suite.sh"), "utf8");
const QUALITY_GROUP_RUNNER = fs.readFileSync(path.resolve("scripts/ci/run-quality-groups.sh"), "utf8");
const SENTRY_REUSABLE_WORKFLOW = `name: Sentry Release
on:
workflow_call:
inputs:
sourcemaps-path:
required: false
type: string
default: "none"
sourcemaps-artifact:
required: false
type: string
default: "none"
jobs:
release:
runs-on: toolctrl-ci
steps:
- name: Download source-map artifact
if: \${{ inputs.sourcemaps-artifact != 'none' }}
uses: actions/download-artifact@v4
with:
name: \${{ inputs.sourcemaps-artifact }}
path: \${{ inputs.sourcemaps-path }}
`;
const fixtures = [];
function writeFile(rootDir, relativePath, content) {
const absolutePath = path.join(rootDir, relativePath);
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
fs.writeFileSync(absolutePath, content);
}
function createFixture(workflowContent = VALID_WORKFLOW) {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "gitea-ci-workflow-"));
fixtures.push(rootDir);
if (workflowContent !== null) {
writeFile(rootDir, ".gitea/workflows/ci.yml", workflowContent);
writeFile(rootDir, ".gitea/workflows/_sentry-release.yml", SENTRY_REUSABLE_WORKFLOW);
writeFile(rootDir, "scripts/ci/run-quality-groups.sh", QUALITY_GROUP_RUNNER);
}
return rootDir;
}
function createIsolatedDockerFixture({ workflow = ISOLATED_DOCKER_WORKFLOW, suite = ISOLATED_DOCKER_SUITE } = {}) {
const rootDir = createFixture(workflow);
if (suite !== null) {
writeFile(rootDir, "scripts/ci/docker-smoke-suite.sh", suite);
}
return rootDir;
}
afterEach(() => {
while (fixtures.length > 0) {
fs.rmSync(fixtures.pop(), { recursive: true, force: true });
}
});
describe("verify-gitea-ci-workflow", () => {
it("passes for a valid minimal Gitea quality workflow with service jobs", () => {
const rootDir = createFixture();
const result = verifyGiteaCiWorkflow(rootDir);
const output = formatGiteaCiWorkflowResult(result);
assert.deepEqual(result.failures, []);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: trigger "push" is disabled so deploy is not queued behind CI on main'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: trigger "workflow_dispatch" is defined'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: quality job "verify-1" pnpm@11.12.0 is pinned'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: quality job "verify-2" pnpm@11.12.0 is pinned'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: quality job "typecheck-test" pnpm@11.12.0 is pinned'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: quality job "build" pnpm@11.12.0 is pinned'));
assert.ok(result.passes.some((pass) => /^\.gitea\/workflows\/ci\.yml:\d+: runner is "toolctrl-ci"$/.test(pass)));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:ci-baseline" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:source-health" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:eslint-baseline" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:api-inventory" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:health-baseline" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:tenant-auth-baseline" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: command "pnpm run verify:tenant-isolation-report" is ordered'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "api" is defined'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "api" includes scoped command "pnpm run verify:openapi-security"'));
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "api" includes scoped command "pnpm --filter @hivegrid/api typecheck"')
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "worker" is defined'));
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "worker" includes scoped command "pnpm run verify:workflow-outbox-native"')
);
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "worker" includes scoped command "pnpm --filter @hivegrid/worker build"')
);
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "worker" includes scoped command "pnpm --filter @hivegrid/worker health"')
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" is defined'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "api" runs independently of "quality"'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: service job "worker" runs independently of "quality"'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" runs independently of "quality"'));
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" includes command "pnpm run verify:db-migrate-status:staging"')
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" scopes staging SSH host to the migrate-status step'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" scopes staging SSH key to the migrate-status step'));
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" verifies private Coolify databases without exposing DATABASE_URL')
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: database job "database" contains no deploy/seed/schema mutation commands'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" is defined'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" does not run Corepack setup'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" does not create an unused Buildx builder'));
assert.ok(
result.passes.some(
(pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" enables BuildKit explicitly with DOCKER_BUILDKIT=1'
)
);
assert.ok(
result.passes.some(
(pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" allows loaded-runner commands up to 90 seconds'
)
);
assert.ok(
result.passes.some(
(pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" allows loaded-runner artifact checks up to 180 seconds'
)
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" builds Docker target "web"'));
assert.ok(result.passes.some((pass) => pass === ".gitea/workflows/ci.yml: Docker smoke web build exercises the public Hocuspocus URL build arg"));
assert.ok(
result.passes.some(
(pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" serializes root Docker targets while Hocuspocus builds in parallel'
)
);
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" validates web Docker artifacts'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" validates web Docker runtime'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" validates API Docker runtime'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" validates worker Docker runtime'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" validates Docker Compose config'));
assert.ok(result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: web source maps are uploaded as artifact "web-client-sourcemaps"'));
assert.ok(
result.passes.some((pass) => pass === ".gitea/workflows/_sentry-release.yml: reusable workflow downloads requested source-map artifacts before upload")
);
assert.ok(
result.passes.some((pass) => pass === '.gitea/workflows/ci.yml: Docker smoke job "docker-smoke" contains no registry login/push/publish commands')
);
assert.ok(result.passes.some((pass) => pass === ".gitea/workflows/ci.yml: no forbidden E2E/browser commands are present"));
assert.ok(result.passes.some((pass) => pass === ".gitea/workflows/ci.yml: CI concurrency cancels superseded pull request and branch runs"));
assert.match(output, /^PASS Gitea CI workflow verification/);
assert.match(output, /Summary: \d+ passed, 0 observed, 0 failed/);
});
it("fails when CI concurrency does not cancel superseded runs", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
/concurrency:\n group: \$\{\{ github\.workflow \}\}-\$\{\{ github\.event\.pull_request\.number \|\| github\.ref \}\}\n cancel-in-progress: true\n\n/u,
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /CI workflow must define concurrency by workflow plus PR\/ref with cancel-in-progress: true/);
});
it("fails when Docker smoke installs pnpm dependencies", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Build Docker smoke targets\n",
" - name: Enable Corepack\n run: corepack enable\n - name: Pin pnpm\n run: corepack prepare pnpm@11.12.0 --activate\n - name: Install dependencies\n run: pnpm install --frozen-lockfile\n - name: Build Docker smoke targets\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
const failures = result.failures.join("\n");
assert.match(failures, /Docker smoke job "docker-smoke" must not run Corepack setup/);
assert.match(failures, /Docker smoke job "docker-smoke" must not run "corepack prepare pnpm@11\.12\.0"/);
assert.match(failures, /Docker smoke job "docker-smoke" must not run "pnpm install --frozen-lockfile"/);
});
it("fails when Docker smoke creates an unused Buildx builder", () => {
const workflow = VALID_WORKFLOW.replace(
" - name: Build Docker smoke targets\n",
" - name: Set up Docker Buildx\n uses: docker/setup-buildx-action@v3\n - name: Build Docker smoke targets\n"
);
const result = verifyGiteaCiWorkflow(createFixture(workflow));
assert.match(result.failures.join("\n"), /Docker smoke job "docker-smoke" must not use docker\/setup-buildx-action/);
});
it("fails when Docker smoke does not set BuildKit to one", () => {
const workflow = VALID_WORKFLOW.replace(' DOCKER_BUILDKIT: "1"\n', ' DOCKER_BUILDKIT: "0"\n');
const result = verifyGiteaCiWorkflow(createFixture(workflow));
assert.match(result.failures.join("\n"), /Docker smoke job "docker-smoke" must set job-level DOCKER_BUILDKIT: "1"/);
});
it("fails when Docker smoke keeps the loaded-runner command timeout at 30 seconds", () => {
const workflow = VALID_WORKFLOW.replace(
' DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS: "90"\n',
' DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS: "30"\n'
);
const result = verifyGiteaCiWorkflow(createFixture(workflow));
assert.match(
result.failures.join("\n"),
/Docker smoke job "docker-smoke" must set job-level DOCKER_SMOKE_COMMAND_TIMEOUT_SECONDS: "90"/
);
});
it("fails when Docker smoke keeps the artifact timeout at 60 seconds", () => {
const workflow = VALID_WORKFLOW.replace(
' DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS: "180"\n',
' DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS: "60"\n'
);
const result = verifyGiteaCiWorkflow(createFixture(workflow));
assert.match(
result.failures.join("\n"),
/Docker smoke job "docker-smoke" must set job-level DOCKER_SMOKE_ARTIFACT_TIMEOUT_SECONDS: "180"/
);
});
it("fails when the workflow file is missing", () => {
const rootDir = createFixture(null);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required Gitea CI workflow file/);
});
it("fails when a required gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" run: bash scripts/ci/run-quality-groups.sh\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "bash scripts\/ci\/run-quality-groups\.sh"/);
});
it.each([
[
"license",
" - name: Verify production dependency licenses\n run: pnpm run verify:licenses\n",
/missing required command "pnpm run verify:licenses"/,
],
[
"audit",
" - name: Audit production dependencies\n run: pnpm audit --prod --audit-level high\n",
/missing required command "pnpm audit --prod --audit-level high"/,
],
])("fails when the global %s supply-chain gate is missing", (_label, step, expected) => {
const result = verifyGiteaCiWorkflow(createFixture(VALID_WORKFLOW.replace(step, "")));
assert.match(result.failures.join("\n"), expected);
});
it("fails when pnpm setup is missing from one split quality job", () => {
// String.replace removes only the first occurrence, i.e. the "verify-1" job's setup.
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Enable Corepack\n run: corepack enable\n", "")
.replace(" - name: Pin pnpm\n run: corepack prepare pnpm@11.12.0 --activate\n", "")
.replace(" - name: Install dependencies\n run: pnpm install --frozen-lockfile\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
const failures = result.failures.join("\n");
assert.match(failures, /\.gitea\/workflows\/ci\.yml: quality job "verify-1" missing Corepack setup/);
assert.match(failures, /\.gitea\/workflows\/ci\.yml: quality job "verify-1" pnpm must be pinned to pnpm@11\.12\.0/);
assert.match(failures, /\.gitea\/workflows\/ci\.yml: quality job "verify-1" missing required command "pnpm install --frozen-lockfile"/);
});
it("fails when a required quality gate only appears in a shell comment", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" run: pnpm run verify:docs-links\n", " run: |\n # pnpm run verify:docs-links\n echo skipped\n")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:docs-links"/);
});
it("fails when a required quality gate is duplicated", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify source health\n run: pnpm run verify:source-health\n",
" - name: Verify source health\n run: pnpm run verify:source-health\n - name: Verify source health again\n run: pnpm run verify:source-health\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: command "pnpm run verify:source-health" must run exactly once to avoid redundant CI gates/
);
});
it("fails when a required quality gate is pass-opened with shell control operators", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" run: bash scripts/ci/run-quality-groups.sh\n",
" run: bash scripts/ci/run-quality-groups.sh || true\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /command "bash scripts\/ci\/run-quality-groups\.sh" must not use shell success masking/);
});
it("fails when a required quality gate disables errexit in a run block", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" run: bash scripts/ci/run-quality-groups.sh\n",
" run: |\n set +e\n bash scripts/ci/run-quality-groups.sh\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /command "bash scripts\/ci\/run-quality-groups\.sh" must not use shell success masking/);
});
it("fails when a service command ends with an unconditional true", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Typecheck API\n run: pnpm --filter @hivegrid/api typecheck\n",
" - name: Typecheck API\n run: |\n pnpm --filter @hivegrid/api typecheck\n true\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: service job "api" command "pnpm --filter @hivegrid\/api typecheck" must not use shell success masking/
);
});
it("accepts block-list needs syntax for dependent jobs", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" needs: [verify-1, verify-2, typecheck-test, build, api, worker, database]\n",
" needs:\n - verify-1\n - verify-2\n - typecheck-test\n - build\n - api\n - worker\n - database\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.deepEqual(result.failures, []);
});
it("fails when a required quality gate is softened at step level", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Run parallel quality groups\n run: bash scripts/ci/run-quality-groups.sh\n",
" - name: Run parallel quality groups\n continue-on-error: true\n run: bash scripts/ci/run-quality-groups.sh\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /command "bash scripts\/ci\/run-quality-groups\.sh" must not use continue-on-error or always\(\)/);
});
it("fails when the tenant policy gate is disabled with a false condition", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify tenant policy contract\n run: pnpm run verify:tenant-policy-contract\n",
" - name: Verify tenant policy contract\n if: ${{ false }}\n run: pnpm run verify:tenant-policy-contract\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /command "pnpm run verify:tenant-policy-contract" must not use conditional execution/);
});
it("fails when the live RLS gate is disabled with a false condition", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify RLS against fresh PostgreSQL\n run: pnpm run verify:rls-live\n env:\n HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY: \"1\"\n",
" - name: Verify RLS against fresh PostgreSQL\n if: false\n run: pnpm run verify:rls-live\n env:\n HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY: \"1\"\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /command "pnpm run verify:rls-live" must not use conditional execution/);
});
it("fails when the live RLS gate permits the feature-branch overlay", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" env:\n HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY: \"1\"\n",
"",
),
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /live RLS gate must set HIVEGRID_RLS_REQUIRE_MIGRATION_CHAIN_PARITY: "1"/);
});
it("fails when the database job drops the RLS release-readiness gate", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Verify RLS release readiness\n run: pnpm run verify:rls-release-readiness\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /database job "database" missing command "pnpm run verify:rls-release-readiness"/);
});
it("fails when a required quality gate adds skip flags or suffixes", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" run: bash scripts/ci/run-quality-groups.sh\n",
" run: bash scripts/ci/run-quality-groups.sh --skip-tests\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /missing required command "bash scripts\/ci\/run-quality-groups\.sh"/);
});
it("fails when worker tests are narrowed to explicit files", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" run: pnpm --filter @hivegrid/worker exec vitest run\n",
" run: pnpm --filter @hivegrid/worker exec vitest run src/handlers.test.ts src/health.test.ts"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: service job "worker" missing scoped command "pnpm --filter @hivegrid\/worker exec vitest run"/
);
});
it("fails when the worker service job omits the workflow outbox native readiness gate", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Verify workflow outbox native readiness\n run: pnpm run verify:workflow-outbox-native\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: service job "worker" missing scoped command "pnpm run verify:workflow-outbox-native"/
);
});
it("fails when the worker service job omits its health check", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Health Check Worker\n run: pnpm --filter @hivegrid/worker health\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: service job "worker" missing scoped command "pnpm --filter @hivegrid\/worker health"/
);
});
it("fails when the env drift gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify env drift\n run: pnpm run verify:env-drift\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:env-drift"/);
});
it("fails when the env drift gate runs after typecheck", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify env drift\n run: pnpm run verify:env-drift\n - name: Verify health baseline\n run: pnpm run verify:health-baseline\n - name: Verify API inventory\n run: pnpm run verify:api-inventory\n",
" - name: Verify health baseline\n run: pnpm run verify:health-baseline\n - name: Verify API inventory\n run: pnpm run verify:api-inventory\n - name: Verify env drift\n run: pnpm run verify:env-drift\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: command "pnpm run verify:health-baseline" must run after previous quality gates/);
});
it("fails when the health baseline gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify health baseline\n run: pnpm run verify:health-baseline\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:health-baseline"/);
});
it("fails when the source health gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify source health\n run: pnpm run verify:source-health\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:source-health"/);
});
it("fails when the ESLint baseline gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify ESLint baseline\n run: pnpm run verify:eslint-baseline\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:eslint-baseline"/);
});
it("fails when the API inventory gate is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify API inventory\n run: pnpm run verify:api-inventory\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:api-inventory"/);
});
it("fails when a service job command only appears in a shell comment", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Typecheck API\n run: pnpm --filter @hivegrid/api typecheck\n",
" - name: Typecheck API\n run: |\n # pnpm --filter @hivegrid/api typecheck\n echo skipped\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: service job "api" missing scoped command "pnpm --filter @hivegrid\/api typecheck"/);
});
it("fails when the tenant isolation report gate is missing", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Verify tenant isolation report\n run: pnpm run verify:tenant-isolation-report\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required command "pnpm run verify:tenant-isolation-report"/);
});
it("fails when a verify-2 gate runs out of its canonical order", () => {
// tenant-isolation-report is canonically the first verify-2 gate; moving it after the
// sidebar/i18n gates pushes those earlier gates out of order within the owning job.
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify tenant isolation report\n run: pnpm run verify:tenant-isolation-report\n - name: Verify sidebar routes\n run: pnpm run verify:sidebar-routes\n - name: Verify i18n\n run: pnpm run verify:i18n\n",
" - name: Verify sidebar routes\n run: pnpm run verify:sidebar-routes\n - name: Verify i18n\n run: pnpm run verify:i18n\n - name: Verify tenant isolation report\n run: pnpm run verify:tenant-isolation-report\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: command "pnpm run verify:sidebar-routes" must run after previous quality gates/);
});
it("fails when a job uses ubuntu-latest instead of the required Gitea runner", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" runs-on: toolctrl-ci\n", " runs-on: ubuntu-latest\n"));
const result = verifyGiteaCiWorkflow(rootDir);
const output = formatGiteaCiWorkflowResult(result);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml:\d+: runs-on must be "toolctrl-ci" but found "ubuntu-latest"/);
assert.match(output, /^FAIL Gitea CI workflow verification/);
});
it("fails when lint runs before typecheck", () => {
const rootDir = createFixture();
writeFile(
rootDir,
"scripts/ci/run-quality-groups.sh",
QUALITY_GROUP_RUNNER.replace(
" pnpm --filter @hivegrid/portal-domain-controller typecheck\n",
" pnpm lint\n pnpm --filter @hivegrid/portal-domain-controller typecheck\n"
).replace(" pnpm lint\n}\n\nrun_foundation_packages", "}\n\nrun_foundation_packages")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /typecheck-lint group must run controller typecheck, web typegen\/typecheck, and lint in order/);
});
it("fails when test readers start before Prisma generation completes", () => {
const rootDir = createFixture();
const unsafeRunner = QUALITY_GROUP_RUNNER.replace("pnpm run db:generate\n", "").replace(
"QUALITY_FAILED=0\n",
"pnpm run db:generate\nQUALITY_FAILED=0\n"
);
writeFile(rootDir, "scripts/ci/run-quality-groups.sh", unsafeRunner);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must generate the Prisma client before starting parallel quality groups/);
});
it.each([
["foundation Vitest pool", "pnpm run test:foundation --maxWorkers=2", "pnpm run test:foundation"],
[
"package workspace fan-out",
"PNPM_CONFIG_WORKSPACE_CONCURRENCY=2 pnpm run test:packages --maxWorkers=1",
"pnpm run test:packages --maxWorkers=1",
],
["package Vitest pool", "pnpm run test:packages --maxWorkers=1", "pnpm run test:packages"],
])("fails when the %s cap is removed", (_name, boundedCommand, unsafeCommand) => {
const rootDir = createFixture();
writeFile(rootDir, "scripts/ci/run-quality-groups.sh", QUALITY_GROUP_RUNNER.replace(boundedCommand, unsafeCommand));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /foundation\/package tests must keep the exact bounded worker and workspace-concurrency commands/);
});
it("fails when the core Vitest pool cap is removed", () => {
const rootDir = createFixture();
writeFile(rootDir, "scripts/ci/run-quality-groups.sh", QUALITY_GROUP_RUNNER.replace("pnpm run test --maxWorkers=4", "pnpm run test"));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /core-tests group must run pnpm run test --maxWorkers=4/);
});
it("fails when one quality process group is omitted", () => {
const rootDir = createFixture();
writeFile(
rootDir,
"scripts/ci/run-quality-groups.sh",
QUALITY_GROUP_RUNNER.replace('start_group "core-tests" run_core_tests\n', "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must start exactly three named background groups and capture every PID/);
});
it("fails when quality process results are not all waited", () => {
const rootDir = createFixture();
writeFile(rootDir, "scripts/ci/run-quality-groups.sh", QUALITY_GROUP_RUNNER.replace('if wait "$pid"; then', "if true; then"));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must wait every named PID, report its status, and propagate aggregate failure/);
});
it("fails when a service job is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(/ worker:\n[\s\S]*$/, ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required service job "worker"/);
});
it("fails when a service job waits for the quality job", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" api:\n runs-on: toolctrl-ci\n steps:\n", " api:\n runs-on: toolctrl-ci\n needs: quality\n steps:\n")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: service job "api" must not wait for "quality"; scoped service checks should run in parallel/
);
assert.doesNotMatch(result.failures.join("\n"), /service job "worker" must not wait for "quality"/);
});
it("fails when a scoped service command is missing from the owning job", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Test API\n run: pnpm exec vitest run apps/api/src\n", "").replace(
" - name: Build Worker\n run: pnpm --filter @hivegrid/worker build\n",
" - name: Build Worker\n run: pnpm --filter @hivegrid/worker build\n - name: Misplaced API tests\n run: pnpm exec vitest run apps/api/src\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: service job "api" missing scoped command "pnpm exec vitest run apps\/api\/src"/);
assert.doesNotMatch(result.failures.join("\n"), /service job "worker" missing scoped command "pnpm exec vitest run apps\/api\/src"/);
});
it("fails when Hocuspocus skips its post-build runtime closure gate", () => {
const result = verifyGiteaCiWorkflow(
createFixture(
VALID_WORKFLOW.replace(
" - name: Verify Hocuspocus runtime dependency closure\n run: pnpm run verify:runtime-closures -- --runtime=hocuspocus\n",
""
)
)
);
assert.match(result.failures.join("\n"), /service job "hocuspocus" missing scoped command "pnpm run verify:runtime-closures -- --runtime=hocuspocus"/);
});
it("fails when the API service job omits the OpenAPI security guard", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" - name: Verify OpenAPI security\n run: pnpm run verify:openapi-security\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: service job "api" missing scoped command "pnpm run verify:openapi-security"/);
});
it("fails when the database job is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(/ database:\n[\s\S]*? docker-smoke:/, " docker-smoke:"));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required database job "database"/);
});
it("fails when the database job waits for the quality job", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" database:\n runs-on: toolctrl-ci\n steps:\n", " database:\n runs-on: toolctrl-ci\n needs: quality\n steps:\n")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" must not wait for "quality"; database verification should run in parallel/
);
});
it("fails when the database job omits migrate status verification", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n env:\n COOLIFY_STAGING_SSH_HOST: ${{ vars.COOLIFY_STAGING_SSH_HOST }}\n COOLIFY_STAGING_SSH_USER: ${{ vars.COOLIFY_STAGING_SSH_USER }}\n COOLIFY_STAGING_WEB_RESOURCE_UUID: ${{ vars.COOLIFY_STAGING_WEB_RESOURCE_UUID }}\n COOLIFY_STAGING_SSH_KEY: ${{ secrets.COOLIFY_STAGING_SSH_KEY }}\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" missing command "pnpm run verify:db-migrate-status:staging"/
);
});
it("fails when the database job omits the step-level staging SSH key secret", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" COOLIFY_STAGING_SSH_KEY: ${{ secrets.COOLIFY_STAGING_SSH_KEY }}\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" must set COOLIFY_STAGING_SSH_KEY: \$\{\{ secrets\.COOLIFY_STAGING_SSH_KEY \}\} on the migrate-status step/
);
});
it("fails when the database job omits the step-level staging host variable", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" COOLIFY_STAGING_SSH_HOST: ${{ vars.COOLIFY_STAGING_SSH_HOST }}\n", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" must set COOLIFY_STAGING_SSH_HOST: \$\{\{ vars\.COOLIFY_STAGING_SSH_HOST \}\} on the migrate-status step/
);
});
it("fails when the database job drops the tenant policy contract gate", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Verify tenant policy contract\n run: pnpm run verify:tenant-policy-contract\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /database job "database" missing command "pnpm run verify:tenant-policy-contract"/);
});
it("fails when the database job exposes DATABASE_URL at job scope", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" database:\n runs-on: toolctrl-ci\n steps:\n - name: Checkout\n",
" database:\n runs-on: toolctrl-ci\n env:\n DATABASE_URL: ${{ secrets.DATABASE_URL }}\n steps:\n - name: Checkout\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: database job "database" must not set DATABASE_URL at job scope/);
});
it("fails when the database job uses the direct DATABASE_URL secret", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" COOLIFY_STAGING_SSH_KEY: ${{ secrets.COOLIFY_STAGING_SSH_KEY }}\n",
" COOLIFY_STAGING_SSH_KEY: ${{ secrets.COOLIFY_STAGING_SSH_KEY }}\n DATABASE_URL: ${{ secrets.DATABASE_URL }}\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" must not require direct DATABASE_URL access to private Coolify databases/
);
});
it("fails when the database job contains a migration deploy command", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n",
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n - name: Deploy migrations\n run: pnpm --filter @hivegrid/db prisma migrate deploy\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" contains forbidden deploy\/seed\/schema mutation command: run: pnpm --filter @hivegrid\/db prisma migrate deploy/
);
});
it("fails when the database job hides a migration deploy command in a multiline run block", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n",
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n - name: Deploy migrations\n run: |\n pnpm run db:generate\n pnpm --filter @hivegrid/db prisma migrate deploy\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" contains forbidden deploy\/seed\/schema mutation command: run: pnpm run db:generate\npnpm --filter @hivegrid\/db prisma migrate deploy/
);
});
it("fails when the database job contains an E2E browser command", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n",
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n - name: Browser check\n run: pnpm exec playwright test\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" contains forbidden E2E\/browser command: run: pnpm exec playwright test/
);
});
it("fails when the database job hides an E2E browser command in a multiline run block", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n",
" - name: Verify database migration status\n run: pnpm run verify:db-migrate-status:staging\n - name: Browser check\n run: |\n pnpm run db:generate\n pnpm exec playwright test\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: database job "database" contains forbidden E2E\/browser command: run: pnpm run db:generate\npnpm exec playwright test/
);
});
it("fails when the Docker smoke job is missing", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(/ docker-smoke:\n[\s\S]*$/, ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: missing required Docker smoke job "docker-smoke"/);
});
it("fails when a Docker target build is missing from the Docker smoke job", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n",
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n - name: Misplaced Docker API build\n run: docker buildx build --target api --file Dockerfile .\n"
).replace(
" docker build --target api --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-api:smoke .\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: Docker smoke job "docker-smoke" missing Docker build for target "api"/);
assert.doesNotMatch(result.failures.join("\n"), /missing Docker build for target "web"/);
});
it("fails when a Docker smoke build omits the revision build arg", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
"docker build --target web --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --build-arg VITE_HOCUSPOCUS_URL=wss://collab.invalid --tag hivegrid-web:smoke .",
"docker build --target web --build-arg VITE_HOCUSPOCUS_URL=wss://collab.invalid --tag hivegrid-web:smoke ."
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: Docker smoke job "docker-smoke" build for target "web" must pass --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567/
);
});
it("fails when the Docker smoke web build does not exercise the Hocuspocus build arg", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(" --build-arg VITE_HOCUSPOCUS_URL=wss://collab.invalid", ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /web build must exercise --build-arg VITE_HOCUSPOCUS_URL=wss:\/\/collab\.invalid/);
});
it("fails when the Docker smoke job contains a forbidden publish command", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Validate Docker Compose config\n run: docker compose config\n",
" - name: Validate Docker Compose config\n run: docker compose config\n - name: Publish image\n run: docker push registry.example.com/hivegrid/api:latest\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: Docker smoke job "docker-smoke" contains forbidden publish command: run: docker push registry\.example\.com\/hivegrid\/api:latest/
);
});
it("fails when the Docker smoke job omits web artifact validation", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Validate web Docker artifacts\n run: docker run --rm --entrypoint /bin/sh hivegrid-web:smoke -lc 'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/apps/web/build-portal/server/index.js && test -d /myapp/apps/web/build-portal/client && test -f /myapp/build/server-runtime/server.js'\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /Docker smoke job "docker-smoke" missing command "docker run --rm --entrypoint \/bin\/sh hivegrid-web:smoke/);
});
it("fails when the Docker smoke job omits web runtime validation", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(" - name: Validate web Docker runtime\n run: bash scripts/docker-web-runtime-smoke.sh hivegrid-web:smoke\n", "")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /Docker smoke job "docker-smoke" missing command "bash scripts\/docker-web-runtime-smoke\.sh hivegrid-web:smoke"/);
});
it("fails when the Docker smoke job omits API runtime validation", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Validate API Docker runtime\n run: bash scripts/docker-api-runtime-smoke.sh hivegrid-api:smoke\n env:\n DOCKER_API_SMOKE_TRANSPORT: exec\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /Docker smoke job "docker-smoke" missing command "bash scripts\/docker-api-runtime-smoke\.sh hivegrid-api:smoke"/);
});
it("fails when the Docker smoke job omits worker runtime validation", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Validate worker Docker runtime\n run: bash scripts/docker-worker-runtime-smoke.sh hivegrid-worker:smoke\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/Docker smoke job "docker-smoke" missing command "bash scripts\/docker-worker-runtime-smoke\.sh hivegrid-worker:smoke"/
);
});
it("fails when the Docker smoke job omits Hocuspocus runtime validation", () => {
const result = verifyGiteaCiWorkflow(
createFixture(
VALID_WORKFLOW.replace(
" - name: Validate Hocuspocus Docker runtime\n run: bash scripts/docker-hocuspocus-runtime-smoke.sh hivegrid-hocuspocus:smoke\n",
""
)
)
);
assert.match(result.failures.join("\n"), /missing command "bash scripts\/docker-hocuspocus-runtime-smoke\.sh hivegrid-hocuspocus:smoke"/);
});
it("fails when the Hocuspocus smoke image uses the root Dockerfile", () => {
const result = verifyGiteaCiWorkflow(
createFixture(VALID_WORKFLOW.replace("docker build --file apps/hocuspocus/Dockerfile --target hocuspocus", "docker build --target hocuspocus"))
);
assert.match(result.failures.join("\n"), /must build Hocuspocus from apps\/hocuspocus\/Dockerfile/);
});
it("fails when a root Docker smoke target is parallelized against the shared cache", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" docker build --target api --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-api:smoke .\n",
" docker build --target api --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-api:smoke . &\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must serialize root Docker targets and propagate failures while Hocuspocus builds in parallel/);
assert.doesNotMatch(result.failures.join("\n"), /missing Docker build for target "api"/);
});
it("fails when Hocuspocus no longer builds alongside the serialized root targets", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" docker build --file apps/hocuspocus/Dockerfile --target hocuspocus --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-hocuspocus:smoke . &\n",
" docker build --file apps/hocuspocus/Dockerfile --target hocuspocus --build-arg APP_REVISION=0123456789abcdef0123456789abcdef01234567 --tag hivegrid-hocuspocus:smoke .\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must serialize root Docker targets and propagate failures while Hocuspocus builds in parallel/);
assert.doesNotMatch(result.failures.join("\n"), /missing Docker build for target "hocuspocus"/);
});
it("fails when the grouped Docker smoke build does not propagate a child failure", () => {
const rootDir = createFixture(VALID_WORKFLOW.replace(' exit "$status"\n', ""));
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must serialize root Docker targets and propagate failures while Hocuspocus builds in parallel/);
});
it("fails when the grouped Docker smoke build captures the root PID too late", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" root_targets_pid=$!\n docker build --file apps/hocuspocus/Dockerfile",
" docker build --file apps/hocuspocus/Dockerfile"
).replace(" --tag hivegrid-hocuspocus:smoke . &\n hocuspocus_pid=$!", " --tag hivegrid-hocuspocus:smoke . &\n root_targets_pid=$!\n hocuspocus_pid=$!")
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must serialize root Docker targets and propagate failures while Hocuspocus builds in parallel/);
});
it("fails when the grouped Docker smoke build resets status after waiting", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" status=0\n wait \"$root_targets_pid\" || status=$?\n wait \"$hocuspocus_pid\" || status=$?",
" wait \"$root_targets_pid\" || status=$?\n wait \"$hocuspocus_pid\" || status=$?\n status=0"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /must serialize root Docker targets and propagate failures while Hocuspocus builds in parallel/);
});
it("fails when the Docker smoke job hides a forbidden publish command in a multiline run block", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Validate Docker Compose config\n run: docker compose config\n",
" - name: Validate Docker Compose config\n run: docker compose config\n - name: Publish image\n run: |\n docker compose config\n docker push registry.example.com/hivegrid/api:latest\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: Docker smoke job "docker-smoke" contains forbidden publish command: run: docker compose config\ndocker push registry\.example\.com\/hivegrid\/api:latest/
);
});
it("does not treat Docker publish commands outside Docker smoke as Docker smoke publish failures", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n",
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n - name: Misplaced publish\n run: docker push registry.example.com/hivegrid/api:latest\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.doesNotMatch(result.failures.join("\n"), /Docker smoke job "docker-smoke" contains forbidden publish command/);
});
it("fails when a forbidden Playwright command is present", () => {
const rootDir = createFixture(`${VALID_WORKFLOW} - name: E2E\n run: pnpm exec playwright test\n`);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /\.gitea\/workflows\/ci\.yml: forbidden E2E\/browser command found: run: pnpm exec playwright test/);
});
it("fails when a forbidden Playwright command is hidden in a multiline run block", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Build\n run: pnpm run build:ci\n",
" - name: Build\n run: |\n pnpm run build:ci\n pnpm exec playwright test\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: forbidden E2E\/browser command found: run: pnpm run build:ci\npnpm exec playwright test/
);
});
it("fails when a forbidden browser command is present in a service job", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n",
" - name: Build API\n run: pnpm --filter @hivegrid/api build\n - name: Browser smoke\n run: pnpm --filter @hivegrid/api exec browser test\n"
)
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/ci\.yml: forbidden E2E\/browser command found: run: pnpm --filter @hivegrid\/api exec browser test/
);
});
it("fails when the internal host override uses the reserved GITEA_ prefix", () => {
const rootDir = createFixture(`env:\n GITEA_HOST_IP: \${{ vars.GITEA_HOST_IP }}\n${VALID_WORKFLOW}`);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /GITEA_HOST_IP is forbidden because Gitea reserves the GITEA_ variable prefix/);
});
it("fails when the internal host override is written without token validation", () => {
const rootDir = createFixture(
`${VALID_WORKFLOW}\n host-smoke:\n runs-on: toolctrl-ci\n steps:\n - name: Resolve internal host\n run: |\n if [ -n \"\${HIVEGRID_GITEA_HOST_IP:-}\" ]; then\n printf '%s git.tool-ctrl.de\\n' \"$HIVEGRID_GITEA_HOST_IP\" >> /etc/hosts\n fi\n`
);
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(result.failures.join("\n"), /HIVEGRID_GITEA_HOST_IP must be validated before writing to \/etc\/hosts/);
});
it("fails when web source maps are not uploaded for the Sentry release", () => {
const rootDir = createFixture(
VALID_WORKFLOW.replace(
" - name: Verify web Sentry sourcemaps\n run: test -n \"$(find apps/web/build-sourcemaps/core -type f -name '*.map' -print -quit)\" && test -n \"$(find apps/web/build-sourcemaps/portal -type f -name '*.map' -print -quit)\"\n - name: Upload web Sentry sourcemaps\n uses: actions/upload-artifact@v4\n with:\n name: web-client-sourcemaps\n path: apps/web/build-sourcemaps\n if-no-files-found: error\n retention-days: 7\n",
""
)
);
const result = verifyGiteaCiWorkflow(rootDir);
const failures = result.failures.join("\n");
assert.match(failures, /missing web source-map validation before Sentry artifact upload/);
assert.match(failures, /must upload apps\/web\/build-sourcemaps as artifact "web-client-sourcemaps"/);
});
it("fails when the reusable Sentry workflow does not download source-map artifacts", () => {
const rootDir = createFixture();
writeFile(rootDir, ".gitea/workflows/_sentry-release.yml", "name: Sentry Release\n");
const result = verifyGiteaCiWorkflow(rootDir);
assert.match(
result.failures.join("\n"),
/\.gitea\/workflows\/_sentry-release\.yml: reusable workflow must accept sourcemaps-artifact and download it to inputs\.sourcemaps-path/
);
});
it("accepts the isolated, SHA-scoped and parallel-wave Docker smoke suite", () => {
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture());
assert.deepEqual(result.failures, []);
assert.ok(result.passes.includes(".gitea/workflows/ci.yml: CI concurrency cancels superseded pull request and branch runs"));
assert.ok(result.passes.includes(".gitea/workflows/ci.yml: run-scoped Docker smokes may execute concurrently across refs"));
assert.ok(result.passes.includes("scripts/ci/docker-smoke-suite.sh: all images build in parallel before the isolated runtime wave"));
assert.ok(result.passes.includes("scripts/ci/docker-smoke-suite.sh: parallel waves collect every child result and propagate failures"));
assert.ok(result.passes.includes("scripts/ci/docker-smoke-suite.sh: every build and runtime phase has a command-level timeout"));
assert.ok(
result.passes.includes(
"scripts/ci/docker-smoke-suite.sh: Compose validation uses a non-secret fixture without rendering resolved values"
)
);
});
it("fails when the isolated Docker smoke suite file is missing", () => {
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite: null }));
assert.match(result.failures.join("\n"), /missing required isolated Docker smoke suite/);
});
it("fails when Docker smoke regains a cross-ref serialization group", () => {
const workflow = ISOLATED_DOCKER_WORKFLOW.replace(
" timeout-minutes: 45\n",
" timeout-minutes: 45\n concurrency:\n group: hivegrid-cloud-docker-smoke\n cancel-in-progress: false\n"
);
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ workflow }));
assert.match(result.failures.join("\n"), /must not use job-level concurrency/);
});
it("fails when workflow concurrency keeps superseded Docker smoke runs queued", () => {
const workflow = ISOLATED_DOCKER_WORKFLOW.replace(" cancel-in-progress: true\n", " cancel-in-progress: false\n");
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ workflow }));
assert.match(result.failures.join("\n"), /must define concurrency by workflow plus PR\/ref with cancel-in-progress: true/);
});
it("fails when Docker smoke stops scoping resources by run attempt", () => {
const workflow = ISOLATED_DOCKER_WORKFLOW.replace(
" DOCKER_SMOKE_RUN_ID: ${{ gitea.run_id }}-${{ gitea.run_attempt }}\n",
" DOCKER_SMOKE_RUN_ID: shared\n"
);
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ workflow }));
assert.match(result.failures.join("\n"), /must set DOCKER_SMOKE_RUN_ID from gitea.run_id and gitea.run_attempt/);
});
it("fails when Docker smoke renders resolved Compose credentials", () => {
const suite = ISOLATED_DOCKER_SUITE.replace(
"docker compose --env-file .env.test config --quiet",
"docker compose config"
);
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /Compose validation must use \.env\.test and --quiet/);
});
it("fails when a global Docker smoke image tag is reintroduced", () => {
const suite = ISOLATED_DOCKER_SUITE.replace('WEB_IMAGE="hivegrid-web:smoke-${DOCKER_SMOKE_RUN_ID}"', 'WEB_IMAGE="hivegrid-web:smoke"');
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
const failures = result.failures.join("\n");
assert.match(failures, /web image tag must include DOCKER_SMOKE_RUN_ID/);
assert.match(failures, /global :smoke image tags are forbidden/);
});
it("fails when artifact validation loses its run-scoped one-shot name", () => {
const suite = ISOLATED_DOCKER_SUITE.replace('docker run --rm --name "$WEB_ARTIFACT_CONTAINER_NAME"', "docker run --rm");
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /web artifact validation must use a run-scoped named container/);
});
it("fails when a fixed fake Docker revision is reintroduced", () => {
const suite = `${ISOLATED_DOCKER_SUITE}\n# 0123456789abcdef0123456789abcdef01234567\n`;
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /fixed fake image revisions are forbidden/);
});
it("fails when the runtime wave starts before the build barrier", () => {
const suite = ISOLATED_DOCKER_SUITE.replace(
" run_parallel_wave builds \\\n build_web build_worker build_api build_mcp build_hocuspocus build_portal_controller\n",
" run_parallel_wave runtimes \\\n validate_web validate_worker validate_api validate_mcp validate_hocuspocus validate_portal_controller\n"
);
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /must complete the six-target build wave before the six-target runtime wave/);
});
it("fails when a runtime phase loses its command timeout", () => {
const suite = ISOLATED_DOCKER_SUITE.replace(
' docker_smoke_run_with_grace "runtime:worker" "$DOCKER_SMOKE_SERVICE_RUNTIME_TIMEOUT_SECONDS" "$DOCKER_SMOKE_RUNTIME_GRACE_SECONDS" \\\n',
""
);
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /missing timeout stages: docker_smoke_run_with_grace "runtime:worker"/);
});
it("fails when parallel-wave child results are not awaited", () => {
const suite = ISOLATED_DOCKER_SUITE.replace(' if wait "${pids[$index]}"; then\n', " if true; then\n");
const result = verifyGiteaCiWorkflow(createIsolatedDockerFixture({ suite }));
assert.match(result.failures.join("\n"), /parallel waves must track, await, and propagate every child result/);
});
it("CLI exits non-zero and prints path-specific output for an invalid workflow", () => {
const rootDir = createFixture(null);
const scriptPath = path.resolve("scripts/verify-gitea-ci-workflow.mjs");
assert.throws(
() => execFileSync(process.execPath, [scriptPath], { cwd: rootDir, encoding: "utf8", stdio: "pipe" }),
(error) => {
assert.equal(error.status, 1);
assert.match(error.stderr, /^FAIL Gitea CI workflow verification/);
assert.match(error.stderr, /FAIL \.gitea\/workflows\/ci\.yml: missing required Gitea CI workflow file/);
return true;
}
);
});
});