const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const projectRoot = path.resolve(__dirname, ".."); function read(relativePath) { return fs.readFileSync(path.join(projectRoot, relativePath), "utf8"); } test("legacy Cloud Run deployment workflow has been removed", () => { const legacyWorkflow = path.join( projectRoot, ".github", "workflows", "cd.yml", ); assert.equal(fs.existsSync(legacyWorkflow), false); }); test("repository automation files exist for CI, container publish, and Dependabot", () => { const expectedFiles = [ "eslint.config.cjs", ".github/workflows/ci.yml", ".github/workflows/codeql.yml", ".github/workflows/image.yml", ".github/workflows/dependabot-auto-merge.yml", ".github/dependabot.yml", ]; for (const relativePath of expectedFiles) { assert.equal( fs.existsSync(path.join(projectRoot, relativePath)), true, `${relativePath} should exist`, ); } }); test("container image workflow publishes to GHCR instead of legacy GCP registries", () => { const workflow = read(".github/workflows/image.yml"); assert.match(workflow, /ghcr\.io/); assert.doesNotMatch(workflow, /gcloud|us-docker\.pkg\.dev/); }); test("container image publishing waits for CI instead of running on pull requests directly", () => { const workflow = read(".github/workflows/image.yml"); assert.match(workflow, /push:\s*\n\s*branches:\s*\n\s*- main/); assert.doesNotMatch(workflow, /pull_request:/); assert.doesNotMatch(workflow, /workflow_run:/); }); test("workflows opt into Node 24 for JavaScript-based GitHub Actions", () => { const workflows = [ read(".github/workflows/ci.yml"), read(".github/workflows/codeql.yml"), read(".github/workflows/image.yml"), read(".github/workflows/dependabot-auto-merge.yml"), ]; for (const workflow of workflows) { assert.match(workflow, /FORCE_JAVASCRIPT_ACTIONS_TO_NODE24:\s*true/); } }); test("CodeQL workflow uses advanced setup with repository-managed configuration", () => { const workflow = read(".github/workflows/codeql.yml"); assert.match(workflow, /uses:\s*actions\/checkout@v\d+/); assert.match(workflow, /uses:\s*github\/codeql-action\/init@v4/); assert.match(workflow, /uses:\s*github\/codeql-action\/analyze@v4/); assert.match(workflow, /language:\s*actions/); assert.match(workflow, /language:\s*javascript-typescript/); }); test("dependabot config covers npm, docker, and github-actions updates", () => { const dependabot = read(".github/dependabot.yml"); assert.match(dependabot, /package-ecosystem: npm/); assert.match(dependabot, /package-ecosystem: docker/); assert.match(dependabot, /package-ecosystem: github-actions/); }); test("dependabot groups include major upgrades instead of suppressing them", () => { const dependabot = read(".github/dependabot.yml"); assert.match(dependabot, /production-dependencies:[\s\S]*-\s+major/); assert.match(dependabot, /development-dependencies:[\s\S]*-\s+major/); assert.match(dependabot, /dependency-name:\s*typescript/); assert.match(dependabot, /dependency-name:\s*"@koa\/bodyparser"/); assert.match(dependabot, /dependency-name:\s*koa/); assert.match(dependabot, /dependency-name:\s*set-cookie-parser/); assert.doesNotMatch(dependabot, /dependency-name:\s*node/); }); test("CI workflow runs lint before tests and build", () => { const workflow = read(".github/workflows/ci.yml"); assert.match(workflow, /run: npm run lint/); assert.match(workflow, /run: npm run test:ci/); assert.match(workflow, /run: npm run build/); }); test("npm-based workflows use the same Node runtime as the container image", () => { const workflows = [ read(".github/workflows/ci.yml"), read(".github/workflows/security-governance.yml"), ]; for (const workflow of workflows) { assert.match(workflow, /node-version:\s*26/); assert.doesNotMatch(workflow, /node-version:\s*22/); } }); test("CI validates Docker builds for pull requests without publishing images", () => { const workflow = read(".github/workflows/ci.yml"); assert.match(workflow, /name: Validate container build/); assert.match(workflow, /if: github\.event_name == 'pull_request'/); assert.match(workflow, /push: false/); }); test("dependabot auto-merge workflow updates stale branches before enabling auto-merge", () => { const workflow = read(".github/workflows/dependabot-auto-merge.yml"); assert.match(workflow, /gh pr update-branch/); assert.match(workflow, /mergeStateStatus/); assert.match(workflow, /workflow_run:/); assert.match(workflow, /workflows:\s*\n\s*- CI/); assert.match(workflow, /gh pr list/); }); test("Dockerfile is self-contained and no longer relies on curl-impersonate", () => { const dockerfile = read("Dockerfile"); assert.match(dockerfile, /FROM node:26-bookworm-slim/); assert.match(dockerfile, /PUPPETEER_SKIP_DOWNLOAD=true/); assert.match(dockerfile, /LOCAL_DB_ROOT=\/tmp\/xread\/db/); assert.match(dockerfile, /STORAGE_ROOT=\/tmp\/xread\/storage/); assert.doesNotMatch(dockerfile, /chown -R xread:xread \/app/); assert.doesNotMatch(dockerfile, /curl-impersonate|LD_PRELOAD/); });