Files
memind/mindspace-remote-server-adapter.test.mjs

705 lines
19 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
createMindSpaceRemoteServerAdapter,
mindspaceRemoteServerAdapterInternals,
validateMindSpaceRemoteContract,
} from './mindspace-remote-server-adapter.mjs';
import {
MINDSPACE_SERVER_ADAPTER_BINDINGS,
MINDSPACE_SERVER_ADAPTER_CONTRACT_VERSION,
MINDSPACE_SERVER_ADAPTER_REQUIRED_CAPABILITIES,
} from './mindspace-server-adapter-contract.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', async () => {
const adapter = createMindSpaceRemoteServerAdapter({
fetchFn: async () => {
throw new Error('should not fetch');
},
});
await assert.rejects(() => adapter.assertReady(), /MINDSPACE_REMOTE_BASE_URL/);
});
test('createMindSpaceRemoteServerAdapter validates remote contract before startup succeeds', 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({
contractVersion:
MINDSPACE_SERVER_ADAPTER_CONTRACT_VERSION,
bindings: MINDSPACE_SERVER_ADAPTER_BINDINGS,
requiredCapabilities:
MINDSPACE_SERVER_ADAPTER_REQUIRED_CAPABILITIES,
buildId: 'mindspace-test',
gitSha: 'abc123',
});
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
await assert.doesNotReject(() => adapter.assertReady());
assert.equal(
calls[0].url,
'https://mindspace.example.com/mindspace/v1/contract',
);
assert.equal(calls[0].init.method, 'GET');
assert.equal(
calls[0].init.headers.Authorization,
'Bearer secret-token',
);
});
test('createMindSpaceRemoteServerAdapter rejects stale remote contracts', async () => {
const adapter = createMindSpaceRemoteServerAdapter({
endpoint: 'https://mindspace.example.com/',
fetchFn: async () => ({
ok: true,
status: 200,
async text() {
return JSON.stringify({
contractVersion: 1,
bindings: {
assetService: ['renderAssetPreview'],
},
requiredCapabilities: [],
});
},
}),
logger: { log() {}, warn() {}, error() {} },
});
await assert.rejects(
() => adapter.assertReady(),
(error) =>
error?.code === 'mindspace_contract_incompatible' &&
/workspacePublicationDeliveryService/.test(error.message),
);
});
test('validateMindSpaceRemoteContract reports missing capabilities and methods', () => {
assert.throws(
() =>
validateMindSpaceRemoteContract({
contractVersion:
MINDSPACE_SERVER_ADAPTER_CONTRACT_VERSION,
bindings: {
chatSaveService: ['createSharedHtml'],
},
requiredCapabilities: ['chat-save-authority'],
}),
/missing method chatSaveService\.materializeWorkspaceHtml/,
);
});
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 exposes the chat save write authority', 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({
html: '<html>ready</html>',
relativePath: 'public/report.html',
changed: true,
});
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const input = {
userId: 'user-1',
sourceContent: 'source',
html: '<html>draft</html>',
relativePath: 'public/report.html',
};
const result =
await adapter.chatSaveService.materializeWorkspaceHtml(input);
assert.equal(result.changed, true);
assert.match(
calls[0].url,
/chatSaveService\/materializeWorkspaceHtml$/,
);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [input],
});
assert.equal(
calls[0].init.headers.Authorization,
'Bearer secret-token',
);
});
test('createMindSpaceRemoteServerAdapter creates shared HTML through MindSpace', 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({
publicUrl:
'https://example.com/MindSpace/user-1/public/shared/page.html',
filename: 'page.html',
});
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const input = {
userId: 'user-1',
html: '<html>page</html>',
sourceFilename: 'page.html',
};
const result =
await adapter.chatSaveService.createSharedHtml(input);
assert.equal(result.filename, 'page.html');
assert.match(
calls[0].url,
/chatSaveService\/createSharedHtml$/,
);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [input],
});
const workspaceInput = {
userId: 'user-1',
sessionId: 'session-1',
relativePath: 'oa/notes.txt',
body: 'generated notes',
};
await adapter.conversationArtifactService
.registerWorkspaceFileArtifact(
workspaceInput,
);
assert.match(
calls[1].url,
/conversationArtifactService\/registerWorkspaceFileArtifact$/,
);
assert.deepEqual(JSON.parse(calls[1].init.body), {
args: [workspaceInput],
});
});
test('createMindSpaceRemoteServerAdapter exposes conversation artifact write authority', 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([
{ artifactId: 'artifact-1' },
]);
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const input = {
userId: 'user-1',
sessionId: 'session-1',
artifactRefs: [
{
relativePath: 'public/report.html',
messageId: 'message-1',
},
],
};
const result =
await adapter.conversationArtifactService
.registerPublicHtmlArtifacts(input);
assert.deepEqual(result, [
{ artifactId: 'artifact-1' },
]);
assert.match(
calls[0].url,
/conversationArtifactService\/registerPublicHtmlArtifacts$/,
);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [input],
});
});
test('createMindSpaceRemoteServerAdapter exposes public Finish write authority', 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({
materialized: ['public/report.html'],
publicHtmlRelativePaths: [
'public/report.html',
],
});
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const input = {
userId: 'user-1',
sessionId: 'session-1',
messages: [],
};
const result =
await adapter.publicFinishService
.syncAfterFinish(input);
assert.deepEqual(result.publicHtmlRelativePaths, [
'public/report.html',
]);
assert.match(
calls[0].url,
/publicFinishService\/syncAfterFinish$/,
);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [input],
});
});
test('createMindSpaceRemoteServerAdapter exposes WeChat Page Data delivery authority', 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({
outcome: { action: 'send' },
deliveryArtifacts: [
{
relativePath:
'public/report.html',
url: 'https://example.com/MindSpace/user-1/public/report.html',
},
],
});
},
};
},
logger: { log() {}, warn() {}, error() {} },
});
const input = {
userId: 'user-1',
reply: {
text: 'published',
messages: [],
},
intent: {
agentText: 'build a survey',
},
publicBaseUrl: 'https://example.com',
};
const result =
await adapter.publicFinishService
.prepareWechatPageDataDelivery(input);
assert.equal(result.outcome.action, 'send');
assert.match(
calls[0].url,
/publicFinishService\/prepareWechatPageDataDelivery$/,
);
assert.deepEqual(JSON.parse(calls[0].init.body), {
args: [input],
});
});
test('createMindSpaceRemoteServerAdapter exposes WeChat HTML and thumbnail authority', async () => {
const calls = [];
const adapter =
createMindSpaceRemoteServerAdapter({
endpoint:
'https://mindspace.example.com/',
authToken: 'secret-token',
fetchFn: async (url, init) => {
calls.push({ url, init });
const thumbnail =
String(url).endsWith(
'/ensureWechatFreshPageThumbnails',
);
return {
ok: true,
status: 200,
async text() {
return JSON.stringify(
thumbnail
? { ok: true }
: {
confirmedArtifacts: [
{
relativePath:
'public/page.html',
url:
'https://example.com/MindSpace/user-1/public/page.html',
},
],
},
);
},
};
},
logger: {
log() {},
warn() {},
error() {},
},
});
const prepareInput = {
userId: 'user-1',
sessionId: 'session-1',
reply: {
text: 'done',
messages: [],
},
};
const thumbnailInput = {
userId: 'user-1',
sessionId: 'session-1',
artifacts: [
{
relativePath:
'public/page.html',
},
],
};
const prepared =
await adapter.publicFinishService
.prepareWechatHtmlDelivery(
prepareInput,
);
const thumbnail =
await adapter.publicFinishService
.ensureWechatFreshPageThumbnails(
thumbnailInput,
);
assert.equal(
prepared.confirmedArtifacts[0]
.relativePath,
'public/page.html',
);
assert.equal(thumbnail.ok, true);
assert.match(
calls[0].url,
/publicFinishService\/prepareWechatHtmlDelivery$/,
);
assert.match(
calls[1].url,
/publicFinishService\/ensureWechatFreshPageThumbnails$/,
);
assert.deepEqual(
JSON.parse(calls[0].init.body),
{ args: [prepareInput] },
);
assert.deepEqual(
JSON.parse(calls[1].init.body),
{ args: [thumbnailInput] },
);
});
test('createMindSpaceRemoteServerAdapter exposes logical workspace delivery authority', 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({
action: 'deliver',
kind: 'html',
ownerId: 'user-1',
relativePath:
'public/report.html',
html: '<html></html>',
});
},
};
},
logger: {
log() {},
warn() {},
error() {},
},
});
const input = {
requestPath:
'/user-1/public/report.html',
};
const result =
await adapter
.workspacePublicationDeliveryService
.resolveWorkspaceRequest(input);
assert.equal(result.kind, 'html');
assert.match(
calls[0].url,
/workspacePublicationDeliveryService\/resolveWorkspaceRequest$/,
);
assert.deepEqual(
JSON.parse(calls[0].init.body),
{ args: [input] },
);
});
test('createMindSpaceRemoteServerAdapter exposes binary and long-image workspace tools', 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({
relativePath:
'public/output.bin',
});
},
};
},
logger: {
log() {},
warn() {},
error() {},
},
});
const binaryInput = {
workspaceRef:
'mindspace://users/user-1/workspace',
sessionId: 'session-1',
packageId: 'cp_session-1',
path: 'public/report.docx',
bodyBase64:
Buffer.from('PK').toString('base64'),
};
const imageInput = {
workspaceRef:
'mindspace://users/user-1/workspace',
sessionId: 'session-1',
packageId: 'cp_session-1',
htmlPath: 'public/report.html',
};
await adapter.workspaceToolService
.writeBinaryFile(binaryInput);
await adapter.workspaceToolService
.generateLongImage(imageInput);
assert.match(
calls[0].url,
/workspaceToolService\/writeBinaryFile$/,
);
assert.match(
calls[1].url,
/workspaceToolService\/generateLongImage$/,
);
assert.deepEqual(
JSON.parse(calls[0].init.body),
{ args: [binaryInput] },
);
assert.deepEqual(
JSON.parse(calls[1].init.body),
{ args: [imageInput] },
);
});
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/);
assert.throws(
() => adapter.conversationPackageRegistry.recordArtifact,
/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',
);
});