/** * #446: страница /convert как артефакт. * * Проверяется не вид, а три обещания, каждое из которых можно нарушить молча: * собранный файл действительно содержит рабочий конвертер, он не делает ни * одного сетевого вызова, и он не отстал от исходников. */ import assert from 'node:assert/strict'; import test from 'node:test'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { TEXT } from '../scripts/sh3d-convert/page/app.mjs'; const root = (relative) => fileURLToPath(new URL(`../${relative}`, import.meta.url)); const page = () => readFileSync(root('scripts/sh3d-convert/dist/index.html'), 'utf8'); const shell = () => readFileSync(root('scripts/sh3d-convert/page/shell.html'), 'utf8'); test('#446 собранная страница не отстала от исходников', () => { // Артефакт коммитится, потому что его раскладывают копированием. Значит он // умеет отстать — и тогда сайт конвертирует не тем кодом, который проверен // гейтами. Сверка с пересборкой это ловит. const run = spawnSync(process.execPath, [root('scripts/sh3d-convert/build-page.mjs'), '--check'], { encoding: 'utf8' }); assert.equal(run.status, 0, `${run.stdout || ''}${run.stderr || ''}`); }); test('#446 в странице нет ни одного сетевого вызова и ни одной внешней загрузки', () => { // Обещание «файл не покидает браузер» обязано быть проверяемым, а не // написанным в тексте страницы. Заодно поэтому же на странице системные // шрифты: к шрифтам — это внешний запрос при загрузке. const text = page(); // Ищутся формы ВЫЗОВА, а не имена: первая редакция теста краснела на // собственном комментарии в app.mjs, где эти API перечислены как запрещённые. // Документация, называющая запрет, — не нарушение запрета. for (const forbidden of [ /\bfetch\s*\(/, /new\s+XMLHttpRequest/, /new\s+WebSocket/, /new\s+EventSource/, /\.sendBeacon\s*\(/, /\bimportScripts\s*\(/, /navigator\.(?:connection|geolocation)/, /fonts\.googleapis/, /fonts\.gstatic/, /\bnavigator\.sendBeacon/, ]) { assert.ok(!forbidden.test(text), `в странице появилось ${forbidden}`); } assert.ok(!/]+src=/i.test(text), 'внешний скрипт'); assert.ok(!/]+href="https?:/i.test(text), 'внешний стиль или шрифт'); assert.ok(!/url\(\s*['"]?https?:/i.test(text), 'внешний ресурс в CSS'); // Ссылки, по которым человек ЩЁЛКАЕТ сам, — не запросы: они законны. assert.ok(text.includes('href="https://github.com/Matysh/houseplan-card"')); }); test('#446 конвертер внутри страницы даёт тот же результат, что и модули', async () => { // Самое важное: страница собирается склейкой, и склейка может тихо потерять // функцию. Поэтому проверяется не текст, а поведение — прогон фикстуры через // код, извлечённый из собранного артефакта. const text = page(); const script = /