diff --git a/test/bundle-freshness.test.mjs b/test/bundle-freshness.test.mjs index dc67ff68..efb96b9e 100644 --- a/test/bundle-freshness.test.mjs +++ b/test/bundle-freshness.test.mjs @@ -1,9 +1,13 @@ import assert from 'node:assert/strict'; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { resolve } from 'node:path'; import test from 'node:test'; -import { assertFreshDemoBundle } from '../demo/bundle-freshness.mjs'; +import { + ALLOW_STALE_BUNDLE, + assertFreshDemoBundle, + assertFreshDemoBundleUnlessAllowed, +} from '../demo/bundle-freshness.mjs'; import { sourceFingerprint } from '../scripts/source-fingerprint.mjs'; const fixtureRoot = () => { @@ -55,3 +59,56 @@ test('bundle freshness uses the target tree fingerprint contract', async () => { rmSync(root, { recursive: true, force: true }); } }); + +test('the launcher gate refuses a stale bundle by default (#236)', async () => { + // Смок против несвежего бандла не падает честно: на #234 три проверки + // покраснели, а четвёртая ПРОШЛА, потому что старый код одинаково врал в двух + // местах, которые сверялись друг с другом. Такой результат читается как + // дефект логики и отправляет искать причину не туда. + const root = fixtureRoot(); + try { + const stale = { evaluate: async () => 'fingerprint-of-an-older-tree' }; + await assert.rejects( + () => assertFreshDemoBundleUnlessAllowed(stale, root, {}), + /stale/, + ); + const fresh = { evaluate: async () => sourceFingerprint(root) }; + assert.equal( + await assertFreshDemoBundleUnlessAllowed(fresh, root, {}), + sourceFingerprint(root), + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('the escape hatch skips the gate but never silently (#236)', async () => { + const root = fixtureRoot(); + const warnings = []; + const realWarn = console.warn; + console.warn = (...args) => warnings.push(args.join(' ')); + try { + const stale = { evaluate: async () => 'stale' }; + const result = await assertFreshDemoBundleUnlessAllowed( + stale, root, { [ALLOW_STALE_BUNDLE]: '1' }, + ); + assert.equal(result, null, 'пропуск возвращает null, а не выдуманный фингерпринт'); + assert.equal(warnings.length, 1, 'пропуск обязан быть слышен'); + assert.match(warnings[0], new RegExp(ALLOW_STALE_BUNDLE)); + assert.match(warnings[0], /#236/); + } finally { + console.warn = realWarn; + rmSync(root, { recursive: true, force: true }); + } +}); + +test('the smoke launcher enforces the gate on the repository root (#236)', async () => { + // Контракт места вызова: гейт живёт в общем лаунчере, поэтому защита есть у + // всех ~128 смоков без правки каждого. И считает фингерпринт по корню + // репозитория, а не по каталогу раздачи demo/srv, где нет src/**. + const source = readFileSync( + new URL('../demo/serve.mjs', import.meta.url), 'utf8', + ); + assert.match(source, /assertFreshDemoBundleUnlessAllowed\(page, REPO_ROOT\)/); + assert.match(source, /const REPO_ROOT = dirname\(dirname\(/); +});