From e3a4ac7655a8c6d376974f35c5e1792acbf9d56e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 10:44:27 +0300 Subject: [PATCH] =?UTF-8?q?test:=20=D0=B4=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=BD=D0=B4=D0=BB=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=8F=D1=89=D0=B5=D0=B5,=20=D0=B0=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D1=81=D0=B8=D0=BD=D1=82=D0=B5=D1=82=D0=B8=D1=87=D0=B5=D1=81?= =?UTF-8?q?=D0=BA=D0=BE=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Логика проверки существовала и была написана правильно: verifyBundleTree и compareBundleTrees в scripts/bundle-tree.mjs. Но применялась только к фикстуре в tmpdir(), поэтому манифест, ссылающийся на пять несуществующих файлов, прожил в dev при 1444 зелёных тестах и зелёном check-docs. Установка через HACS получила бы 404 на каждом ленивом импорте. Второй тест спрашивает git, а не файловую систему, и это не перестраховка. Дефект родился так: пересборка дала чанки с новыми хешами содержимого, `git commit -a --amend` удалил старые (отслеживались) и не добавил новые (не отслеживались). На машине автора проверка наличия файлов прошла бы — файлы там были. Отличить «собрано» от «закоммичено» умеет только индекс. Пропуск проверки при недоступном git — громкий: тихий пропуск это тот самый класс, из-за которого задача и появилась. Доказательство пользы исполнением: оба теста прогнаны на c665c7d3, коммите до починки, и оба падают — первый с «manifest asset is missing: houseplan-assets/editor-DMlizeQy.js», второй с перечислением десяти путей вне индекса. Мутанта не добавляю намеренно. Это утверждение о состоянии дерева, а не о логике: на здоровом дереве ослабленная проверка проходит, то есть мутант выживает, а выживающий мутант хуже отсутствующего. Логику verifyBundleTree по-прежнему держат синтетические мутанты в bundle-assets.test.mjs. Issue: #349 User-Visible: no --- test/bundle-tree-committed.test.mjs | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/bundle-tree-committed.test.mjs 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: собрано, но не закоммичено'); +});