import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
createMindSpaceChatSaveService,
mindspaceChatSaveServiceInternals,
} from './mindspace-chat-save-service.mjs';
const USER_ID = '11111111-1111-4111-8111-111111111111';
test('chat save authority creates localized shared HTML and its public URL inside MindSpace', async () => {
const calls = [];
const service = createMindSpaceChatSaveService({
pool: { query: async () => [[]] },
h5Root: '/srv/h5',
storageRoot: '/srv/storage',
resolveMindSpaceUserPublishDirFn: () =>
'/srv/h5/MindSpace/user-1',
async inlinePrivateAssetsInHtmlFn(
pool,
storageRoot,
userId,
html,
) {
calls.push({
kind: 'inline',
pool,
storageRoot,
userId,
html,
});
return { html: 'localized' };
},
rewriteWorkspacePublicAssetReferencesFn(
html,
relativePath,
) {
calls.push({
kind: 'rewrite',
html,
relativePath,
});
return 'shared';
},
async mkdirFn(directory, options) {
calls.push({ kind: 'mkdir', directory, options });
},
async writeFileFn(...args) {
calls.push({ kind: 'write', args });
},
randomUUIDFn: () =>
'12345678-1234-4123-8123-123456789abc',
buildMindSpacePublicUrlForUserFn(input) {
calls.push({ kind: 'url', input });
return 'https://example.com/MindSpace/user-1/public/shared/report-12345678.html';
},
});
const result = await service.createSharedHtml({
userId: 'user-1',
html: 'private',
sourceFilename: '../unsafe report.html',
});
assert.equal(
result.relativePath,
'public/shared/unsafe-report-12345678.html',
);
assert.equal(
result.publicUrl,
'https://example.com/MindSpace/user-1/public/shared/report-12345678.html',
);
assert.equal(calls[0].kind, 'inline');
assert.equal(calls[1].kind, 'rewrite');
assert.equal(calls[2].kind, 'mkdir');
assert.equal(calls[3].kind, 'write');
assert.equal(
calls[3].args[0],
'/srv/h5/MindSpace/user-1/public/shared/unsafe-report-12345678.html',
);
assert.equal(calls[3].args[1], 'shared');
assert.equal(calls[4].kind, 'url');
});
test('chat save authority validates and reads workspace HTML inside MindSpace', async () => {
const calls = [];
const service = createMindSpaceChatSaveService({
pool: { query: async () => [[]] },
h5Root: '/srv/h5',
storageRoot: '/srv/storage',
resolvePublishHtmlAbsolutePathFn(
h5Root,
userId,
relativePath,
) {
calls.push({
kind: 'resolve',
h5Root,
userId,
relativePath,
});
return '/srv/h5/MindSpace/user-1/public/page.html';
},
async readFileFn(...args) {
calls.push({ kind: 'read', args });
return '
Page';
},
});
const result = await service.readWorkspaceHtml({
userId: 'user-1',
relativePath: 'public/page.html',
});
assert.equal(
result.html,
'Page',
);
assert.equal(result.relativePath, 'public/page.html');
assert.equal(calls[0].kind, 'resolve');
assert.equal(calls[1].kind, 'read');
});
test('chat save authority repairs, materializes, and writes workspace HTML inside MindSpace', async () => {
const h5Root = await fs.mkdtemp(
path.join(os.tmpdir(), 'mindspace-chat-save-authority-'),
);
const storageRoot = path.join(h5Root, 'data', 'mindspace');
const storageKey = 'users/u1/assets/asset-good/source.png';
const htmlPath = path.join(
h5Root,
'MindSpace',
USER_ID,
'public',
'report.html',
);
await fs.mkdir(path.dirname(htmlPath), { recursive: true });
await fs.mkdir(
path.dirname(path.join(storageRoot, storageKey)),
{ recursive: true },
);
await fs.writeFile(htmlPath, 'old', 'utf8');
await fs.writeFile(
path.join(storageRoot, storageKey),
Buffer.from('png payload'),
);
const pool = {
async query(sql) {
if (sql.includes('SELECT id FROM h5_assets')) {
return [[{ id: 'asset-good' }]];
}
if (sql.includes('JOIN h5_asset_versions')) {
return [[{
id: 'asset-good',
mime_type: 'image/png',
original_filename: 'source.png',
storage_key: storageKey,
}]];
}
throw new Error(`Unexpected SQL: ${sql}`);
},
};
let writeCount = 0;
const service = createMindSpaceChatSaveService({
pool,
h5Root,
storageRoot,
writeFileFn: async (...args) => {
writeCount += 1;
return fs.writeFile(...args);
},
});
const sourceContent =
`/api/mindspace/v1/assets/asset-good/download`;
const html =
'
';
const first = await service.materializeWorkspaceHtml({
userId: USER_ID,
sourceContent,
html,
relativePath: 'public/report.html',
});
assert.equal(first.changed, true);
assert.equal(first.repairedAssetReferences, true);
assert.equal(first.materializedAssetCount, 1);
assert.equal(first.relativePath, 'public/report.html');
assert.match(first.html, /\.tmp-images\/asset-good\.png/);
assert.doesNotMatch(first.html, /\/api\/mindspace\/v1\/assets\//);
assert.equal(await fs.readFile(htmlPath, 'utf8'), first.html);
assert.equal(
(
await fs.readFile(
path.join(
h5Root,
'MindSpace',
USER_ID,
'public',
'.tmp-images',
'asset-good.png',
),
)
).toString(),
'png payload',
);
const second = await service.materializeWorkspaceHtml({
userId: USER_ID,
sourceContent,
html: first.html,
relativePath: 'public/report.html',
});
assert.equal(second.changed, false);
assert.equal(writeCount, 1);
});
test('chat save authority renders preview thumbnail svg inside MindSpace', async () => {
const h5Root = await fs.mkdtemp(
path.join(os.tmpdir(), 'mindspace-chat-save-thumb-'),
);
const storageRoot = path.join(h5Root, 'data', 'mindspace');
const htmlPath = path.join(
h5Root,
'MindSpace',
USER_ID,
'public',
'report.html',
);
await fs.mkdir(path.dirname(htmlPath), { recursive: true });
await fs.writeFile(htmlPath, 'Report', 'utf8');
const pool = { query: async () => [[]] };
const service = createMindSpaceChatSaveService({
pool,
h5Root,
storageRoot,
});
const rendered = await service.renderPreviewThumbnailSvg({
userId: USER_ID,
relativePath: 'public/report.html',
html: 'Report',
title: 'Report',
subtitle: 'Summary',
force: true,
});
assert.match(rendered.svg, /