// tests/lib/retired-references.test.mjs // No tracked text file, and no tracked path, may contain a term from the // local-only list in tests/.excluded-terms.local.txt (dead references to // retired repositories). The list is gitignored on purpose: the terms never // live in tracked text. Without the file the scan is skipped, loudly. // // When this test fails, rewrite the hit neutrally (describe the pattern, not // the repository); do NOT widen EXEMPT to hide it. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { execFileSync } from 'node:child_process'; import { existsSync, mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const TERMS_FILE = join(ROOT, 'tests', '.excluded-terms.local.txt'); const MISSING = `${TERMS_FILE} not found; retired-reference scan not run`; // Named exceptions: tracked path -> reason. Empty on purpose; an entry must // still hit (a stale exemption fails below). const EXEMPT = new Map([]); function loadTerms() { return readFileSync(TERMS_FILE, 'utf-8').split('\n') .map(l => l.trim().toLowerCase()) .filter(l => l && !l.startsWith('#')); } function trackedFiles(root) { const out = execFileSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf-8' }); return out.split('\0').filter(Boolean); } // Returns one "path:line" (or "path: ") entry per hit. The matched // text is not echoed, so a failure message never repeats a term. function scan(root, files, terms) { const hits = []; const hit = s => { const l = s.toLowerCase(); return terms.some(t => l.includes(t)); }; for (const rel of files) { if (hit(rel)) hits.push(`${rel}: `); const buf = readFileSync(join(root, rel)); if (buf.includes(0)) continue; // binary buf.toString('utf-8').split('\n').forEach((line, i) => { if (hit(line)) hits.push(`${rel}:${i + 1}`); }); } return hits; } test('scan finds a planted term in content and in a path name (known-positive)', (t) => { if (!existsSync(TERMS_FILE)) return t.skip(MISSING); const terms = loadTerms(); assert.ok(terms.length > 0, `${TERMS_FILE} holds no terms`); const dir = mkdtempSync(join(tmpdir(), 'retired-refs-')); try { const named = `${terms[0]}-notes.txt`; writeFileSync(join(dir, 'clean.md'), 'nothing to see\n'); writeFileSync(join(dir, 'content.md'), `line one\nsee ${terms[0].toUpperCase()} here\n`); writeFileSync(join(dir, named), 'clean body\n'); const hits = scan(dir, ['clean.md', 'content.md', named], terms); assert.deepEqual(hits, ['content.md:2', `${named}: `]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test('the term list itself is not tracked', () => { const tracked = trackedFiles(ROOT); assert.ok(!tracked.includes('tests/.excluded-terms.local.txt')); }); test('no tracked file or path contains a retired term', (t) => { if (!existsSync(TERMS_FILE)) return t.skip(MISSING); const files = trackedFiles(ROOT); assert.ok(files.length > 100, `git ls-files returned ${files.length} files; refusing to pass on an empty scan`); const hits = scan(ROOT, files.filter(f => !EXEMPT.has(f)), loadTerms()); assert.deepEqual(hits, [], `${hits.length} retired reference(s):\n${hits.join('\n')}`); }); test('every EXEMPT entry is tracked and still hits', (t) => { if (!existsSync(TERMS_FILE)) return t.skip(MISSING); const files = new Set(trackedFiles(ROOT)); for (const rel of EXEMPT.keys()) { assert.ok(files.has(rel), `EXEMPT names untracked ${rel}`); assert.ok(scan(ROOT, [rel], loadTerms()).length > 0, `EXEMPT entry ${rel} no longer hits; remove it`); } });