import assert from 'node:assert/strict';
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { checkInternalLinks } from '../scripts/lib/internal-links.mjs';
async function withBuiltSite(run) {
const root = await mkdtemp(join(tmpdir(), 'incognito-links-'));
try {
await run(root);
} finally {
await rm(root, { recursive: true, force: true });
}
}
test('reports a missing built internal route', async () => {
await withBuiltSite(async (root) => {
await writeFile(join(root, 'index.html'), 'Missing');
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.deepEqual(result.broken.map(({ href }) => href), ['/missing/']);
});
});
test('accepts an existing directory index route', async () => {
await withBuiltSite(async (root) => {
await mkdir(join(root, 'about'), { recursive: true });
await writeFile(join(root, 'index.html'), 'About');
await writeFile(join(root, 'about/index.html'), '
About
');
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.equal(result.broken.length, 0);
});
});
test('accepts an existing exact-file link', async () => {
await withBuiltSite(async (root) => {
await writeFile(join(root, 'index.html'), 'Manual');
await writeFile(join(root, 'manual.pdf'), 'manual');
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.equal(result.broken.length, 0);
});
});
test('maps the root route to the built root index', async () => {
await withBuiltSite(async (root) => {
await writeFile(join(root, 'index.html'), 'Home');
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.equal(result.broken.length, 0);
});
});
test('removes the configured base prefix before resolving built routes', async () => {
await withBuiltSite(async (root) => {
await mkdir(join(root, 'about'), { recursive: true });
await writeFile(join(root, 'index.html'), [
'About',
'Missing base prefix',
].join(''));
await writeFile(join(root, 'about/index.html'), 'About
');
const result = await checkInternalLinks({ distRoot: root, base: '/wiki/' });
assert.deepEqual(result.broken.map(({ href }) => href), ['/about/']);
assert.equal(result.broken[0].resolvedPath, '/about/');
});
});
test('resolves relative routes from the source HTML route', async () => {
await withBuiltSite(async (root) => {
await mkdir(join(root, 'guide', 'install'), { recursive: true });
await mkdir(join(root, 'guide', 'about'), { recursive: true });
await writeFile(join(root, 'guide/install/index.html'), 'About');
await writeFile(join(root, 'guide/about/index.html'), 'About
');
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.equal(result.broken.length, 0);
});
});
test('ignores fragments, remote protocols, and Astro-generated assets', async () => {
await withBuiltSite(async (root) => {
await writeFile(join(root, 'index.html'), [
'Section',
'Email',
'Call',
'Remote',
'',
].join(''));
const result = await checkInternalLinks({ distRoot: root, base: '/' });
assert.equal(result.broken.length, 0);
});
});
for (const { name, href } of [
{ name: 'encoded slash', href: '/..%2Fmanual.pdf' },
{ name: 'encoded backslash', href: '/..%5Cmanual.pdf' },
]) {
test(`rejects ${name} traversal even when the escaped target exists`, async () => {
const siteRoot = await mkdtemp(join(tmpdir(), 'incognito-links-escape-'));
const distRoot = join(siteRoot, 'dist');
try {
await mkdir(distRoot);
await writeFile(join(siteRoot, 'manual.pdf'), 'outside dist');
await writeFile(join(distRoot, 'index.html'), `Escaped`);
const result = await checkInternalLinks({ distRoot, base: '/' });
assert.deepEqual(result.broken.map((broken) => broken.href), [href]);
} finally {
await rm(siteRoot, { recursive: true, force: true });
}
});
}