440 lines
12 KiB
JavaScript
440 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' },
|
|
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 workspaceDeliverOptions = null;
|
|
let plazaPostOptions = null;
|
|
let plazaOpsOptions = null;
|
|
const dependencies = {
|
|
pool,
|
|
h5Root: '/workspace',
|
|
env: {
|
|
H5_DEFAULT_TIMEZONE: 'Asia/Singapore',
|
|
PLAZA_REDIS_URL: 'redis://plaza',
|
|
},
|
|
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' });
|
|
},
|
|
registerPublicHtmlArtifactsForConversation() {},
|
|
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;
|
|
},
|
|
createWorkspacePageDeliverServiceFn(options) {
|
|
workspaceDeliverOptions = options;
|
|
return { id: 'workspace-deliver' };
|
|
},
|
|
ensurePageDataHtmlPagesBoundFn() {},
|
|
resolveMindSpaceRuntimeConfigFn() {
|
|
return { storageRoot: '/storage' };
|
|
},
|
|
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 workspaceDeliverOptions() {
|
|
return workspaceDeliverOptions;
|
|
},
|
|
get plazaPostOptions() {
|
|
return plazaPostOptions;
|
|
},
|
|
get plazaOpsOptions() {
|
|
return plazaOpsOptions;
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
test('domain bootstrap requires pool, runtime, and artifact registration', async () => {
|
|
await assert.rejects(
|
|
() => bootstrapPortalDomainServices(),
|
|
/requires pool and runtime/,
|
|
);
|
|
await assert.rejects(
|
|
() =>
|
|
bootstrapPortalDomainServices({
|
|
pool: {},
|
|
runtime: {},
|
|
}),
|
|
/requires artifact registration/,
|
|
);
|
|
});
|
|
|
|
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.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',
|
|
);
|
|
});
|
|
|
|
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,
|
|
);
|
|
assert.equal(
|
|
setup.captured.workspaceDeliverOptions.storageRoot,
|
|
'/storage',
|
|
);
|
|
});
|
|
|
|
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',
|
|
),
|
|
);
|
|
});
|