merge: server architecture modularization
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { bootstrapPortalIntegrationServices } from './portal-integration-services-bootstrap.mjs';
|
||||
|
||||
function createSetup(overrides = {}) {
|
||||
const calls = [];
|
||||
const pool = { id: 'pool' };
|
||||
const userAuth = {
|
||||
async getUserById(userId) {
|
||||
calls.push(['get-user', userId]);
|
||||
return { id: userId, username: 'John' };
|
||||
},
|
||||
setRechargeNotifier(notifier) {
|
||||
calls.push(['set-recharge-notifier']);
|
||||
this.rechargeNotifier = notifier;
|
||||
},
|
||||
};
|
||||
const sessionAccess = { id: 'session-access' };
|
||||
const tkmindProxy = {
|
||||
apiFetch: { id: 'api-fetch' },
|
||||
startSessionForUser(userId, options) {
|
||||
calls.push(['start-session', userId, options]);
|
||||
return { sessionId: 'session-1' };
|
||||
},
|
||||
async resolveTarget(sessionId) {
|
||||
calls.push(['resolve-target', sessionId]);
|
||||
return 'http://target';
|
||||
},
|
||||
apiFetchTo(target, pathname, init) {
|
||||
calls.push([
|
||||
'api-fetch-to',
|
||||
target,
|
||||
pathname,
|
||||
init,
|
||||
]);
|
||||
return { ok: true };
|
||||
},
|
||||
submitSessionReplyForUser(...args) {
|
||||
calls.push(['submit-reply', ...args]);
|
||||
return { accepted: true };
|
||||
},
|
||||
applySessionLlmProvider(sessionId) {
|
||||
calls.push(['apply-provider', sessionId]);
|
||||
return { applied: true };
|
||||
},
|
||||
};
|
||||
const wechatMpService = {
|
||||
enabled: true,
|
||||
sendTextToUser(userId, text) {
|
||||
calls.push(['send-wechat-text', userId, text]);
|
||||
return { sent: true };
|
||||
},
|
||||
};
|
||||
const notificationDispatcher = {
|
||||
async sendRechargeSuccess(options) {
|
||||
calls.push(['recharge-success', options]);
|
||||
},
|
||||
};
|
||||
const scheduleService = { id: 'schedule' };
|
||||
const sessionSnapshotService = {
|
||||
isEnabled() {
|
||||
return true;
|
||||
},
|
||||
refresh(...args) {
|
||||
calls.push(['snapshot-refresh', ...args]);
|
||||
return { refreshed: true };
|
||||
},
|
||||
};
|
||||
let wechatOptions;
|
||||
let notificationOptions;
|
||||
let reminderOptions;
|
||||
let pageEditOptions;
|
||||
let timerCallback;
|
||||
let timerDelay;
|
||||
let unrefCount = 0;
|
||||
const subscriptionService = {
|
||||
async processAutoRenewals() {
|
||||
calls.push(['auto-renew']);
|
||||
return { renewed: 2, failed: 1 };
|
||||
},
|
||||
async expireStaleSubscriptions() {
|
||||
calls.push(['expire-subscriptions']);
|
||||
return 3;
|
||||
},
|
||||
};
|
||||
const options = {
|
||||
pool,
|
||||
h5Root: '/app',
|
||||
env: {
|
||||
H5_SCHEDULE_ENABLED: '1',
|
||||
H5_REMINDER_WORKER_ENABLED: '1',
|
||||
},
|
||||
usersRoot: '/users',
|
||||
wechatMpConfig: { enabled: true },
|
||||
authPool: pool,
|
||||
userAuth,
|
||||
sessionAccess,
|
||||
tkmindProxy,
|
||||
scheduleService,
|
||||
wechatScheduleLlmConfigService: {
|
||||
id: 'schedule-llm',
|
||||
},
|
||||
llmProviderService: { id: 'llm' },
|
||||
chatIntentRouter: {
|
||||
classifySessionAction(options) {
|
||||
calls.push(['classify-session', options]);
|
||||
return { action: 'chat' };
|
||||
},
|
||||
},
|
||||
systemDisclosurePolicyService: {
|
||||
id: 'system-disclosure-policy',
|
||||
},
|
||||
sessionSnapshotService,
|
||||
mindSpaceAnalyticsConfig: { enabled: true },
|
||||
subscriptionService,
|
||||
apiTarget: 'http://api',
|
||||
apiSecret: 'secret',
|
||||
mindSpacePages: { id: 'pages' },
|
||||
mindSpacePageLiveEdit: { id: 'live-edit' },
|
||||
logger: {
|
||||
log(...args) {
|
||||
calls.push(['log', ...args]);
|
||||
},
|
||||
warn(...args) {
|
||||
calls.push(['warn', ...args]);
|
||||
},
|
||||
},
|
||||
async loadWechatMpModuleFn(h5Root) {
|
||||
calls.push(['load-wechat', h5Root]);
|
||||
return {
|
||||
createWechatMpService(receivedOptions) {
|
||||
calls.push(['create-wechat']);
|
||||
wechatOptions = receivedOptions;
|
||||
return wechatMpService;
|
||||
},
|
||||
};
|
||||
},
|
||||
resolveMindSpaceRuntimeConfigFn(h5Root, env) {
|
||||
calls.push(['runtime-config', h5Root, env]);
|
||||
return { storageRoot: '/storage' };
|
||||
},
|
||||
resolveAnalyticsOwnerSegmentFn(user) {
|
||||
calls.push(['owner-segment', user]);
|
||||
return 'segment';
|
||||
},
|
||||
resolveAnalyticsOwnerLabelFn(user) {
|
||||
calls.push(['owner-label', user]);
|
||||
return 'label';
|
||||
},
|
||||
sendMindSpaceAnalyticsEventFn(event) {
|
||||
calls.push(['analytics', event]);
|
||||
return Promise.resolve();
|
||||
},
|
||||
createNotificationDispatcherFn(receivedOptions) {
|
||||
calls.push(['notification-dispatcher']);
|
||||
notificationOptions = receivedOptions;
|
||||
return notificationDispatcher;
|
||||
},
|
||||
startScheduleReminderWorkerFn(receivedOptions) {
|
||||
calls.push(['reminder-worker']);
|
||||
reminderOptions = receivedOptions;
|
||||
return { id: 'reminder-worker' };
|
||||
},
|
||||
createPageEditSessionServiceFn(receivedOptions) {
|
||||
calls.push(['page-edit']);
|
||||
pageEditOptions = receivedOptions;
|
||||
return { id: 'page-edit' };
|
||||
},
|
||||
setIntervalFn(callback, delay) {
|
||||
calls.push(['set-interval']);
|
||||
timerCallback = callback;
|
||||
timerDelay = delay;
|
||||
return {
|
||||
unref() {
|
||||
unrefCount += 1;
|
||||
calls.push(['timer-unref']);
|
||||
},
|
||||
};
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return {
|
||||
calls,
|
||||
options,
|
||||
pool,
|
||||
userAuth,
|
||||
sessionAccess,
|
||||
tkmindProxy,
|
||||
wechatMpService,
|
||||
notificationDispatcher,
|
||||
scheduleService,
|
||||
sessionSnapshotService,
|
||||
subscriptionService,
|
||||
getCaptured() {
|
||||
return {
|
||||
wechatOptions,
|
||||
notificationOptions,
|
||||
reminderOptions,
|
||||
pageEditOptions,
|
||||
timerCallback,
|
||||
timerDelay,
|
||||
unrefCount,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('requires Portal integration dependencies', async () => {
|
||||
await assert.rejects(
|
||||
bootstrapPortalIntegrationServices(),
|
||||
/requires Portal integration dependencies/,
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves WeChat service configuration and proxy callbacks', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const { wechatOptions } = setup.getCaptured();
|
||||
|
||||
assert.deepEqual(wechatOptions.pageDataFinishGuard, {
|
||||
pool: setup.pool,
|
||||
h5Root: '/app',
|
||||
storageRoot: '/storage',
|
||||
});
|
||||
assert.equal(
|
||||
wechatOptions.apiFetch,
|
||||
setup.tkmindProxy.apiFetch,
|
||||
);
|
||||
assert.equal(
|
||||
wechatOptions.scheduleService,
|
||||
setup.scheduleService,
|
||||
);
|
||||
assert.equal(
|
||||
wechatOptions.systemDisclosurePolicyService,
|
||||
setup.options.systemDisclosurePolicyService,
|
||||
);
|
||||
assert.deepEqual(
|
||||
wechatOptions.startAgentSession({
|
||||
userId: 'user-1',
|
||||
workingDir: '/workspace',
|
||||
sessionPolicy: { sandboxed: true },
|
||||
}),
|
||||
{ sessionId: 'session-1' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
await wechatOptions.sessionApiFetch(
|
||||
'session-1',
|
||||
'/status',
|
||||
{ method: 'GET' },
|
||||
),
|
||||
{ ok: true },
|
||||
);
|
||||
assert.deepEqual(
|
||||
wechatOptions.submitSessionReply({
|
||||
userId: 'user-1',
|
||||
sessionId: 'session-1',
|
||||
requestId: 'request-1',
|
||||
userMessage: 'hello',
|
||||
options: { deep: true },
|
||||
}),
|
||||
{ accepted: true },
|
||||
);
|
||||
assert.deepEqual(
|
||||
wechatOptions.sessionIntentClassifier({
|
||||
text: 'hello',
|
||||
}),
|
||||
{ action: 'chat' },
|
||||
);
|
||||
assert.deepEqual(
|
||||
wechatOptions.applySessionLlmProvider('session-1'),
|
||||
{ applied: true },
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves snapshot refresh through the resolved session target', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const { wechatOptions } = setup.getCaptured();
|
||||
|
||||
assert.equal(
|
||||
typeof wechatOptions.refreshSessionSnapshot,
|
||||
'function',
|
||||
);
|
||||
assert.deepEqual(
|
||||
wechatOptions.refreshSessionSnapshot(
|
||||
'session-1',
|
||||
'user-1',
|
||||
),
|
||||
{ refreshed: true },
|
||||
);
|
||||
const refreshCall = setup.calls.find(
|
||||
([name]) => name === 'snapshot-refresh',
|
||||
);
|
||||
const fetchSnapshot = refreshCall[3];
|
||||
assert.deepEqual(
|
||||
await fetchSnapshot('/snapshot', {
|
||||
method: 'GET',
|
||||
}),
|
||||
{ ok: true },
|
||||
);
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, sessionId]) =>
|
||||
name === 'resolve-target' &&
|
||||
sessionId === 'session-1',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves generated-page analytics projection', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const { wechatOptions } = setup.getCaptured();
|
||||
|
||||
await wechatOptions.onPageGenerated({
|
||||
userId: 'user-1',
|
||||
sessionId: 'session-1',
|
||||
artifacts: [
|
||||
{
|
||||
relativePath: 'public/page.html',
|
||||
url: 'https://example/page',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
setup.calls.filter(([name]) => name === 'get-user')
|
||||
.length,
|
||||
2,
|
||||
);
|
||||
const analyticsCall = setup.calls.find(
|
||||
([name]) => name === 'analytics',
|
||||
);
|
||||
assert.deepEqual(analyticsCall[1], {
|
||||
config: { enabled: true },
|
||||
eventName: 'page_generated',
|
||||
ownerId: 'user-1',
|
||||
ownerSegment: 'segment',
|
||||
ownerLabel: 'label',
|
||||
pageId: 'public/page.html',
|
||||
publicationId: 'session-1',
|
||||
agentRunId: 'session-1',
|
||||
channel: 'wechat_mp',
|
||||
url: 'https://example/page',
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves notification, recharge, reminder, and page-edit wiring', async () => {
|
||||
const setup = createSetup();
|
||||
const result =
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
|
||||
assert.equal(
|
||||
captured.notificationOptions.sendWechatTextToUser(
|
||||
'user-1',
|
||||
'hello',
|
||||
).sent,
|
||||
true,
|
||||
);
|
||||
await setup.userAuth.rechargeNotifier({
|
||||
userId: 'user-1',
|
||||
title: 'Recharge',
|
||||
body: 'Success',
|
||||
dedupeKey: 'dedupe-1',
|
||||
});
|
||||
assert.deepEqual(captured.reminderOptions, {
|
||||
scheduleService: setup.scheduleService,
|
||||
notificationDispatcher:
|
||||
setup.notificationDispatcher,
|
||||
});
|
||||
assert.equal(result.scheduleReminderWorker.id, 'reminder-worker');
|
||||
assert.deepEqual(captured.pageEditOptions, {
|
||||
apiTarget: 'http://api',
|
||||
apiSecret: 'secret',
|
||||
userAuth: setup.userAuth,
|
||||
sessionAccess: setup.sessionAccess,
|
||||
pageService: setup.options.mindSpacePages,
|
||||
pageLiveEdit: setup.options.mindSpacePageLiveEdit,
|
||||
llmProviderService:
|
||||
setup.options.llmProviderService,
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves subscription timer work, logs, and unref', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
|
||||
assert.equal(captured.timerDelay, 60 * 60 * 1000);
|
||||
assert.equal(captured.unrefCount, 1);
|
||||
await captured.timerCallback();
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, message]) =>
|
||||
name === 'log' &&
|
||||
message === 'Auto-renewed 2 subscription(s)',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, message]) =>
|
||||
name === 'log' &&
|
||||
message ===
|
||||
'Auto-renew failed for 1 subscription(s) (balance insufficient)',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, message]) =>
|
||||
name === 'log' &&
|
||||
message === 'Expired 3 stale subscription(s)',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps optional integrations disabled and contains timer failures', async () => {
|
||||
const setup = createSetup({
|
||||
env: {},
|
||||
authPool: null,
|
||||
scheduleService: null,
|
||||
subscriptionService: {
|
||||
async processAutoRenewals() {
|
||||
throw new Error('renew failed');
|
||||
},
|
||||
async expireStaleSubscriptions() {
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
sessionSnapshotService: {
|
||||
isEnabled: () => false,
|
||||
},
|
||||
async loadWechatMpModuleFn() {
|
||||
return {
|
||||
createWechatMpService(options) {
|
||||
setup.disabledWechatOptions = options;
|
||||
return { enabled: false };
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const result =
|
||||
await bootstrapPortalIntegrationServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
await captured.timerCallback();
|
||||
|
||||
assert.equal(
|
||||
setup.disabledWechatOptions.pageDataFinishGuard,
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
setup.disabledWechatOptions.scheduleService,
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
setup.disabledWechatOptions.refreshSessionSnapshot,
|
||||
null,
|
||||
);
|
||||
assert.equal(result.scheduleReminderWorker, null);
|
||||
assert.equal(
|
||||
captured.notificationOptions.sendWechatTextToUser,
|
||||
null,
|
||||
);
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, message, error]) =>
|
||||
name === 'warn' &&
|
||||
message ===
|
||||
'Subscription expiry check failed:' &&
|
||||
error?.message === 'renew failed',
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user