import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
buildMindSpaceCanonicalRedirect,
decodeMindSpaceRouteSegment,
recoverMisplacedMindSpacePublicHtml,
resolveMindSpacePublicRequest,
resolveMindSpaceHtmlFallbackRedirect,
resolveMindSpacePublishOwnerKey,
resolveMindSpacePublicRouteTarget,
resolveMindSpaceSimilarHtmlRedirect,
} from './mindspace-public-route.mjs';
test('resolveMindSpacePublishOwnerKey keeps uuid and resolves usernames', async () => {
assert.equal(
await resolveMindSpacePublishOwnerKey('123e4567-e89b-12d3-a456-426614174000'),
'123e4567-e89b-12d3-a456-426614174000',
);
assert.equal(
await resolveMindSpacePublishOwnerKey('John', async (username) => `${username}-id`),
'john-id',
);
assert.equal(await resolveMindSpacePublishOwnerKey('../bad', async () => 'x'), null);
});
test('public route target resolves inside publish root', () => {
const result = resolveMindSpacePublicRouteTarget({
h5Root: '/tmp/h5',
ownerKey: 'user-1',
parts: ['public', 'report.html'],
});
assert.equal(result.targetDir, path.resolve('/tmp/h5/MindSpace/user-1'));
assert.equal(result.insideRoot, true);
assert.equal(result.resolvedPath, path.resolve('/tmp/h5/MindSpace/user-1/public/report.html'));
});
test('canonical redirect helpers normalize route paths', () => {
assert.equal(buildMindSpaceCanonicalRedirect('user-1', ['public', 'a b.html']), '/MindSpace/user-1/public/a%20b.html');
assert.equal(resolveMindSpaceSimilarHtmlRedirect({ ownerKey: 'user-1', relativePath: 'public/a b.html' }), '/MindSpace/user-1/public/a%20b.html');
});
test('decodeMindSpaceRouteSegment safely decodes segments', () => {
assert.equal(decodeMindSpaceRouteSegment('report%201.html'), 'report 1.html');
assert.equal(decodeMindSpaceRouteSegment('%E4%B8%AD%E6%96%87'), '中文');
});
test('resolveMindSpaceHtmlFallbackRedirect points html to public zone sibling', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-public-route-'));
const targetDir = path.join(root, 'MindSpace', 'user-1');
const publicDir = path.join(targetDir, 'public');
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(publicDir, 'report.html'), 'ok');
const redirect = resolveMindSpaceHtmlFallbackRedirect({
ownerKey: 'user-1',
targetDir,
resolvedRoot: path.resolve(targetDir),
rest: ['report.html'],
});
assert.equal(redirect, '/MindSpace/user-1/public/report.html');
});
test('recoverMisplacedMindSpacePublicHtml copies misplaced file into public zone', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-public-recover-'));
const targetDir = path.join(root, 'MindSpace', 'user-1');
fs.mkdirSync(path.join(root, 'public'), { recursive: true });
fs.mkdirSync(targetDir, { recursive: true });
fs.writeFileSync(path.join(root, 'public', 'report.html'), 'demo', { encoding: 'utf8', flag: 'w' });
let ensured = null;
const recovered = await recoverMisplacedMindSpacePublicHtml({
h5Root: root,
targetDir,
resolvedRoot: path.resolve(targetDir),
rest: ['public', 'report.html'],
ensureThumbnail: async (...args) => {
ensured = args;
},
logger: { warn() {} },
});
assert.equal(recovered, path.join(targetDir, 'public', 'report.html'));
assert.equal(fs.existsSync(recovered), true);
assert.deepEqual(ensured, [targetDir, 'public/report.html']);
});
test('resolveMindSpacePublicRequest coordinates canonical redirect, serve, and not found', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-public-request-'));
const ownerKey = '123e4567-e89b-12d3-a456-426614174000';
const targetDir = path.join(root, 'MindSpace', ownerKey);
fs.mkdirSync(path.join(targetDir, 'public'), { recursive: true });
fs.writeFileSync(path.join(targetDir, 'public', 'report.html'), 'demo');
const redirected = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: '/John/public/report.html',
resolveUsernameToUserId: async () => ownerKey,
});
assert.deepEqual(redirected, {
action: 'redirect',
status: 301,
location: `/MindSpace/${ownerKey}/public/report.html`,
ownerKey,
});
const served = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/report.html`,
});
assert.equal(served.action, 'serve');
assert.equal(served.filePath, path.join(targetDir, 'public', 'report.html'));
const missing = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/missing.html`,
});
assert.deepEqual(missing, {
action: 'not_found',
reason: 'missing_file',
ownerKey,
});
});
test('resolveMindSpacePublicRequest keeps legacy browser-storage pages available during migration', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-public-storage-ban-'));
const ownerKey = '123e4567-e89b-12d3-a456-426614174000';
const targetDir = path.join(root, 'MindSpace', ownerKey, 'public');
fs.mkdirSync(targetDir, { recursive: true });
fs.writeFileSync(
path.join(targetDir, 'ledger.html'),
'',
);
fs.writeFileSync(
path.join(targetDir, 'ledger.js'),
'sessionStorage.setItem("ledger", "[]")',
);
const result = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/ledger.html`,
});
assert.equal(result.action, 'serve');
assert.equal(result.filePath, path.join(targetDir, 'ledger.html'));
const scriptResult = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/ledger.js`,
});
assert.equal(scriptResult.action, 'serve');
assert.equal(scriptResult.filePath, path.join(targetDir, 'ledger.js'));
});
test('resolveMindSpacePublicRequest serves missing thumbnail png when svg sidecar exists', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ms-public-thumb-'));
const ownerKey = '123e4567-e89b-12d3-a456-426614174000';
const targetDir = path.join(root, 'MindSpace', ownerKey);
const publicDir = path.join(targetDir, 'public');
fs.mkdirSync(publicDir, { recursive: true });
fs.writeFileSync(path.join(publicDir, 'report.thumbnail.svg'), '');
const served = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/report.thumbnail.png`,
});
assert.equal(served.action, 'serve');
assert.equal(served.filePath, path.join(publicDir, 'report.thumbnail.png'));
assert.equal(fs.existsSync(served.filePath), false);
const stillMissing = await resolveMindSpacePublicRequest({
h5Root: root,
requestPath: `/${ownerKey}/public/missing.thumbnail.png`,
});
assert.deepEqual(stillMissing, {
action: 'not_found',
reason: 'missing_file',
ownerKey,
});
});