Files
Faset_Engine/docs/studies/map/map.test.mjs
T

79 lines
4.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, mkdir, writeFile, symlink, rm, truncate } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { readDocument } from './file-access.mjs';
import { resolveDocumentLink } from './file-links.mjs';
test('relative report/source links preserve document context and source lines', () => {
const from = 'docs/studies/14-engine-blueprint.md';
const cases = [
['09-unity-ux-extensibility-study.md', 'docs/studies/09-unity-ux-extensibility-study.md', null],
['../ARCHITECTURE.md', 'docs/ARCHITECTURE.md', null],
['../../../UnrealEngine/Engine/example.cpp:123', '../UnrealEngine/Engine/example.cpp', '123'],
['../../../godot/scene/main/node.cpp#L77', '../godot/scene/main/node.cpp', '77'],
['report.md:12', 'docs/studies/report.md', '12'],
['a%20b.md', 'docs/studies/a b.md', null],
];
for (const [href, expected, line] of cases) {
const result = new URL(resolveDocumentLink(href, from), 'http://localhost:4178');
assert.equal(result.searchParams.get('file'), expected);
assert.equal(result.searchParams.get('line'), line);
}
assert.equal(resolveDocumentLink('https://example.com/docs', from), 'https://example.com/docs');
assert.equal(resolveDocumentLink('#section', from), '#section');
for (const name of ['README.md', 'PLAN.md']) {
const result = new URL(resolveDocumentLink('../../../' + name, 'docs/studies/map/README.md'), 'http://localhost:4178');
assert.equal(result.searchParams.get('file'), name);
}
for (const href of ['javascript:alert(1)', 'data:text/plain,hello', '//example.com/file', '/etc/passwd', 'C:\\private.md']) assert.equal(resolveDocumentLink(href, from), null);
});
test('file access is restricted to documentation and available sibling checkouts', async t => {
const workspace = await mkdtemp(path.join(tmpdir(), 'faset-map-'));
t.after(() => rm(workspace, { recursive: true, force: true }));
const project = path.join(workspace, 'Faset_Engine');
const files = {
'Faset_Engine/README.md': 'project',
'Faset_Engine/PLAN.md': 'plan',
'Faset_Engine/docs/ARCHITECTURE.md': 'architecture',
'Faset_Engine/docs/studies/report.md': 'report',
'Faset_Engine/private.md': 'private',
'Faset_Engine/docs/.private.md': 'hidden',
'Faset_Engine/docs/node_modules/private.md': 'dependency',
'Faset_Engine/docs/program.exe': 'binary',
'outside.md': 'outside',
'UnrealEngine/Engine/example.cpp': 'source',
'UnrealEngine-other/example.cpp': 'wrong root',
};
for (const [name, text] of Object.entries(files)) {
const target = path.join(workspace, name);
await mkdir(path.dirname(target), { recursive: true });
await writeFile(target, text);
}
assert.equal((await readDocument(project, 'README.md')).text, 'project');
assert.equal((await readDocument(project, 'PLAN.md')).text, 'plan');
assert.equal((await readDocument(project, 'docs/studies/../ARCHITECTURE.md')).path, 'docs/ARCHITECTURE.md');
assert.equal((await readDocument(project, '../UnrealEngine/Engine/example.cpp')).path, '../UnrealEngine/Engine/example.cpp');
for (const requested of ['private.md', '../outside.md', '../UnrealEngine-other/example.cpp', 'docs/.private.md', 'docs/node_modules/private.md', 'docs/program.exe', path.join(project, 'README.md')]) {
await assert.rejects(readDocument(project, requested), { status: 403 });
}
await assert.rejects(readDocument(project, '../godot/missing.cpp'), { code: 'ENOENT' });
await writeFile(path.join(project, 'docs/large.txt'), '');
await truncate(path.join(project, 'docs/large.txt'), 8 * 1024 * 1024 + 1);
await assert.rejects(readDocument(project, 'docs/large.txt'), { status: 403 });
try {
await symlink(path.join(workspace, 'outside.md'), path.join(project, 'docs/escape.md'));
} catch (error) {
if (error.code === 'EPERM') { t.diagnostic('Symlink case skipped: creating symlinks is not permitted on this host.'); return; }
throw error;
}
await assert.rejects(readDocument(project, 'docs/escape.md'), { status: 403 });
for (const name of ['README.md', 'PLAN.md']) {
await rm(path.join(project, name));
await symlink(path.join(workspace, 'outside.md'), path.join(project, name));
await assert.rejects(readDocument(project, name), { status: 403 });
}
});