Зачем: аудит владельца (docs/audit/2026-09-26/AUDIT.md) стал основным реестром дефектов; без сопоставления исполнители и приёмка говорят о разных ID для одной ошибки (TASK-GROK-2 §2). Что: теги переименованы по таблице владельца ([K3/N2], [K13/SYNC-A], [S5/SYNC-C], [D6/OWN-01], [K17/OWN-10] и т. д.), ключи known-defects.json — так же; N9–N12, F7, F10 остаются одиночными, как в таблице. Сверх таблицы, по тексту AUDIT.md: K1/SYNC-I (анонимный доступ без токенов — это K1 на /sync), K1/PILOT-1 (подстановки без :? в пилотном compose прямо названы в K1), K2/PILOT-2 (NODE_ENV=development в пилоте — суть K2). SYNC-C0, SYNC-K, OWN-03..06/08/09, PILOT-3 ID владельца не имеют. run-e2e принимает «/» в теге. Прогон: 38 red / 9 green, храповик OK — те же сценарии, новые имена.
124 lines
5.8 KiB
JavaScript
124 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
||
// Запуск e2e с «храповиком» известных дефектов.
|
||
// * Сценарии проверяют ПРАВИЛЬНОЕ поведение, поэтому открытые дефекты дают красные тесты.
|
||
// * tests/e2e/known-defects.json — список ID, красный цвет которых сейчас ожидаем.
|
||
// * exit 0, если: все [HARNESS]-тесты зелёные И каждый красный тест есть в списке
|
||
// И ни один ID из списка не стал полностью зелёным (исправленный дефект нужно
|
||
// убрать из списка — иначе регрессию потом никто не заметит).
|
||
// * --no-known-defects: строгий режим, exit = код vitest (все дефекты красные).
|
||
// Полный вывод пишется в tests/e2e/evidence/e2e-run.txt, таблица — в e2e-table.md.
|
||
// tests/e2e/evidence/ не хранится в git (.gitignore): в CI это артефакт задачи e2e.
|
||
import { spawn } from 'node:child_process';
|
||
import { createWriteStream, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||
import path from 'node:path';
|
||
import { nodeTool } from './node-commands.mjs';
|
||
|
||
const repoRoot = path.resolve(import.meta.dirname, '../..');
|
||
const evidence = path.join(repoRoot, 'tests/e2e/evidence');
|
||
mkdirSync(evidence, { recursive: true });
|
||
const strict = process.argv.includes('--no-known-defects');
|
||
const jsonPath = path.join(evidence, 'e2e-results.json');
|
||
const logPath = path.join(evidence, 'e2e-run.txt');
|
||
const extra = process.argv.slice(2).filter((arg) => !arg.startsWith('--no-known-defects'));
|
||
|
||
const vitestArgs = [
|
||
'run',
|
||
'--config',
|
||
'tests/e2e/vitest.e2e.config.ts',
|
||
'--reporter=verbose',
|
||
'--reporter=json',
|
||
`--outputFile.json=${jsonPath}`,
|
||
...extra,
|
||
];
|
||
const log = createWriteStream(logPath);
|
||
const vitest = nodeTool('vitest', vitestArgs);
|
||
log.write(`$ node <vitest.mjs> ${vitestArgs.join(' ')}\n`);
|
||
const exitCode = await new Promise((resolve) => {
|
||
const child = spawn(vitest.command, vitest.args, { cwd: repoRoot, env: process.env });
|
||
for (const stream of [child.stdout, child.stderr]) {
|
||
stream.on('data', (chunk) => {
|
||
process.stdout.write(chunk);
|
||
log.write(chunk);
|
||
});
|
||
}
|
||
child.on('close', (code) => resolve(code ?? 1));
|
||
});
|
||
log.end();
|
||
|
||
let results;
|
||
try {
|
||
results = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
||
} catch {
|
||
console.error('[run-e2e] нет JSON-результатов: стенд не поднялся (см. e2e-run.txt)');
|
||
process.exit(1);
|
||
}
|
||
const known = strict
|
||
? {}
|
||
: JSON.parse(readFileSync(path.join(repoRoot, 'tests/e2e/known-defects.json'), 'utf8')).defects;
|
||
|
||
const byId = new Map();
|
||
for (const file of results.testResults) {
|
||
for (const test of file.assertionResults) {
|
||
const id = /^\[([A-Z0-9/-]+)\]/.exec(test.title)?.[1] ?? 'UNTAGGED';
|
||
const entry = byId.get(id) ?? { passed: 0, failed: 0, tests: [] };
|
||
if (test.status === 'passed') entry.passed += 1;
|
||
else if (test.status === 'failed') entry.failed += 1;
|
||
entry.tests.push({ title: test.title, status: test.status });
|
||
byId.set(id, entry);
|
||
}
|
||
}
|
||
|
||
const problems = [];
|
||
// Страховка: в evidence не должно попасть ничего похожего на секреты стенда
|
||
// (hex-токены 48/64 символа, токены устройств kstaff_dev_*, URL с паролем).
|
||
const secretPatterns = [
|
||
/\b[0-9a-f]{48}\b/,
|
||
/\b[0-9a-f]{64}\b/,
|
||
/kstaff_dev_[0-9A-Z]{26}_[0-9a-f]{16,}/,
|
||
/postgres:\/\/[^\s:@]+:[^\s@]+@/,
|
||
];
|
||
for (const name of readdirSync(evidence)) {
|
||
const content = readFileSync(path.join(evidence, name), 'utf8');
|
||
for (const pattern of secretPatterns) {
|
||
if (pattern.test(content))
|
||
problems.push(`evidence/${name}: найдено похожее на секрет (${pattern})`);
|
||
}
|
||
}
|
||
const rows = [];
|
||
for (const [id, entry] of [...byId.entries()].sort()) {
|
||
const red = entry.failed > 0;
|
||
let verdict;
|
||
if (id === 'HARNESS' || id === 'UNTAGGED') {
|
||
verdict = red ? 'СТЕНД СЛОМАН' : 'зелёный (позитивный контроль)';
|
||
if (red || id === 'UNTAGGED') problems.push(`${id}: ${red ? 'красный' : 'тест без ID'}`);
|
||
} else if (red && known[id]) verdict = 'красный (известный дефект)';
|
||
else if (red) {
|
||
verdict = 'красный (НЕ в known-defects)';
|
||
problems.push(`${id}: новый красный тест — регрессия или новый дефект`);
|
||
} else if (known[id]) {
|
||
verdict = 'зелёный — ИСПРАВЛЕН, уберите из known-defects.json';
|
||
problems.push(`${id}: дефект исправлен, обновите known-defects.json`);
|
||
} else verdict = 'зелёный';
|
||
rows.push(
|
||
`| ${id} | ${red ? 'RED' : 'GREEN'} | ${entry.passed}/${entry.passed + entry.failed} | ${verdict} |`,
|
||
);
|
||
}
|
||
for (const id of Object.keys(known)) {
|
||
if (!byId.has(id)) problems.push(`${id}: есть в known-defects.json, но сценария нет`);
|
||
}
|
||
const table = ['| ID | Цвет | Зелёных/всего | Вердикт |', '|---|---|---|---|', ...rows].join('\n');
|
||
writeFileSync(path.join(evidence, 'e2e-table.md'), `${table}\n`);
|
||
console.log(`\n[run-e2e] vitest exit=${exitCode}\n${table}`);
|
||
|
||
if (strict) {
|
||
if (problems.some((p) => p.startsWith('evidence/'))) {
|
||
console.error(`\n[run-e2e] FAIL:\n- ${problems.join('\n- ')}`);
|
||
process.exit(1);
|
||
}
|
||
process.exit(exitCode);
|
||
}
|
||
if (problems.length > 0) {
|
||
console.error(`\n[run-e2e] FAIL:\n- ${problems.join('\n- ')}`);
|
||
process.exit(1);
|
||
}
|
||
console.log('\n[run-e2e] OK: стенд работает, все красные сценарии — известные открытые дефекты');
|