Files
brain/tools/tdd-real-test-verifier.mjs
T

53 lines
2.3 KiB
JavaScript

/**
* TDD-gate real-test verification — router-gate v4 spec §3.11.
* Pure ядро + тонкая fs-обёртка. Закрывает sentinel-gaming (touch test без expect/it).
*
* DEVIATION: spec §3.11 показывает @babel/parser AST. Babel не установлен —
* regex-based extraction per спек note "basic regex + import check; AST refinement future".
*/
import fs from 'node:fs';
import path from 'node:path';
export function extractImportsAndStrings(content) {
const out = [];
// import ... from '<x>'
for (const m of content.matchAll(/from\s+['"]([^'"]+)['"]/g)) out.push(m[1]);
// require('<x>')
for (const m of content.matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)) out.push(m[1]);
// any string literals (single or double quoted)
// NOTE: import paths captured above are also matched here; duplicates are harmless for the downstream .some() check.
for (const m of content.matchAll(/['"]([^'"\n]+)['"]/g)) out.push(m[1]);
return out;
}
export function verifyRealTestContent(content, editedFilesList) {
const src = String(content || '');
if (!/\bexpect\s*\(/.test(src)) return { valid: false, reason: 'no_expect_call' };
if (!/\b(test|it)\s*\(/.test(src)) return { valid: false, reason: 'no_test_block' };
const list = editedFilesList || [];
if (list.length === 0) return { valid: true }; // no prod files to cover
const strings = extractImportsAndStrings(src);
const hasRelevant = list.some((editedFile) => {
const base = path.basename(editedFile, path.extname(editedFile));
// KNOWN: basename substring match can false-positive for short basenames (e.g. 'User' matched inside 'CurrentUser').
// Acceptable at regex-approximation fidelity; AST refinement deferred (§3.11).
return strings.some((s) => s.includes(base));
});
if (!hasRelevant) return { valid: false, reason: 'no_coverage_of_edited_files' };
return { valid: true };
}
export function verifyRealTest(testFilePath, editedFilesList, opts = {}) {
const { readFile = fs.readFileSync } = opts;
let content;
try {
content = readFile(testFilePath, 'utf8');
} catch (err) {
const reason = err && err.code === 'ENOENT' ? 'test_file_not_found' : 'test_file_read_error';
return { valid: false, reason };
}
return verifyRealTestContent(content, editedFilesList);
}