test(suites): seed security/slow/integration suites via measured retags

Renames (git mv) with all references updated (ci-test-scope RULES,
windows-parity allowlist, test-file-count allowlist, docs in 6 locales):

- 5 scanner tests -> *.security.test.cjs — the 'Run security tests' CI step
  ran zero files since the suite taxonomy landed; it is now honest.
- graphify-auto-update -> *.slow.test.cjs (36s, slowest file in the suite;
  e2e gsd-tools spawns) — runs on full-matrix lanes and push to next.
- installer-migration-install-integration -> *.integration.test.cjs
  (13s; an integration test by its own name).

Coverage gate measured after retags: 88.55% lines (gate 70%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colin
2026-06-09 23:01:00 -04:00
parent a647053dcf
commit cd5db1f8db
22 changed files with 161 additions and 30 deletions

View File

@@ -1,7 +1,7 @@
# Adversarial security fixtures (#3596)
Reusable hostile payloads consumed by
`tests/security-prompt-injection.test.cjs`.
`tests/security-prompt-injection.security.test.cjs`.
The fixtures here are pure data — they are loaded by the test as input
to the production code under test (hooks, validators, sanitizers, CLI).

View File

@@ -0,0 +1,95 @@
'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 os = require('node:os');
const path = require('path');
const { cleanup } = require('./helpers.cjs');
const ROOT = path.join(__dirname, '..');
const SCRIPT = path.join(ROOT, 'scripts', 'lint-regression-test-names.cjs');
let sandbox;
function runLint({ files, allowlist }) {
const testsDir = path.join(sandbox, `tests-${Math.random().toString(36).slice(2)}`);
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));
return spawnSync(process.execPath, [SCRIPT], {
cwd: ROOT,
encoding: 'utf8',
env: {
...process.env,
GSD_LINT_REGRESSION_TESTS_DIR: testsDir,
GSD_LINT_REGRESSION_ALLOWLIST: allowlistPath,
},
});
}
describe('lint-regression-test-names', () => {
before(() => {
sandbox = fs.mkdtempSync(path.join(os.tmpdir(), '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}`);
});
});

View File

@@ -12,7 +12,7 @@
* deeper than 50, which is intentional.
*
* Note: security-scan.yml legitimately uses fetch-depth: 0 and is NOT covered
* by this test (see tests/security-scan.test.cjs).
* by this test (see tests/security-scan.security.test.cjs).
*/
const { describe, test } = require('node:test');

View File

@@ -58,7 +58,7 @@ const ALLOWLIST = new Set([
'hooks/gsd-prompt-guard.js', // The prompt guard hook
'hooks/gsd-read-injection-scanner.js', // The read injection scanner (contains patterns)
'tests/security.test.cjs', // Security tests
'tests/prompt-injection-scan.test.cjs', // This file
'tests/prompt-injection-scan.security.test.cjs', // This file
]);
// Workflows that exceed the 50K strict-mode size threshold due to legitimate

View File

@@ -49,12 +49,12 @@ const SELF = path.basename(__filename);
const KNOWN_OFFENDERS = Object.freeze({
splitNewlineOnFileContent: new Set([
'release-coverage-scope.test.cjs',
'secret-scan-lint.test.cjs',
'security-scan.test.cjs',
'secret-scan-lint.security.test.cjs',
'security-scan.security.test.cjs',
]),
fenceRegexLiteralNewline: new Set([
'bug-2995-post-install-script-paths.test.cjs',
'security-scan.test.cjs',
'security-scan.security.test.cjs',
]),
frontmatterAnchorLiteralNewline: new Set([
'bug-1967-cache-invalidation.test.cjs',