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, }); });