Test conversation package route boundary
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
function defaultSendData(res, _req, data) {
|
||||
return res.json({ data });
|
||||
}
|
||||
|
||||
function defaultRouteError(res, _req, error) {
|
||||
return res.status(500).json({ message: error?.message || 'MindSpace 服务异常' });
|
||||
}
|
||||
|
||||
function resolveRegistry(input) {
|
||||
return typeof input === 'function' ? input() : input;
|
||||
}
|
||||
|
||||
export function createGetConversationPackageHandler({
|
||||
registry,
|
||||
getRegistry,
|
||||
userAuth,
|
||||
getUserAuth,
|
||||
ensureMindSpaceEnabled = () => true,
|
||||
sendData = defaultSendData,
|
||||
mindSpaceError = defaultRouteError,
|
||||
} = {}) {
|
||||
return async function getConversationPackage(req, res) {
|
||||
const activeRegistry = resolveRegistry(getRegistry ?? registry);
|
||||
if (!activeRegistry || !ensureMindSpaceEnabled(res, req)) {
|
||||
if (res.headersSent) return undefined;
|
||||
return res.status(503).json({ message: 'MindSpace 对话包未启用' });
|
||||
}
|
||||
const sessionId = String(req.params?.sessionId ?? '').trim();
|
||||
if (!sessionId) return res.status(400).json({ message: '缺少 sessionId' });
|
||||
|
||||
try {
|
||||
const activeUserAuth = resolveRegistry(getUserAuth ?? userAuth);
|
||||
if (!(await activeUserAuth.ownsSession(req.currentUser.id, sessionId))) {
|
||||
return res.status(403).json({ message: '无权访问该会话' });
|
||||
}
|
||||
const manifest = await activeRegistry.readManifestForSession({
|
||||
userId: req.currentUser.id,
|
||||
sessionId,
|
||||
});
|
||||
if (!manifest) return res.status(404).json({ message: '对话包不存在' });
|
||||
return sendData(res, req, manifest);
|
||||
} catch (error) {
|
||||
return mindSpaceError(res, req, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createGetConversationPackageHandler } from './mindspace-conversation-package-routes.mjs';
|
||||
|
||||
function createResponseRecorder() {
|
||||
return {
|
||||
statusCode: 200,
|
||||
headersSent: false,
|
||||
body: null,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json(payload) {
|
||||
this.body = payload;
|
||||
this.headersSent = true;
|
||||
return this;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('GET conversation package returns manifest for owned sessions', async () => {
|
||||
const handler = createGetConversationPackageHandler({
|
||||
registry: {
|
||||
async readManifestForSession(input) {
|
||||
assert.deepEqual(input, { userId: 'user-1', sessionId: 'session-1' });
|
||||
return { packageId: 'cp_session-1', artifacts: [{ artifactId: 'ca_1', kind: 'public_html' }] };
|
||||
},
|
||||
},
|
||||
userAuth: {
|
||||
async ownsSession(userId, sessionId) {
|
||||
assert.equal(userId, 'user-1');
|
||||
assert.equal(sessionId, 'session-1');
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
const res = createResponseRecorder();
|
||||
|
||||
await handler({ currentUser: { id: 'user-1' }, params: { sessionId: 'session-1' } }, res);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.deepEqual(res.body, {
|
||||
data: { packageId: 'cp_session-1', artifacts: [{ artifactId: 'ca_1', kind: 'public_html' }] },
|
||||
});
|
||||
});
|
||||
|
||||
test('GET conversation package blocks sessions owned by another user', async () => {
|
||||
const handler = createGetConversationPackageHandler({
|
||||
registry: {
|
||||
async readManifestForSession() {
|
||||
assert.fail('registry should not be called for unauthorized sessions');
|
||||
},
|
||||
},
|
||||
userAuth: {
|
||||
async ownsSession() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
const res = createResponseRecorder();
|
||||
|
||||
await handler({ currentUser: { id: 'user-1' }, params: { sessionId: 'session-2' } }, res);
|
||||
|
||||
assert.equal(res.statusCode, 403);
|
||||
assert.deepEqual(res.body, { message: '无权访问该会话' });
|
||||
});
|
||||
|
||||
test('GET conversation package returns 404 when package has not been recorded', async () => {
|
||||
const handler = createGetConversationPackageHandler({
|
||||
registry: {
|
||||
async readManifestForSession() {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
userAuth: {
|
||||
async ownsSession() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
const res = createResponseRecorder();
|
||||
|
||||
await handler({ currentUser: { id: 'user-1' }, params: { sessionId: 'session-empty' } }, res);
|
||||
|
||||
assert.equal(res.statusCode, 404);
|
||||
assert.deepEqual(res.body, { message: '对话包不存在' });
|
||||
});
|
||||
|
||||
test('GET conversation package respects MindSpace feature gates', async () => {
|
||||
const handler = createGetConversationPackageHandler({
|
||||
registry: {},
|
||||
userAuth: {
|
||||
async ownsSession() {
|
||||
assert.fail('user ownership should not be checked when feature gate rejects');
|
||||
},
|
||||
},
|
||||
ensureMindSpaceEnabled(res) {
|
||||
res.status(403).json({ message: 'MindSpace 已关闭' });
|
||||
return false;
|
||||
},
|
||||
});
|
||||
const res = createResponseRecorder();
|
||||
|
||||
await handler({ currentUser: { id: 'user-1' }, params: { sessionId: 'session-1' } }, res);
|
||||
|
||||
assert.equal(res.statusCode, 403);
|
||||
assert.deepEqual(res.body, { message: 'MindSpace 已关闭' });
|
||||
});
|
||||
+1
-1
@@ -38,7 +38,7 @@
|
||||
"build:portal-runtime": "node scripts/build-portal-runtime.mjs",
|
||||
"check:mindspace-public-links": "node scripts/check-mindspace-public-links.mjs --downloads-only",
|
||||
"check:mindspace-public-links:all": "node scripts/check-mindspace-public-links.mjs --all-links",
|
||||
"test": "node --test auth.test.mjs asr-proxy.test.mjs billing.test.mjs billing-recharge.test.mjs wechat-pay.test.mjs wechat-oauth.test.mjs wechat-mp.test.mjs schedule-intent.test.mjs schedule-reminder-worker.test.mjs capabilities.test.mjs policies.test.mjs chat-skills.test.mjs chat-finish-sync.test.mjs conversation-display.test.mjs user-publish.test.mjs user-memory-profile.test.mjs skills-registry.test.mjs agent-run-gateway.test.mjs agent-run-routes.test.mjs tool-gateway.test.mjs mindspace.test.mjs mindspace-scan.test.mjs mindspace-assets.test.mjs mindspace-pages.test.mjs mindspace-html-download-links.test.mjs mindspace-page-purge.test.mjs mindspace-publications.test.mjs mindspace-public-links.test.mjs mindspace-chat-save.test.mjs mindspace-public-finish-sync.test.mjs mindspace-chat-context.test.mjs mindspace-canonical-url.test.mjs mindspace-conversation-package.test.mjs mindspace-conversation-package-registry.test.mjs mindspace-conversation-package-store.test.mjs mindspace-conversation-schema.test.mjs mindspace-service.test.mjs mindspace-storage-adapter.test.mjs mindspace-content-scan.test.mjs mindspace-html-localize.test.mjs mindspace-visual-editor.test.mjs mindspace-cleanup.test.mjs mindspace-thumbnails.test.mjs mindspace-workspace-thumbnails.test.mjs mindspace-workspace-sync.test.mjs mindspace-asset-preview.test.mjs mindspace-agent-jobs.test.mjs mindspace-agent-runner.test.mjs mindspace-sandbox-mcp.test.mjs message-stream.test.mjs plaza-posts.test.mjs plaza-interactions.test.mjs plaza-algorithm.test.mjs plaza-seo.test.mjs plaza-ops.test.mjs user-auth.test.mjs llm-providers.test.mjs admin-guard.test.mjs user-feedback.test.mjs",
|
||||
"test": "node --test auth.test.mjs asr-proxy.test.mjs billing.test.mjs billing-recharge.test.mjs wechat-pay.test.mjs wechat-oauth.test.mjs wechat-mp.test.mjs schedule-intent.test.mjs schedule-reminder-worker.test.mjs capabilities.test.mjs policies.test.mjs chat-skills.test.mjs chat-finish-sync.test.mjs conversation-display.test.mjs user-publish.test.mjs user-memory-profile.test.mjs skills-registry.test.mjs agent-run-gateway.test.mjs agent-run-routes.test.mjs tool-gateway.test.mjs mindspace.test.mjs mindspace-scan.test.mjs mindspace-assets.test.mjs mindspace-pages.test.mjs mindspace-html-download-links.test.mjs mindspace-page-purge.test.mjs mindspace-publications.test.mjs mindspace-public-links.test.mjs mindspace-chat-save.test.mjs mindspace-public-finish-sync.test.mjs mindspace-chat-context.test.mjs mindspace-canonical-url.test.mjs mindspace-conversation-package.test.mjs mindspace-conversation-package-registry.test.mjs mindspace-conversation-package-routes.test.mjs mindspace-conversation-package-store.test.mjs mindspace-conversation-schema.test.mjs mindspace-service.test.mjs mindspace-storage-adapter.test.mjs mindspace-content-scan.test.mjs mindspace-html-localize.test.mjs mindspace-visual-editor.test.mjs mindspace-cleanup.test.mjs mindspace-thumbnails.test.mjs mindspace-workspace-thumbnails.test.mjs mindspace-workspace-sync.test.mjs mindspace-asset-preview.test.mjs mindspace-agent-jobs.test.mjs mindspace-agent-runner.test.mjs mindspace-sandbox-mcp.test.mjs message-stream.test.mjs plaza-posts.test.mjs plaza-interactions.test.mjs plaza-algorithm.test.mjs plaza-seo.test.mjs plaza-ops.test.mjs user-auth.test.mjs llm-providers.test.mjs admin-guard.test.mjs user-feedback.test.mjs",
|
||||
"verify:chat-finish-sync": "node scripts/verify-chat-finish-sync.mjs",
|
||||
"verify:public-finish-sync-runtime": "node scripts/verify-public-finish-sync-runtime.mjs",
|
||||
"verify:mindspace-publish-guards": "node scripts/verify-mindspace-publish-guards.mjs",
|
||||
|
||||
+8
-21
@@ -43,6 +43,7 @@ import { ensureMindSpaceConfig } from './mindspace-config.mjs';
|
||||
import { createAssetService } from './mindspace-assets.mjs';
|
||||
import { createPageService, pageInternals, inlinePrivateAssetsInHtml, normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
|
||||
import { createConversationPackageRegistry } from './mindspace-conversation-package-registry.mjs';
|
||||
import { createGetConversationPackageHandler } from './mindspace-conversation-package-routes.mjs';
|
||||
import { createConversationPackageStore } from './mindspace-conversation-package-store.mjs';
|
||||
import { createMindSpaceServiceFacade } from './mindspace-service.mjs';
|
||||
import { createLocalMindSpaceStorageAdapter } from './mindspace-storage-adapter.mjs';
|
||||
@@ -2487,27 +2488,13 @@ api.post('/internal/agent/jobs/:jobId/complete', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
api.get('/mindspace/v1/conversation-packages/:sessionId', async (req, res) => {
|
||||
if (!mindSpaceConversationPackageRegistry || !ensureMindSpaceEnabled(res, req)) {
|
||||
if (res.headersSent) return;
|
||||
return res.status(503).json({ message: 'MindSpace 对话包未启用' });
|
||||
}
|
||||
const sessionId = String(req.params.sessionId ?? '').trim();
|
||||
if (!sessionId) return res.status(400).json({ message: '缺少 sessionId' });
|
||||
if (!(await userAuth.ownsSession(req.currentUser.id, sessionId))) {
|
||||
return res.status(403).json({ message: '无权访问该会话' });
|
||||
}
|
||||
try {
|
||||
const manifest = await mindSpaceConversationPackageRegistry.readManifestForSession({
|
||||
userId: req.currentUser.id,
|
||||
sessionId,
|
||||
});
|
||||
if (!manifest) return res.status(404).json({ message: '对话包不存在' });
|
||||
return sendData(res, req, manifest);
|
||||
} catch (error) {
|
||||
return mindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
api.get('/mindspace/v1/conversation-packages/:sessionId', createGetConversationPackageHandler({
|
||||
getRegistry: () => mindSpaceConversationPackageRegistry,
|
||||
getUserAuth: () => userAuth,
|
||||
ensureMindSpaceEnabled,
|
||||
sendData,
|
||||
mindSpaceError,
|
||||
}));
|
||||
|
||||
api.post('/mindspace/v1/uploads', async (req, res) => {
|
||||
if (!mindSpaceAssets || !ensureMindSpaceEnabled(res, req, { upload: true })) return;
|
||||
|
||||
Reference in New Issue
Block a user