Files
memind/mindspace-remote-server-adapter.test.mjs
john 53eb0014ba fix(mindspace): map publication RPC errors to HTTP status codes
Return 404/401/403 from MindSpace RPC for known publication errors and
preserve error codes through the Portal remote adapter.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 19:44:58 +08:00

159 lines
5.3 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
createMindSpaceRemoteServerAdapter,
mindspaceRemoteServerAdapterInternals,
} from './mindspace-remote-server-adapter.mjs';
test('buildRemoteOperationUrl normalizes endpoint and encodes operation path', () => {
assert.equal(
mindspaceRemoteServerAdapterInternals.buildRemoteOperationUrl(
'https://mindspace.example.com/',
'assetService',
'renderAssetPreview',
'/internal/mindspace-adapter',
),
'https://mindspace.example.com/internal/mindspace-adapter/assetService/renderAssetPreview',
);
});
test('createMindSpaceRemoteServerAdapter validates endpoint at startup', () => {
const adapter = createMindSpaceRemoteServerAdapter({
fetchFn: async () => {
throw new Error('should not fetch');
},
});
assert.throws(() => adapter.assertReady(), /MINDSPACE_REMOTE_BASE_URL/);
});
test('createMindSpaceRemoteServerAdapter proxies server binding methods through fetch transport', async () => {
const calls = [];
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com/',
authToken: 'secret-token',
operationBasePath: '/internal/mindspace-adapter/',
timeoutMs: 3000,
fetchFn: async (url, init) => {
calls.push({ url, init });
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({ ok: true, source: 'remote' });
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const result = await adapter.assetService.renderAssetPreview({ userId: 'u1', assetId: 'a1' });
assert.deepEqual(result, { ok: true, source: 'remote' });
assert.equal(calls.length, 1);
assert.equal(
calls[0].url,
'https://mindspace.example.com/internal/mindspace-adapter/assetService/renderAssetPreview',
);
assert.equal(calls[0].init.method, 'POST');
assert.equal(calls[0].init.headers.Authorization, 'Bearer secret-token');
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [{ userId: 'u1', assetId: 'a1' }],
});
});
test('createMindSpaceRemoteServerAdapter exposes conversation package read methods', async () => {
const calls = [];
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com/',
authToken: 'secret-token',
fetchFn: async (url, init) => {
calls.push({ url, init });
return {
ok: true,
status: 200,
async text() {
return JSON.stringify({ packageId: 'cp-session-1' });
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
await adapter.conversationPackageRegistry.readManifestForSession({
userId: 'user-1',
sessionId: 'session-1',
});
await adapter.conversationPackageRegistry.readArtifactObject({
userId: 'user-1',
sessionId: 'session-1',
artifactId: 'artifact-1',
});
assert.match(calls[0].url, /conversationPackageRegistry\/readManifestForSession$/);
assert.match(calls[1].url, /conversationPackageRegistry\/readArtifactObject$/);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [{ userId: 'user-1', sessionId: 'session-1' }],
});
assert.deepEqual(JSON.parse(calls[1].init.body), {
args: [{ userId: 'user-1', sessionId: 'session-1', artifactId: 'artifact-1' }],
});
assert.equal(calls[0].init.headers.Authorization, 'Bearer secret-token');
assert.equal(calls[1].init.headers.Authorization, 'Bearer secret-token');
});
test('createMindSpaceRemoteServerAdapter rejects unknown binding methods', () => {
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com',
fetchFn: async () => ({ ok: true, status: 204, text: async () => '' }),
logger: { log() {}, warn() {}, error() {} },
});
assert.throws(() => adapter.assetService.notARealMethod, /does not expose/);
});
test('createMindSpaceRemoteServerAdapter surfaces remote conversation package errors with binding context', async () => {
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com/',
authToken: 'secret-token',
fetchFn: async () => ({
ok: false,
status: 502,
async text() {
return 'upstream manifest missing';
},
}),
logger: { log() {}, warn() {}, error() {} },
});
await assert.rejects(
() => adapter.conversationPackageRegistry.readManifestForSession({
userId: 'user-1',
sessionId: 'session-1',
}),
/conversationPackageRegistry\.readManifestForSession\(\) failed with 502: upstream manifest missing/,
);
});
test('createMindSpaceRemoteServerAdapter preserves publication error codes from rpc payload', async () => {
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com/',
authToken: 'secret-token',
fetchFn: async () => ({
ok: false,
status: 404,
async text() {
return JSON.stringify({
message: '公开页面不存在',
code: 'publication_not_found',
});
},
}),
logger: { log() {}, warn() {}, error() {} },
});
await assert.rejects(
() => adapter.publicationService.resolvePublic('john', 'missing-page', null, null, {}),
(error) => error instanceof Error && error.code === 'publication_not_found',
);
});