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 { fileURLToPath } from 'node:url'; import { buildPublicUrl, buildPublicZonePageUrl, buildPublishConstraints, buildSandboxSessionConstraints, ensureUserPublishLayout, renderWorkspaceHints, isPathInsidePublishDir, migrateUserPublishDir, PUBLISH_ROOT_DIR, PUBLIC_ZONE_DIR, resolveLegacyPublishDir, resolveGoosedSandboxRoot, resolvePageDataDeliveryBaseUrl, } from './user-publish.mjs'; import { syncSkillsToWorkspace } from './skills-registry.mjs'; import { listPlatformSkillCatalog } from './skills-registry.mjs'; const USER_ID = 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e'; test('publish dir and public url use stable user id', () => { const platformRoot = path.dirname(fileURLToPath(import.meta.url)); const h5Root = fs.mkdtempSync(path.join(os.tmpdir(), 'h5-')); const layout = ensureUserPublishLayout({ h5Root, publicBaseUrl: 'https://m.tkmind.cn', user: { id: USER_ID, username: 'john' }, }); assert.equal(layout.slug, USER_ID); assert.equal(layout.username, 'john'); assert.ok(layout.publishDir.endsWith(`${path.sep}${PUBLISH_ROOT_DIR}${path.sep}${USER_ID}`)); assert.equal( resolveGoosedSandboxRoot(h5Root, { id: USER_ID, username: 'john' }), layout.publishDir, ); assert.equal( resolveGoosedSandboxRoot(h5Root, { id: USER_ID, username: 'john' }, { MEMIND_SHARED_PUBLISH_ROOT: '/shared/MindSpace', }), path.join('/shared/MindSpace', USER_ID), ); assert.equal(layout.publicUrl, `https://m.tkmind.cn/${PUBLISH_ROOT_DIR}/${USER_ID}/`); assert.equal( buildPublicUrl('https://m.tkmind.cn', USER_ID, 'report.html'), `https://m.tkmind.cn/${PUBLISH_ROOT_DIR}/${USER_ID}/report.html`, ); assert.equal( buildPublicZonePageUrl('https://m.tkmind.cn', USER_ID, 'report.html'), `https://m.tkmind.cn/${PUBLISH_ROOT_DIR}/${USER_ID}/${PUBLIC_ZONE_DIR}/report.html`, ); assert.equal( buildPublicZonePageUrl('https://m.tkmind.cn', USER_ID, `${PUBLIC_ZONE_DIR}/report.html`), `https://m.tkmind.cn/${PUBLISH_ROOT_DIR}/${USER_ID}/${PUBLIC_ZONE_DIR}/report.html`, ); const catalog = listPlatformSkillCatalog(platformRoot); syncSkillsToWorkspace({ h5Root: platformRoot, publishDir: layout.publishDir, skillMap: { 'static-page-publish': true }, catalog, user: { id: USER_ID, username: 'john' }, publicBaseUrl: 'https://m.tkmind.cn', }); const skillPath = path.join(layout.publishDir, '.agents', 'skills', 'static-page-publish', 'SKILL.md'); const docxSkillPath = path.join(layout.publishDir, '.agents', 'skills', 'docx-generate', 'SKILL.md'); const longImageSkillPath = path.join(layout.publishDir, '.agents', 'skills', 'long-image-download', 'SKILL.md'); const docxScriptPath = path.join(layout.publishDir, '.agents', 'skills', 'docx-generate', 'generate_docx.py'); assert.ok(fs.existsSync(skillPath)); assert.ok(fs.existsSync(docxSkillPath)); assert.ok(fs.existsSync(longImageSkillPath)); assert.ok(fs.existsSync(docxScriptPath)); const skillText = fs.readFileSync(skillPath, 'utf8'); const docxSkillText = fs.readFileSync(docxSkillPath, 'utf8'); const longImageSkillText = fs.readFileSync(longImageSkillPath, 'utf8'); assert.match(skillText, new RegExp(`m\\.tkmind\\.cn/${PUBLISH_ROOT_DIR}/${USER_ID}/${PUBLIC_ZONE_DIR}/`)); assert.match(skillText, /public\/report\.html/); assert.match(skillText, /\[.*\]\(.*\)/); assert.match(skillText, /mindspace-cover/); assert.match(skillText, /\.thumbnail\.svg/); assert.match(skillText, /docx-generate/); assert.match(skillText, /long-image-download/); assert.match(skillText, /generate_long_image/); assert.match(skillText, /禁止使用 `localStorage`/); assert.match(skillText, /IndexedDB/); assert.match(skillText, /PostgreSQL schema/); assert.match(skillText, /page-data-collect/); assert.match(docxSkillText, /public\/文件名\.docx/); assert.match(longImageSkillText, /public\/report\.long\.png/); assert.match(skillText, /report\.docx|伴生下载/); }); test('migrateUserPublishDir merges legacy username directory', () => { const h5Root = fs.mkdtempSync(path.join(os.tmpdir(), 'h5-')); const legacyDir = resolveLegacyPublishDir(h5Root, { username: 'john' }); fs.mkdirSync(legacyDir, { recursive: true }); fs.writeFileSync(path.join(legacyDir, 'legacy.html'), ''); const layout = ensureUserPublishLayout({ h5Root, publicBaseUrl: 'https://m.tkmind.cn', user: { id: USER_ID, username: 'john' }, }); assert.ok(fs.existsSync(path.join(layout.publishDir, 'legacy.html'))); }); test('buildPublishConstraints scopes default search to user workspace', () => { const publishDir = `/var/h5/${PUBLISH_ROOT_DIR}/${USER_ID}`; const constraints = buildPublishConstraints({ slug: USER_ID, username: 'john', displayName: 'John', publicBaseUrl: 'https://m.tkmind.cn', publishDir, }); assert.match(constraints, /唯一文件根目录/); assert.match(constraints, /禁止把用户叫作 TKMind/); assert.match(constraints, /\*\*John\*\*/); assert.doesNotMatch(constraints, /对用户统一称 \*\*TKMind\*\*/); assert.match(constraints, /禁止用公网 URL 列目录/); assert.match(constraints, /public\/页面\.html/); assert.match(constraints, /docx-generate/); assert.match(constraints, /public\/\*\.docx/); assert.match(constraints, /long-image-download/); assert.match(constraints, /generate_long_image/); assert.match(constraints, /\/public\//); }); test('buildSandboxSessionConstraints documents shell and forbids public url browsing', () => { const text = buildSandboxSessionConstraints({ baseConstraints: 'base', developerTools: ['write', 'edit', 'shell', 'tree'], }); assert.match(text, /shell 已开启/); assert.match(text, /禁止.*curl/); assert.match(text, /list_dir 可用/); assert.match(text, /public\/xxx\.html/); assert.match(text, /docx-generate/); assert.match(text, /public\/\*\.docx/); assert.match(text, /\/public\//); assert.match(text, /当前用户专属 PostgreSQL 数据空间/); assert.match(text, /localStorage/); assert.match(text, /IndexedDB/); assert.match(text, /write_file\/edit_file 会直接拒绝/); }); test('renderWorkspaceHints includes docx download guidance for published pages', () => { const text = renderWorkspaceHints({ slug: USER_ID, username: 'john', publishDir: `/var/h5/${PUBLISH_ROOT_DIR}/${USER_ID}`, displayName: 'John', }); assert.match(text, /docx-generate/); assert.match(text, /public\/\*\.docx/); assert.match(text, /long-image-download/); assert.match(text, /generate_long_image/); assert.match(text, /apps__create_app/); assert.match(text, /Page Data API/); assert.match(text, /asset\.htmlSrc/); assert.match(text, /workspaceRelativePath.*禁止直接写入 HTML/); assert.match(text, /未验证文件已落盘/); }); test('isPathInsidePublishDir blocks escape', () => { const publishDir = `/var/h5/${PUBLISH_ROOT_DIR}/${USER_ID}`; assert.equal(isPathInsidePublishDir(publishDir, `/var/h5/${PUBLISH_ROOT_DIR}/${USER_ID}/report.html`), true); assert.equal(isPathInsidePublishDir(publishDir, `/var/h5/${PUBLISH_ROOT_DIR}/other-id/x.html`), false); }); test('resolvePageDataDeliveryBaseUrl prefers Portal when public base points at local Vite', () => { assert.equal( resolvePageDataDeliveryBaseUrl({ H5_PUBLIC_BASE_URL: 'http://127.0.0.1:5173', H5_PORT: '8081' }), 'http://127.0.0.1:8081', ); assert.equal( resolvePageDataDeliveryBaseUrl({ MEMIND_PAGE_DATA_DELIVERY_BASE_URL: 'http://127.0.0.1:8081', H5_PUBLIC_BASE_URL: 'http://127.0.0.1:5173', }), 'http://127.0.0.1:8081', ); assert.equal( resolvePageDataDeliveryBaseUrl({ H5_PUBLIC_BASE_URL: 'https://m.tkmind.cn' }), 'https://m.tkmind.cn', ); });