202 lines
6.5 KiB
JavaScript
202 lines
6.5 KiB
JavaScript
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'),
|
|
'<html><body>ok</body></html>',
|
|
'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',
|
|
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',
|
|
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',
|
|
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'],
|
|
]);
|
|
});
|