/** * 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 '' for (const m of content.matchAll(/from\s+['"]([^'"]+)['"]/g)) out.push(m[1]); // require('') 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); }