merge: server architecture modularization
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { bootstrapPortalMemorySessionServices } from './portal-memory-session-services-bootstrap.mjs';
|
||||
|
||||
function createSetup(overrides = {}) {
|
||||
const calls = [];
|
||||
const pool = { id: 'pool' };
|
||||
const llmProviderService = { id: 'llm' };
|
||||
const userAuth = { id: 'user-auth' };
|
||||
const sessionAccess = { id: 'session-access' };
|
||||
const memoryV2ConfigService = {
|
||||
async getRuntimeState() {
|
||||
calls.push(['runtime-state']);
|
||||
return {
|
||||
overrides: {
|
||||
MEMORY_BACKEND: 'pgvector',
|
||||
SHARED: 'override',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
const conversationMemoryService = {
|
||||
id: 'conversation-memory',
|
||||
};
|
||||
const memoryV2 = { id: 'memory-v2' };
|
||||
const episodicMemoryService = {
|
||||
id: 'episodic',
|
||||
async ensureSchema() {
|
||||
calls.push(['episodic-schema']);
|
||||
},
|
||||
};
|
||||
const sessionSnapshotService = { id: 'snapshot' };
|
||||
const sessionStreamStore = { id: 'stream-store' };
|
||||
const directChatService = { id: 'direct-chat' };
|
||||
const chatIntentRouter = { id: 'intent-router' };
|
||||
const systemDisclosurePolicyService = {
|
||||
id: 'system-disclosure-policy',
|
||||
async initialize() {
|
||||
calls.push(['system-disclosure-initialize']);
|
||||
},
|
||||
};
|
||||
let skillRuntimeOptions;
|
||||
let agentCodeRunOptions;
|
||||
let conversationOptions;
|
||||
let memoryV2Options;
|
||||
let episodicOptions;
|
||||
let snapshotOptions;
|
||||
let streamStoreOptions;
|
||||
let directChatOptions;
|
||||
let routerOptions;
|
||||
|
||||
const options = {
|
||||
pool,
|
||||
h5Root: '/app',
|
||||
env: {
|
||||
BASE_ONLY: 'base',
|
||||
SHARED: 'base',
|
||||
},
|
||||
llmProviderService,
|
||||
userAuth,
|
||||
sessionAccess,
|
||||
logger: {
|
||||
warn(...args) {
|
||||
calls.push(['warn', ...args]);
|
||||
},
|
||||
},
|
||||
createMemoryV2AdminConfigServiceFn(receivedPool) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['memory-config']);
|
||||
return memoryV2ConfigService;
|
||||
},
|
||||
createSkillRuntimeAdminConfigServiceFn(
|
||||
receivedPool,
|
||||
receivedOptions,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['skill-config']);
|
||||
skillRuntimeOptions = receivedOptions;
|
||||
return { id: 'skill-config' };
|
||||
},
|
||||
createSystemDisclosurePolicyServiceFn(
|
||||
receivedPool,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['system-disclosure-config']);
|
||||
return systemDisclosurePolicyService;
|
||||
},
|
||||
createAgentCodeRunAdminConfigServiceFn(
|
||||
receivedPool,
|
||||
receivedOptions,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['agent-code-run-config']);
|
||||
agentCodeRunOptions = receivedOptions;
|
||||
return { id: 'agent-code-run-config' };
|
||||
},
|
||||
createWechatScheduleLlmConfigServiceFn(
|
||||
receivedPool,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['wechat-schedule-config']);
|
||||
return { id: 'wechat-schedule-config' };
|
||||
},
|
||||
createConversationMemoryServiceFn(
|
||||
receivedPool,
|
||||
receivedOptions,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['conversation-memory']);
|
||||
conversationOptions = receivedOptions;
|
||||
return conversationMemoryService;
|
||||
},
|
||||
async createManagedMemoryV2RuntimeFn(
|
||||
receivedOptions,
|
||||
) {
|
||||
calls.push(['memory-runtime']);
|
||||
memoryV2Options = receivedOptions;
|
||||
return memoryV2;
|
||||
},
|
||||
createEpisodicMemoryServiceFn(
|
||||
receivedPool,
|
||||
receivedOptions,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['episodic-memory']);
|
||||
episodicOptions = receivedOptions;
|
||||
return episodicMemoryService;
|
||||
},
|
||||
createSessionSnapshotServiceFn(
|
||||
receivedPool,
|
||||
receivedOptions,
|
||||
) {
|
||||
assert.equal(receivedPool, pool);
|
||||
calls.push(['snapshot']);
|
||||
snapshotOptions = receivedOptions;
|
||||
return sessionSnapshotService;
|
||||
},
|
||||
isSessionStreamReplayEnabledFn() {
|
||||
calls.push(['replay-enabled']);
|
||||
return true;
|
||||
},
|
||||
createSessionStreamStoreFn(receivedOptions) {
|
||||
calls.push(['stream-store']);
|
||||
streamStoreOptions = receivedOptions;
|
||||
return sessionStreamStore;
|
||||
},
|
||||
createDirectChatServiceFn(receivedOptions) {
|
||||
calls.push(['direct-chat']);
|
||||
directChatOptions = receivedOptions;
|
||||
return directChatService;
|
||||
},
|
||||
createManagedChatIntentRouterFn(
|
||||
receivedOptions,
|
||||
) {
|
||||
calls.push(['intent-router']);
|
||||
routerOptions = receivedOptions;
|
||||
return chatIntentRouter;
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return {
|
||||
calls,
|
||||
options,
|
||||
pool,
|
||||
llmProviderService,
|
||||
userAuth,
|
||||
sessionAccess,
|
||||
memoryV2ConfigService,
|
||||
conversationMemoryService,
|
||||
memoryV2,
|
||||
episodicMemoryService,
|
||||
systemDisclosurePolicyService,
|
||||
sessionSnapshotService,
|
||||
sessionStreamStore,
|
||||
getCaptured() {
|
||||
return {
|
||||
skillRuntimeOptions,
|
||||
agentCodeRunOptions,
|
||||
conversationOptions,
|
||||
memoryV2Options,
|
||||
episodicOptions,
|
||||
snapshotOptions,
|
||||
streamStoreOptions,
|
||||
directChatOptions,
|
||||
routerOptions,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('requires the Memory and Session dependencies', async () => {
|
||||
await assert.rejects(
|
||||
bootstrapPortalMemorySessionServices(),
|
||||
/requires memory and session dependencies/,
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves Memory, Session, and chat assembly order', async () => {
|
||||
const setup = createSetup();
|
||||
const result =
|
||||
await bootstrapPortalMemorySessionServices(
|
||||
setup.options,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
setup.calls.map(([name]) => name),
|
||||
[
|
||||
'memory-config',
|
||||
'skill-config',
|
||||
'system-disclosure-config',
|
||||
'system-disclosure-initialize',
|
||||
'agent-code-run-config',
|
||||
'wechat-schedule-config',
|
||||
'conversation-memory',
|
||||
'memory-runtime',
|
||||
'episodic-memory',
|
||||
'episodic-schema',
|
||||
'snapshot',
|
||||
'replay-enabled',
|
||||
'stream-store',
|
||||
'direct-chat',
|
||||
'intent-router',
|
||||
],
|
||||
);
|
||||
assert.equal(
|
||||
result.memoryV2ConfigService,
|
||||
setup.memoryV2ConfigService,
|
||||
);
|
||||
assert.equal(
|
||||
result.systemDisclosurePolicyService,
|
||||
setup.systemDisclosurePolicyService,
|
||||
);
|
||||
assert.equal(result.agentCodeRunPolicyService.id, 'agent-code-run-config');
|
||||
assert.equal(
|
||||
result.sessionStreamStore,
|
||||
setup.sessionStreamStore,
|
||||
);
|
||||
assert.equal(result.directChatService.id, 'direct-chat');
|
||||
assert.equal(result.chatIntentRouter.id, 'intent-router');
|
||||
});
|
||||
|
||||
test('preserves configuration and runtime service wiring', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalMemorySessionServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
|
||||
assert.deepEqual(captured.skillRuntimeOptions, {
|
||||
h5Root: '/app',
|
||||
});
|
||||
assert.deepEqual(captured.agentCodeRunOptions, {
|
||||
env: {
|
||||
BASE_ONLY: 'base',
|
||||
SHARED: 'base',
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
captured.conversationOptions.llmProviderService,
|
||||
setup.llmProviderService,
|
||||
);
|
||||
assert.deepEqual(captured.memoryV2Options, {
|
||||
legacyMemoryService: setup.conversationMemoryService,
|
||||
configService: setup.memoryV2ConfigService,
|
||||
mysqlPool: setup.pool,
|
||||
});
|
||||
assert.equal(
|
||||
captured.episodicOptions.logger,
|
||||
setup.options.logger,
|
||||
);
|
||||
assert.deepEqual(captured.snapshotOptions, {
|
||||
conversationMemoryService:
|
||||
setup.conversationMemoryService,
|
||||
memoryV2: setup.memoryV2,
|
||||
episodicMemoryService: setup.episodicMemoryService,
|
||||
});
|
||||
assert.deepEqual(captured.streamStoreOptions, {
|
||||
pool: setup.pool,
|
||||
});
|
||||
});
|
||||
|
||||
test('merges runtime overrides and falls back to the base environment', async () => {
|
||||
const setup = createSetup();
|
||||
const result =
|
||||
await bootstrapPortalMemorySessionServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
|
||||
assert.deepEqual(
|
||||
await captured.conversationOptions.getEffectiveEnv(),
|
||||
{
|
||||
BASE_ONLY: 'base',
|
||||
SHARED: 'override',
|
||||
MEMORY_BACKEND: 'pgvector',
|
||||
},
|
||||
);
|
||||
assert.equal(
|
||||
captured.conversationOptions.getEffectiveEnv,
|
||||
captured.episodicOptions.getEffectiveEnv,
|
||||
);
|
||||
|
||||
setup.memoryV2ConfigService.getRuntimeState =
|
||||
async () => {
|
||||
throw new Error('config unavailable');
|
||||
};
|
||||
assert.deepEqual(await result.getEffectiveEnv(), {
|
||||
BASE_ONLY: 'base',
|
||||
SHARED: 'base',
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves Direct Chat and Intent Router dependencies', async () => {
|
||||
const setup = createSetup();
|
||||
await bootstrapPortalMemorySessionServices(
|
||||
setup.options,
|
||||
);
|
||||
const captured = setup.getCaptured();
|
||||
|
||||
assert.deepEqual(captured.directChatOptions, {
|
||||
userAuth: setup.userAuth,
|
||||
sessionAccess: setup.sessionAccess,
|
||||
llmProviderService: setup.llmProviderService,
|
||||
sessionSnapshotService: setup.sessionSnapshotService,
|
||||
memoryV2: setup.memoryV2,
|
||||
conversationMemoryService:
|
||||
setup.conversationMemoryService,
|
||||
episodicMemoryService: setup.episodicMemoryService,
|
||||
});
|
||||
assert.deepEqual(captured.routerOptions, {
|
||||
llmProviderService: setup.llmProviderService,
|
||||
memoryV2: setup.memoryV2,
|
||||
conversationMemoryService:
|
||||
setup.conversationMemoryService,
|
||||
episodicMemoryService: setup.episodicMemoryService,
|
||||
configService: setup.memoryV2ConfigService,
|
||||
});
|
||||
});
|
||||
|
||||
test('degrades episodic schema failures and can disable replay storage', async () => {
|
||||
const setup = createSetup({
|
||||
createEpisodicMemoryServiceFn() {
|
||||
return {
|
||||
async ensureSchema() {
|
||||
throw new Error('schema unavailable');
|
||||
},
|
||||
};
|
||||
},
|
||||
isSessionStreamReplayEnabledFn: () => false,
|
||||
});
|
||||
|
||||
const result =
|
||||
await bootstrapPortalMemorySessionServices(
|
||||
setup.options,
|
||||
);
|
||||
|
||||
assert.equal(result.sessionStreamStore, null);
|
||||
assert.ok(
|
||||
setup.calls.some(
|
||||
([name, message, detail]) =>
|
||||
name === 'warn' &&
|
||||
message ===
|
||||
'[episodic-memory] schema setup degraded; snapshot fallback remains available:' &&
|
||||
detail === 'schema unavailable',
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user