Files
www/tests/site.test.mjs
T
claudeandClaude Opus 5.5 849a020cbc Homepage: seamless theme music that keeps playing on /members/
Adds the eom.dev theme (Strudel) as a 16-bar Web Audio loop (Opus, AAC
fallback) with a Theme on/off button. Playback starts on the first
interaction unless the visitor turned it off. While it plays, links
between the homepage and membership pages are fetched and swapped in
place, so the music doesn't restart; sign-in, checkout, billing and
forms remain real navigations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:03:24 -05:00

452 lines
24 KiB
JavaScript
Executable File

import test from 'node:test';
import assert from 'node:assert/strict';
import { request } from 'node:http';
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { startPreview, parsePort } from '../scripts/preview.mjs';
const repository = new URL('../', import.meta.url);
// Small scanner for this site's static markup, not a general-purpose HTML validator.
function attributes(tag) {
return new Map([...tag.matchAll(/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/gu)]
.map(([, name, double, single]) => [name.toLowerCase(), double ?? single]));
}
function text(html) {
return html.replace(/<[^>]*>/gu, ' ').replace(/&amp;/gu, '&')
.replace(/&(?:nbsp|#160);/gu, ' ').replace(/\s+/gu, ' ').trim();
}
function links(html) {
return [...html.matchAll(/<a\b([^>]*)>([\s\S]*?)<\/a>/giu)]
.map(([, tag, inner]) => ({ attrs: attributes(tag), inner }));
}
function get(server, path = '/', method = 'GET', headers = {}) {
// Raw request paths preserve traversal probes that fetch/URL would normalize away.
return new Promise((accept, reject) => {
const req = request({ host: '127.0.0.1', port: server.address().port, path,
method, headers, agent: false }, res => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('error', reject);
res.on('end', () => accept({ status: res.statusCode, headers: res.headers,
body: Buffer.concat(chunks) }));
});
req.on('error', reject);
req.setTimeout(3_000, () => req.destroy(new Error('Preview request timed out')));
req.end();
});
}
async function preview(t, options) {
const server = await startPreview({ ...options, port: 0 });
t.after(() => new Promise((accept, reject) => {
server.close(error => error ? reject(error) : accept());
server.closeAllConnections();
}));
assert.equal(server.address().address, '127.0.0.1');
return server;
}
async function homepage(t) {
const response = await get(await preview(t));
assert.equal(response.status, 200);
return response.body.toString('utf8');
}
test('homepage assembles only the three common includes, with complete metadata and JSON-LD', async t => {
const html = await homepage(t);
assert.doesNotMatch(html, /<!--\s*#/u);
assert.match(html, /<!doctype html>/iu);
assert.match(html, /<html\s+lang="en"/u);
assert.equal([...html.matchAll(/<h1\b/giu)].length, 1);
assert.equal([...html.matchAll(/<head>/giu)].length, 1);
assert.equal([...html.matchAll(/<footer>/giu)].length, 1);
assert.match(text(html.match(/<title>([\s\S]*?)<\/title>/iu)?.[1] ?? ''), /Eric.*Meehan/iu);
const metas = [...html.matchAll(/<meta\b[^>]*>/giu)].map(([tag]) => attributes(tag));
assert.ok(metas.some(meta => meta.get('charset')?.toLowerCase() === 'utf-8'));
assert.ok(metas.some(meta => meta.get('name') === 'viewport' && meta.get('content')));
for (const name of ['description', 'og:title', 'og:description', 'twitter:title', 'twitter:description']) {
const meta = metas.find(item => (item.get('name') ?? item.get('property')) === name);
assert.ok(meta?.get('content'), `Missing ${name}`);
for (const topic of [/music/iu, /films?/iu, /engineering/iu]) assert.match(meta.get('content'), topic);
}
const canonical = [...html.matchAll(/<link\b[^>]*>/giu)].map(([tag]) => attributes(tag))
.find(attrs => attrs.get('rel') === 'canonical');
assert.equal(canonical?.get('href'), 'https://www.eom.dev/');
for (const [name, expected] of [['og:url', canonical.get('href')], ['og:type', 'website'],
['og:site_name', 'eom.dev'], ['twitter:card', 'summary']]) {
assert.equal(metas.find(meta => (meta.get('name') ?? meta.get('property')) === name)?.get('content'), expected);
}
assert.equal(new URL(metas.find(meta => meta.get('property') === 'og:image')?.get('content')).protocol, 'https:');
assert.ok(metas.find(meta => meta.get('property') === 'og:image:alt')?.get('content'));
const schemas = [...html.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/giu)]
.filter(([, tag]) => attributes(tag).get('type') === 'application/ld+json')
.map(([, , json]) => JSON.parse(json));
assert.ok(schemas.length > 0, 'Missing JSON-LD');
for (const schema of schemas) {
assert.equal(schema['@context'], 'https://schema.org');
assert.equal(schema['@type'], 'Person');
assert.match(schema.name, /Meehan/u);
assert.equal(schema.url, canonical.get('href'));
assert.match(schema.jobTitle, /musician/iu);
assert.ok(Array.isArray(schema.sameAs) && schema.sameAs.length > 0);
for (const url of [schema.url, schema.image, ...schema.sameAs]) {
assert.equal(new URL(url).protocol, 'https:');
}
}
});
test('unique anchors, valid local fragments, and four creative paths ahead of services', async t => {
const html = await homepage(t);
const ids = [...html.matchAll(/<[a-z][^>]*>/giu)].map(([tag]) => attributes(tag).get('id'))
.filter(id => id !== undefined);
assert.equal(new Set(ids).size, ids.length, 'Duplicate IDs');
assert.ok(ids.every(id => id && !/\s/u.test(id)), 'IDs must be nonempty and whitespace-free');
const destinations = new Set(links(html).map(link => link.attrs.get('href')));
for (const href of destinations) {
assert.ok(href?.trim(), 'Every link needs a destination');
const url = new URL(href.replaceAll('&amp;', '&'), 'https://www.eom.dev/');
assert.ok(['https:', 'mailto:'].includes(url.protocol), `Unsafe link: ${href}`);
if (url.origin === 'https://www.eom.dev' && url.pathname === '/' && url.hash) {
assert.ok(ids.includes(decodeURIComponent(url.hash.slice(1))), `Broken fragment: ${href}`);
}
}
for (const id of ['films', 'music', 'projects', 'gaming']) {
assert.ok(destinations.has(`#${id}`), `Missing creative path ${id}`);
assert.ok(ids.indexOf(id) >= 0 && ids.indexOf(id) < ids.indexOf('services'), `${id} must precede services`);
}
const cards = [...html.matchAll(/<article\b([^>]*)>([\s\S]*?)<\/article>/giu)]
.filter(([, tag]) => attributes(tag).get('class')?.split(/\s/u).includes('path-card'));
assert.equal(cards.length, 4);
for (const id of ['films', 'music', 'projects', 'gaming']) {
assert.ok(cards.some(([, , card]) => links(card).some(link => link.attrs.get('href') === `#${id}`)));
}
for (const [, tag] of html.matchAll(/<[a-z]\w*\b([^>]*)>/giu)) {
for (const id of (attributes(tag).get('aria-labelledby') ?? '').split(/\s/u).filter(Boolean)) {
assert.ok(ids.includes(id), `Broken aria-labelledby: ${id}`);
}
}
});
test('featured films and music retain actual video destinations and labeled image links', async t => {
const html = await homepage(t);
const anchors = links(html);
for (const [id, label] of [
['b16e17cc-2545-40ad-b9fa-b9200400aff5', /Great Smokey Mountains/iu],
['678e7a68-9beb-48b2-b4cc-8cfede773a81', /La Banda Maguey/iu],
['f9613754-234e-4053-b194-341154f007c5', /Tidal Waltz/iu],
]) {
const href = `https://peertube.eom.dev/videos/watch/${id}`;
const matching = anchors.filter(link => link.attrs.get('href') === href);
assert.ok(matching.length > 0, `Missing featured video ${id}`);
const imageLink = matching.find(link => /<img\b/iu.test(link.inner));
assert.ok(imageLink, `Video ${id} needs an image link`);
assert.match(imageLink.attrs.get('aria-label') ?? text(imageLink.inner), label);
}
for (const [tag] of html.matchAll(/<img\b[^>]*>/giu)) {
const attrs = attributes(tag);
assert.ok(attrs.has('alt'), 'Images need alt text, even if decorative');
assert.ok(Number(attrs.get('width')) > 0 && Number(attrs.get('height')) > 0);
assert.ok(attrs.get('src'));
}
for (const { attrs, inner } of anchors) {
const visible = text(inner.replace(/<[^>]*\baria-hidden="true"[^>]*>[\s\S]*?<\/[^>]+>/giu, ''));
const imageLabels = [...inner.matchAll(/<img\b[^>]*>/giu)]
.map(([tag]) => attributes(tag).get('alt') ?? '').join('');
assert.ok(attrs.get('aria-label')?.trim() || visible || imageLabels.trim(), `Unlabeled link ${attrs.get('href')}`);
}
});
test('prior game, service, account and donation destinations remain intact', async t => {
const html = await homepage(t);
const destinations = new Set(links(html).map(link => link.attrs.get('href')));
for (const href of [
'https://gitea.eom.dev/WikiDeck/wikideck', 'https://button.eom.dev/',
'https://gitea.eom.dev/Gaming/the-button', 'https://discourse.eom.dev/t/luanti-season-v/690',
'https://gitea.eom.dev/explore/repos', 'https://discourse.eom.dev/',
'https://element.eom.dev/', 'https://gitea.eom.dev/', 'https://mastodon.eom.dev/',
'https://peertube.eom.dev/', 'https://owncast.eom.dev/', 'https://discourse.eom.dev/latest',
'https://peertube.eom.dev/videos/browse', 'https://mastodon.eom.dev/@eric/',
'mailto:eric@eom.dev', '/eric@eom.dev.pub', '/members/', '/members/register/',
'https://donate.stripe.com/dRm28qczOcDx05Nfd9cQU00', '/xmrig.json',
]) assert.ok(destinations.has(href), `Missing preserved destination: ${href}`);
assert.match(html, /luanti\.eom\.dev:30000/u);
assert.match(html, /48mFvxYDSCzMy5a3Zaj7ewApbX4GwXVXX9T1JC8mqBC19s9UcrNq8eaKkQ6GKdhHGQGmhdpvMd3mcbjf4eQbP8Yv7KdAREw/u);
});
test('live publication regions and automatic collection browser are wired to the API module', async t => {
const html = await homepage(t);
const regions = [...html.matchAll(/<(?:section|div)\b[^>]*\bdata-peertube-feed\b[^>]*>/giu)]
.map(([tag]) => attributes(tag));
assert.deepEqual(regions.map(region => region.get('data-channels')),
['eric_channel', 'eric_music', 'eric_tech', 'eric_gaming']);
assert.equal([...html.matchAll(/\bdata-feed-items\b/gu)].length, 4);
assert.equal([...html.matchAll(/\bdata-feed-status\b/gu)].length, 4);
assert.match(html, /<script type="module" src="\/static\/peertube-feeds\.mjs"><\/script>/u);
assert.match(html, /<label for="peertube-collection">/u);
assert.match(html, /<select id="peertube-collection" disabled>/u);
assert.match(html, /data-library-items/u);
assert.match(html, /data-library-status role="status" aria-live="polite"/u);
assert.match(html, /<noscript>/u);
const response = await get(await preview(t), '/static/peertube-feeds.mjs');
assert.equal(response.status, 200);
assert.equal(response.headers['content-type'], 'text/javascript; charset=utf-8');
});
test('homepage theme music loads its module and seamless loop assets', async t => {
const html = await homepage(t);
assert.match(html, /<script type="module" src="\/static\/theme-player\.mjs"><\/script>/u);
const server = await preview(t);
for (const [path, type] of [
['/static/theme-player.mjs', 'text/javascript; charset=utf-8'],
['/static/audio/eom-theme-loop.ogg', 'audio/ogg'],
['/static/audio/eom-theme-loop.m4a', 'audio/mp4'],
['/static/audio/eom-theme.mp3', 'audio/mpeg'],
]) {
const response = await get(server, path);
assert.equal(response.status, 200, path);
assert.equal(response.headers['content-type'], type, path);
}
const player = await readFile(new URL('htdocs/static/theme-player.mjs', repository), 'utf8');
// Sign-in, checkout and billing must stay real navigations, never fetched in place.
for (const route of ['login', 'logout', 'oidc', 'checkout', 'billing', 'stripe', 'shkeeper'])
assert.match(player, new RegExp(`HARD_NAV = .*\\b${route}\\b`, 'u'), route);
});
test('gaming is separate from engineering and writing links to Discourse', async t => {
const html = await homepage(t);
const sections = [...html.matchAll(/<section\b([^>]*)>([\s\S]*?)<\/section>/giu)]
.map(([, tag, content]) => ({ id: attributes(tag).get('id'), content }));
const engineering = sections.find(section => section.id === 'projects').content;
const gaming = sections.find(section => section.id === 'gaming').content;
assert.match(engineering, /data-channels="eric_tech"/u);
assert.doesNotMatch(engineering, /eric_gaming|luanti\.eom\.dev:30000/u);
assert.match(gaming, /data-channels="eric_gaming"/u);
assert.match(gaming, /luanti\.eom\.dev:30000/u);
assert.match(gaming, /https:\/\/button\.eom\.dev\//u);
assert.doesNotMatch(engineering, /<h3>Wikideck<\/h3>/u);
assert.match(gaming, /<h3>Wikideck<\/h3>/u);
assert.ok(gaming.indexOf('<h3>Luanti</h3>') < gaming.indexOf('<h3>The Button</h3>'));
assert.ok(gaming.indexOf('<h3>Luanti</h3>') < gaming.indexOf('<h3>Wikideck</h3>'));
assert.ok(links(html).some(link => link.attrs.get('href') === 'https://discourse.eom.dev/' && /writing/iu.test(text(link.inner))));
assert.match(html, /href="\/#gaming">Gaming/u);
});
test('my projects follows public cross-organization Gitea activity', async t => {
const html = await homepage(t);
assert.match(html, /<h2 id="projects-title">My projects\.<\/h2>/u);
assert.doesNotMatch(html, /Things I wanted to build/u);
assert.match(html, /id="gitea-projects"/u);
assert.match(html, /data-project-items/u);
assert.match(html, /data-project-status role="status" aria-live="polite"/u);
assert.ok(links(html).some(link => link.attrs.get('href') === 'https://gitea.eom.dev/eric?tab=activity'));
assert.match(html, /<script type="module" src="\/static\/gitea-projects\.mjs"><\/script>/u);
const response = await get(await preview(t), '/static/gitea-projects.mjs');
assert.equal(response.status, 200);
assert.equal(response.headers['content-type'], 'text/javascript; charset=utf-8');
});
test('membership templates and routes preserve account and payment actions without emulation', async () => {
// Inspect only: no Django rendering, database, authentication, or network calls.
const template = await readFile(new URL('membership/templates/members.html', repository), 'utf8');
for (const name of ['home', 'register', 'login', 'profile', 'portal', 'logout', 'checkout', 'crypto_checkout']) {
assert.match(template, new RegExp(`\\{% url 'members:${name}'`), `Missing portal action ${name}`);
}
const routes = await readFile(new URL('membership/members/urls.py', repository), 'utf8');
for (const route of ['register/', 'login/', 'logout/', 'profile/', 'billing/', 'checkout/<slug:tier>/']) {
assert.ok(routes.includes(`path("${route}"`), `Missing account route ${route}`);
}
});
test('homepage groups creative sections before the library and membership distinguishes tier benefits', async t => {
const html = await homepage(t);
const order = ['films', 'music', 'projects', 'gaming', 'peertube-library'];
const positions = order.map(id => html.indexOf(`id="${id}"`));
assert.ok(positions.every((position, index) => position >= 0 && (!index || position > positions[index - 1])));
const template = await readFile(new URL('membership/templates/members.html', repository), 'utf8');
assert.match(template, /services included at your membership level/u);
assert.match(template, /Bronze adds exclusive Discourse and Matrix content; Silver adds previews when available; Gold adds project-topic polls/u);
assert.ok(template.indexOf('class="member-grid"') < template.indexOf('Community services by level'));
assert.match(template, /No paid-exclusive game servers are currently included/u);
assert.match(template, /There is no fixed release schedule/u);
assert.match(template, /personal consulting, managed hosting, guaranteed support/u);
assert.doesNotMatch(template, /One account for all of eom\.dev/u);
});
test('creative positioning has no appointments or consulting sales calls', async t => {
const html = await homepage(t);
const template = await readFile(new URL('membership/templates/members.html', repository), 'utf8');
for (const page of [html, template]) {
assert.doesNotMatch(page, /apps\/appointments|calendly\.com|cal\.com\//iu);
assert.doesNotMatch(text(page), /(?:book|schedule|buy|request)\s+(?:a\s+)?(?:free\s+)?(?:introductory\s+|sales\s+|consulting\s+)?(?:call|consultation)|hire me (?:for|to provide)/iu);
}
assert.match(text(html), /music/iu);
assert.match(text(html), /films/iu);
assert.match(text(html), /creative engineering/iu);
});
test('preview serves static MIME, no-store/nosniff and HEAD without a body', async t => {
const server = await preview(t);
for (const [path, mime] of [
['/', 'text/html; charset=utf-8'], ['/index.html', 'text/html; charset=utf-8'],
['/static/home.css', 'text/css; charset=utf-8'], ['/static/favicon.svg', 'image/svg+xml'],
['/static/profile_2026.jpg', 'image/jpeg'], ['/favicon.ico', 'image/x-icon'],
['/xmrig.json', 'application/json; charset=utf-8'], ['/robots.txt', 'text/plain; charset=utf-8'],
['/sitemap.xml', 'application/xml; charset=utf-8'],
]) {
const response = await get(server, path);
assert.equal(response.status, 200, path);
assert.equal(response.headers['content-type'], mime, path);
assert.equal(response.headers['cache-control'], 'no-store');
assert.equal(response.headers['x-content-type-options'], 'nosniff');
assert.equal(Number(response.headers['content-length']), response.body.length);
const head = await get(server, path, 'HEAD');
assert.equal(head.status, response.status);
assert.equal(head.headers['content-type'], mime);
assert.equal(head.headers['content-length'], response.headers['content-length']);
assert.equal(head.body.length, 0);
}
assert.deepEqual((await get(server, '/?preview=true')).body, (await get(server, '/')).body);
});
test('preview rejects traversal, dotfiles, malformed encodings, private files and unsafe methods', async t => {
const server = await preview(t);
for (const path of [
'/../README.md', '/static/../../membership/config/settings.py', '/%2e%2e/README.md',
'/static/%2E%2E/index.html', '/%252e%252e/README.md', '/%2f../README.md',
'/static%5c..%5cindex.html', '/static\\..\\index.html', '/.env', '/%2eenv',
'/.git/config', '/static/.secret', '/%', '/%GG', '/%C0%AF', '/%00',
'/index.html:secret', '/index.html.', '/index.html%20', '/CON', '//README.md',
]) {
const response = await get(server, path);
assert.ok([400, 403].includes(response.status), `${path}: ${response.status}`);
assert.equal(response.headers['x-content-type-options'], 'nosniff');
assert.equal(response.headers['cache-control'], 'no-store');
assert.doesNotMatch(response.body.toString(), /Z:\\|Repositories|Traceback/u);
}
for (const path of ['/README.md', '/membership/config/settings.py', '/conf/httpd.conf',
'/scripts/preview.mjs', '/tests/site.test.mjs', '/static/', '/missing.html']) {
assert.equal((await get(server, path)).status, 404, path);
assert.equal((await get(server, path, 'HEAD')).body.length, 0);
}
for (const method of ['POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'TRACE']) {
const response = await get(server, '/', method);
assert.equal(response.status, 405, method);
assert.equal(response.headers.allow, 'GET, HEAD');
}
assert.equal((await get(server, '/', 'GET', { Host: 'attacker.example' })).status, 403);
});
test('members routes are explicitly preview-only, not authentication or billing emulation', async t => {
const server = await preview(t);
for (const path of ['/members', '/members/', '/members/register/', '/members/login/',
'/members/profile/', '/members/billing/', '/members/checkout/gold/']) {
const response = await get(server, path);
assert.equal(response.status, 503, path);
assert.match(response.body.toString(), /local preview only/iu);
assert.match(response.body.toString(), /dynamic membership portal is unavailable/iu);
assert.doesNotMatch(response.body.toString(), /<form|<input|csrf|\{%/iu);
assert.equal(response.headers['set-cookie'], undefined);
const head = await get(server, path, 'HEAD');
assert.equal(head.status, 503);
assert.equal(head.body.length, 0);
assert.equal(head.headers['content-length'], response.headers['content-length']);
assert.equal((await get(server, path, 'POST')).status, 405);
}
});
async function fixture(t) {
const directory = await mkdtemp(join(tmpdir(), 'www-preview-'));
t.after(() => rm(directory, { recursive: true, force: true }));
const root = join(directory, 'htdocs');
await mkdir(join(root, 'common'), { recursive: true });
await writeFile(join(directory, 'private.txt'), 'outside-public-root');
return { directory, root };
}
test('SSI is literal, nonrecursive and cannot include arbitrary files or execute directives', async t => {
const { root } = await fixture(t);
for (const name of ['header', 'topnav', 'footer']) {
await writeFile(join(root, 'common', `${name}.html`), `<p>${name} $&</p>`);
}
const server = await preview(t, { publicRoot: root });
await writeFile(join(root, 'index.html'), ['header', 'topnav', 'footer']
.map(name => `<!--#include virtual="/common/${name}.html" -->`).join('\n'));
const response = await get(server);
assert.equal(response.status, 200);
assert.doesNotMatch(response.body.toString(), /<!--#/u);
assert.match(response.body.toString(), /header \$&/u);
for (const directive of [
'<!--#include virtual="/../private.txt" -->', '<!--#include file="../private.txt" -->',
'<!--#exec cmd="echo should-never-execute" -->', '<!--#include virtual="/common/other.html" -->',
]) {
await writeFile(join(root, 'index.html'), directive);
const rejected = await get(server);
assert.equal(rejected.status, 500);
assert.doesNotMatch(rejected.body.toString(), /outside-public-root|should-never-execute/u);
}
await writeFile(join(root, 'index.html'), '<!--#include virtual="/common/header.html" -->');
await writeFile(join(root, 'common', 'header.html'), '<!--#include virtual="/common/footer.html" -->');
assert.equal((await get(server)).status, 500);
});
test('realpath containment blocks directory symlinks/junctions, including common SSI directories', async t => {
const { directory, root } = await fixture(t);
const outside = join(directory, 'outside');
await mkdir(outside);
await writeFile(join(outside, 'header.html'), 'outside-public-root');
try {
await symlink(outside, join(root, 'escape-dir'), process.platform === 'win32' ? 'junction' : 'dir');
} catch (error) {
if (['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) {
t.skip(`Directory symlinks unavailable on this host (${error.code}).`);
return;
}
throw error;
}
const server = await preview(t, { publicRoot: root });
assert.equal((await get(server, '/escape-dir/header.html')).status, 403);
await rm(join(root, 'common'), { recursive: true });
await symlink(outside, join(root, 'common'), process.platform === 'win32' ? 'junction' : 'dir');
await writeFile(join(root, 'index.html'), '<!--#include virtual="/common/header.html" -->');
const response = await get(server);
assert.equal(response.status, 403);
assert.doesNotMatch(response.body.toString(), /outside-public-root/u);
});
test('realpath containment blocks file symlinks and hidden targets, including SSI', async t => {
const { directory, root } = await fixture(t);
await writeFile(join(root, '.hidden.txt'), 'hidden-target');
try {
await symlink(join(directory, 'private.txt'), join(root, 'escape.txt'), 'file');
await symlink(join(root, '.hidden.txt'), join(root, 'alias.txt'), 'file');
await symlink(join(directory, 'private.txt'), join(root, 'common', 'header.html'), 'file');
} catch (error) {
if (['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) {
t.skip(`File symlinks unavailable on this host (${error.code}); run on a symlink-capable host too.`);
return;
}
throw error;
}
await writeFile(join(root, 'index.html'), '<!--#include virtual="/common/header.html" -->');
const server = await preview(t, { publicRoot: root });
for (const path of ['/escape.txt', '/alias.txt', '/']) {
const response = await get(server, path);
assert.equal(response.status, 403, path);
assert.doesNotMatch(response.body.toString(), /outside-public-root|hidden-target/u);
}
});
test('port parsing defaults to 8082 and accepts only numeric ports in range', () => {
assert.equal(parsePort(), 8082);
assert.equal(parsePort('0'), 0);
assert.equal(parsePort('65535'), 65535);
for (const value of ['', '-1', '65536', '1.5', '8082x', ' 8082', 'Infinity', '127.0.0.1:8082']) {
assert.throws(() => parsePort(value), /Port must be numeric/u);
}
});