// tests/lib/test-integrity.test.mjs // The floor guard (P9 (2)): a diff that weakens the tests is a finding, never a // pass. Exit 0 = clean, 1 = findings, 2 = could not run (never green). // Borrowed: agent-skills "floor guard" (MIT) - see lib/verification/test-integrity.mjs. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { analyzeDiff, isTestPath } from '../../lib/verification/test-integrity.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const CLI = join(ROOT, 'lib', 'verification', 'test-integrity.mjs'); const FIX = join(ROOT, 'tests', 'fixtures', 'test-integrity'); const fixture = (name) => readFileSync(join(FIX, name), 'utf8'); function cli(args, opts = {}) { const r = spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', ...opts }); return { status: r.status, stdout: r.stdout, stderr: r.stderr }; } const kinds = (r) => r.findings.map((f) => f.kind); test('floor guard: a clean diff (fix + a new test) is clean -> exit 0', () => { const r = analyzeDiff(fixture('clean.diff')); assert.equal(r.status, 'clean', JSON.stringify(r.findings)); assert.deepEqual(r.findings, []); assert.equal(cli(['--diff', join(FIX, 'clean.diff')]).status, 0); }); test('floor guard: a diff that removes an assert -> finding removed-assert, exit 1', () => { const r = analyzeDiff(fixture('removed-assert.diff')); assert.equal(r.status, 'findings'); assert.deepEqual(kinds(r), ['removed-assert']); assert.equal(r.findings[0].file, 'tests/sum.test.mjs'); assert.equal(cli(['--diff', join(FIX, 'removed-assert.diff')]).status, 1); }); test('floor guard: a diff that skips a test -> finding skipped-test, exit 1', () => { const r = analyzeDiff(fixture('skipped-test.diff')); assert.deepEqual(kinds(r), ['skipped-test']); assert.equal(cli(['--diff', join(FIX, 'skipped-test.diff')]).status, 1); }); test('floor guard: a deleted test, a muted check, a stub and a loosened threshold are each found', () => { assert.deepEqual(kinds(analyzeDiff(fixture('deleted-test.diff'))), ['deleted-test']); assert.deepEqual(kinds(analyzeDiff(fixture('muted-check.diff'))), ['muted-check']); assert.deepEqual(kinds(analyzeDiff(fixture('stub.diff'))), ['stub']); assert.deepEqual(kinds(analyzeDiff(fixture('loosened-threshold.diff'))), ['loosened-threshold']); }); test('floor guard: a TIGHTENED threshold is not a finding (direction is read, not just change)', () => { assert.deepEqual(analyzeDiff(fixture('tightened-threshold.diff')).findings, []); }); test('floor guard: an unreadable diff -> status unreadable, exit 2 (never green)', () => { assert.equal(analyzeDiff(fixture('unreadable.diff')).status, 'unreadable'); const r = cli(['--diff', join(FIX, 'unreadable.diff')]); assert.equal(r.status, 2, r.stdout + r.stderr); assert.equal(cli(['--diff', join(FIX, 'does-not-exist.diff')]).status, 2); assert.equal(cli([]).status, 2, 'no input is "could not run", not clean'); }); test('floor guard: hunk line counts decide what is content - a removed "-- x" line is not a file header', () => { const diff = [ 'diff --git a/tests/a.test.mjs b/tests/a.test.mjs', '--- a/tests/a.test.mjs', '+++ b/tests/a.test.mjs', '@@ -1,4 +1,3 @@', " test('a', () => {", '--- an old comment', '+++ counter', '- assert.equal(1, 1);', ' });', '\\ No newline at end of file', ].join('\n'); const r = analyzeDiff(diff); assert.deepEqual(r.findings.map((f) => [f.kind, f.file]), [['removed-assert', 'tests/a.test.mjs']]); }); test('floor guard: skipIf and node\'s lowercase t.skip() / t.todo() are skips too', () => { const diff = (line) => [ 'diff --git a/tests/a.test.mjs b/tests/a.test.mjs', '--- a/tests/a.test.mjs', '+++ b/tests/a.test.mjs', '@@ -1,1 +1,2 @@', " test('a', (t) => {", `+${line}`, ].join('\n'); for (const l of [" it.skipIf(process.env.CI)('x', () => {});", ' t.skip();', " t.todo('later');", "test('b', { todo: true }, () => {});"]) { assert.deepEqual(analyzeDiff(diff(l)).findings.map((f) => f.kind), ['skipped-test'], l); } }); test('floor guard: renaming a test out of the runner\'s pattern is a deleted test (--base never pairs renames)', () => { const dir = mkdtempSync(join(tmpdir(), 'floor-guard-rename-')); const git = (...a) => { const r = spawnSync('git', a, { cwd: dir, encoding: 'utf8' }); assert.equal(r.status, 0, `git ${a.join(' ')}: ${r.stderr}`); return r.stdout.trim(); }; try { git('init', '-q'); git('config', 'user.email', 't@example.invalid'); git('config', 'user.name', 'test'); writeFileSync(join(dir, 'foo.test.js'), "test('a', () => {\n assert.equal(1, 1);\n});\n"); git('add', 'foo.test.js'); git('commit', '-q', '-m', 'base'); const base = git('rev-parse', 'HEAD'); git('mv', 'foo.test.js', 'foo.test.js.bak'); const r = cli(['--base', base, '--cwd', dir, '--json']); assert.equal(r.status, 1, r.stdout + r.stderr); assert.ok(JSON.parse(r.stdout).findings.some((f) => f.kind === 'deleted-test'), r.stdout); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('floor guard: prose files are not code - "not implemented" in a .md is not a stub', () => { const diff = [ 'diff --git a/docs/guide.md b/docs/guide.md', '--- a/docs/guide.md', '+++ b/docs/guide.md', '@@ -1,1 +1,2 @@', ' # Guide', "+Never write `throw new Error('not implemented')` or an eslint-disable as a fix.", ].join('\n'); assert.deepEqual(analyzeDiff(diff).findings, []); }); test('floor guard: an empty diff is clean (nothing changed weakens nothing)', () => { assert.equal(analyzeDiff('').status, 'clean'); }); test('floor guard: --json prints the findings with file and line', () => { const r = cli(['--diff', join(FIX, 'removed-assert.diff'), '--json']); const out = JSON.parse(r.stdout); assert.equal(out.status, 'findings'); assert.equal(out.findings[0].kind, 'removed-assert'); assert.equal(out.findings[0].file, 'tests/sum.test.mjs'); assert.equal(typeof out.findings[0].line, 'number'); }); test('floor guard: --base reads the diff from git in --cwd', () => { const dir = mkdtempSync(join(tmpdir(), 'floor-guard-')); const git = (...a) => { const r = spawnSync('git', a, { cwd: dir, encoding: 'utf8' }); assert.equal(r.status, 0, `git ${a.join(' ')}: ${r.stderr}`); return r.stdout.trim(); }; try { git('init', '-q'); git('config', 'user.email', 't@example.invalid'); git('config', 'user.name', 'test'); writeFileSync(join(dir, 'a.test.mjs'), "test('a', () => {\n assert.equal(1, 1);\n assert.equal(2, 2);\n});\n"); git('add', 'a.test.mjs'); git('commit', '-q', '-m', 'base'); const base = git('rev-parse', 'HEAD'); writeFileSync(join(dir, 'a.test.mjs'), "test('a', () => {\n assert.equal(1, 1);\n});\n"); const r = cli(['--base', base, '--cwd', dir, '--json']); assert.equal(r.status, 1, r.stdout + r.stderr); assert.deepEqual(JSON.parse(r.stdout).findings.map((f) => f.kind), ['removed-assert']); assert.equal(cli(['--base', 'no-such-ref', '--cwd', dir]).status, 2, 'an unresolvable base is "could not run"'); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('floor guard: test-path recognition covers the common layouts', () => { for (const p of ['tests/a.mjs', 'test/a.py', 'src/__tests__/a.ts', 'a.test.js', 'pkg/a_test.go', 'app/test_a.py', 'spec/a_spec.rb', 'a.spec.ts']) { assert.ok(isTestPath(p), p); } for (const p of ['lib/a.mjs', 'src/contest.ts', 'README.md']) assert.ok(!isTestPath(p), p); });