ktg-plugin-marketplace/docs/marketplace-polyrepo-migration/migration/sc-checks.test.mjs
Kjell Tore Guttormsen fef4b33c97 fix(migration): remediate 6 MAJOR + 3 MINOR trekreview findings + stale rename test
MAJOR
- 9e97cd5 40-validate-standalone.sh: route a target's sc2_gate to its dedicated
  gate (config-audit → 50-config-audit-sc2.sh), mirroring 99-dryrun.sh, so --all
  no longer falsely FAILs config-audit on the machine-locked v5.0.0 tests.
- 1708e90 99-dryrun.sh: assert EXACTLY one tag survives (F5); a partial tag-strip
  no longer silently reports the wrong tag via head -1.
- 4e494c8 99-dryrun.sh: capture the SC2 standalone failing set from the dry-run's
  own prepped extract ($dest), not the 40-validate side-effect clean room.
- aeb6292 00-preflight.sh: assert every map path is whitespace/glob-free, making
  the word-split path handling in 99-dryrun.sh sound.
- 5d112cb extract the SC6 DROP + SC2 regression detectors into sc6-check.sh /
  sc2-regression.sh and add sc-checks.test.mjs — a negative test proving each
  detector FIRES (force-fresh re-extraction would undo a planted file-drop).
- 9e588ca 10-extract.sh re-asserts git filter-repo before use (self-heal runs
  preflight only on a missing mirror); RUNBOOK lists git-filter-repo + python3>=3.6.

MINOR
- bc0f8a7 plugin-map.json: reset ms-ai-architect blob_strip_safe to null
  (00-preflight.sh populates it per run).
- 8d649e9 99-dryrun.sh: gate SC6 behind extract success; a failed extract is
  labelled (extract failed), not a content DROP.
- 4044c49 99-dryrun.sh: guard mktemp — an empty capture is an error, not a
  false zero-regression PASS.

Also: 00-preflight.test.mjs asserted all 3 'renamed' plugins carry >=2 paths, but
llm-security became single-path in 836b8e9 (copilot was a coexisting plugin, not a
rename) — a stale pre-existing failure. Aligned the test to the ratified map and
added a positive single-path lock against re-introducing the 87-file-drop defect.

Verified: full dry-run 11/11, 0 pushes; sc-checks/99-dryrun/40-validate/00-preflight/
60-rewrite suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:17:12 +02:00

92 lines
4.8 KiB
JavaScript

// Negative + positive coverage for the two highest-stakes dry-run detectors (5d112cb).
//
// 99-dryrun.sh force-fresh re-extracts all 11 targets every run (10-extract.sh wipes $dest first), so a
// "plant a dropped file in $WORK/<key>" negative test against the full dry-run would be silently undone by
// the re-extract. So the two load-bearing detectors are extracted into standalone, side-effect-free scripts
// — sc6-check.sh (the SC6 DROP detector, the guard against the llm-security 87-file-drop class) and
// sc2-regression.sh (the SC2 regression-FAIL detector) — which 99-dryrun.sh now calls. This suite drives
// those exact scripts directly, proving each detector FIRES (DROP / regression → label + non-zero exit) and
// does NOT false-positive (retained content / subset failures → exit 0). When the dry-run calls them and
// they exit non-zero, it sets ok=0 → exit 1, so the dry-run's "report DROP/regression + exit non-zero"
// behaviour follows by construction from this coverage.
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const SC6 = path.join(here, 'sc6-check.sh');
const SC2 = path.join(here, 'sc2-regression.sh');
function run(script, args) {
return spawnSync('bash', [script, ...args], { encoding: 'utf8' });
}
// --- sc6-check.sh — the SC6 DROP detector ---------------------------------------------------------------
test('SC6 DROP fires: a real shortfall (llm-security 87-file class) → DROP label + non-zero exit', () => {
const r = run(SC6, ['334', '421', 'false']); // the actual dual-rename defect: 334 of 421 files retained
assert.notEqual(r.status, 0, 'a non-blob-strip shortfall must exit non-zero (the dry-run sets ok=0)');
assert.match(r.stdout, /334\/421 DROP/, `DROP label expected: ${r.stdout}`);
});
test('SC6 blob-strip shortfall is allowed: intentional >1MB strip → (blob-strip) label + exit 0', () => {
const r = run(SC6, ['100', '200', 'True']); // ms-ai-architect screenshot blob-bomb: legitimate shortfall
assert.equal(r.status, 0, `blob-strip shortfall must exit 0: ${r.stderr}`);
assert.match(r.stdout, /100\/200 \(blob-strip\)/, `blob-strip label expected: ${r.stdout}`);
assert.ok(!/DROP/.test(r.stdout), 'a blob-strip shortfall must NOT be labelled DROP');
});
test('SC6 retained content: ext == live → no DROP, exit 0', () => {
const r = run(SC6, ['421', '421', 'false']);
assert.equal(r.status, 0, `full retention must exit 0: ${r.stderr}`);
assert.equal(r.stdout.trim(), '421/421');
});
test('SC6 more files than live (e.g. path-rename overlap) → no DROP, exit 0', () => {
const r = run(SC6, ['500', '421', 'false']);
assert.equal(r.status, 0);
assert.ok(!/DROP/.test(r.stdout), `${r.stdout}`);
});
// --- sc2-regression.sh — the SC2 regression-FAIL detector -----------------------------------------------
function fixtureFiles(standalone, baseline) {
const dir = mkdtempSync(path.join(os.tmpdir(), 'sc2-'));
const sf = path.join(dir, 'sf');
const bf = path.join(dir, 'bf');
writeFileSync(sf, standalone.length ? standalone.join('\n') + '\n' : '');
writeFileSync(bf, baseline.length ? baseline.join('\n') + '\n' : '');
return { dir, sf, bf };
}
test('SC2 regression fires: a standalone-only failure (not in baseline) → printed + non-zero exit', () => {
// The extraction introduced a NEW failure absent from the in-repo baseline — a real regression.
const { dir, sf, bf } = fixtureFiles(['extract regressed test', 'shared pre-existing fail'], ['shared pre-existing fail']);
try {
const r = run(SC2, [sf, bf]);
assert.equal(r.status, 1, `a regression must exit 1 (the dry-run sets ok=0): ${r.stdout}${r.stderr}`);
assert.match(r.stdout, /extract regressed test/, `the regressing test name must be reported: ${r.stdout}`);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('SC2 subset PASS: standalone failures ⊆ baseline (pre-existing red) → exit 0, nothing printed', () => {
const { dir, sf, bf } = fixtureFiles(['shared pre-existing fail'], ['shared pre-existing fail', 'other baseline-only fail']);
try {
const r = run(SC2, [sf, bf]);
assert.equal(r.status, 0, `a subset must exit 0 (no migration regression): ${r.stdout}${r.stderr}`);
assert.equal(r.stdout.trim(), '', 'no regression should be printed for a subset');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('SC2 missing capture file is an ERROR (exit 2), never a silent zero-regression PASS (4044c49)', () => {
const r = run(SC2, ['/nonexistent/sf', '/nonexistent/bf']);
assert.equal(r.status, 2, `a missing capture file must be a hard error, not a false PASS: ${r.stdout}`);
});