Merge manifest hygiene and reconcile runtime exclusions with CSP response capture

This commit is contained in:
Jonathan Sykes
2026-10-09 15:12:39 +08:00
14 changed files with 22494 additions and 15 deletions

View File

@@ -20,6 +20,7 @@ writeFileSync(join(publicDir, 'root.css'), 'body{color:red}');
writeFileSync(join(publicDir, 'sw.js'), "const BUILD_TAG = typeof __BUILD_TAG__ !== 'undefined' ? __BUILD_TAG__ : 'v-test';\n");
writeFileSync(join(publicDir, 'fonts', 'font.woff2'), 'font');
writeFileSync(join(publicDir, 'icons', 'icon.png'), 'icon');
for (const name of ['probe.test.js', 'README.md', 'admin.html', 'fonts/font-OFL.txt']) writeFileSync(join(publicDir, name), 'direct-only file');
async function freePort() {
return await new Promise((resolvePort, reject) => {
@@ -65,19 +66,35 @@ beforeAll(async () => { server = await startServer(); });
afterAll(async () => { await stopServer(server); rmSync(root, { recursive: true, force: true }); });
describe('static delivery characterization', () => {
test('build tag is stable for the same tree and changes when any served file changes', async () => {
test('build tag is stable for the same tree and changes only when runtime files change', async () => {
const first = await (await fetch(`${server.base}/api/version`)).json();
await stopServer(server);
server = await startServer();
const same = await (await fetch(`${server.base}/api/version`)).json();
expect(same.buildTag).toBe(first.buildTag);
writeFileSync(join(publicDir, 'unlisted.txt'), 'included in recursive build tag');
writeFileSync(join(publicDir, 'unlisted.js'), 'runtime build input');
await stopServer(server);
server = await startServer();
const changed = await (await fetch(`${server.base}/api/version`)).json();
expect(changed.buildTag).not.toBe(first.buildTag);
});
test('excluded files retain direct bodies, hashes, compression and cache headers', async () => {
const { createHash } = await import('node:crypto');
const expected = createHash('sha256').update('direct-only file').digest('hex').slice(0, 10);
const manifest = await (await fetch(`${server.base}/api/manifest`)).json();
for (const path of ['/probe.test.js', '/README.md', '/admin.html', '/fonts/font-OFL.txt']) {
expect(manifest.files[path]).toBeUndefined();
for (const query of ['', `?v=${expected}`, '?v=stale']) {
const response = await fetch(`${server.base}${path}${query}`);
expect(response.status).toBe(200);
expect(response.headers.get('x-asset-hash')).toBe(expected);
expect(await response.text()).toBe('direct-only file');
expect(response.headers.get('cache-control')).toBe(query === '?v=stale' ? 'no-store' : query ? 'public, max-age=31536000, immutable' : path.startsWith('/fonts/') ? 'public, max-age=2592000' : 'no-cache');
}
}
});
test('unreadable public tree uses the fixed dev-build fallback', async () => {
const moved = join(root, 'public.saved');
Bun.spawnSync(['mv', publicDir, moved]);