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 { createMindSpaceLocalServerAdapter } from './mindspace-local-server-adapter.mjs'; test('createMindSpaceLocalServerAdapter wires backfill and public html hydrator through local adapter seams', async () => { const h5Root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-local-server-adapter-')); await fs.mkdir(path.join(h5Root, 'MindSpace', 'user-1', 'public', 'landing'), { recursive: true }); await fs.writeFile( path.join(h5Root, 'MindSpace', 'user-1', 'public', 'landing', 'index.html'), 'ok', 'utf8', ); const snapshots = new Map([ [ 'session-1', { session: { name: 'Published from chat' }, messages: [ { role: 'assistant', content: [ { type: 'text', text: 'Preview ready at public/landing/index.html', }, ], }, ], }, ], ]); const pool = { query: async () => { throw new Error('not implemented in unit test'); }, }; const registered = []; const adapter = createMindSpaceLocalServerAdapter({ pool, h5Root, env: { MINDSPACE_STORAGE_ROOT: '/srv/mindspace-data', H5_PUBLIC_BASE_URL: 'https://example.com', }, maxFileBytes: 8192, publicPageLimit: 8, resolveUserIdForAgentSession: async () => 'user-1', resolveWorkspaceRoot: async () => path.join(h5Root, 'MindSpace', 'user-1'), resolveSessionSnapshot: async (sessionId) => snapshots.get(sessionId) ?? null, registerPublicHtmlArtifactsForConversation: async (payload) => { registered.push(payload); return payload.artifactRefs; }, logger: { log() {}, warn() {}, error() {} }, }); const hydrated = await adapter.serviceFacade.prepareConversationPackageRead({ user: { id: 'user-1' }, sessionId: 'session-1', }); assert.ok(adapter.conversationPackageRegistry); assert.equal(adapter.storageRoot, '/srv/mindspace-data'); assert.ok(Array.isArray(hydrated)); assert.equal(hydrated.length, 1); assert.equal(registered.length, 1); assert.equal(registered[0].publishDir, path.join(h5Root, 'MindSpace', 'user-1')); assert.equal(registered[0].title, 'Published from chat'); assert.equal(registered[0].artifactRefs[0].relativePath, 'public/landing/index.html'); }); test('createMindSpaceLocalServerAdapter starts publication cleanup and agent worker timers through adapter helpers', async () => { const intervals = []; const loggerCalls = []; const adapter = createMindSpaceLocalServerAdapter({ pool: { query: async () => { throw new Error('not implemented in unit test'); }, }, h5Root: '/tmp/h5', env: { MINDSPACE_STORAGE_ROOT: '/srv/mindspace-data', H5_PUBLIC_BASE_URL: 'https://example.com', }, maxFileBytes: 8192, publicPageLimit: 8, resolveUserIdForAgentSession: async () => 'user-1', resolveWorkspaceRoot: async () => path.join(h5Root, 'MindSpace', 'user-1'), logger: { log: (...args) => loggerCalls.push(['log', ...args]), warn: (...args) => loggerCalls.push(['warn', ...args]), error: (...args) => loggerCalls.push(['error', ...args]), }, setIntervalFn: (fn, ms) => { const timer = { fn, ms, unref() {} }; intervals.push(timer); return timer; }, }); adapter.publicationService.cleanupExpiredUnconfirmedPublications = async () => ({ cleaned: 2 }); adapter.agentJobService.claimNextJob = async () => null; adapter.agentJobService.reapStaleJobs = async () => 1; const publicationTimer = adapter.startPublicationCleanup(); const workerTimers = adapter.startAgentWorker({ enabled: true, concurrency: 2, pollMs: 1500, staleMs: 90_000, runner: { runJob: async () => {}, }, }); assert.equal(publicationTimer.ms, 60_000); assert.equal(workerTimers.workerTimer.ms, 1500); assert.equal(workerTimers.reaperTimer.ms, 60_000); await publicationTimer.fn(); await workerTimers.workerTimer.fn(); await workerTimers.reaperTimer.fn(); assert.equal(intervals.length, 3); assert.ok( loggerCalls.some( ([level, message]) => level === 'log' && String(message).includes('Auto-privatized 2 expired unconfirmed publications'), ), ); assert.ok( loggerCalls.some( ([level, message]) => level === 'log' && String(message).includes('Agent job worker enabled (concurrency=2, poll=1500ms)'), ), ); assert.ok( loggerCalls.some( ([level, message]) => level === 'warn' && String(message).includes('Agent worker reaped 1 stale running job(s)'), ), ); }); test('createMindSpaceLocalServerAdapter startBackgroundJobs aggregates startup side effects', async () => { const intervals = []; const workspaceCalls = []; const adapter = createMindSpaceLocalServerAdapter({ pool: { query: async () => { throw new Error('not implemented in unit test'); }, }, h5Root: '/tmp/h5', env: { MINDSPACE_STORAGE_ROOT: '/srv/mindspace-data', H5_PUBLIC_BASE_URL: 'https://example.com', }, maxFileBytes: 8192, publicPageLimit: 8, resolveUserIdForAgentSession: async () => 'user-1', resolveWorkspaceRoot: async () => path.join(h5Root, 'MindSpace', 'user-1'), logger: { log() {}, warn() {}, error() {} }, setIntervalFn: (fn, ms) => { const timer = { fn, ms, unref() {} }; intervals.push(timer); return timer; }, }); adapter.publicationService.cleanupExpiredUnconfirmedPublications = async () => ({ cleaned: 0 }); adapter.agentJobService.claimNextJob = async () => null; adapter.agentJobService.reapStaleJobs = async () => 0; adapter.assetService.expireStaleUploads = async () => {}; const jobs = adapter.startBackgroundJobs({ publicationCleanupIntervalMs: 61_000, agentWorker: { enabled: true, concurrency: 2, pollMs: 1500, staleMs: 90_000, }, agentRunner: { runJob: async () => {} }, workspaceMaintenanceEnabled: true, publishRoot: '/tmp/h5/MindSpace', startWorkspaceThumbnailWatcher: (root) => workspaceCalls.push(['thumb', root]), startWorkspaceAssetSyncWatcher: (payload) => workspaceCalls.push(['asset', payload.publishRoot]), syncUserWorkspaceByDirKey: async () => {}, expireStaleUploadsIntervalMs: 30_000, }); assert.equal(jobs.publicationCleanup.ms, 61_000); assert.equal(jobs.agentWorker.workerTimer.ms, 1500); assert.equal(jobs.workspaceMaintenance.expireStaleUploadsTimer.ms, 30_000); assert.deepEqual(workspaceCalls, [ ['thumb', '/tmp/h5/MindSpace'], ['asset', '/tmp/h5/MindSpace'], ]); });