diff --git a/test/bundle-tree-committed.test.mjs b/test/bundle-tree-committed.test.mjs new file mode 100644 index 00000000..a0ec36c1 --- /dev/null +++ b/test/bundle-tree-committed.test.mjs @@ -0,0 +1,59 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; + +import { compareBundleTrees, readBundleManifest, verifyBundleTree } from '../scripts/bundle-tree.mjs'; + +// #349. Логика проверки дерева бандла существовала и была написана правильно — +// но применялась только к синтетической фикстуре в tmpdir(). Поэтому манифест, +// ссылающийся на пять несуществующих файлов, прожил в dev незамеченным: 1444 +// зелёных теста, зелёный check-docs, и установка через HACS получила бы 404 на +// каждом ленивом импорте. +// +// Здесь те же функции спрашиваются о НАСТОЯЩЕМ дереве репозитория. + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const COPIES = ['dist', 'custom_components/houseplan/frontend']; + +test('манифест бандла не ссылается в никуда, обе копии целы и равны (#349)', () => { + for (const copy of COPIES) { + // verifyBundleTree отвечает сразу на три вопроса: файл существует, его + // sha256 совпадает с манифестом, путь не выходит за корень копии. + assert.doesNotThrow(() => verifyBundleTree(resolve(ROOT, copy)), `${copy}: дерево бандла битое`); + } + assert.doesNotThrow( + () => compareBundleTrees(resolve(ROOT, COPIES[0]), resolve(ROOT, COPIES[1])), + 'копии бандла разошлись: HACS ставит вторую, а сверяется первая', + ); +}); + +test('каждый файл манифеста отслеживается git, а не только лежит на диске (#349)', () => { + // Дефект #349 родился именно здесь. Пересборка дала чанки с новыми хешами + // содержимого; `git commit -a --amend` удалил старые (они отслеживались) и + // НЕ добавил новые (они не отслеживались). Проверка файловой системы на + // машине автора прошла бы: файлы там были. Отличить «собрано» от + // «закоммичено» умеет только git. + const listed = spawnSync('git', ['-C', ROOT, 'ls-files', '-z', ...COPIES], { encoding: 'utf8' }); + if (listed.status !== 0) { + // Громко, а не молча: тихий пропуск проверки — тот самый класс, из-за + // которого эта задача и появилась. + console.log('ПРОПУЩЕНО: git ls-files недоступен, отслеживаемость не проверена' + + ` (${(listed.stderr || '').trim() || 'нет вывода'})`); + return; + } + const tracked = new Set(listed.stdout.split('\0').filter(Boolean)); + assert.ok(tracked.size, 'git ls-files не вернул ни одного файла — проверьте вызов'); + const missing = []; + for (const copy of COPIES) { + const manifest = readBundleManifest(resolve(ROOT, copy)); + assert.ok(manifest.files.length, `${copy}: манифест без файлов`); + for (const file of manifest.files) { + const relative = `${copy}/${file.path}`; + if (!tracked.has(relative)) missing.push(relative); + } + } + assert.deepEqual(missing, [], + 'манифест ссылается на файлы, которых нет в индексе git: собрано, но не закоммичено'); +});