373 lines
12 KiB
JavaScript
373 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { attachPortalMindSpaceChatShareRoutes } from './portal-mindspace-chat-share-routes.mjs';
|
|
|
|
function createRouterRecorder() {
|
|
const routes = new Map();
|
|
return {
|
|
routes,
|
|
get(path, handler) {
|
|
routes.set(`GET ${path}`, handler);
|
|
},
|
|
post(path, handler) {
|
|
routes.set(`POST ${path}`, handler);
|
|
},
|
|
};
|
|
}
|
|
|
|
function createResponseRecorder() {
|
|
return {
|
|
statusCode: 200,
|
|
body: undefined,
|
|
status(code) {
|
|
this.statusCode = code;
|
|
return this;
|
|
},
|
|
json(body) {
|
|
this.body = body;
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createRequest(overrides = {}) {
|
|
return {
|
|
body: {},
|
|
query: {},
|
|
currentUser: { id: 'user-1', username: 'alice' },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createBundle() {
|
|
return {
|
|
resolvedHtml: {
|
|
content: '<html>Page</html>',
|
|
filename: 'page.html',
|
|
},
|
|
};
|
|
}
|
|
|
|
function createDependencies(overrides = {}) {
|
|
const services = {
|
|
pages: { id: 'pages' },
|
|
publications: { id: 'publications' },
|
|
plazaPosts: { id: 'plaza-posts' },
|
|
};
|
|
const calls = {
|
|
data: [],
|
|
errors: [],
|
|
plazaErrors: [],
|
|
};
|
|
return {
|
|
calls,
|
|
services,
|
|
dependencies: {
|
|
h5Root: '/workspace',
|
|
env: {},
|
|
getMindSpacePages: () => services.pages,
|
|
getMindSpacePublications: () => services.publications,
|
|
getPlazaPosts: () => services.plazaPosts,
|
|
resolveChatSaveBundle: async () => createBundle(),
|
|
resolvePlazaPostUrl: (postId) => `/plaza/p/${postId}`,
|
|
sendData(res, req, data, status = 200) {
|
|
calls.data.push({ res, req, data, status });
|
|
return res.status(status).json({ data });
|
|
},
|
|
sendError(res, req, status, code, message) {
|
|
calls.errors.push({ res, req, status, code, message });
|
|
return res.status(status).json({ error: { code, message } });
|
|
},
|
|
handlePlazaError(res, req, error) {
|
|
calls.plazaErrors.push({ res, req, error });
|
|
return res.status(409).json({ plaza: error.code });
|
|
},
|
|
handleMindSpaceError(res, req, error) {
|
|
calls.errors.push({ res, req, error });
|
|
return res.status(418).json({ mapped: error.message });
|
|
},
|
|
logger: {
|
|
log() {},
|
|
info() {},
|
|
error() {},
|
|
},
|
|
...overrides,
|
|
},
|
|
};
|
|
}
|
|
|
|
test('chat share module preserves route inventory and order', () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalMindSpaceChatShareRoutes(api, createDependencies().dependencies);
|
|
assert.deepEqual([...api.routes.keys()], [
|
|
'POST /mindspace/v1/pages/quick-share-from-chat',
|
|
'POST /mindspace/v1/pages/quick-plaza-from-chat',
|
|
'GET /mindspace/v1/pages/quick-plaza-from-public-html/status',
|
|
'POST /mindspace/v1/pages/quick-plaza-from-public-html',
|
|
]);
|
|
});
|
|
|
|
test('quick share preserves localization, path, write, and 201 response', async () => {
|
|
const calls = [];
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
getAuthPool: () => ({ id: 'pool' }),
|
|
resolveMindSpaceRuntimeConfigFn: () => ({
|
|
storageRoot: '/storage',
|
|
}),
|
|
async inlinePrivateAssetsInHtmlFn(
|
|
pool,
|
|
storageRoot,
|
|
userId,
|
|
html,
|
|
) {
|
|
calls.push({ kind: 'inline', pool, storageRoot, userId, html });
|
|
return { html: '<html>Localized</html>' };
|
|
},
|
|
resolveMindSpaceUserPublishDirFn: () => '/workspace/user',
|
|
async mkdir(dir, options) {
|
|
calls.push({ kind: 'mkdir', dir, options });
|
|
},
|
|
rewriteWorkspacePublicAssetReferencesFn(html, relativePath) {
|
|
calls.push({ kind: 'rewrite', html, relativePath });
|
|
return '<html>Rewritten</html>';
|
|
},
|
|
async writeFile(filePath, content, encoding) {
|
|
calls.push({ kind: 'write', filePath, content, encoding });
|
|
},
|
|
randomUUID: () => '12345678-aaaa-bbbb-cccc-dddddddddddd',
|
|
buildMindSpacePublicUrlForUserFn(input) {
|
|
calls.push({ kind: 'url', input });
|
|
return 'https://example.com/MindSpace/user/public/shared/page-12345678.html';
|
|
},
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(api, dependencies.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /mindspace/v1/pages/quick-share-from-chat')(
|
|
createRequest({ body: { session_id: 'session-1' } }),
|
|
res,
|
|
);
|
|
assert.equal(res.statusCode, 201);
|
|
assert.deepEqual(res.body, {
|
|
data: {
|
|
publicUrl:
|
|
'https://example.com/MindSpace/user/public/shared/page-12345678.html',
|
|
filename: 'page-12345678.html',
|
|
},
|
|
});
|
|
assert.deepEqual(calls.find((call) => call.kind === 'inline'), {
|
|
kind: 'inline',
|
|
pool: { id: 'pool' },
|
|
storageRoot: '/storage',
|
|
userId: 'user-1',
|
|
html: '<html>Page</html>',
|
|
});
|
|
assert.deepEqual(calls.find((call) => call.kind === 'rewrite'), {
|
|
kind: 'rewrite',
|
|
html: '<html>Localized</html>',
|
|
relativePath: 'public/shared/page-12345678.html',
|
|
});
|
|
assert.deepEqual(calls.find((call) => call.kind === 'write'), {
|
|
kind: 'write',
|
|
filePath: '/workspace/user/public/shared/page-12345678.html',
|
|
content: '<html>Rewritten</html>',
|
|
encoding: 'utf8',
|
|
});
|
|
});
|
|
|
|
test('quick Plaza from chat preserves dependencies, timing, result, and errors', async () => {
|
|
const forwarded = [];
|
|
let clock = 1_000;
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
resolveMindSpaceUserPublishDirFn: () => '/workspace/user',
|
|
now: () => {
|
|
clock += 25;
|
|
return clock;
|
|
},
|
|
async quickPlazaFromChatFn(input) {
|
|
forwarded.push(input);
|
|
return {
|
|
pageId: 'page-1',
|
|
publicationId: 'publication-1',
|
|
post: { id: 'post-1' },
|
|
};
|
|
},
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(api, dependencies.dependencies);
|
|
const req = createRequest({ body: { title: 'Post title' } });
|
|
await api.routes.get('POST /mindspace/v1/pages/quick-plaza-from-chat')(
|
|
req,
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(dependencies.calls.data[0].status, 201);
|
|
assert.deepEqual(forwarded[0], {
|
|
user: req.currentUser,
|
|
h5Root: '/workspace',
|
|
bundle: createBundle(),
|
|
body: { title: 'Post title' },
|
|
mindSpacePages: dependencies.services.pages,
|
|
mindSpacePublications: dependencies.services.publications,
|
|
plazaPosts: dependencies.services.plazaPosts,
|
|
publishDir: '/workspace/user',
|
|
});
|
|
|
|
const failureApi = createRouterRecorder();
|
|
const plazaFailure = Object.assign(new Error('conflict'), {
|
|
code: 'ALREADY_PUBLISHED',
|
|
});
|
|
const failureDependencies = createDependencies({
|
|
quickPlazaFromChatFn: async () => {
|
|
throw plazaFailure;
|
|
},
|
|
resolveMindSpaceUserPublishDirFn: () => '/workspace/user',
|
|
mapPlazaError: () => 409,
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(
|
|
failureApi,
|
|
failureDependencies.dependencies,
|
|
);
|
|
await failureApi.routes.get(
|
|
'POST /mindspace/v1/pages/quick-plaza-from-chat',
|
|
)(createRequest(), createResponseRecorder());
|
|
assert.equal(
|
|
failureDependencies.calls.plazaErrors[0].error,
|
|
plazaFailure,
|
|
);
|
|
});
|
|
|
|
test('quick Plaza status preserves relative path, viewer result, and URL', async () => {
|
|
const forwarded = [];
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
async getQuickPlazaFromPublicHtmlStatusFn(input) {
|
|
forwarded.push(input);
|
|
return {
|
|
published: true,
|
|
post: { id: 'post-1' },
|
|
};
|
|
},
|
|
resolvePlazaPostUrl(postId, req) {
|
|
assert.equal(req.query.relativePath, ' public/page.html ');
|
|
return `https://plaza.example.com/p/${postId}`;
|
|
},
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(api, dependencies.dependencies);
|
|
const req = createRequest({
|
|
query: { relativePath: ' public/page.html ' },
|
|
});
|
|
await api.routes.get(
|
|
'GET /mindspace/v1/pages/quick-plaza-from-public-html/status',
|
|
)(req, createResponseRecorder());
|
|
assert.equal(forwarded[0].relativePath, 'public/page.html');
|
|
assert.equal(forwarded[0].mindSpacePages, dependencies.services.pages);
|
|
assert.deepEqual(dependencies.calls.data[0].data, {
|
|
published: true,
|
|
post: { id: 'post-1' },
|
|
plaza_url: 'https://plaza.example.com/p/post-1',
|
|
});
|
|
});
|
|
|
|
test('quick Plaza public HTML preserves publish input, URL, and 201 response', async () => {
|
|
const forwarded = [];
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
resolveMindSpaceUserPublishDirFn: () => '/workspace/user',
|
|
async quickPlazaFromPublicHtmlFn(input) {
|
|
forwarded.push(input);
|
|
return {
|
|
pageId: 'page-1',
|
|
publicationId: 'publication-1',
|
|
post: { id: 'post-1' },
|
|
};
|
|
},
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(api, dependencies.dependencies);
|
|
await api.routes.get(
|
|
'POST /mindspace/v1/pages/quick-plaza-from-public-html',
|
|
)(
|
|
createRequest({
|
|
body: { relative_path: ' public/page.html ' },
|
|
}),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(forwarded[0].relativePath, 'public/page.html');
|
|
assert.equal(forwarded[0].publishDir, '/workspace/user');
|
|
assert.equal(dependencies.calls.data[0].status, 201);
|
|
assert.deepEqual(dependencies.calls.data[0].data, {
|
|
pageId: 'page-1',
|
|
publicationId: 'publication-1',
|
|
post: { id: 'post-1' },
|
|
plaza_url: '/plaza/p/post-1',
|
|
});
|
|
});
|
|
|
|
test('quick Plaza public HTML enriches already-published errors with URL', async () => {
|
|
const failure = Object.assign(new Error('already published'), {
|
|
code: 'ALREADY_PUBLISHED',
|
|
details: { post_id: 'post-existing' },
|
|
});
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
resolveMindSpaceUserPublishDirFn: () => '/workspace/user',
|
|
quickPlazaFromPublicHtmlFn: async () => {
|
|
throw failure;
|
|
},
|
|
mapPlazaError: () => 409,
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(api, dependencies.dependencies);
|
|
await api.routes.get(
|
|
'POST /mindspace/v1/pages/quick-plaza-from-public-html',
|
|
)(createRequest(), createResponseRecorder());
|
|
assert.equal(
|
|
failure.details.plaza_url,
|
|
'/plaza/p/post-existing',
|
|
);
|
|
assert.equal(dependencies.calls.plazaErrors[0].error, failure);
|
|
});
|
|
|
|
test('chat share routes preserve availability gates and MindSpace fallback errors', async () => {
|
|
const unavailableApi = createRouterRecorder();
|
|
const unavailable = createDependencies({
|
|
getMindSpacePages: () => null,
|
|
getMindSpacePublications: () => null,
|
|
getPlazaPosts: () => null,
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(
|
|
unavailableApi,
|
|
unavailable.dependencies,
|
|
);
|
|
const shareRes = createResponseRecorder();
|
|
await unavailableApi.routes.get(
|
|
'POST /mindspace/v1/pages/quick-share-from-chat',
|
|
)(createRequest(), shareRes);
|
|
assert.equal(shareRes.statusCode, 503);
|
|
|
|
for (const route of [
|
|
'POST /mindspace/v1/pages/quick-plaza-from-chat',
|
|
'GET /mindspace/v1/pages/quick-plaza-from-public-html/status',
|
|
'POST /mindspace/v1/pages/quick-plaza-from-public-html',
|
|
]) {
|
|
const res = createResponseRecorder();
|
|
await unavailableApi.routes.get(route)(createRequest(), res);
|
|
assert.equal(res.statusCode, 503, route);
|
|
assert.equal(res.body.error.code, 'plaza_unavailable');
|
|
}
|
|
|
|
const failureApi = createRouterRecorder();
|
|
const failure = new Error('share failed');
|
|
const dependencies = createDependencies({
|
|
resolveChatSaveBundle: async () => {
|
|
throw failure;
|
|
},
|
|
});
|
|
attachPortalMindSpaceChatShareRoutes(
|
|
failureApi,
|
|
dependencies.dependencies,
|
|
);
|
|
await failureApi.routes.get(
|
|
'POST /mindspace/v1/pages/quick-share-from-chat',
|
|
)(createRequest(), createResponseRecorder());
|
|
assert.equal(dependencies.calls.errors.at(-1).error, failure);
|
|
});
|