c7718c683b
Memind CI / Test, build, and release guards (push) Successful in 1m36s
Publication routes and share shells were missing the Rybbit tracker, and bootstrap now reuses the stored MindSpace analytics secret when Rybbit env secret is absent. Co-authored-by: Cursor <cursoragent@cursor.com>
446 lines
12 KiB
JavaScript
446 lines
12 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { bootstrapPortalDomainServices } from './portal-domain-services-bootstrap.mjs';
|
|
|
|
function createBootstrapSetup(overrides = {}) {
|
|
const calls = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
calls.push({ kind: 'query', sql, params });
|
|
if (sql.includes('agent_session_id')) {
|
|
return [[{ user_id: 'agent-user' }]];
|
|
}
|
|
if (sql.includes('username')) {
|
|
return [[{ id: 'named-user' }]];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const pageService = {
|
|
findPageByRelativePath() {},
|
|
};
|
|
const adapter = {
|
|
serviceFacade: { id: 'facade' },
|
|
chatSaveService: { id: 'chat-save' },
|
|
conversationArtifactService: {
|
|
id: 'conversation-artifacts',
|
|
},
|
|
publicFinishService: { id: 'public-finish' },
|
|
workspacePublicationDeliveryService: {
|
|
id: 'workspace-publication-delivery',
|
|
},
|
|
workspacePageDeliveryService: {
|
|
id: 'workspace-deliver',
|
|
},
|
|
conversationPackageRegistry: { id: 'registry' },
|
|
assetService: { id: 'assets' },
|
|
pageService,
|
|
pageSyncService: { id: 'page-sync' },
|
|
pageLiveEditService: { id: 'live-edit' },
|
|
assetAgentService: { id: 'asset-agent' },
|
|
publicationService: { id: 'publications' },
|
|
cleanupService: { id: 'cleanup' },
|
|
assertReady() {
|
|
calls.push({ kind: 'adapter-ready' });
|
|
},
|
|
};
|
|
const plazaRedis = {
|
|
enabled: true,
|
|
invalidateFeedCaches() {
|
|
calls.push({ kind: 'invalidate-feed' });
|
|
},
|
|
};
|
|
let adapterOptions = null;
|
|
let pageDataOptions = null;
|
|
let pageDataPublicOptions = null;
|
|
let plazaPostOptions = null;
|
|
let plazaOpsOptions = null;
|
|
const dependencies = {
|
|
pool,
|
|
h5Root: '/workspace',
|
|
env: {
|
|
H5_DEFAULT_TIMEZONE: 'Asia/Singapore',
|
|
PLAZA_REDIS_URL: 'redis://plaza',
|
|
MEMIND_RYBBIT_ENABLED: 'true',
|
|
MEMIND_RYBBIT_SITE_ID: '2',
|
|
},
|
|
runtime: {
|
|
maxFileBytes: 100,
|
|
aiDailyLimit: 2,
|
|
publicPageLimit: 3,
|
|
monthlyViewLimit: 4,
|
|
remote: { enabled: false },
|
|
},
|
|
analyticsConfig: {
|
|
enabled: false,
|
|
hostPath: '/old',
|
|
},
|
|
getUserAuth: () => ({
|
|
resolveWorkingDir: (userId) => `/users/${userId}`,
|
|
}),
|
|
getSessionSnapshotService: () => ({
|
|
get: (sessionId) => ({ sessionId }),
|
|
}),
|
|
onMindSearchSchemaReady({ pool: receivedPool }) {
|
|
assert.equal(receivedPool, pool);
|
|
calls.push({ kind: 'auth-pool-ready' });
|
|
},
|
|
workspaceMaintenanceEnabled: true,
|
|
logger: {
|
|
log(message) {
|
|
calls.push({ kind: 'log', message });
|
|
},
|
|
},
|
|
async initSchemaFn(receivedPool) {
|
|
assert.equal(receivedPool, pool);
|
|
calls.push({ kind: 'init-schema' });
|
|
},
|
|
createMindSearchConfigServiceFn(receivedPool) {
|
|
assert.equal(receivedPool, pool);
|
|
return {
|
|
async ensureSchema() {
|
|
calls.push({ kind: 'mind-search-schema' });
|
|
},
|
|
};
|
|
},
|
|
async ensureMindSpaceConfigFn(receivedPool, options) {
|
|
assert.equal(receivedPool, pool);
|
|
calls.push({ kind: 'mindspace-config', options });
|
|
},
|
|
async loadMindSpaceConfigFn() {
|
|
calls.push({ kind: 'load-mindspace-config' });
|
|
return {
|
|
analytics: {
|
|
enabled: true,
|
|
websiteId: 'website-1',
|
|
idSecret: 'secret-1',
|
|
},
|
|
};
|
|
},
|
|
createScheduleServiceFn(receivedPool, options) {
|
|
calls.push({ kind: 'schedule', receivedPool, options });
|
|
return { id: 'schedule' };
|
|
},
|
|
createPageDataServiceFn(options) {
|
|
pageDataOptions = options;
|
|
return { id: 'page-data' };
|
|
},
|
|
createPageDataPublicServiceFn(options) {
|
|
pageDataPublicOptions = options;
|
|
return { id: 'page-data-public' };
|
|
},
|
|
createFeedbackServiceFn() {
|
|
return { id: 'feedback' };
|
|
},
|
|
createMindSpaceServiceFn(receivedPool, options) {
|
|
calls.push({ kind: 'mindspace', receivedPool, options });
|
|
return { id: 'mindspace' };
|
|
},
|
|
createMindSpaceServerAdapterFn(options) {
|
|
adapterOptions = options;
|
|
return adapter;
|
|
},
|
|
assertMindSpaceServerAdapterContractFn(received) {
|
|
calls.push({ kind: 'adapter-contract' });
|
|
return received;
|
|
},
|
|
async ensureAlgorithmConfigFn() {
|
|
calls.push({ kind: 'algorithm-schema' });
|
|
},
|
|
async loadAlgorithmConfigFn() {
|
|
calls.push({ kind: 'algorithm-load' });
|
|
return { version: 1 };
|
|
},
|
|
async createPlazaRedisFn(url, receivedPool) {
|
|
calls.push({ kind: 'redis', url, receivedPool });
|
|
return plazaRedis;
|
|
},
|
|
createPlazaSeoServiceFn() {
|
|
return {
|
|
notifyPostPublished(postId) {
|
|
calls.push({ kind: 'seo-published', postId });
|
|
},
|
|
};
|
|
},
|
|
createPlazaInteractionServiceFn(_pool, options) {
|
|
calls.push({ kind: 'interactions', options });
|
|
return {
|
|
async loadViewerReactions(viewerId, postIds) {
|
|
calls.push({
|
|
kind: 'viewer-reactions',
|
|
viewerId,
|
|
postIds,
|
|
});
|
|
return {};
|
|
},
|
|
};
|
|
},
|
|
createPlazaEventServiceFn() {
|
|
return { id: 'events' };
|
|
},
|
|
createPlazaRecommendServiceFn(_pool, options) {
|
|
calls.push({ kind: 'recommend', options });
|
|
return { id: 'recommend' };
|
|
},
|
|
createPlazaPostServiceFn(_pool, options) {
|
|
plazaPostOptions = options;
|
|
return {
|
|
reviewPost(...args) {
|
|
calls.push({ kind: 'review-post', args });
|
|
},
|
|
};
|
|
},
|
|
createPlazaOpsServiceFn(_pool, options) {
|
|
plazaOpsOptions = options;
|
|
return {
|
|
async loadActiveFeaturedPosts(viewerId) {
|
|
return { viewerId };
|
|
},
|
|
};
|
|
},
|
|
startPlazaTasksFn(options) {
|
|
calls.push({ kind: 'plaza-tasks', options });
|
|
},
|
|
recalculateHotScoresFn() {},
|
|
writebackPublicationsFn() {},
|
|
formatPostRowFn() {},
|
|
publishKeyUuid: /^uuid-/,
|
|
...overrides,
|
|
};
|
|
return {
|
|
calls,
|
|
pool,
|
|
adapter,
|
|
pageService,
|
|
plazaRedis,
|
|
dependencies,
|
|
captured: {
|
|
get adapterOptions() {
|
|
return adapterOptions;
|
|
},
|
|
get pageDataOptions() {
|
|
return pageDataOptions;
|
|
},
|
|
get pageDataPublicOptions() {
|
|
return pageDataPublicOptions;
|
|
},
|
|
get plazaPostOptions() {
|
|
return plazaPostOptions;
|
|
},
|
|
get plazaOpsOptions() {
|
|
return plazaOpsOptions;
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
test('domain bootstrap requires pool and runtime', async () => {
|
|
await assert.rejects(
|
|
() => bootstrapPortalDomainServices(),
|
|
/requires pool and runtime/,
|
|
);
|
|
});
|
|
|
|
test('domain bootstrap preserves schema and service assembly order', async () => {
|
|
const setup = createBootstrapSetup();
|
|
const result = await bootstrapPortalDomainServices(
|
|
setup.dependencies,
|
|
);
|
|
assert.deepEqual(
|
|
setup.calls
|
|
.filter((call) =>
|
|
[
|
|
'mind-search-schema',
|
|
'auth-pool-ready',
|
|
'init-schema',
|
|
'mindspace-config',
|
|
'load-mindspace-config',
|
|
'adapter-contract',
|
|
'adapter-ready',
|
|
'algorithm-schema',
|
|
'algorithm-load',
|
|
'redis',
|
|
'plaza-tasks',
|
|
].includes(call.kind),
|
|
)
|
|
.map((call) => call.kind),
|
|
[
|
|
'mind-search-schema',
|
|
'auth-pool-ready',
|
|
'init-schema',
|
|
'mindspace-config',
|
|
'load-mindspace-config',
|
|
'adapter-contract',
|
|
'adapter-ready',
|
|
'algorithm-schema',
|
|
'algorithm-load',
|
|
'redis',
|
|
'plaza-tasks',
|
|
],
|
|
);
|
|
assert.equal(result.mindSpace.id, 'mindspace');
|
|
assert.equal(result.mindSpaceAssets.id, 'assets');
|
|
assert.equal(result.mindSpaceChatSave.id, 'chat-save');
|
|
assert.equal(
|
|
result.mindSpaceConversationArtifacts.id,
|
|
'conversation-artifacts',
|
|
);
|
|
assert.equal(
|
|
result.mindSpacePublicFinish.id,
|
|
'public-finish',
|
|
);
|
|
assert.equal(
|
|
result
|
|
.mindSpaceWorkspacePublicationDelivery
|
|
.id,
|
|
'workspace-publication-delivery',
|
|
);
|
|
assert.equal(result.mindSpacePages, setup.pageService);
|
|
assert.equal(result.workspacePageDeliver.id, 'workspace-deliver');
|
|
assert.equal(result.mindSpaceCleanup.id, 'cleanup');
|
|
assert.equal(result.plazaRedis, setup.plazaRedis);
|
|
assert.equal(
|
|
result.mindSpaceAnalyticsConfig.enabled,
|
|
true,
|
|
);
|
|
assert.equal(
|
|
result.mindSpaceAnalyticsConfig.hostPath,
|
|
'/analytics',
|
|
);
|
|
assert.equal(
|
|
result.mindSpaceAnalyticsConfig.scriptPath,
|
|
'/analytics/script.js',
|
|
);
|
|
assert.equal(result.mindSpaceRybbitConfig.enabled, true);
|
|
assert.equal(result.mindSpaceRybbitConfig.siteId, '2');
|
|
assert.equal(result.mindSpaceRybbitConfig.idSecret, 'secret-1');
|
|
});
|
|
|
|
test('domain bootstrap preserves delayed user, snapshot, and Page Data dependencies', async () => {
|
|
let activeUserAuth = {
|
|
resolveWorkingDir: (userId) => `/first/${userId}`,
|
|
};
|
|
const snapshot = { id: 'snapshot-1' };
|
|
const setup = createBootstrapSetup({
|
|
getUserAuth: () => activeUserAuth,
|
|
getSessionSnapshotService: () => ({
|
|
get: () => snapshot,
|
|
}),
|
|
});
|
|
await bootstrapPortalDomainServices(setup.dependencies);
|
|
|
|
assert.equal(
|
|
await setup.captured.pageDataOptions.resolveWorkspaceRoot({
|
|
workspaceRoot: '/explicit',
|
|
}),
|
|
'/explicit',
|
|
);
|
|
activeUserAuth = {
|
|
resolveWorkingDir: (userId) => `/second/${userId}`,
|
|
};
|
|
assert.equal(
|
|
await setup.captured.pageDataOptions.resolveWorkspaceRoot({
|
|
id: 'user-1',
|
|
}),
|
|
'/second/user-1',
|
|
);
|
|
assert.equal(
|
|
setup.captured.pageDataPublicOptions.getPool(),
|
|
setup.pool,
|
|
);
|
|
assert.equal(
|
|
setup.captured.pageDataPublicOptions.resolveH5Root(),
|
|
'/workspace',
|
|
);
|
|
assert.equal(
|
|
setup.captured.adapterOptions.resolveWorkspaceRoot(
|
|
'user-2',
|
|
),
|
|
'/second/user-2',
|
|
);
|
|
assert.equal(
|
|
setup.captured.adapterOptions.resolveSessionSnapshot(
|
|
'session-1',
|
|
),
|
|
snapshot,
|
|
);
|
|
assert.equal(
|
|
await setup.captured.adapterOptions.resolveUserIdForAgentSession(
|
|
'session-1',
|
|
),
|
|
'agent-user',
|
|
);
|
|
assert.equal(
|
|
setup.captured.adapterOptions.syncWorkspaceAssetsEnabled,
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('domain bootstrap preserves directory and Plaza cross-service callbacks', async () => {
|
|
const setup = createBootstrapSetup();
|
|
const result = await bootstrapPortalDomainServices(
|
|
setup.dependencies,
|
|
);
|
|
assert.equal(
|
|
await result.resolveUserIdByDirKey('uuid-user'),
|
|
'uuid-user',
|
|
);
|
|
assert.equal(
|
|
await result.resolveUserIdByDirKey('alice'),
|
|
'named-user',
|
|
);
|
|
|
|
assert.deepEqual(
|
|
await setup.captured.plazaPostOptions.loadFeaturedPosts(
|
|
'viewer-1',
|
|
),
|
|
{ viewerId: 'viewer-1' },
|
|
);
|
|
setup.captured.plazaPostOptions.onPostPublished('post-1');
|
|
await setup.captured.plazaPostOptions.loadViewerReactions(
|
|
'viewer-1',
|
|
['post-1'],
|
|
);
|
|
setup.captured.plazaOpsOptions.reviewPost('post-1');
|
|
setup.captured.plazaOpsOptions.invalidateFeedCaches();
|
|
assert.deepEqual(
|
|
setup.calls
|
|
.filter((call) =>
|
|
[
|
|
'seo-published',
|
|
'viewer-reactions',
|
|
'review-post',
|
|
'invalidate-feed',
|
|
].includes(call.kind),
|
|
)
|
|
.map((call) => call.kind),
|
|
[
|
|
'seo-published',
|
|
'viewer-reactions',
|
|
'review-post',
|
|
'invalidate-feed',
|
|
],
|
|
);
|
|
});
|
|
|
|
test('passive candidate runtime does not start Plaza background tasks', async () => {
|
|
const setup = createBootstrapSetup({
|
|
env: {
|
|
H5_DEFAULT_TIMEZONE: 'Asia/Singapore',
|
|
MEMIND_RUNTIME_ROLE: 'candidate',
|
|
MEMIND_CANARY_PASSIVE_RUNTIME: '1',
|
|
},
|
|
});
|
|
|
|
await bootstrapPortalDomainServices(setup.dependencies);
|
|
|
|
assert.equal(setup.calls.some((call) => call.kind === 'plaza-tasks'), false);
|
|
assert.ok(
|
|
setup.calls.some(
|
|
(call) =>
|
|
call.kind === 'log'
|
|
&& call.message === 'Passive candidate runtime: Plaza background tasks disabled',
|
|
),
|
|
);
|
|
});
|