test(kb-update): gate tracked text against a local-only excluded-terms list
The gate reads `tests/.excluded-terms.local.json` (gitignored via `*.local.json`): a pattern plus known positives and negatives. With the list present it fails on any tracked text line that matches; without it both tests are skipped with a message, so a fresh clone stays green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
544934dc57
commit
f774e2b158
1 changed files with 65 additions and 0 deletions
65
tests/kb-update/test-excluded-terms.test.mjs
Normal file
65
tests/kb-update/test-excluded-terms.test.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// tests/kb-update/test-excluded-terms.test.mjs
|
||||
// Preventive gate: no tracked text file may contain a term from the
|
||||
// maintainer's excluded-terms list. Examples, fixtures and placeholders use
|
||||
// generic, fictitious material instead.
|
||||
//
|
||||
// The list lives in `tests/.excluded-terms.local.json` (gitignored via
|
||||
// `*.local.json`) and holds `pattern`, `flags`, `positives` and `negatives`.
|
||||
// Without that file the gate is skipped with a message, so a fresh clone runs
|
||||
// green; with it, the known positives prove the pattern can find before the
|
||||
// empty result over the tree is trusted.
|
||||
//
|
||||
// Population: `git ls-files` (tracked files only). Binary files (NUL byte in
|
||||
// the first 8000 bytes, as git decides) are skipped: text in a screenshot is
|
||||
// rendered pixels, so a byte match there says nothing about what it shows.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const pluginRoot = fileURLToPath(new URL('../../', import.meta.url));
|
||||
const listPath = fileURLToPath(new URL('../.excluded-terms.local.json', import.meta.url));
|
||||
|
||||
const skip = existsSync(listPath)
|
||||
? false
|
||||
: 'tests/.excluded-terms.local.json not present (local-only list) — gate skipped';
|
||||
|
||||
function loadList() {
|
||||
const list = JSON.parse(readFileSync(listPath, 'utf8'));
|
||||
return { ...list, regex: new RegExp(list.pattern, list.flags ?? '') };
|
||||
}
|
||||
|
||||
function trackedFiles() {
|
||||
return execFileSync('git', ['ls-files', '-z'], { cwd: pluginRoot, encoding: 'utf8' })
|
||||
.split('\0')
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function isBinary(buf) {
|
||||
return buf.subarray(0, 8000).includes(0);
|
||||
}
|
||||
|
||||
test('pattern matches every known positive and no known negative', { skip }, () => {
|
||||
const { regex, positives = [], negatives = [] } = loadList();
|
||||
assert.ok(positives.length > 0, 'the list must carry known positives');
|
||||
for (const s of positives) assert.match(s, regex, s);
|
||||
for (const s of negatives) assert.doesNotMatch(s, regex, s);
|
||||
});
|
||||
|
||||
test('no tracked text file contains an excluded term', { skip }, () => {
|
||||
const { regex } = loadList();
|
||||
const files = trackedFiles();
|
||||
assert.ok(files.length > 500, `git ls-files returned ${files.length} files — population too small to trust`);
|
||||
const hits = [];
|
||||
for (const rel of files) {
|
||||
let buf;
|
||||
try { buf = readFileSync(pluginRoot + rel); } catch { continue; }
|
||||
if (isBinary(buf)) continue;
|
||||
buf.toString('utf8').split('\n').forEach((line, i) => {
|
||||
if (regex.test(line)) hits.push(`${rel}:${i + 1}`);
|
||||
});
|
||||
}
|
||||
assert.deepEqual(hits, [], `excluded terms in tracked files:\n ${hits.join('\n ')}`);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue