feat: finalize mindspace service extraction phase a
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
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'],
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user