The filename check sat inside the content word scan, which skips the third-party folders, so vendor/arasaac-loader.js passed. Filenames are now checked in every folder; only the content scan leaves third-party data out (its dictionaries hold the Italian word "sclera"). Planted: files named for a set in vendor/, voices/, models/lang/ (a folder) and pkg/ are caught. prerender-static.sh's comment and the README now describe both this and the decompressed magic-byte check. Co-Authored-By: Claude Opus 5.5 <[email protected]>
141 lines
6.5 KiB
JavaScript
141 lines
6.5 KiB
JavaScript
// node --test tools/symbols/check-no-nc.test.mjs — planted files.
|
||
import assert from 'node:assert/strict';
|
||
import { spawnSync } from 'node:child_process';
|
||
import { randomBytes } from 'node:crypto';
|
||
import { copyFileSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
||
import { tmpdir } from 'node:os';
|
||
import { dirname, join } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import test from 'node:test';
|
||
import { gzipSync, brotliCompressSync } from 'node:zlib';
|
||
import { check, PACK_MAGIC } from './check-no-nc.mjs';
|
||
|
||
function site(files, prefix = '') {
|
||
const dir = mkdtempSync(join(tmpdir(), 'check-no-nc-'));
|
||
const base = {
|
||
'index.html': '<!doctype html><title>AAC</title>',
|
||
'pkg/aac-web.0123456789ab.js': 'export default 1;',
|
||
'sw.js': `const PRECACHE_URLS = ["/","/pkg/aac-web.0123456789ab.js"];`,
|
||
'voices/c/g2p/it/dict.tsv': 'sclera\tˈsklɛra\n',
|
||
'voices/c/g2p/fr/verb.csv': 'musclera,muscler\n',
|
||
'models/lang/it.0123456789ab.bin': 'sclera scleral',
|
||
};
|
||
// The usual files sit under the app's prefix; planted ones where they say.
|
||
const all = { ...Object.fromEntries(Object.entries(base).map(([k, v]) => [prefix + k, v])), ...files };
|
||
for (const [path, content] of Object.entries(all)) {
|
||
const p = join(dir, path);
|
||
mkdirSync(dirname(p), { recursive: true });
|
||
writeFileSync(p, content);
|
||
}
|
||
return dir;
|
||
}
|
||
const clean = { packs: [], named: [], precached: [] };
|
||
|
||
test('a clean build passes, whatever the third-party dictionaries hold', () => {
|
||
for (const prefix of ['', 'app/']) {
|
||
const dir = site({}, prefix);
|
||
assert.deepEqual(check(dir, prefix), clean);
|
||
rmSync(dir, { recursive: true });
|
||
}
|
||
});
|
||
|
||
test('a file carrying a pack\'s bytes is caught, whatever its name or place', () => {
|
||
const dir = site({ 'pkg/x.bin': Buffer.concat([PACK_MAGIC, Buffer.from('rest')]), 'voices/y.dat': PACK_MAGIC });
|
||
assert.deepEqual(check(dir).packs.sort(), ['pkg/x.bin', 'voices/y.dat']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('a pack compressed as a .br or .gz sidecar is caught in every folder, third-party ones too', () => {
|
||
// Incompressible, so each sidecar is larger than the part of it first read.
|
||
const pack = Buffer.concat([PACK_MAGIC, randomBytes(200_000)]);
|
||
const dir = site({
|
||
'voices/c/g2p/it/z.tsv.br': brotliCompressSync(pack),
|
||
'models/lang/w.bin.gz': gzipSync(pack),
|
||
'pkg/blob.bin.br': brotliCompressSync(pack),
|
||
'vendor/v.js.gz': gzipSync(pack),
|
||
// Compressed, but not a pack.
|
||
'voices/c/g2p/it/ok.tsv.br': brotliCompressSync('ciao\tˈtʃao\n'),
|
||
'pkg/ok.js.gz': gzipSync('export default 2;'),
|
||
// Named like a sidecar, not compressed, and not a pack.
|
||
'ai/raw.br': 'not brotli',
|
||
});
|
||
assert.deepEqual(check(dir).packs.sort(), ['models/lang/w.bin.gz', 'pkg/blob.bin.br', 'vendor/v.js.gz', 'voices/c/g2p/it/z.tsv.br']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('anything under models/symbols/, or any *.pack, is caught', () => {
|
||
const dir = site({ 'models/symbols/LICENSE.md': 'licence', 'pkg/a.pack': 'x', 'ai/b.pack.gz': 'x' });
|
||
assert.deepEqual(check(dir).packs.sort(), ['ai/b.pack.gz', 'models/symbols/LICENSE.md', 'pkg/a.pack']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('the service worker precaching a pack is caught', () => {
|
||
const dir = site({ 'sw.js': 'const PRECACHE_URLS = ["/","/models/symbols/x.0123456789ab.pack"];' });
|
||
assert.deepEqual(check(dir).precached, ['/models/symbols/x.0123456789ab.pack']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('a worker whose precache list cannot be read fails', () => {
|
||
const dir = site({ 'sw.js': 'const PRECACHE_URLS = __PRECACHE_URLS__;' });
|
||
assert.throws(() => check(dir), /no PRECACHE_URLS list/);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('an own file naming a set is caught, sidecars decompressed', () => {
|
||
const dir = site({
|
||
'pkg/aac-web.0123456789ab.wasm': 'xx ARASAAC yy',
|
||
'symbols/LICENSES.md': '# Sclera',
|
||
'index.html.gz': gzipSync('<p>arasaac</p>'),
|
||
'pkg/s.js.br': brotliCompressSync('sclera'),
|
||
});
|
||
assert.deepEqual(check(dir).named.sort(), ['index.html.gz', 'pkg/aac-web.0123456789ab.wasm', 'pkg/s.js.br', 'symbols/LICENSES.md']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('a file named for a set is caught in every folder, third-party ones too', () => {
|
||
const dir = site({
|
||
'vendor/arasaac-loader.js': 'export {};',
|
||
'voices/c/Sclera.txt': '',
|
||
'models/lang/arasaac/w.bin': 'x',
|
||
'pkg/sclera.js': 'export {};',
|
||
});
|
||
assert.deepEqual(check(dir).named.sort(), ['models/lang/arasaac/w.bin', 'pkg/sclera.js', 'vendor/arasaac-loader.js', 'voices/c/Sclera.txt']);
|
||
rmSync(dir, { recursive: true });
|
||
});
|
||
|
||
test('under /app/, the third-party folders are the app\'s', () => {
|
||
const dir = site({ 'app/pkg/x.js': 'sclera', 'app/voices/y.txt': 'sclera' }, 'app/');
|
||
assert.deepEqual(check(dir, 'app/').named, ['app/pkg/x.js']);
|
||
// The same folders outside the app are not third-party data.
|
||
const other = site({ 'voices/x.txt': 'sclera' }, 'app/');
|
||
assert.deepEqual(check(other, 'app/').named, ['voices/x.txt']);
|
||
rmSync(dir, { recursive: true });
|
||
rmSync(other, { recursive: true });
|
||
});
|
||
|
||
test('run as a command, as prerender-static.sh does, from any path, it checks', () => {
|
||
const script = fileURLToPath(new URL('./check-no-nc.mjs', import.meta.url));
|
||
const tmp = mkdtempSync(join(tmpdir(), 'check-no-nc-cli-'));
|
||
const spaced = join(tmp, 'with space');
|
||
mkdirSync(spaced);
|
||
copyFileSync(script, join(spaced, 'check-no-nc.mjs'));
|
||
symlinkSync(dirname(script), join(tmp, 'linked'));
|
||
const ways = [
|
||
{ name: 'as prerender-static.sh', args: [script] },
|
||
{ name: 'from a path with a space', args: [join(spaced, 'check-no-nc.mjs')] },
|
||
{ name: 'through a symlinked directory', args: [join(tmp, 'linked', 'check-no-nc.mjs')] },
|
||
{ name: 'relative, inside the spaced path', args: ['check-no-nc.mjs'], cwd: spaced },
|
||
];
|
||
const planted = site({ 'index.html': '<!doctype html><p>ARASAAC</p>' });
|
||
const clean = site({});
|
||
for (const way of ways) {
|
||
const run = (dir) => spawnSync(process.execPath, [...way.args, dir], { cwd: way.cwd, encoding: 'utf8' });
|
||
const bad = run(planted);
|
||
assert.equal(bad.status, 1, `${way.name}: ${bad.stdout}${bad.stderr}`);
|
||
assert.match(bad.stderr, /names them in 1 file\(s\):\n index\.html/, way.name);
|
||
const good = run(clean);
|
||
assert.equal(good.status, 0, `${way.name}: ${good.stdout}${good.stderr}`);
|
||
assert.match(good.stdout, /✓ no symbol pack/, way.name);
|
||
}
|
||
for (const dir of [tmp, planted, clean]) rmSync(dir, { recursive: true });
|
||
});
|