Files
Agent-System/web/src/data/storage.test.ts
T
WeiCHENandCursor 636eb0ccf1 fix(web): align workbench user ids with auth seed
Unify MOCK_USERS with backend auth (admin id=1), merge session identity on login, and migrate demo seeds/tests to id 2/3/4 for 张工/李/王.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:22 +00:00

134 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import {
DEFAULT_MESSAGES,
DEFAULT_SESSIONS,
buildPrivateSessionId,
} from './storage';
import {
filterSessionsForUser,
resolveActiveSessionForUser,
ensurePrivateSessionsForUser,
assertNoCrossUserChatLeak,
} from './sessionPrivacy';
import { ChatMessage } from '../types/workbench';
test('每人独立私聊:同一项目不同用户拥有不同 sessionId', () => {
const zhang = DEFAULT_SESSIONS.filter((s) => s.userId === 2 && s.projectId === 3);
const li = DEFAULT_SESSIONS.filter((s) => s.userId === 3 && s.projectId === 3);
const wang = DEFAULT_SESSIONS.filter((s) => s.userId === 4 && s.projectId === 3);
assert.equal(zhang.length, 1);
assert.equal(li.length, 1);
assert.equal(wang.length, 1);
assert.equal(zhang[0].sessionId, buildPrivateSessionId('cusa', 2));
assert.equal(li[0].sessionId, buildPrivateSessionId('cusa', 3));
assert.equal(wang[0].sessionId, buildPrivateSessionId('cusa', 4));
assert.notEqual(zhang[0].sessionId, li[0].sessionId);
});
test('左侧会话列表按当前用户过滤,不可见他人私聊', () => {
const zhangView = filterSessionsForUser(DEFAULT_SESSIONS, 2);
const liView = filterSessionsForUser(DEFAULT_SESSIONS, 3);
assert.ok(zhangView.every((s) => s.userId === 2));
assert.ok(liView.every((s) => s.userId === 3));
assert.ok(!zhangView.some((s) => s.sessionId === buildPrivateSessionId('cusa', 3)));
assert.ok(!liView.some((s) => s.sessionId === buildPrivateSessionId('cusa', 2)));
});
test('消息严格归属各自私聊 session,无跨用户混聊', () => {
const zhangSess = buildPrivateSessionId('cusa', 2);
const liSess = buildPrivateSessionId('cusa', 3);
const wangSess = buildPrivateSessionId('cusa', 4);
const zhangMsgs = DEFAULT_MESSAGES.filter((m) => m.sessionId === zhangSess);
const liMsgs = DEFAULT_MESSAGES.filter((m) => m.sessionId === liSess);
const wangMsgs = DEFAULT_MESSAGES.filter((m) => m.sessionId === wangSess);
assert.ok(zhangMsgs.length >= 4, '张工私聊应有实质对话');
assert.ok(liMsgs.length >= 4, '李主管私聊应有实质对话');
assert.ok(wangMsgs.length >= 2, '王总监私聊应有实质对话');
assert.ok(!zhangMsgs.some((m) => m.role === 'user' && m.authorName === '李主管'));
assert.ok(!zhangMsgs.some((m) => m.role === 'user' && m.authorName === '王总监'));
assert.ok(!liMsgs.some((m) => m.role === 'user' && m.authorName === '张工'));
assert.ok(!liMsgs.some((m) => m.role === 'user' && m.authorName === '王总监'));
assert.ok(!wangMsgs.some((m) => m.role === 'user' && m.authorName === '张工'));
assert.ok(!wangMsgs.some((m) => m.role === 'user' && m.authorName === '李主管'));
const leak = assertNoCrossUserChatLeak(DEFAULT_SESSIONS, DEFAULT_MESSAGES);
assert.equal(leak.ok, true, `跨用户泄漏: ${leak.leaks.join('; ')}`);
});
test('切换用户时 resolve 到该用户同项目私聊会话', () => {
const next = resolveActiveSessionForUser(
DEFAULT_SESSIONS,
3,
3,
buildPrivateSessionId('cusa', 2)
);
assert.equal(next, buildPrivateSessionId('cusa', 3));
});
test('resolve 优先有消息的私聊,跳过空壳 sess-p{N}-u{id}', () => {
const shell = {
sessionId: 'sess-p3-u2',
userId: 2,
projectId: 3,
title: '数字化超声手术吸引系统 (CUSA-Ultra)',
deviceType: '高频超声手术吸引切割系统',
stage: 'V0.5' as const,
stageLabel: 'V0.5',
messageCount: 0,
updatedAt: '刚刚',
};
const mixed = [shell, ...DEFAULT_SESSIONS];
const counts: Record<string, number> = {};
for (const m of DEFAULT_MESSAGES) {
counts[m.sessionId] = (counts[m.sessionId] || 0) + 1;
}
const next = resolveActiveSessionForUser(mixed, 2, 3, 'sess-p3-u2', counts);
assert.equal(next, buildPrivateSessionId('cusa', 2));
});
test('ensurePrivateSessionsForUser 为缺失用户补齐空私聊', () => {
const onlyZhang = DEFAULT_SESSIONS.filter((s) => s.userId === 2);
const ensured = ensurePrivateSessionsForUser(onlyZhang, 3);
const liCusa = ensured.find((s) => s.userId === 3 && s.projectId === 3);
assert.ok(liCusa, '应为李主管补齐 CUSA 私聊会话');
assert.equal(liCusa!.messageCount, 0);
});
test('多项目会话消息隔离测试仍成立(endo vs ultrasound)', () => {
const endo = buildPrivateSessionId('endo', 2);
const ultra = buildPrivateSessionId('ultrasound', 2);
const sess1Msgs = DEFAULT_MESSAGES.filter((m) => m.sessionId === endo);
const sess2Msgs = DEFAULT_MESSAGES.filter((m) => m.sessionId === ultra);
assert.ok(sess1Msgs.length >= 2);
assert.ok(sess2Msgs.length >= 2);
const sess1Combined = sess1Msgs.map((m) => m.content).join(' ');
assert.ok(sess1Combined.includes('荧光内窥镜'));
assert.ok(!sess1Combined.includes('超声声输出安全标准'));
const sess2Combined = sess2Msgs.map((m) => m.content).join(' ');
assert.ok(sess2Combined.includes('便携式掌上无线超声诊断仪'));
assert.ok(!sess2Combined.includes('4K超高清荧光内窥镜系统立项与可行性报告'));
const allMessages: ChatMessage[] = [...DEFAULT_MESSAGES];
allMessages.push({
id: 'm-new-1',
sessionId: endo,
role: 'user',
authorName: '张工',
content: '请测算一下双棱镜分光成本',
timestamp: '14:30',
});
const filteredSess2 = allMessages.filter((m) => m.sessionId === ultra);
assert.equal(filteredSess2.length, sess2Msgs.length);
assert.ok(!filteredSess2.some((m) => m.content.includes('双棱镜分光成本')));
});