import { readFileSync, existsSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { join } from 'node:path'; import test from 'node:test'; import assert from 'node:assert/strict'; const repoRoot = fileURLToPath(new URL('..', import.meta.url)); const read = (...parts) => readFileSync(join(repoRoot, ...parts), 'utf8'); // The suite went unrun for the whole life of the upstream project: nothing // invoked `node --test`, so every assertion in tests/ was decoration. These // checks are about the machinery that runs the tests, not the product. test('the workflow exists in both places Gitea might scan', () => { // Which location a Gitea instance picks up depends on its config, and we // cannot verify the server's config from here. for (const path of [['.gitea', 'workflows', 'tests.yml'], ['.github', 'workflows', 'tests.yml']]) { assert.ok(existsSync(join(repoRoot, ...path)), `${path.join('/')} is missing`); } }); test('both copies of the workflow run the same thing', () => { const steps = (yaml) => [...yaml.matchAll(/^\s+run:\s*(.+)$/gm)].map((m) => m[1].trim()); const gitea = steps(read('.gitea', 'workflows', 'tests.yml')); const github = steps(read('.github', 'workflows', 'tests.yml')); assert.deepEqual(gitea, github, 'the two workflow copies have drifted apart'); // Bare `node --test`, not `node --test tests/` — see the assertion below. assert.ok(gitea.some((s) => /node --test\s*$/.test(s)), 'neither copy actually runs the tests'); assert.ok(gitea.some((s) => s.includes('--check')), 'neither copy checks the generated files'); }); test('workflows target the self-hosted runner label', () => { // Gitea act_runner on our instance registers as internal_docker. ubuntu-latest // queues forever because no runner claims it. const workflowPaths = [ ['.gitea', 'workflows', 'tests.yml'], ['.github', 'workflows', 'tests.yml'], ['.github', 'workflows', 'release.yml'], ['.github', 'workflows', 'track-upstream.yml'], ]; for (const path of workflowPaths) { const yaml = read(...path); assert.match(yaml, /runs-on:\s*internal_docker/, `${path.join('/')} should use internal_docker`); assert.doesNotMatch(yaml, /runs-on:\s*ubuntu-latest/, `${path.join('/')} still asks for ubuntu-latest`); } }); test('the workflow does not depend on a runner fetching setup actions', () => { // On a self-hosted Gitea, setup-node has to be pulled from GitHub. A node // container removes one thing that can be missing on a fresh runner. const yaml = read('.gitea', 'workflows', 'tests.yml'); // Comments discuss setup-node on purpose; only the steps matter. const steps = yaml.split(/\r?\n/).filter((line) => !line.trim().startsWith('#')).join('\n'); assert.doesNotMatch(steps, /setup-node/, 'prefer a node container over setup-node here'); assert.match(steps, /image:\s*node:/, 'the job should run in a node container'); // The container tag has to match what we pin, or CI tests a different runtime // than the product ships. const pinned = read('NODE_VERSION').trim().replace(/^v/, ''); assert.match( steps, new RegExp(`image:\\s*node:${pinned.replace(/\./g, '\\.')}`), `the container should be node:${pinned} to match NODE_VERSION`, ); }); test('a pre-push hook backs CI up, and says how to skip it', () => { const hook = read('.githooks', 'pre-push'); assert.match(hook, /node --test tests\//, 'the hook should run the suite'); assert.match(hook, /build-messages\.mjs --check/, 'the hook should catch stale generated files'); // A hook that cannot be bypassed gets deleted by the first person it blocks // at a bad moment. assert.match(hook, /--no-verify/, 'the hook should document how to skip it'); // It must not pretend to have passed when node is unavailable. assert.match(hook, /skipping tests/, 'the hook should say so when it cannot run'); }); test('the hook is documented where someone cloning will see it', () => { // A hook nobody installs protects nobody. core.hooksPath is per-clone. const claude = read('CLAUDE.md'); assert.match(claude, /core\.hooksPath/, 'CLAUDE.md should say how to enable the hook'); }); test('nothing invokes the form of node --test that does not work', () => { // `node --test tests/` makes Node load the path as a module: "Cannot find // module .../tests", exit 1, zero tests run. It was the documented command in // CLAUDE.md for the life of the project — which is a good part of why the // suite went unrun. Bare `node --test` discovers **/*.test.mjs correctly. const offenders = []; for (const path of [ ['.githooks', 'pre-push'], ['.gitea', 'workflows', 'tests.yml'], ['.github', 'workflows', 'tests.yml'], ['CLAUDE.md'], ]) { read(...path).split(/\r?\n/).forEach((line, i) => { // A bare directory argument. Naming a specific .mjs file is fine. if (/node --test\s+tests\/\s*($|[>|&])/.test(line)) { offenders.push(`${path.join('/')}:${i + 1}`); } }); } assert.deepEqual(offenders, [], `these run a form that executes nothing:\n${offenders.join('\n')}`); });