229 lines
9.4 KiB
JavaScript
229 lines
9.4 KiB
JavaScript
/**
|
|
* Tests for the `.planning/`-root artifact writer-registry completeness
|
|
* guard (epic #3180, ADR-3180 §8.4 deliverable C, Phase 12 #3310) —
|
|
* `scripts/lint-planning-artifact-writer-drift.cjs`.
|
|
*
|
|
* Unlike its `lint-*-drift.cjs` siblings, this guard is a bare pass/fail
|
|
* completeness check (no ratchet baseline) — see the guard's own module
|
|
* docblock. Tests exercise the guard's PURE functions directly with
|
|
* in-memory strings, mirroring `tests/planning-snapshot-bypass-drift.test.cjs`'s
|
|
* structure — no shelling out to the CLI, except the one real-tree
|
|
* regression test which imports and calls `scanRepo` in-process.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
|
|
const drift = require('../scripts/lint-planning-artifact-writer-drift.cjs');
|
|
const {
|
|
scanFileForCandidates,
|
|
findArtifactWriterDrift,
|
|
scanRepo,
|
|
stripLineComment,
|
|
} = drift;
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..');
|
|
const FAKE_FILE = path.join('src', 'fake-writer.cts');
|
|
|
|
// ─── Case 1: the guard passes clean against the REAL src/ tree ───────────
|
|
// This is the main regression-proof test: the design doc's ground-truth
|
|
// sweep found zero current violations (every real root-artifact writer is
|
|
// already registered), so a red run here means either a genuine new/newly
|
|
// discovered registry gap or a detector regression — never "expected".
|
|
|
|
describe('scanRepo — real src/ tree', () => {
|
|
test('reports zero violations against the actual src/*.cts tree', () => {
|
|
const violations = scanRepo(REPO_ROOT);
|
|
assert.deepStrictEqual(
|
|
violations,
|
|
[],
|
|
`unexpected .planning/-root writer-registry violation(s): ${JSON.stringify(violations, null, 2)}`,
|
|
);
|
|
});
|
|
|
|
test('the real tree also has REAL statically-resolvable candidates (the detector is not silently inert)', () => {
|
|
// A guard that never matches anything would also report zero
|
|
// violations — this proves the detector actually found real writers
|
|
// (ROADMAP.md/STATE.md/config.json/MILESTONES.md/REQUIREMENTS.md) and
|
|
// they were individually checked, not just skipped wholesale.
|
|
const driftScanLib = require('../scripts/lib/drift-scan.cjs');
|
|
const candidates = driftScanLib.scanTree({
|
|
root: REPO_ROOT,
|
|
scanDirs: drift.SCAN_DIRS,
|
|
scanExt: drift.SCAN_EXT,
|
|
onFile(rel, text) {
|
|
return scanFileForCandidates(text, rel);
|
|
},
|
|
});
|
|
assert.ok(candidates.length > 0, 'expected at least one statically-resolvable .planning/-root write in src/*.cts');
|
|
const filenames = new Set(candidates.map((c) => c.filename));
|
|
assert.ok(filenames.has('ROADMAP.md'));
|
|
assert.ok(filenames.has('STATE.md'));
|
|
assert.ok(filenames.has('config.json'));
|
|
});
|
|
});
|
|
|
|
// ─── Case 2: an unregistered literal filename is flagged ─────────────────
|
|
|
|
describe('findArtifactWriterDrift — unregistered filename is a violation', () => {
|
|
test('a write to an unregistered .planning/-root file is flagged, naming the file', () => {
|
|
const source = [
|
|
'function cmdWriteFoo(cwd) {',
|
|
" const fooPath = path.join(planningRoot(cwd), 'FOO.md');",
|
|
' platformWriteSync(fooPath, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
// Injected predicate (no build:lib dependency for this unit test):
|
|
// everything is canonical EXCEPT 'FOO.md'.
|
|
const isCanonical = (name) => name !== 'FOO.md';
|
|
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, isCanonical);
|
|
assert.strictEqual(out.length, 1);
|
|
assert.strictEqual(out[0].filename, 'FOO.md');
|
|
assert.strictEqual(out[0].file, FAKE_FILE.replace(/\\/g, '/'));
|
|
assert.strictEqual(out[0].line, 3);
|
|
assert.strictEqual(out[0].text, 'platformWriteSync(fooPath, content);');
|
|
});
|
|
|
|
test('a registered filename resolved through the identical path is NOT flagged', () => {
|
|
const source = [
|
|
'function cmdWriteFoo(cwd) {',
|
|
" const fooPath = path.join(planningRoot(cwd), 'FOO.md');",
|
|
' platformWriteSync(fooPath, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const isCanonical = () => true; // everything canonical
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, isCanonical);
|
|
assert.deepStrictEqual(out, []);
|
|
});
|
|
|
|
test('an inline path.join (no intermediate variable) is also detected and checked', () => {
|
|
const source = [
|
|
'function cmdWriteBar(cwd) {',
|
|
" platformWriteSync(path.join(planningRoot(cwd), 'BAR.json'), content);",
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, (name) => name !== 'BAR.json');
|
|
assert.strictEqual(out.length, 1);
|
|
assert.strictEqual(out[0].filename, 'BAR.json');
|
|
});
|
|
|
|
test('an object-literal-property binding (RepairPaths-style) resolves the same as a local const', () => {
|
|
const source = [
|
|
'function repairPaths(cwd) {',
|
|
' const rootBase = planningRoot(cwd);',
|
|
' return {',
|
|
" fooPath: path.join(rootBase, 'FOO.md'),",
|
|
' };',
|
|
'}',
|
|
'function repair(cwd) {',
|
|
' const { fooPath } = repairPaths(cwd);',
|
|
' platformWriteSync(fooPath, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, (name) => name !== 'FOO.md');
|
|
assert.strictEqual(out.length, 1);
|
|
assert.strictEqual(out[0].filename, 'FOO.md');
|
|
assert.strictEqual(out[0].line, 9);
|
|
});
|
|
|
|
test('planningPaths(cwd).<prop> (a full-path property, not a directory) resolves to its known file name', () => {
|
|
const source = [
|
|
'function cmdWriteState(cwd) {',
|
|
' const p = planningPaths(cwd).state;',
|
|
' platformWriteSync(p, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, () => false); // everything fails -> must see STATE.md
|
|
assert.strictEqual(out.length, 1);
|
|
assert.strictEqual(out[0].filename, 'STATE.md');
|
|
});
|
|
});
|
|
|
|
// ─── Case 3: a runtime-computed target is silently skipped ───────────────
|
|
|
|
describe('scanFileForCandidates — dynamic targets are never candidates (not pass, not fail)', () => {
|
|
test('a template-literal file name (inline) produces NO candidate at all', () => {
|
|
const source = [
|
|
'function cmdWriteDynamic(cwd, name) {',
|
|
' platformWriteSync(path.join(planningRoot(cwd), `${name}.md`), content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const candidates = scanFileForCandidates(source, FAKE_FILE);
|
|
assert.deepStrictEqual(candidates, [], 'a runtime-computed filename must never be reported as a candidate, checked or otherwise');
|
|
});
|
|
|
|
test('a template-literal file name (via an intermediate variable) also produces no candidate', () => {
|
|
const source = [
|
|
'function cmdWriteDynamic(cwd, name) {',
|
|
' const dynPath = path.join(planningRoot(cwd), `${name}.md`);',
|
|
' platformWriteSync(dynPath, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const candidates = scanFileForCandidates(source, FAKE_FILE);
|
|
assert.deepStrictEqual(candidates, []);
|
|
});
|
|
|
|
test('findArtifactWriterDrift over the same dynamic-target source reports no violation either (never a false pass or false fail)', () => {
|
|
const source = [
|
|
'function cmdWriteDynamic(cwd, name) {',
|
|
' platformWriteSync(path.join(planningRoot(cwd), `${name}.md`), content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
// Even an isCanonical that rejects EVERYTHING must not surface a
|
|
// violation here, because the write was never a candidate to begin with.
|
|
const out = findArtifactWriterDrift(source, FAKE_FILE, () => false);
|
|
assert.deepStrictEqual(out, []);
|
|
});
|
|
|
|
test('a workstream-scoped planningDir(cwd, ws) call is ambiguous and is never treated as an unambiguous root', () => {
|
|
const source = [
|
|
'function cmdWriteScoped(cwd, ws) {',
|
|
" const p = path.join(planningDir(cwd, ws), 'config.json');",
|
|
' platformWriteSync(p, content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const candidates = scanFileForCandidates(source, FAKE_FILE);
|
|
assert.deepStrictEqual(candidates, [], 'planningDir(cwd, ws) can resolve under .planning/workstreams/<ws>/ — must be skipped, not treated as root');
|
|
});
|
|
|
|
test('a nested write (under milestones/) is never treated as a root-level candidate', () => {
|
|
const source = [
|
|
'function cmdArchive(cwd, version) {',
|
|
" const archiveDir = path.join(planningRoot(cwd), 'milestones');",
|
|
' platformWriteSync(path.join(archiveDir, `${version}-ROADMAP.md`), content);',
|
|
'}',
|
|
'',
|
|
].join('\n');
|
|
const candidates = scanFileForCandidates(source, FAKE_FILE);
|
|
assert.deepStrictEqual(candidates, []);
|
|
});
|
|
});
|
|
|
|
// ─── stripLineComment — comment/string safety ─────────────────────────────
|
|
|
|
describe('stripLineComment', () => {
|
|
test('strips a trailing // comment but preserves a quoted string containing //', () => {
|
|
const line = " const x = 'http://example.com'; // not real";
|
|
assert.strictEqual(stripLineComment(line), " const x = 'http://example.com'; ");
|
|
});
|
|
|
|
test('a // inside a string does not truncate the line early', () => {
|
|
const line = "platformWriteSync(path.join(planningRoot(cwd), '//weird.md'), c);";
|
|
// The string content is preserved verbatim even though it contains //.
|
|
assert.ok(stripLineComment(line).includes("'//weird.md'"));
|
|
});
|
|
});
|