Files
memind/server/portal-mindspace-chat-share-routes.test.mjs
T
2026-07-27 15:34:35 +08:00

368 lines
11 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' },
chatSave: {
async createSharedHtml() {
return {
publicUrl:
'https://example.com/MindSpace/user-1/public/shared/page-default.html',
filename: 'page-default.html',
relativePath:
'public/shared/page-default.html',
};
},
async readWorkspaceHtml(input) {
return {
html: '<html><title>Page</title></html>',
relativePath: input.relativePath,
};
},
},
publications: { id: 'publications' },
plazaPosts: { id: 'plaza-posts' },
};
const calls = {
data: [],
errors: [],
plazaErrors: [],
};
return {
calls,
services,
dependencies: {
h5Root: '/workspace',
getMindSpacePages: () => services.pages,
getMindSpaceChatSave: () => services.chatSave,
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 delegates shared HTML write and URL authority to MindSpace', async () => {
const calls = [];
const api = createRouterRecorder();
const dependencies = createDependencies({
getMindSpaceChatSave: () => ({
async createSharedHtml(input) {
calls.push(input);
return {
publicUrl:
'https://example.com/MindSpace/user/public/shared/page-12345678.html',
filename: 'page-12345678.html',
relativePath:
'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[0], {
userId: 'user-1',
html: '<html>Page</html>',
sourceFilename: 'page.html',
});
});
test('quick Plaza from chat preserves dependencies, timing, result, and errors', async () => {
const forwarded = [];
let clock = 1_000;
const api = createRouterRecorder();
const dependencies = createDependencies({
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,
bundle: createBundle(),
body: { title: 'Post title' },
mindSpacePages: dependencies.services.pages,
mindSpacePublications: dependencies.services.publications,
plazaPosts: dependencies.services.plazaPosts,
});
const failureApi = createRouterRecorder();
const plazaFailure = Object.assign(new Error('conflict'), {
code: 'ALREADY_PUBLISHED',
});
const failureDependencies = createDependencies({
quickPlazaFromChatFn: async () => {
throw plazaFailure;
},
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({
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(
typeof forwarded[0].readWorkspaceHtml,
'function',
);
assert.deepEqual(
await forwarded[0].readWorkspaceHtml({
userId: 'user-1',
relativePath: 'public/page.html',
}),
{
html: '<html><title>Page</title></html>',
relativePath: 'public/page.html',
},
);
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({
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,
getMindSpaceChatSave: () => 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);
});