289 lines
8.1 KiB
JavaScript
289 lines
8.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { attachPortalMindSpaceAgentOperationRoutes } from './portal-mindspace-agent-operation-routes.mjs';
|
|
|
|
function createRouterRecorder() {
|
|
const routes = new Map();
|
|
return {
|
|
routes,
|
|
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: {},
|
|
ip: '127.0.0.1',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createDependencies(overrides = {}) {
|
|
const calls = {
|
|
data: [],
|
|
errors: [],
|
|
mapped: [],
|
|
};
|
|
return {
|
|
calls,
|
|
dependencies: {
|
|
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 } });
|
|
},
|
|
handleMindSpaceError(res, req, error) {
|
|
calls.mapped.push({ res, req, error });
|
|
return res.status(418).json({ mapped: error.message });
|
|
},
|
|
...overrides,
|
|
},
|
|
};
|
|
}
|
|
|
|
test('MindSpace agent operation module preserves route inventory and order', () => {
|
|
const api = createRouterRecorder();
|
|
attachPortalMindSpaceAgentOperationRoutes(api);
|
|
assert.deepEqual([...api.routes.keys()], [
|
|
'POST /agent/mindspace_page_patch',
|
|
'POST /agent/mindspace_asset_delete',
|
|
'POST /agent/mindspace_asset_download',
|
|
]);
|
|
});
|
|
|
|
test('page patch preserves payload projection and unavailable response', async () => {
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
getMindSpacePageLiveEdit: () => ({
|
|
async applyAgentPatch(body) {
|
|
assert.deepEqual(body, { page_id: 'page-1', patch: 'title' });
|
|
return {
|
|
page: {
|
|
id: 'page-1',
|
|
title: 'Updated',
|
|
summary: 'Summary',
|
|
versionNo: 3,
|
|
updatedAt: '2026-07-23T00:00:00Z',
|
|
},
|
|
liveRevision: 7,
|
|
internal: 'hidden',
|
|
};
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(api, dependencies.dependencies);
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /agent/mindspace_page_patch')(
|
|
createRequest({ body: { page_id: 'page-1', patch: 'title' } }),
|
|
res,
|
|
);
|
|
assert.deepEqual(res.body, {
|
|
data: {
|
|
pageId: 'page-1',
|
|
title: 'Updated',
|
|
summary: 'Summary',
|
|
versionNo: 3,
|
|
updatedAt: '2026-07-23T00:00:00Z',
|
|
liveRevision: 7,
|
|
},
|
|
});
|
|
|
|
const unavailableApi = createRouterRecorder();
|
|
attachPortalMindSpaceAgentOperationRoutes(unavailableApi);
|
|
const unavailableRes = createResponseRecorder();
|
|
await unavailableApi.routes.get('POST /agent/mindspace_page_patch')(
|
|
createRequest(),
|
|
unavailableRes,
|
|
);
|
|
assert.equal(unavailableRes.statusCode, 503);
|
|
assert.deepEqual(unavailableRes.body, { message: 'MindSpace 未启用' });
|
|
});
|
|
|
|
test('page patch preserves expected and fallback error mapping', async () => {
|
|
for (const [code, expectedStatus] of [
|
|
['forbidden', 403],
|
|
['page_binding_mismatch', 403],
|
|
['invalid_request', 400],
|
|
]) {
|
|
const api = createRouterRecorder();
|
|
const failure = Object.assign(new Error(code), { code });
|
|
const dependencies = createDependencies({
|
|
getMindSpacePageLiveEdit: () => ({
|
|
async applyAgentPatch() {
|
|
throw failure;
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(api, dependencies.dependencies);
|
|
await api.routes.get('POST /agent/mindspace_page_patch')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(dependencies.calls.errors[0].status, expectedStatus);
|
|
assert.equal(dependencies.calls.errors[0].code, code);
|
|
}
|
|
|
|
const api = createRouterRecorder();
|
|
const failure = new Error('patch failed');
|
|
const dependencies = createDependencies({
|
|
getMindSpacePageLiveEdit: () => ({
|
|
async applyAgentPatch() {
|
|
throw failure;
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(api, dependencies.dependencies);
|
|
await api.routes.get('POST /agent/mindspace_page_patch')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(dependencies.calls.mapped[0].error, failure);
|
|
});
|
|
|
|
test('asset delete preserves body, audit, response, and error mapping', async () => {
|
|
const audit = [];
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
getMindSpaceAssetAgent: () => ({
|
|
async applyAgentDelete(body) {
|
|
assert.deepEqual(body, { asset_id: 'asset-1', confirm: true });
|
|
return { userId: 'user-1', assetId: 'asset-1', deleted: true };
|
|
},
|
|
}),
|
|
getMindSpaceAudit: () => ({
|
|
async write(entry) {
|
|
audit.push(entry);
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(api, dependencies.dependencies);
|
|
const req = createRequest({
|
|
body: { asset_id: 'asset-1', confirm: true },
|
|
ip: '10.0.0.8',
|
|
});
|
|
const res = createResponseRecorder();
|
|
await api.routes.get('POST /agent/mindspace_asset_delete')(req, res);
|
|
assert.deepEqual(audit, [{
|
|
userId: 'user-1',
|
|
action: 'asset.delete',
|
|
objectType: 'asset',
|
|
objectId: 'asset-1',
|
|
ip: '10.0.0.8',
|
|
metadata: { via: 'agent' },
|
|
}]);
|
|
assert.deepEqual(dependencies.calls.data[0].data, {
|
|
userId: 'user-1',
|
|
assetId: 'asset-1',
|
|
deleted: true,
|
|
});
|
|
|
|
for (const [code, expectedStatus] of [
|
|
['forbidden', 403],
|
|
['invalid_request', 400],
|
|
['confirmation_required', 400],
|
|
]) {
|
|
const failureApi = createRouterRecorder();
|
|
const failureDependencies = createDependencies({
|
|
getMindSpaceAssetAgent: () => ({
|
|
async applyAgentDelete() {
|
|
throw Object.assign(new Error(code), { code });
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(
|
|
failureApi,
|
|
failureDependencies.dependencies,
|
|
);
|
|
await failureApi.routes.get('POST /agent/mindspace_asset_delete')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(failureDependencies.calls.errors[0].status, expectedStatus);
|
|
}
|
|
});
|
|
|
|
test('asset download preserves audit metadata and error mapping', async () => {
|
|
const audit = [];
|
|
const api = createRouterRecorder();
|
|
const dependencies = createDependencies({
|
|
getMindSpaceAssetAgent: () => ({
|
|
async applyAgentDownload(body) {
|
|
assert.deepEqual(body, { asset_id: 'asset-2' });
|
|
return {
|
|
userId: 'user-2',
|
|
assetId: 'asset-2',
|
|
relativePath: 'downloads/report.docx',
|
|
};
|
|
},
|
|
}),
|
|
getMindSpaceAudit: () => ({
|
|
async write(entry) {
|
|
audit.push(entry);
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(api, dependencies.dependencies);
|
|
await api.routes.get('POST /agent/mindspace_asset_download')(
|
|
createRequest({
|
|
body: { asset_id: 'asset-2' },
|
|
ip: '10.0.0.9',
|
|
}),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.deepEqual(audit[0], {
|
|
userId: 'user-2',
|
|
action: 'asset.download',
|
|
objectType: 'asset',
|
|
objectId: 'asset-2',
|
|
ip: '10.0.0.9',
|
|
metadata: {
|
|
via: 'agent',
|
|
relativePath: 'downloads/report.docx',
|
|
},
|
|
});
|
|
|
|
for (const [code, expectedStatus] of [
|
|
['forbidden', 403],
|
|
['invalid_request', 400],
|
|
['asset_not_found', 400],
|
|
]) {
|
|
const failureApi = createRouterRecorder();
|
|
const failureDependencies = createDependencies({
|
|
getMindSpaceAssetAgent: () => ({
|
|
async applyAgentDownload() {
|
|
throw Object.assign(new Error(code), { code });
|
|
},
|
|
}),
|
|
});
|
|
attachPortalMindSpaceAgentOperationRoutes(
|
|
failureApi,
|
|
failureDependencies.dependencies,
|
|
);
|
|
await failureApi.routes.get('POST /agent/mindspace_asset_download')(
|
|
createRequest(),
|
|
createResponseRecorder(),
|
|
);
|
|
assert.equal(failureDependencies.calls.errors[0].status, expectedStatus);
|
|
}
|
|
});
|