143 lines
4.8 KiB
JavaScript
143 lines
4.8 KiB
JavaScript
'use strict';
|
|
|
|
// Tests for scripts/lint-regression-test-names.cjs — the identity ratchet
|
|
// that bans NEW top-level bug-NNNN test files (2026-06 CI audit). Uses the
|
|
// script's env overrides to point at sandbox fixture dirs; never touches the
|
|
// real tests/ directory or allowlist.
|
|
|
|
const { describe, test, before, after } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { spawnSync } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const SCRIPT = path.join(ROOT, 'scripts', 'lint-regression-test-names.cjs');
|
|
|
|
let sandbox;
|
|
|
|
let fixtureCount = 0;
|
|
|
|
function runLint({ files, allowlist, args = [] }) {
|
|
const testsDir = path.join(sandbox, `tests-${fixtureCount++}`);
|
|
fs.mkdirSync(testsDir, { recursive: true });
|
|
for (const f of files) fs.writeFileSync(path.join(testsDir, f), '');
|
|
const allowlistPath = path.join(testsDir, 'allowlist.json');
|
|
fs.writeFileSync(allowlistPath, JSON.stringify(allowlist));
|
|
const r = spawnSync(process.execPath, [SCRIPT, ...args], {
|
|
cwd: ROOT,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
GSD_LINT_REGRESSION_TESTS_DIR: testsDir,
|
|
GSD_LINT_REGRESSION_ALLOWLIST: allowlistPath,
|
|
},
|
|
});
|
|
r.allowlistPath = allowlistPath;
|
|
return r;
|
|
}
|
|
|
|
describe('lint-regression-test-names', () => {
|
|
before(() => {
|
|
sandbox = createTempDir('gsd-lint-regression-');
|
|
});
|
|
|
|
after(() => {
|
|
cleanup(sandbox);
|
|
});
|
|
|
|
test('passes when every bug-* file is grandfathered', () => {
|
|
const r = runLint({
|
|
files: ['bug-100-old.test.cjs', 'module.test.cjs'],
|
|
allowlist: ['bug-100-old.test.cjs'],
|
|
});
|
|
assert.strictEqual(r.status, 0, `stderr: ${r.stderr}`);
|
|
});
|
|
|
|
test('fails on a novel bug-* file with fold-into-module guidance', () => {
|
|
const r = runLint({
|
|
files: ['bug-100-old.test.cjs', 'bug-200-new.test.cjs'],
|
|
allowlist: ['bug-100-old.test.cjs'],
|
|
});
|
|
assert.notStrictEqual(r.status, 0);
|
|
assert.match(r.stderr, /bug-200-new\.test\.cjs/);
|
|
assert.match(r.stderr, /owning module/);
|
|
});
|
|
|
|
test('fails on a stale allowlist entry (ratchet-down enforcement)', () => {
|
|
const r = runLint({
|
|
files: ['bug-100-old.test.cjs'],
|
|
allowlist: ['bug-100-old.test.cjs', 'bug-300-gone.test.cjs'],
|
|
});
|
|
assert.notStrictEqual(r.status, 0);
|
|
assert.match(r.stderr, /bug-300-gone\.test\.cjs/);
|
|
});
|
|
|
|
test('ignores non-bug test files and suite-marked non-bug names', () => {
|
|
const r = runLint({
|
|
files: ['module.test.cjs', 'feature.integration.test.cjs', 'debug-1-not-a-bug.test.cjs'],
|
|
allowlist: [],
|
|
});
|
|
assert.strictEqual(r.status, 0, `stderr: ${r.stderr}`);
|
|
});
|
|
|
|
test('catches a suite-marked bug-* file too (no marker escape hatch)', () => {
|
|
const r = runLint({
|
|
files: ['bug-400-sneaky.security.test.cjs'],
|
|
allowlist: [],
|
|
});
|
|
assert.notStrictEqual(r.status, 0);
|
|
assert.match(r.stderr, /bug-400-sneaky\.security\.test\.cjs/);
|
|
});
|
|
|
|
test('repo baseline passes (real tests/ dir against real allowlist)', () => {
|
|
const r = spawnSync(process.execPath, [SCRIPT], { cwd: ROOT, encoding: 'utf8' });
|
|
assert.strictEqual(r.status, 0, `stderr: ${r.stderr}\nstdout: ${r.stdout}`);
|
|
});
|
|
|
|
test('novel-offender failure names the --update drift-repair path', () => {
|
|
const r = runLint({
|
|
files: ['bug-500-inherited.test.cjs'],
|
|
allowlist: [],
|
|
});
|
|
assert.notStrictEqual(r.status, 0);
|
|
assert.match(r.stderr, /--update/);
|
|
});
|
|
|
|
test('--update regenerates the allowlist from the tests dir (grandfather + prune)', () => {
|
|
const r = runLint({
|
|
files: ['bug-100-kept.test.cjs', 'bug-200-new.test.cjs'],
|
|
allowlist: ['bug-100-kept.test.cjs', 'bug-300-gone.test.cjs'],
|
|
args: ['--update'],
|
|
});
|
|
assert.strictEqual(r.status, 0, `stderr: ${r.stderr}`);
|
|
assert.match(r.stdout, /grandfathered: bug-200-new\.test\.cjs/);
|
|
assert.match(r.stdout, /pruned: bug-300-gone\.test\.cjs/);
|
|
assert.deepStrictEqual(
|
|
JSON.parse(fs.readFileSync(r.allowlistPath, 'utf8')),
|
|
['bug-100-kept.test.cjs', 'bug-200-new.test.cjs'],
|
|
);
|
|
});
|
|
|
|
test('--update is a no-op when already in sync', () => {
|
|
const r = runLint({
|
|
files: ['bug-100-kept.test.cjs'],
|
|
allowlist: ['bug-100-kept.test.cjs'],
|
|
args: ['--update'],
|
|
});
|
|
assert.strictEqual(r.status, 0, `stderr: ${r.stderr}`);
|
|
assert.match(r.stdout, /already in sync/);
|
|
assert.deepStrictEqual(
|
|
JSON.parse(fs.readFileSync(r.allowlistPath, 'utf8')),
|
|
['bug-100-kept.test.cjs'],
|
|
);
|
|
});
|
|
|
|
test('unknown arguments are rejected', () => {
|
|
const r = runLint({ files: [], allowlist: [], args: ['--frobnicate'] });
|
|
assert.strictEqual(r.status, 2);
|
|
assert.match(r.stderr, /unknown argument/);
|
|
});
|
|
});
|