Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
147 lines
5.1 KiB
JavaScript
147 lines
5.1 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 fs = require('fs');
|
|
const path = require('path');
|
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
|
const { runNode } = require('./helpers/process-seam.cjs');
|
|
const { toLegacyResult } = require('./helpers/git-fixture.cjs');
|
|
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.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 result = runNode([SCRIPT, ...args], {
|
|
cwd: ROOT,
|
|
timeoutMs: PROBE_TIMEOUT_MS,
|
|
env: {
|
|
...process.env,
|
|
MSD_LINT_REGRESSION_TESTS_DIR: testsDir,
|
|
MSD_LINT_REGRESSION_ALLOWLIST: allowlistPath,
|
|
},
|
|
});
|
|
// Composes toLegacyResult with the site-specific allowlistPath rather than
|
|
// folding it into the shared helper — see git-fixture.cjs's toLegacyResult
|
|
// JSDoc.
|
|
return { ...toLegacyResult(result), allowlistPath };
|
|
}
|
|
|
|
describe('lint-regression-test-names', () => {
|
|
before(() => {
|
|
sandbox = createTempDir('msd-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 = runNode([SCRIPT], { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS });
|
|
assert.strictEqual(r.exitCode, 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/);
|
|
});
|
|
});
|