import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { createConversationPackageRegistry } from './mindspace-conversation-package-registry.mjs'; import { createConversationPackageStore } from './mindspace-conversation-package-store.mjs'; import { createMindSpaceServiceFacade } from './mindspace-service.mjs'; import { createLocalMindSpaceStorageAdapter } from './mindspace-storage-adapter.mjs'; function createFakePool() { const packages = new Map(); const artifacts = new Map(); return { async query(sql, params = []) { if (/INSERT INTO h5_conversation_packages/i.test(sql)) { const [ id, user_id, session_id, title, status, storage_prefix, manifest_asset_id, created_at, updated_at, ] = params; packages.set(`${user_id}:${session_id}`, { id, user_id, session_id, title, status, storage_prefix, manifest_asset_id, created_at, updated_at, }); return [{ affectedRows: 1 }]; } if (/FROM h5_conversation_packages/i.test(sql)) { return [[packages.get(`${params[0]}:${params[1]}`)].filter(Boolean)]; } if (/INSERT INTO h5_conversation_artifacts/i.test(sql)) { const [ id, package_id, artifact_kind, role, asset_id, page_id, publication_id, agent_run_id, message_id, display_name, mime_type, size_bytes, storage_key, canonical_url, sort_order, created_at, ] = params; artifacts.set(id, { id, package_id, artifact_kind, role, asset_id, page_id, publication_id, agent_run_id, message_id, display_name, mime_type, size_bytes, storage_key, canonical_url, sort_order, created_at, }); return [{ affectedRows: 1 }]; } if (/FROM h5_conversation_artifacts/i.test(sql)) { return [ [...artifacts.values()] .filter((row) => row.package_id === params[0]) .sort( (a, b) => Number(a.sort_order) - Number(b.sort_order) || Number(a.created_at) - Number(b.created_at) || String(a.id).localeCompare(String(b.id)), ), ]; } throw new Error(`unexpected query: ${sql}`); }, }; } test('createConversationPackageRegistry validates required collaborators', () => { assert.throws( () => createConversationPackageRegistry({ store: {}, service: {} }), (error) => error.code === 'missing_conversation_package_store', ); }); test('conversation package registry records artifacts and writes manifest through storage facade', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-registry-')); const service = createMindSpaceServiceFacade({ storageAdapter: createLocalMindSpaceStorageAdapter(root), publicBaseUrl: 'https://m.tkmind.cn', }); const store = createConversationPackageStore(createFakePool()); const registry = createConversationPackageRegistry({ store, service }); const packageRecord = await registry.ensurePackage({ userId: 'user-1', sessionId: 'session-1', title: '研究简报', now: 1000, }); await registry.recordArtifact({ id: 'ca_page', packageId: packageRecord.id, artifactKind: 'public_html', displayName: 'brief.html', canonicalUrl: 'https://m.tkmind.cn/MindSpace/user-1/public/brief.html', sortOrder: 1, now: 2000, }); const result = await registry.writeManifestForSession({ userId: 'user-1', sessionId: 'session-1', }); assert.equal(result.storageKey, 'users/user-1/conversations/session-1/manifest.json'); const manifestJson = await service.getPackageObject({ packageRecord, relativePath: 'manifest.json' }); const manifest = JSON.parse(manifestJson.toString('utf8')); assert.equal(manifest.title, '研究简报'); assert.deepEqual(manifest.artifacts, [ { artifactId: 'ca_page', kind: 'public_html', displayName: 'brief.html', canonicalUrl: 'https://m.tkmind.cn/MindSpace/user-1/public/brief.html', sortOrder: 1, createdAt: 2000, }, ]); }); test('conversation package registry reads manifest without writing storage', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-registry-')); const service = createMindSpaceServiceFacade({ storageAdapter: createLocalMindSpaceStorageAdapter(root), publicBaseUrl: 'https://m.tkmind.cn', }); const store = createConversationPackageStore(createFakePool()); const registry = createConversationPackageRegistry({ store, service }); const packageRecord = await registry.ensurePackage({ userId: 'user-1', sessionId: 'session-1', title: '输入与页面', now: 1000, }); await registry.recordArtifact({ id: 'ca_image', packageId: packageRecord.id, artifactKind: 'input_image', displayName: 'cover.png', sortOrder: 1, now: 1000, }); const manifest = await registry.readManifestForSession({ userId: 'user-1', sessionId: 'session-1', }); assert.equal(manifest.packageId, 'cp_session-1'); assert.equal(manifest.title, '输入与页面'); assert.deepEqual( manifest.artifacts.map((item) => item.kind), ['input_image'], ); assert.equal( await service.statPackageObject({ packageRecord, relativePath: 'manifest.json' }).then((stat) => stat.exists), false, ); }); test('conversation package registry stores and reads private artifact objects', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-registry-')); const service = createMindSpaceServiceFacade({ storageAdapter: createLocalMindSpaceStorageAdapter(root), publicBaseUrl: 'https://m.tkmind.cn', }); const store = createConversationPackageStore(createFakePool()); const registry = createConversationPackageRegistry({ store, service }); const { packageRecord, writeResult } = await registry.putObjectForSession({ userId: 'user-1', sessionId: 'session-1', title: '导出文档', relativePath: 'artifacts/report.docx', body: Buffer.from('docx-body'), }); const artifact = await registry.recordArtifact({ id: 'ca_docx', packageId: packageRecord.id, artifactKind: 'docx', displayName: 'report.docx', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', storageKey: writeResult.key, sortOrder: 10, now: 2000, }); const result = await registry.readArtifactObject({ userId: 'user-1', sessionId: 'session-1', artifactId: artifact.id, }); assert.equal(result.artifact.displayName, 'report.docx'); assert.equal(result.body.toString('utf8'), 'docx-body'); }); test('conversation package registry skips manifest writes for unknown sessions', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-registry-')); const registry = createConversationPackageRegistry({ store: createConversationPackageStore(createFakePool()), service: createMindSpaceServiceFacade({ storageAdapter: createLocalMindSpaceStorageAdapter(root), publicBaseUrl: 'https://m.tkmind.cn', }), }); assert.equal(await registry.writeManifestForSession({ userId: 'user-1', sessionId: 'missing' }), null); });