7f8d692d16
Fix DEV logout cookie clearing, materialize selected MindSpace OA assets before agent runs, and recover zombie runs from synced workspace pages. Add client run wait timeout, harness retry limits, page-edit asset forwarding, and logout/john2 scenario tests. Co-authored-by: Cursor <cursoragent@cursor.com>
205 lines
6.7 KiB
JavaScript
205 lines
6.7 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',
|
|
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'],
|
|
]);
|
|
});
|