Files
memind/session-broker.test.mjs
john 14a00774d9 feat(h5): web 联网能力、实时查询路由与 session Finish 对齐
- 新增 web 能力并挂载 platform/web(web_search/fetch_url)
- 实时查询强制 web skill,router fallback 与 await session Finish
- Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:06:26 +08:00

182 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createSessionBroker, createSessionAccess, isSessionBrokerEnabled } from './session-broker.mjs';
function createMockUserAuth(overrides = {}) {
const store = new Map();
return {
async registerAgentSession(userId, sessionId, target = 0) {
const isLegacyIndex =
typeof target === 'number' || /^\d+$/.test(String(target));
const node = isLegacyIndex ? Number(target) : 0;
const targetUrl = isLegacyIndex ? null : String(target);
store.set(sessionId, {
userId,
node,
target: targetUrl,
origin: 'h5',
});
},
async getSessionTarget(sessionId) {
const row = store.get(sessionId);
return { target: row?.target ?? null, node: row?.node ?? 0 };
},
async ownsSession(userId, sessionId) {
return store.get(sessionId)?.userId === userId;
},
async unregisterAgentSession(userId, sessionId) {
if (store.get(sessionId)?.userId === userId) store.delete(sessionId);
},
async setSessionOrigin(sessionId, origin) {
const row = store.get(sessionId);
if (row) row.origin = origin;
},
async getSessionOrigins(sessionIds = []) {
return new Map(
sessionIds
.map((sessionId) => {
const row = store.get(sessionId);
return row ? [sessionId, row.origin] : null;
})
.filter(Boolean),
);
},
...overrides,
_store: store,
};
}
test('isSessionBrokerEnabled defaults to false', () => {
assert.equal(isSessionBrokerEnabled({}), false);
assert.equal(isSessionBrokerEnabled({ MEMIND_SESSION_BROKER_ENABLED: '0' }), false);
assert.equal(isSessionBrokerEnabled({ MEMIND_SESSION_BROKER_ENABLED: '1' }), true);
});
test('validateOwnership delegates to userAuth', async () => {
const userAuth = createMockUserAuth();
const broker = createSessionBroker({ userAuth });
await userAuth.registerAgentSession('user-1', 'sess-1', 'https://127.0.0.1:18006');
assert.equal(await broker.validateOwnership('user-1', 'sess-1'), true);
assert.equal(await broker.validateOwnership('user-2', 'sess-1'), false);
assert.equal(await broker.validateOwnership('user-1', ''), false);
});
test('registerSession records direct chat h5-direct target', async () => {
const userAuth = createMockUserAuth();
const broker = createSessionBroker({ userAuth });
await broker.registerSession({
userId: 'user-1',
sessionId: 'h5direct_abc',
target: 'h5-direct',
origin: 'h5',
});
assert.equal(await broker.validateOwnership('user-1', 'h5direct_abc'), true);
const resolved = await broker.resolveSessionTarget('h5direct_abc');
assert.equal(resolved.target, 'h5-direct');
assert.equal(resolved.node, 0);
});
test('resolveSessionTarget prefers goosed_target over legacy node', async () => {
const userAuth = createMockUserAuth();
const broker = createSessionBroker({ userAuth });
await userAuth.registerAgentSession('user-1', 'sess-url', 'https://127.0.0.1:18007');
assert.deepEqual(await broker.resolveSessionTarget('sess-url'), {
target: 'https://127.0.0.1:18007',
node: 0,
});
await userAuth.registerAgentSession('user-1', 'sess-node', 2);
assert.deepEqual(await broker.resolveSessionTarget('sess-node'), {
target: null,
node: 2,
});
});
test('resolveSession includes origin for wechat sessions', async () => {
const userAuth = createMockUserAuth();
const broker = createSessionBroker({ userAuth });
await broker.registerSession({
userId: 'user-wx',
sessionId: 'wx-sess-1',
target: 'https://127.0.0.1:18006',
origin: 'wechat',
});
assert.deepEqual(await broker.resolveSession('wx-sess-1'), {
sessionId: 'wx-sess-1',
target: 'https://127.0.0.1:18006',
node: 0,
origin: 'wechat',
});
});
test('unregisterSession removes ownership', async () => {
const userAuth = createMockUserAuth();
const broker = createSessionBroker({ userAuth });
await broker.registerSession({
userId: 'user-1',
sessionId: 'sess-del',
target: 1,
});
assert.equal(await broker.validateOwnership('user-1', 'sess-del'), true);
await broker.unregisterSession({ userId: 'user-1', sessionId: 'sess-del' });
assert.equal(await broker.validateOwnership('user-1', 'sess-del'), false);
});
test('createSessionBroker requires userAuth session helpers', () => {
assert.throws(() => createSessionBroker({ userAuth: {} }), /registerAgentSession/);
});
test('createSessionAccess with enabled=false uses userAuth directly', async () => {
const userAuth = createMockUserAuth();
const access = createSessionAccess({ userAuth, enabled: false });
assert.equal(access.enabled, false);
await access.registerAgentSession('user-1', 'sess-fallback', 'https://127.0.0.1:18006');
assert.equal(await access.validateOwnership('user-1', 'sess-fallback'), true);
assert.deepEqual(await access.getSessionTarget('sess-fallback'), {
target: 'https://127.0.0.1:18006',
node: 0,
});
});
test('createSessionAccess with enabled=true routes through broker', async () => {
const userAuth = createMockUserAuth();
const access = createSessionAccess({ userAuth, enabled: true });
assert.equal(access.enabled, true);
await access.registerSession({
userId: 'user-1',
sessionId: 'sess-broker',
target: 'h5-direct',
origin: 'wechat',
});
assert.equal(await access.validateOwnership('user-1', 'sess-broker'), true);
const resolved = await access.resolveSession('sess-broker');
assert.equal(resolved.origin, 'wechat');
assert.equal(resolved.target, 'h5-direct');
});
test('createSessionBroker emits metrics when enabled', async () => {
const userAuth = createMockUserAuth();
const lines = [];
const broker = createSessionBroker({
userAuth,
metricsEnabled: true,
logger: { log: (line) => lines.push(line) },
});
await broker.registerSession({
userId: 'user-1',
sessionId: 'sess-metrics',
target: 'https://127.0.0.1:18006',
});
await broker.registerSession({
userId: 'user-1',
sessionId: 'sess-metrics',
target: 'https://127.0.0.1:18006',
});
assert.equal(await broker.validateOwnership('user-2', 'sess-metrics'), false);
await userAuth.registerAgentSession('user-1', 'sess-node-only', 2);
await broker.resolveSessionTarget('sess-node-only');
assert.ok(lines.some((line) => line.includes('register.duplicate')));
assert.ok(lines.some((line) => line.includes('ownership.denied')));
assert.ok(lines.some((line) => line.includes('resolve_target.miss')));
});