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>
This commit is contained in:
WeiCHEN
2026-09-13 02:50:22 +00:00
committed by weichen
co-authored by Cursor
parent a8c8cba447
commit 636eb0ccf1
19 changed files with 217 additions and 5685 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ test('isTemplateOnly 交付物应正确识别为初始模板展示态', () => {
title: '用户初始需求收集表',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: true,
+1 -1
View File
@@ -139,7 +139,7 @@ export const AppShell: React.FC<AppShellProps> = ({
onOpenUserSettings={onOpenUserSettings}
onOpenAdminSettings={onOpenAdminSettings}
onOpenProjectMembers={onOpenProjectMembers}
isAdmin={currentUser.roleId === 'role-admin' || currentUser.name.includes('管理员')}
isAdmin={currentUser.roleId === 'role-admin'}
/>
{!leftCollapsed && (
@@ -33,7 +33,7 @@ export const ProjectMembersModal: React.FC<ProjectMembersModalProps> = ({
if (!isOpen || !project) return null;
const isCreator = currentUser.id === project.creatorId;
const isAdmin = currentUser.id === 4 || currentUser.roleId === 'role-admin';
const isAdmin = currentUser.roleId === 'role-admin';
const canManage = isCreator || isAdmin;
// 筛选尚未加入该项目的人员
+9 -9
View File
@@ -36,7 +36,7 @@ export const MOCK_DOCUMENTS: Record<string, MockDocumentMeta> = {
title: '4K超高清荧光内窥镜系统立项与可行性报告',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'drafting',
@@ -97,7 +97,7 @@ $$
title: '微创外科内窥镜全球竞品技术与临床深度调研报告',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'reviewing',
@@ -151,7 +151,7 @@ flowchart TD
title: '4K荧光内窥镜光学分光与电路架构初步设计规格书',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'approving',
@@ -272,7 +272,7 @@ $$
title: '4K双路CMOS核心器件供应链与成本灵敏度分析 (项目共享)',
stage: 'V0.5',
category: 'shared',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
sharedAt: '2026-09-08 15:20',
version: 'V1.0',
@@ -311,7 +311,7 @@ $$
title: '微创医疗战略商务谈判底线与预付款让利推演备忘 (仅我可见)',
stage: 'V1.0',
category: 'private',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V0.1',
lifecycleState: 'drafting',
@@ -377,7 +377,7 @@ $$
title: '数字化高频超声手术吸引系统立项需求与产品定义',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'approved',
@@ -408,7 +408,7 @@ $$
title: '数字化高频超声手术吸引系统立项与可行性法规论证报告',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'approved',
@@ -445,7 +445,7 @@ $$
title: '24kHz高频空化钛合金刀头微裂纹与疲劳剥落风险专项分析报告',
stage: 'V0.5',
category: 'shared',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'approved',
@@ -478,7 +478,7 @@ $$
title: 'CDO超声手术系统工程样机研发与型检预算投资核算单',
stage: 'V1.0',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
version: 'V1.0',
lifecycleState: 'drafting',
+12 -12
View File
@@ -14,12 +14,12 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
title: '超声吸引系统 (NeuroPulse-CUSA)',
deviceType: '数字化高频超声手术吸引系统',
description: '神经外科颅内微创高频超声吸引与切割系统研发项目',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
createdAt: '2026-09-08 09:00',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
department: '机器人与微创器械研发中心',
position: '方案工程师',
@@ -28,7 +28,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-08 09:00',
},
{
userId: 2,
userId: 3,
userName: '李主管',
department: '机器人与微创内窥镜研发中心',
position: '研发项目技术主管',
@@ -37,7 +37,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-08 09:10',
},
{
userId: 3,
userId: 4,
userName: '王总监',
department: '医疗器械研发事业部',
position: '微创器械研发总监',
@@ -52,12 +52,12 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
title: '数字化超声手术吸引系统 (CUSA-Ultra)',
deviceType: '高频超声手术吸引切割系统',
description: '多频段钛合金压电换能手术吸引切割设备研发',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
createdAt: '2026-09-07 14:00',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
department: '机器人与微创器械研发中心',
position: '方案工程师',
@@ -66,7 +66,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-07 14:00',
},
{
userId: 2,
userId: 3,
userName: '李主管',
department: '机器人与微创内窥镜研发中心',
position: '研发项目技术主管',
@@ -75,7 +75,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-07 14:10',
},
{
userId: 3,
userId: 4,
userName: '王总监',
department: '医疗器械研发事业部',
position: '微创器械研发总监',
@@ -90,12 +90,12 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
title: `${p.name} (${p.shortName})`,
deviceType: p.deviceType,
description: `对标临床 ${p.customer},面向 ${p.targetMarket} 准入。${p.intendedUse}`,
creatorId: p.creatorUserId || 1,
creatorId: p.creatorUserId || 2,
creatorName: '张工',
createdAt: '2026-09-11 08:30',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
department: '机器人与微创器械研发中心',
position: '方案工程师',
@@ -104,7 +104,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-11 08:30',
},
{
userId: 2,
userId: 3,
userName: '李主管',
department: '机器人与微创内窥镜研发中心',
position: '研发项目技术主管',
@@ -113,7 +113,7 @@ export const DEFAULT_PROJECTS: ProjectInfo[] = [
joinedAt: '2026-09-11 08:40',
},
{
userId: 3,
userId: 4,
userName: '王总监',
department: '医疗器械研发事业部',
position: '微创器械研发总监',
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -187,8 +187,8 @@ export function assertNoCrossUserChatLeak(
}
function guessUserNameById(userId?: number): string | null {
if (userId === 1) return '张工';
if (userId === 2) return '李主管';
if (userId === 3) return '王总监';
if (userId === 2) return '张工';
if (userId === 3) return '李主管';
if (userId === 4) return '王总监';
return null;
}
+27 -30
View File
@@ -14,33 +14,33 @@ import {
import { ChatMessage } from '../types/workbench';
test('每人独立私聊:同一项目不同用户拥有不同 sessionId', () => {
const zhang = DEFAULT_SESSIONS.filter((s) => s.userId === 1 && s.projectId === 3);
const li = DEFAULT_SESSIONS.filter((s) => s.userId === 2 && s.projectId === 3);
const wang = DEFAULT_SESSIONS.filter((s) => s.userId === 3 && s.projectId === 3);
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', 1));
assert.equal(li[0].sessionId, buildPrivateSessionId('cusa', 2));
assert.equal(wang[0].sessionId, buildPrivateSessionId('cusa', 3));
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, 1);
const liView = filterSessionsForUser(DEFAULT_SESSIONS, 2);
const zhangView = filterSessionsForUser(DEFAULT_SESSIONS, 2);
const liView = filterSessionsForUser(DEFAULT_SESSIONS, 3);
assert.ok(zhangView.every((s) => s.userId === 1));
assert.ok(liView.every((s) => s.userId === 2));
assert.ok(!zhangView.some((s) => s.sessionId === buildPrivateSessionId('cusa', 2)));
assert.ok(!liView.some((s) => s.sessionId === buildPrivateSessionId('cusa', 1)));
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', 1);
const liSess = buildPrivateSessionId('cusa', 2);
const wangSess = buildPrivateSessionId('cusa', 3);
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);
@@ -50,13 +50,10 @@ test('消息严格归属各自私聊 session,无跨用户混聊', () => {
assert.ok(liMsgs.length >= 4, '李主管私聊应有实质对话');
assert.ok(wangMsgs.length >= 2, '王总监私聊应有实质对话');
// 张工窗口不得出现李主管/王总监作为 user 发言人
assert.ok(!zhangMsgs.some((m) => m.role === 'user' && m.authorName === '李主管'));
assert.ok(!zhangMsgs.some((m) => m.role === 'user' && m.authorName === '王总监'));
// 李主管窗口不得出现张工/王总监 user 发言
assert.ok(!liMsgs.some((m) => m.role === 'user' && m.authorName === '张工'));
assert.ok(!liMsgs.some((m) => m.role === 'user' && m.authorName === '王总监'));
// 王总监窗口不得出现张工/李主管 user 发言
assert.ok(!wangMsgs.some((m) => m.role === 'user' && m.authorName === '张工'));
assert.ok(!wangMsgs.some((m) => m.role === 'user' && m.authorName === '李主管'));
@@ -67,17 +64,17 @@ test('消息严格归属各自私聊 session,无跨用户混聊', () => {
test('切换用户时 resolve 到该用户同项目私聊会话', () => {
const next = resolveActiveSessionForUser(
DEFAULT_SESSIONS,
2,
3,
buildPrivateSessionId('cusa', 1)
3,
buildPrivateSessionId('cusa', 2)
);
assert.equal(next, buildPrivateSessionId('cusa', 2));
assert.equal(next, buildPrivateSessionId('cusa', 3));
});
test('resolve 优先有消息的私聊,跳过空壳 sess-p{N}-u{id}', () => {
const shell = {
sessionId: 'sess-p3-u1',
userId: 1,
sessionId: 'sess-p3-u2',
userId: 2,
projectId: 3,
title: '数字化超声手术吸引系统 (CUSA-Ultra)',
deviceType: '高频超声手术吸引切割系统',
@@ -91,21 +88,21 @@ test('resolve 优先有消息的私聊,跳过空壳 sess-p{N}-u{id}', () => {
for (const m of DEFAULT_MESSAGES) {
counts[m.sessionId] = (counts[m.sessionId] || 0) + 1;
}
const next = resolveActiveSessionForUser(mixed, 1, 3, 'sess-p3-u1', counts);
assert.equal(next, buildPrivateSessionId('cusa', 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 === 1);
const ensured = ensurePrivateSessionsForUser(onlyZhang, 2);
const liCusa = ensured.find((s) => s.userId === 2 && s.projectId === 3);
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', 1);
const ultra = buildPrivateSessionId('ultrasound', 1);
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);
+47 -47
View File
@@ -16,7 +16,7 @@ export const STORAGE_KEYS = {
DELIVERABLES: `${STORAGE_PREFIX}deliverables`,
DOC_CACHE: `${STORAGE_PREFIX}doc_cache`,
ROLES: `${STORAGE_PREFIX}roles`,
USERS: `${STORAGE_PREFIX}users`,
USERS: `${STORAGE_PREFIX}users_v7`,
};
export {
@@ -43,17 +43,6 @@ export const DEFAULT_SESSIONS: ProjectSession[] = [
...NEUROPULSE_SESSIONS,
...TEN_PROJECTS_SESSIONS,
// CUSA-Ultra:张工 / 李主管 / 王总监 各一条私聊
{
sessionId: buildPrivateSessionId('cusa', 1),
userId: 1,
projectId: 3,
title: '数字化超声手术吸引系统 (CUSA-Ultra)',
deviceType: '高频超声手术吸引切割系统',
stage: 'V0.5',
stageLabel: 'V0.5 方案成型与法规对齐',
messageCount: 12,
updatedAt: '刚刚',
},
{
sessionId: buildPrivateSessionId('cusa', 2),
userId: 2,
@@ -62,7 +51,7 @@ export const DEFAULT_SESSIONS: ProjectSession[] = [
deviceType: '高频超声手术吸引切割系统',
stage: 'V0.5',
stageLabel: 'V0.5 方案成型与法规对齐',
messageCount: 8,
messageCount: 12,
updatedAt: '刚刚',
},
{
@@ -73,13 +62,24 @@ export const DEFAULT_SESSIONS: ProjectSession[] = [
deviceType: '高频超声手术吸引切割系统',
stage: 'V0.5',
stageLabel: 'V0.5 方案成型与法规对齐',
messageCount: 8,
updatedAt: '刚刚',
},
{
sessionId: buildPrivateSessionId('cusa', 4),
userId: 4,
projectId: 3,
title: '数字化超声手术吸引系统 (CUSA-Ultra)',
deviceType: '高频超声手术吸引切割系统',
stage: 'V0.5',
stageLabel: 'V0.5 方案成型与法规对齐',
messageCount: 4,
updatedAt: '刚刚',
},
// 其他项目默认归属张工私聊
{
sessionId: buildPrivateSessionId('endo', 1),
userId: 1,
sessionId: buildPrivateSessionId('endo', 2),
userId: 2,
projectId: 1,
title: '4K超高清荧光内窥镜系统',
deviceType: '硬管荧光腹腔内窥镜',
@@ -89,8 +89,8 @@ export const DEFAULT_SESSIONS: ProjectSession[] = [
updatedAt: '14:20',
},
{
sessionId: buildPrivateSessionId('ultrasound', 1),
userId: 1,
sessionId: buildPrivateSessionId('ultrasound', 2),
userId: 2,
projectId: 2,
title: '便携式掌上无线超声诊断仪',
deviceType: '便携式超声探头',
@@ -112,14 +112,14 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
// ==================================================================================
{
id: 'm-cusa-0',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'system',
content: '项目助手已就位,当前项目【数字化高频超声手术吸引系统 (CUSA-Ultra)】进入【V0.5 方案成型与法规对齐】阶段。全生命周期法定责任人已指定:编制人 张工、审核人 李主管、批准人 王总监。',
timestamp: '16:00',
},
{
id: 'm-cusa-1',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '🎯 项目初始画像与责任人就绪',
@@ -135,7 +135,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-2',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'user',
authorName: '张工',
content:
@@ -144,7 +144,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-3',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '📚 监管法规与核心参数咨询',
@@ -154,7 +154,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-4',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'user',
authorName: '张工',
content:
@@ -163,7 +163,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-5',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '☕ 生活调剂与健康关怀',
@@ -173,7 +173,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-6',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'user',
authorName: '张工',
content:
@@ -182,7 +182,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-7',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '📝 编制人补充关键工程事实',
@@ -199,14 +199,14 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
// --- 李主管私聊会话开场 ---
{
id: 'm-cusa-li-0',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'system',
content: '项目助手已就位。您以【审核人·李主管】身份进入 CUSA-Ultra 项目私聊工作区;此处仅您与智能体可见,编制人/批准人对话不会出现在本窗口。',
timestamp: '16:14',
},
{
id: 'm-cusa-li-1',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'assistant',
authorName: '项目助手',
intentTag: '🎯 审核人私聊就绪',
@@ -215,7 +215,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-8',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'user',
authorName: '李主管',
content:
@@ -224,7 +224,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-9',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'assistant',
authorName: '项目助手',
intentTag: '⚖️ 审核人合规与临床评价咨询',
@@ -234,7 +234,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-10',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'user',
authorName: '李主管',
content:
@@ -243,7 +243,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-11',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'assistant',
authorName: '项目助手',
intentTag: '🔬 流体力学物理方程学术推演',
@@ -253,7 +253,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-12',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'user',
authorName: '李主管',
content:
@@ -262,7 +262,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-13',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'assistant',
authorName: '项目助手',
intentTag: '🛡️ 审核人补充法规强标约束',
@@ -272,7 +272,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-14',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'user',
authorName: '张工',
content:
@@ -281,7 +281,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-15',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '⚡ 自由专项推演任务完成',
@@ -297,7 +297,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-16',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'user',
authorName: '张工',
content:
@@ -306,7 +306,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-17',
sessionId: buildPrivateSessionId('cusa', 1),
sessionId: buildPrivateSessionId('cusa', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '🔒 文档锁定并提请合规审核',
@@ -316,7 +316,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-18',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'user',
authorName: '李主管',
content:
@@ -325,7 +325,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-19',
sessionId: buildPrivateSessionId('cusa', 2),
sessionId: buildPrivateSessionId('cusa', 3),
role: 'assistant',
authorName: '项目助手',
intentTag: '📋 审核通过流转至终审',
@@ -337,14 +337,14 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
// --- 王总监私聊会话开场 ---
{
id: 'm-cusa-wang-0',
sessionId: buildPrivateSessionId('cusa', 3),
sessionId: buildPrivateSessionId('cusa', 4),
role: 'system',
content: '项目助手已就位。您以【批准人·王总监】身份进入 CUSA-Ultra 项目私聊工作区;此处仅您与智能体可见。',
timestamp: '16:44',
},
{
id: 'm-cusa-wang-1',
sessionId: buildPrivateSessionId('cusa', 3),
sessionId: buildPrivateSessionId('cusa', 4),
role: 'assistant',
authorName: '项目助手',
intentTag: '🎯 批准人私聊就绪',
@@ -353,7 +353,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-20',
sessionId: buildPrivateSessionId('cusa', 3),
sessionId: buildPrivateSessionId('cusa', 4),
role: 'user',
authorName: '王总监',
content:
@@ -362,7 +362,7 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
},
{
id: 'm-cusa-21',
sessionId: buildPrivateSessionId('cusa', 3),
sessionId: buildPrivateSessionId('cusa', 4),
role: 'assistant',
authorName: '项目助手',
intentTag: '🎉 项目立项阶段终审批准发布',
@@ -380,14 +380,14 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
// 张工私聊:4K超高清荧光内窥镜 (sess-endo-u1)
{
id: 'm-001-0',
sessionId: buildPrivateSessionId('endo', 1),
sessionId: buildPrivateSessionId('endo', 2),
role: 'system',
content: '项目助手已就位,当前项目进入【V0.5 方案成型与法规对齐】阶段。',
timestamp: '14:00',
},
{
id: 'm-001-1',
sessionId: buildPrivateSessionId('endo', 1),
sessionId: buildPrivateSessionId('endo', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '🎯 推进立项阶段',
@@ -417,14 +417,14 @@ export const DEFAULT_MESSAGES: ChatMessage[] = [
// 张工私聊:便携式掌上无线超声诊断仪 (sess-ultrasound-u1)
{
id: 'm-002-0',
sessionId: buildPrivateSessionId('ultrasound', 1),
sessionId: buildPrivateSessionId('ultrasound', 2),
role: 'system',
content: '项目助手已就位,当前项目进入【V0.1 需求摸底与产品定义】阶段。',
timestamp: '09:30',
},
{
id: 'm-002-1',
sessionId: buildPrivateSessionId('ultrasound', 1),
sessionId: buildPrivateSessionId('ultrasound', 2),
role: 'assistant',
authorName: '项目助手',
intentTag: '💡 掌超项目摸底',
+19 -19
View File
@@ -53,7 +53,7 @@ const SAMPLE_DELIVERABLES: DeliverableItem[] = [
title: '4K超高清荧光内窥镜立项报告',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
@@ -89,7 +89,7 @@ const SAMPLE_DELIVERABLES: DeliverableItem[] = [
title: 'Sony vs 长光辰芯 CMOS 传感器成本分析',
stage: 'V0.5',
category: 'shared',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
sharedAt: '2026-09-08 15:00',
isFixedWorkflow: false,
@@ -108,7 +108,7 @@ const SAMPLE_DELIVERABLES: DeliverableItem[] = [
title: '张工个人商务底线与让利谈判备忘',
stage: 'V1.0',
category: 'private',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: false,
isTemplateOnly: false,
@@ -126,7 +126,7 @@ const SAMPLE_DELIVERABLES: DeliverableItem[] = [
title: '李主管专属:团队工时饱和度与绩效评估',
stage: 'V0.5',
category: 'private',
creatorId: 2,
creatorId: 3,
creatorName: '李主管',
isFixedWorkflow: false,
isTemplateOnly: false,
@@ -140,9 +140,9 @@ const SAMPLE_DELIVERABLES: DeliverableItem[] = [
];
test('三层交付物多用户隐私绝对隔离测试 (Multi-user Strict Privacy Isolation)', () => {
// 1. 张工 (ID: 1) 登录:必须看到 2个工作流文档 + 1个共享文档 + 张工自己的私密文档 (共4个)
// 1. 张工 (ID: 2) 登录:必须看到 2个工作流文档 + 1个共享文档 + 张工自己的私密文档 (共4个)
// 严禁看到李主管的私密文档!
const zhangView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 1);
const zhangView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 2);
assert.equal(zhangView.length, 4);
assert.ok(zhangView.some((d) => d.docId === 'doc-wf-1'));
assert.ok(zhangView.some((d) => d.docId === 'doc-wf-2'));
@@ -150,9 +150,9 @@ test('三层交付物多用户隐私绝对隔离测试 (Multi-user Strict Privac
assert.ok(zhangView.some((d) => d.docId === 'doc-priv-zhang'));
assert.ok(!zhangView.some((d) => d.docId === 'doc-priv-li'), '张工界面中绝不可泄漏李主管的私密备忘');
// 2. 李主管 (ID: 2) 登录:必须看到 2个工作流文档 + 1个共享文档 + 李主管自己的私密文档 (共4个)
// 2. 李主管 (ID: 3) 登录:必须看到 2个工作流文档 + 1个共享文档 + 李主管自己的私密文档 (共4个)
// 严禁看到张工的私密文档!
const liView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 2);
const liView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 3);
assert.equal(liView.length, 4);
assert.ok(liView.some((d) => d.docId === 'doc-wf-1'));
assert.ok(liView.some((d) => d.docId === 'doc-wf-2'));
@@ -160,8 +160,8 @@ test('三层交付物多用户隐私绝对隔离测试 (Multi-user Strict Privac
assert.ok(liView.some((d) => d.docId === 'doc-priv-li'));
assert.ok(!liView.some((d) => d.docId === 'doc-priv-zhang'), '李主管界面中绝不可泄漏张工的私密谈判备忘');
// 3. 王总监 (ID: 3) 登录:王总监未生成任何私密文档,只应看到 2个工作流文档 + 1个共享文档 (共3个)
const wangView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 3);
// 3. 王总监 (ID: 4) 登录:王总监未生成任何私密文档,只应看到 2个工作流文档 + 1个共享文档 (共3个)
const wangView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 4);
assert.equal(wangView.length, 3);
assert.ok(wangView.every((d) => d.category !== 'private'), '无私密文档的用户只看到公共受控与共享内容');
});
@@ -183,8 +183,8 @@ test('一键共享至项目参考库流转测试 (Promote to Shared Reference Te
d.docId === sharedDoc.docId ? sharedDoc : d
);
// 李主管 (ID: 2) 重新刷新视图:现在李主管可以查阅这份被共享出来的文档!
const liViewAfterShare = filterVisibleDeliverables(updatedDeliverables, 2);
// 李主管 (ID: 3) 重新刷新视图:现在李主管可以查阅这份被共享出来的文档!
const liViewAfterShare = filterVisibleDeliverables(updatedDeliverables, 3);
assert.ok(
liViewAfterShare.some((d) => d.docId === 'doc-priv-zhang'),
'一旦创建者一键共享,项目组成员李主管立即具有查阅权限'
@@ -194,24 +194,24 @@ test('一键共享至项目参考库流转测试 (Promote to Shared Reference Te
test('撤回共享恢复私有草稿测试 (Revoke Shared Reference Test)', () => {
const sharedDoc = SAMPLE_DELIVERABLES.find((d) => d.docId === 'doc-sh-1')!;
assert.equal(sharedDoc.category, 'shared');
assert.equal(sharedDoc.creatorId, 1);
assert.equal(sharedDoc.creatorId, 2);
// 1. 非创建者 (李主管 ID: 2) 尝试撤回:必须抛出权限越权异常
// 1. 非创建者 (李主管 ID: 3) 尝试撤回:必须抛出权限越权异常
assert.throws(
() => revokeShareToPrivate(sharedDoc, 2),
() => revokeShareToPrivate(sharedDoc, 3),
/只有该共享文档的创建者本人方可撤回为私有/
);
// 2. 创建者 (张工 ID: 1) 正确执行撤回
const revokedDoc = revokeShareToPrivate(sharedDoc, 1);
// 2. 创建者 (张工 ID: 2) 正确执行撤回
const revokedDoc = revokeShareToPrivate(sharedDoc, 2);
assert.equal(revokedDoc.category, 'private');
assert.equal(revokedDoc.sharedAt, undefined);
// 3. 验证撤回后李主管 (ID: 2) 的视图已彻底不可见
// 3. 验证撤回后李主管 (ID: 3) 的视图已彻底不可见
const updatedDeliverables = SAMPLE_DELIVERABLES.map((d) =>
d.docId === revokedDoc.docId ? revokedDoc : d
);
const liViewAfterRevoke = filterVisibleDeliverables(updatedDeliverables, 2);
const liViewAfterRevoke = filterVisibleDeliverables(updatedDeliverables, 3);
assert.ok(
!liViewAfterRevoke.some((d) => d.docId === 'doc-sh-1'),
'撤回私有后,其他成员立刻无法访问该文档'
@@ -9,7 +9,7 @@ test('集成验证:项目切换时交付物严格隔离,新建项目即刻
projectId: 1,
projectName: '4K超高清荧光内窥镜系统',
deviceType: '硬管荧光腹腔内窥镜',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
});
@@ -18,7 +18,7 @@ test('集成验证:项目切换时交付物严格隔离,新建项目即刻
projectId: 2,
projectName: '便携式掌上无线超声诊断仪',
deviceType: '便携式超声探头',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
});
@@ -64,7 +64,7 @@ test('集成验证:三层隐私与项目物理隔离双重约束', () => {
projectId: 1,
projectName: '项目A',
deviceType: '器械A',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
});
@@ -72,7 +72,7 @@ test('集成验证:三层隐私与项目物理隔离双重约束', () => {
projectId: 2,
projectName: '项目B',
deviceType: '器械B',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
});
@@ -84,7 +84,7 @@ test('集成验证:三层隐私与项目物理隔离双重约束', () => {
title: '张工专属推演备忘',
stage: 'V0.5',
category: 'private',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: false,
isTemplateOnly: false,
@@ -103,7 +103,7 @@ test('集成验证:三层隐私与项目物理隔离双重约束', () => {
title: '李主管专属预算底稿',
stage: 'V0.5',
category: 'private',
creatorId: 2,
creatorId: 3,
creatorName: '李主管',
isFixedWorkflow: false,
isTemplateOnly: false,
@@ -120,14 +120,14 @@ test('集成验证:三层隐私与项目物理隔离双重约束', () => {
// 1. 张工在项目 1:可看 7 份工作流模板 + 自己的私密文档 = 8 份 (李主管私密文档绝对不可见)
const zhangP1Visible = allDocs
.filter((d) => d.projectId === 1)
.filter((d) => d.category === 'workflow' || d.category === 'shared' || d.creatorId === 1);
.filter((d) => d.category === 'workflow' || d.category === 'shared' || d.creatorId === 2);
assert.strictEqual(zhangP1Visible.length, 8);
assert.ok(!zhangP1Visible.some((d) => d.docId === 'doc-p1-priv-li'));
// 2. 张工在项目 2:仅可看项目 2 的 7 份模板,项目 1 的任何交付物(包括张工自己的私密文档)均不穿透
const zhangP2Visible = allDocs
.filter((d) => d.projectId === 2)
.filter((d) => d.category === 'workflow' || d.category === 'shared' || d.creatorId === 1);
.filter((d) => d.category === 'workflow' || d.category === 'shared' || d.creatorId === 2);
assert.strictEqual(zhangP2Visible.length, 7);
assert.ok(zhangP2Visible.every((d) => d.projectId === 2));
});
+6 -6
View File
@@ -20,7 +20,7 @@ const sampleUsers: Record<string, UserProfile> = {
roleId: 'role-admin',
},
director: {
id: 3,
id: 4,
name: '王总监',
employeeId: 'SMDF-0018',
department: '医疗器械研发事业部',
@@ -28,7 +28,7 @@ const sampleUsers: Record<string, UserProfile> = {
roleId: 'role-director',
},
lead: {
id: 2,
id: 3,
name: '李主管',
employeeId: 'SMDF-0205',
department: '机器人与微创内窥镜研发中心',
@@ -36,7 +36,7 @@ const sampleUsers: Record<string, UserProfile> = {
roleId: 'role-tech-lead',
},
engineer: {
id: 1,
id: 2,
name: '张工',
employeeId: 'SMDF-0821',
department: '机器人与微创内窥镜研发中心',
@@ -60,7 +60,7 @@ const createMockDoc = (overrides: Partial<DeliverableItem> = {}): DeliverableIte
title: '测试受控交付物',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
@@ -177,7 +177,7 @@ test('用例 6: 单文档覆盖 - public (放宽可见性,无 workflow:view_al
test('用例 7: 私密文档隔离强红线 (category === private 无论任何管理员或单文档覆盖,非创建者绝对不可见)', () => {
const privateDraft = createMockDoc({
category: 'private',
creatorId: 1, // 张工创建
creatorId: 2, // 张工创建
creatorName: '张工',
visibilityRule: {
mode: 'public', // 即使误设 public 也绝不能穿透 private 隔离
@@ -232,7 +232,7 @@ test('用例 9: filterVisibleDeliverablesWithRBAC 能够批处理过滤大盘交
docId: 'doc-3',
title: '张工个人草案',
category: 'private',
creatorId: 1,
creatorId: 2,
}),
];
+7 -7
View File
@@ -45,7 +45,7 @@ test('集成场景 1: 动态新增合规审计员角色,并配置其仅能查
title: '常规内部立项可行性报告',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
@@ -63,7 +63,7 @@ test('集成场景 1: 动态新增合规审计员角色,并配置其仅能查
title: '器械注册与临床评价白皮书 (开放审计)',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
@@ -86,7 +86,7 @@ test('集成场景 1: 动态新增合规审计员角色,并配置其仅能查
title: '张工敏感商业测算草案',
stage: 'V0.1',
category: 'private',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: false,
isTemplateOnly: false,
@@ -120,7 +120,7 @@ test('集成场景 2: 动态修改角色权限矩阵,权限即刻全量生效'
assert.equal(engineerRole.permissions.includes('project:create'), false);
const zhangUser: UserProfile = {
id: 1,
id: 2,
name: '张工',
employeeId: 'SMDF-0821',
department: '微创中心',
@@ -146,7 +146,7 @@ test('集成场景 3: 单文档覆盖 designated_only 严格收紧与撤回恢
const roles = DEFAULT_ROLES;
const zhangAuthor: UserProfile = {
id: 1,
id: 2,
name: '张工',
employeeId: 'SMDF-0821',
department: '微创中心',
@@ -155,7 +155,7 @@ test('集成场景 3: 单文档覆盖 designated_only 严格收紧与撤回恢
};
const liReviewer: UserProfile = {
id: 2,
id: 3,
name: '李主管',
employeeId: 'SMDF-0205',
department: '微创中心',
@@ -179,7 +179,7 @@ test('集成场景 3: 单文档覆盖 designated_only 严格收紧与撤回恢
title: '涉密荧光滤光片设计图',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
+31
View File
@@ -0,0 +1,31 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mergeSessionUser } from './mergeSessionUser';
import { UserProfile } from '../types/docgov';
const catalogEngineer: UserProfile = {
id: 1,
name: '张工',
employeeId: 'RD-001',
department: '研发组',
position: '工程师',
roleId: 'role-engineer',
projectPermissions: { 'doc-001': 'author' },
};
test('mergeSessionUser prefers session identity over stale catalog entry', () => {
const sessionAdmin: UserProfile = {
id: 1,
name: '系统管理员',
employeeId: 'SYS-000',
department: '系统运维部',
position: '系统管理员',
roleId: 'role-admin',
};
const merged = mergeSessionUser(sessionAdmin, catalogEngineer);
assert.equal(merged.id, 1);
assert.equal(merged.name, '系统管理员');
assert.equal(merged.roleId, 'role-admin');
assert.deepEqual(merged.projectPermissions, { 'doc-001': 'author' });
});
+16
View File
@@ -0,0 +1,16 @@
import { UserProfile } from '../types/docgov';
/**
* 登录/session 用户身份优先,演示目录仅补充 projectPermissions 等扩展字段。
*/
export function mergeSessionUser(sessionUser: UserProfile, catalog?: UserProfile): UserProfile {
if (!catalog) {
return sessionUser;
}
return {
...catalog,
...sessionUser,
projectPermissions: catalog.projectPermissions ?? sessionUser.projectPermissions,
};
}
+21 -21
View File
@@ -39,19 +39,19 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
id: 9001,
title: '全磁悬浮人工心脏离心泵研发项目',
deviceType: '植入式有源医疗器械',
creatorId: 1, // 张工
creatorId: 2, // 张工
creatorName: '张工',
createdAt: '2026-09-11 10:00',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
role: 'creator',
status: 'active',
joinedAt: '2026-09-11 10:00',
},
{
userId: 2,
userId: 3,
userName: '李主管',
role: 'reviewer',
status: 'active',
@@ -63,7 +63,7 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
const allSessions: ProjectSession[] = [
{
sessionId: 'sess-p9001-u1',
userId: 1,
userId: 2,
projectId: 9001,
title: testProject.title,
deviceType: testProject.deviceType,
@@ -74,7 +74,7 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
},
{
sessionId: 'sess-p9001-u2',
userId: 2,
userId: 3,
projectId: 9001,
title: testProject.title,
deviceType: testProject.deviceType,
@@ -98,14 +98,14 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
const currentProjects = [testProject];
// 张工 (userId: 1) 可见
const zhangVisible = filterProjectSessionsForUser(allSessions, currentProjects, 1, false);
// 张工 (userId: 2) 可见
const zhangVisible = filterProjectSessionsForUser(allSessions, currentProjects, 2, false);
assert.equal(zhangVisible.length, 1);
assert.equal(zhangVisible[0].sessionId, 'sess-p9001-u1');
assert.equal(zhangVisible[0].isExited, false);
// 李主管 (userId: 2) 可见
const liVisible = filterProjectSessionsForUser(allSessions, currentProjects, 2, false);
// 李主管 (userId: 3) 可见
const liVisible = filterProjectSessionsForUser(allSessions, currentProjects, 3, false);
assert.equal(liVisible.length, 1);
assert.equal(liVisible[0].sessionId, 'sess-p9001-u2');
assert.equal(liVisible[0].isExited, false);
@@ -120,12 +120,12 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
id: 9002,
title: '高精内窥镜荧光摄像系统',
deviceType: '医用光学成像设备',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
createdAt: '2026-09-11 10:10',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
role: 'creator',
status: 'active',
@@ -137,7 +137,7 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
const allSessions: ProjectSession[] = [
{
sessionId: 'sess-p9002-u1',
userId: 1,
userId: 2,
projectId: 9002,
title: project.title,
deviceType: project.deviceType,
@@ -148,7 +148,7 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
},
{
sessionId: 'sess-p9002-u3',
userId: 3,
userId: 4,
projectId: 9002,
title: project.title,
deviceType: project.deviceType,
@@ -159,21 +159,21 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
},
];
// 添加王总监 (userId: 3) 为批准人
// 添加王总监 (userId: 4) 为批准人
project.members.push({
userId: 3,
userId: 4,
userName: '王总监',
role: 'approver',
status: 'active',
joinedAt: '2026-09-11 10:15',
});
const wangVisible = filterProjectSessionsForUser(allSessions, [project], 3, false);
const wangVisible = filterProjectSessionsForUser(allSessions, [project], 4, false);
assert.equal(wangVisible.length, 1, '成为项目成员后会话即刻显现');
assert.equal(wangVisible[0].sessionId, 'sess-p9002-u3');
assert.equal(wangVisible[0].isExited, false);
const membership = getProjectMembership(project, 3);
const membership = getProjectMembership(project, 4);
assert.equal(membership?.role, 'approver', '职责分配必须正确记录');
assert.equal(membership?.status, 'active', '状态必须为 active');
});
@@ -183,12 +183,12 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
id: 9003,
title: '穿刺手术导航机器人系统',
deviceType: '手术机器人与空间配准系统',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
createdAt: '2026-09-11 10:20',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
role: 'creator',
status: 'active',
@@ -247,12 +247,12 @@ test('项目立项、成员职责分配与退出留痕治理测试套件', async
id: 9004,
title: '高度机密自研微创系统',
deviceType: '核心技术研发',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
createdAt: '2026-09-11 10:35',
members: [
{
userId: 1,
userId: 2,
userName: '张工',
role: 'creator',
status: 'active',
+3 -3
View File
@@ -16,7 +16,7 @@ describe('用户上传文档私密归档与共享流转测试', () => {
assert.ok(deliv, `项目 ${pid} 必须存在 URS 交付物`);
assert.equal(deliv.category, 'shared', '张工上传后已共享至项目协作参考库');
assert.equal(deliv.space, 'project_shared', '必须在项目共享参考空间');
assert.equal(deliv.creatorId, 1, '编制人必须为张工 (id=1)');
assert.equal(deliv.creatorId, 2, '编制人必须为张工 (id=2)');
// 2. 检查 documents (AST)
const doc = (seed.documents as any)[expectedDocId];
@@ -50,7 +50,7 @@ describe('用户上传文档私密归档与共享流转测试', () => {
// 6. 验证系统流程联动:提审待办派发与审核人处的连续两条助手消息 (第一条专业回复,第二条确认卡)
seed.projects.forEach((proj: any) => {
const pid = proj.id;
const u2Sess = seed.sessions.find((s: any) => s.projectId === pid && s.userId === 2);
const u2Sess = seed.sessions.find((s: any) => s.projectId === pid && s.userId === 3);
assert.ok(u2Sess, `项目 ${pid} 必须存在李主管会话`);
const u2Msgs = seed.messages.filter((m: any) => m.sessionId === u2Sess!.sessionId);
assert.equal(u2Msgs.length, 7, `李主管会话必须包含 7 条完整流转消息`);
@@ -59,7 +59,7 @@ describe('用户上传文档私密归档与共享流转测试', () => {
assert.equal(u2Msgs[4].role, 'assistant', '第 5 条连续为助手消息');
assert.ok(u2Msgs[4].interactiveCard, '连续第 2 条助手消息必须挂载初审确认卡');
const u3Sess = seed.sessions.find((s: any) => s.projectId === pid && s.userId === 3);
const u3Sess = seed.sessions.find((s: any) => s.projectId === pid && s.userId === 4);
assert.ok(u3Sess, `项目 ${pid} 必须存在王总监会话`);
const u3Msgs = seed.messages.filter((m: any) => m.sessionId === u3Sess!.sessionId);
assert.equal(u3Msgs.length, 7, `王总监会话必须包含 7 条完整流转消息`);
+3 -3
View File
@@ -60,7 +60,7 @@ export const testUser: UserProfile = {
export const testSession: ProjectSession = {
sessionId: 'sess-001',
userId: 1,
userId: 2,
projectId: 1,
title: '4K超高清荧光内窥镜系统',
deviceType: '硬管荧光腹腔内窥镜',
@@ -123,7 +123,7 @@ export const testDeliverable: DeliverableItem = {
title: '注册可行性评估报告',
stage: 'V0.5',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: false,
@@ -143,7 +143,7 @@ test('DeliverableItem 必须包含 projectId 与 isTemplateOnly 属性', () => {
title: '用户初始需求收集表',
stage: 'V0.1',
category: 'workflow',
creatorId: 1,
creatorId: 2,
creatorName: '张工',
isFixedWorkflow: true,
isTemplateOnly: true,