mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 06:59:46 +00:00
Аудит v1.71.0-beta.1 (§3.2 M1/M2/M3/M6/M9) прогнал по мутанту на каждый контракт: пять снятий защиты не покраснили ни один тест. Восьмой подряд случай проверок, не умеющих падать, и первый — в бете, закрывавшей #421, задачу ровно об этом. Каждый свидетель ниже проверен отрицательным прогоном: мутант краснит ровно свой тест и не задевает остальные. 1. Полное декодирование растра. `test_supported_raster_headers_and_full_decode` спрашивал w/h/mime — их даёт header-парсер; обрезка `PNG_1X1[:33]` теряет IEND и отбивается там же. No-op блока Pillow оставлял 35 pass. Новый свидетель: PNG с верными сигнатурой, IHDR, IEND, длинами и CRC, но с текстом вместо zlib-потока в IDAT. Оба прежних теста переименованы — их имена обещали то, чего они не проверяли. Пропуск без Pillow остался (validate_asset глотает ImportError осознанно), но в окружении с Home Assistant отсутствие Pillow теперь красное само по себе — иначе свидетель молча скипался бы в каноне. 2. Канонизация SVG. `ValidatedAsset(canonical,…)` → `ValidatedAsset(data,…)` не краснило ничего: все тесты смотрели w/h/mime, ни один — байты. Свидетель сверяет байты целиком: пролог и комментарий не переживают канонизацию, пустой элемент сжимается. Вторая половина — `_check_size(canonical)`. Экранирование `>` в тексте раздувает документ вчетверо: загрузка 1.84 МиБ канонизуется в 7.35 МиБ и до сих пор проходила входной контроль. Свидетель — ровно такая. 3. Гард внешних URL. Все три «внешних» кейса корпуса ловились другими правилами (тег не из словаря, атрибут не из словаря, ветвь href), и `if False:` не краснило ничего. Свидетель: разрешённый тег, разрешённый атрибут, пять токенов — сработать может только сам гард, сообщение это подтверждает. 4. Проекция декора. Единственный кейс задавал flip_h и opacity: 2 → 1; ожидание неотличимо от «opacity игнорируется», а flip_v не проверялся вовсе. Свидетели: четыре комбинации отражений, opacity 0.4 и 0 против заглушки, форма asset_id с согласованным url (прежняя строка ловилась сравнением url, поэтому регулярку id можно было удалить незаметно). 5. Гард benchmark из #423. Доказательство было циклическим: тест вырезал из текста подстроку и спрашивал регулярку, находит ли она её, — а регулярка искала именно её. Динамический режим `--guard-probe` существовал с #423 и не вызывался ни одним прогоном. Теперь он в `demo/guard/verify-guard.mjs` (умеет аргументы и файл выше каталога), а тест из обнаружения требует у нового page-benchmark режим пробы и запись в верификаторе. Мутантов в реестре стало восемь новых: четыре бэкендных, три юнитных, один на пробах гарда. У #423 их было ноль — единственная задача с циклическим тестом и она же единственная без мутантов. Гейты: typecheck зелёный; npm test 1790 tests, 1789 pass, 0 fail; pytest без HA 310 passed, 2 skipped; npm run build зелёный, dist не изменился (продуктовый код не тронут); mutation-gate --check применяет все восемь якорей. Полный прогон новых мутантов — следующим шагом. Issue: #430 User-Visible: no
This commit is contained in:
@@ -4683,6 +4683,106 @@ const MUTANT_DEFINITIONS = [
|
||||
+ " errorCode: '',",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-raster-full-decode-skipped',
|
||||
guard: 'python3 -m pytest tests_backend/test_decor_assets.py -q -p no:cacheprovider',
|
||||
because: 'a valid-looking PNG whose IDAT is not a zlib stream must be refused before it '
|
||||
+ 'enters the authenticated store; header parsing answers w/h/mime and cannot answer '
|
||||
+ 'whether the raster decodes at all (#51 AC, аудит #430 п.1)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/decor_assets.py',
|
||||
find: ' with Image.open(BytesIO(data)) as image:\n'
|
||||
+ ' image.load()\n'
|
||||
+ ' if image.size != (width, height):\n'
|
||||
+ ' raise DecorAssetError("invalid_image", "Image dimensions are inconsistent")\n'
|
||||
+ ' if getattr(image, "is_animated", False):\n'
|
||||
+ ' raise DecorAssetError("unsupported_image", "Animated images are unsupported")',
|
||||
replace: ' _ = (Image, BytesIO)',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-svg-canonical-bytes-discarded',
|
||||
guard: 'python3 -m pytest tests_backend/test_decor_assets.py -q -p no:cacheprovider',
|
||||
because: 'the stored SVG must be the re-serialised canonical form, not the upload: keeping '
|
||||
+ 'the original bytes silently reinstates whatever the parser dropped — prologue, '
|
||||
+ 'comments, exotic spelling of the same tree (#51 ТЗ §3, аудит #430 п.2)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/decor_assets.py',
|
||||
find: 'return ValidatedAsset(canonical, "image/svg+xml"',
|
||||
replace: 'return ValidatedAsset(data, "image/svg+xml"',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-svg-canonical-size-unchecked',
|
||||
guard: 'python3 -m pytest tests_backend/test_decor_assets.py -q -p no:cacheprovider',
|
||||
because: 'canonicalisation can grow the document fourfold by escaping text, so the 2 MiB '
|
||||
+ 'limit must be re-applied to the canonical bytes: a 1.84 MiB upload otherwise lands '
|
||||
+ 'as 7.35 MiB in the store (аудит #430 п.2)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/decor_assets.py',
|
||||
find: ' _check_size(canonical)\n',
|
||||
replace: '',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-svg-external-url-guard-off',
|
||||
guard: 'python3 -m pytest tests_backend/test_decor_assets.py -q -p no:cacheprovider',
|
||||
because: 'javascript:, data:, http:, https: and // inside an allowed attribute of an allowed '
|
||||
+ 'tag are caught by this rule alone; every "external" case of the original corpus was '
|
||||
+ 'caught by tag or attribute allowlists instead (аудит #430 п.3)',
|
||||
patches: [{
|
||||
file: 'custom_components/houseplan/decor_assets.py',
|
||||
find: 'if any(token in low for token in ("javascript:", "data:", "http:", "https:", "//")):',
|
||||
replace: 'if False:',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-image-flip-v-ignored',
|
||||
guard: 'node --test test/decor-assets.test.mjs',
|
||||
because: 'vertical flip is half of the image projection contract and had no witness of its '
|
||||
+ 'own: the single #51 case set flip_h only, so dropping flip_v stayed green (#51 AC3, '
|
||||
+ 'аудит #430 п.4)',
|
||||
patches: [{
|
||||
file: 'src/decor-assets.ts',
|
||||
find: '${shape.flip_v ? -1 : 1}',
|
||||
replace: '1',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-image-opacity-ignored',
|
||||
guard: 'node --test test/decor-assets.test.mjs',
|
||||
because: 'the projection must carry the shape opacity; the only case asserted opacity 2 → 1, '
|
||||
+ 'an expectation indistinguishable from hardcoding 1 (#51 AC4, аудит #430 п.4)',
|
||||
patches: [{
|
||||
file: 'src/decor-assets.ts',
|
||||
find: 'const opacity = clamp01(shape.opacity, 1);',
|
||||
replace: 'const opacity = 1;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-asset-id-shape-unchecked',
|
||||
guard: 'node --test test/decor-assets.test.mjs',
|
||||
because: 'the catalog row must prove its own asset_id shape: the malformed row of #51 kept '
|
||||
+ 'the url of a real asset, so the url comparison caught it and the id regex could be '
|
||||
+ 'deleted unnoticed (аудит #430 п.4)',
|
||||
patches: [{
|
||||
file: 'src/decor-assets.ts',
|
||||
find: " if (!DECOR_ASSET_ID_RE.test(String(row.asset_id || '')) || row.url !== expectedUrl",
|
||||
replace: ' if (row.url !== expectedUrl',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'benchmark-page-verdict-unwatched',
|
||||
guard: 'node demo/guard/verify-guard.mjs',
|
||||
because: 'the page benchmark of #423 must register its page with watchPage, and that must be '
|
||||
+ 'proven by running it: the previous proof asked a regexp whether it still finds the '
|
||||
+ 'substring the same test had just deleted (аудит #430 п.5)',
|
||||
patches: [{
|
||||
file: 'demo/benchmark_backdrop_decode.mjs',
|
||||
find: 'const page = watchPage(await (await browser.newContext()).newPage());',
|
||||
replace: 'const page = await (await browser.newContext()).newPage();',
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user