Files
memind/server/portal-integration-services-bootstrap.test.mjs
T
john 62caf4134b feat(wechat): add Portal worker for daily news morning draft auto push
Run scheduled page generation before push time and write WeChat drafts from admin config so news morning reports no longer require manual pushes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 08:48:02 +08:00

598 lines
16 KiB
JavaScript

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 mindSpacePublicFinish = {
async prepareWechatHtmlDelivery() {
return { confirmedArtifacts: [] };
},
async ensureWechatFreshPageThumbnails() {
return { ok: true };
},
async prepareWechatPageDataDelivery() {
return { outcome: { action: 'skip' } };
},
};
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',
codeRoot: '/runtime',
env: {
H5_SCHEDULE_ENABLED: '1',
H5_REMINDER_WORKER_ENABLED: '1',
},
usersRoot: '/users',
wechatMpConfig: { enabled: true },
mindSpacePublicFinish,
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;
},
};
},
resolveAnalyticsOwnerSegmentFn(user) {
calls.push(['owner-segment', user]);
return 'segment';
},
resolveAnalyticsOwnerLabelFn(user) {
calls.push(['owner-label', user]);
return 'label';
},
resolveAnalyticsPlanFn(user) {
calls.push(['plan-type', user]);
return 'pro';
},
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' };
},
createWechatNewsMorningDraftServiceFn(pool, options) {
calls.push(['news-morning-draft-service', pool, options]);
return { id: 'news-morning-draft-service' };
},
startWechatNewsMorningDraftWorkerFn(receivedOptions) {
calls.push(['news-morning-draft-worker']);
return { id: 'news-morning-draft-worker', runOnce: async () => {} };
},
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(
setup.calls.find(([kind]) => kind === 'load-wechat'),
['load-wechat', '/runtime'],
);
assert.equal(
wechatOptions.pageDataFinishGuard,
setup.options.mindSpacePublicFinish,
);
assert.equal(
wechatOptions.pageDataDeliveryReviewer,
null,
);
assert.equal(
wechatOptions.htmlDeliveryAuthority,
setup.options.mindSpacePublicFinish,
);
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(
setup.calls.find(([kind]) => kind === 'start-session'),
[
'start-session',
'user-1',
{
workingDir: '/workspace',
sessionPolicy: { sandboxed: true },
origin: 'wechat',
},
],
);
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('wires the isolated WeChat Page Data reviewer only when its channel flag is enabled', async () => {
const toolGateway = { id: 'wechat-review-tool-gateway' };
const reviewer = { reviewIfNeeded() {} };
let gatewayOptions;
let reviewerOptions;
const setup = createSetup({
wechatMpConfig: {
enabled: true,
pageDataAiderReviewEnabled: true,
},
createToolGatewayFn(options) {
gatewayOptions = options;
return toolGateway;
},
createPageDataDeliveryCodeReviewServiceFn(options) {
reviewerOptions = options;
return reviewer;
},
});
await bootstrapPortalIntegrationServices(setup.options);
const { wechatOptions } = setup.getCaptured();
assert.equal(gatewayOptions.llmProviderService, setup.options.llmProviderService);
assert.equal(reviewerOptions.toolGateway, toolGateway);
assert.equal(reviewerOptions.userAuth, setup.options.userAuth);
assert.equal(wechatOptions.pageDataDeliveryReviewer, reviewer);
});
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,
1,
);
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',
planType: 'pro',
generatedAt: analyticsCall[1].generatedAt,
pageId: 'public/page.html',
publicationId: 'session-1',
agentRunId: 'session-1',
channel: 'wechat_mp',
url: 'https://example/page',
});
assert.match(
analyticsCall[1].generatedAt,
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
);
});
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.equal(result.wechatNewsMorningDraftWorker.id, 'news-morning-draft-worker');
assert.ok(setup.calls.some(([name]) => name === 'news-morning-draft-service'));
assert.ok(setup.calls.some(([name]) => name === 'news-morning-draft-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: {},
wechatMpConfig: { enabled: false },
mindSpacePublicFinish: 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
.htmlDeliveryAuthority,
null,
);
assert.equal(
setup.disabledWechatOptions.scheduleService,
null,
);
assert.equal(
setup.disabledWechatOptions.refreshSessionSnapshot,
null,
);
assert.equal(result.scheduleReminderWorker, null);
assert.equal(result.wechatNewsMorningDraftWorker, 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',
),
);
});
test('passive candidate runtime disables singleton reminder and subscription timers', async () => {
const setup = createSetup({
env: {
H5_SCHEDULE_ENABLED: '1',
H5_REMINDER_WORKER_ENABLED: '1',
MEMIND_RUNTIME_ROLE: 'candidate',
MEMIND_CANARY_PASSIVE_RUNTIME: '1',
},
});
const result = await bootstrapPortalIntegrationServices(setup.options);
const captured = setup.getCaptured();
assert.equal(result.scheduleReminderWorker, null);
assert.equal(result.wechatNewsMorningDraftWorker, null);
assert.equal(result.subscriptionExpiryTimer, null);
assert.equal(captured.reminderOptions, undefined);
assert.equal(captured.timerCallback, undefined);
assert.equal(setup.calls.some(([name]) => name === 'reminder-worker'), false);
assert.equal(setup.calls.some(([name]) => name === 'set-interval'), false);
});