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: 'ready', relativePath: 'public/report.html', changed: true, }); }, }; }, logger: { log() {}, warn() {}, error() {} }, }); const input = { userId: 'user-1', sourceContent: 'source', html: 'draft', 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: 'page', 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: '', }); }, }; }, 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', ); });