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', ), ); });