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>
134 lines
5.4 KiB
TypeScript
134 lines
5.4 KiB
TypeScript
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('双棱镜分光成本')));
|
||
});
|