14a00774d9
- 新增 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>
182 lines
6.4 KiB
JavaScript
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')));
|
|
});
|