230 lines
7.7 KiB
TypeScript
230 lines
7.7 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DeliverableItem } from '../types/workbench';
|
|
|
|
/**
|
|
* 交付物可见性判定规则纯函数 (与 App.tsx 保持完全一致的安全性契约)
|
|
*/
|
|
export function filterVisibleDeliverables(
|
|
deliverables: DeliverableItem[],
|
|
currentUserId: number
|
|
): DeliverableItem[] {
|
|
return deliverables.filter((doc) => {
|
|
if (doc.category === 'workflow' || doc.category === 'shared') {
|
|
return true;
|
|
}
|
|
return doc.creatorId === currentUserId;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 共享状态流转纯函数
|
|
*/
|
|
export function promoteToShared(doc: DeliverableItem, sharedAt: string): DeliverableItem {
|
|
if (doc.category === 'workflow') {
|
|
throw new Error('受控工作流交付物不可变更为项目共享类型');
|
|
}
|
|
return {
|
|
...doc,
|
|
category: 'shared',
|
|
sharedAt,
|
|
};
|
|
}
|
|
|
|
export function revokeShareToPrivate(doc: DeliverableItem, operatorId: number): DeliverableItem {
|
|
if (doc.category !== 'shared') {
|
|
throw new Error('非项目共享文档无法执行撤回操作');
|
|
}
|
|
if (doc.creatorId !== operatorId) {
|
|
throw new Error('只有该共享文档的创建者本人方可撤回为私有');
|
|
}
|
|
return {
|
|
...doc,
|
|
category: 'private',
|
|
sharedAt: undefined,
|
|
};
|
|
}
|
|
|
|
const SAMPLE_DELIVERABLES: DeliverableItem[] = [
|
|
{
|
|
docId: 'doc-wf-1',
|
|
projectId: 1,
|
|
taskKey: 'feasibility',
|
|
title: '4K超高清荧光内窥镜立项报告',
|
|
stage: 'V0.1',
|
|
category: 'workflow',
|
|
creatorId: 1,
|
|
creatorName: '张工',
|
|
isFixedWorkflow: true,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
updatedAt: '2026-09-08 14:00',
|
|
},
|
|
{
|
|
docId: 'doc-wf-2',
|
|
projectId: 1,
|
|
taskKey: 'quote',
|
|
title: 'CDO商业报价清单',
|
|
stage: 'V1.0',
|
|
category: 'workflow',
|
|
creatorId: 4,
|
|
creatorName: '王商务',
|
|
isFixedWorkflow: true,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '王商务',
|
|
reviewer: '张工',
|
|
approver: '李总',
|
|
updatedAt: '2026-09-08 14:30',
|
|
},
|
|
{
|
|
docId: 'doc-sh-1',
|
|
projectId: 1,
|
|
taskKey: 'supply_chain',
|
|
title: 'Sony vs 长光辰芯 CMOS 传感器成本分析',
|
|
stage: 'V0.5',
|
|
category: 'shared',
|
|
creatorId: 1,
|
|
creatorName: '张工',
|
|
sharedAt: '2026-09-08 15:00',
|
|
isFixedWorkflow: false,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
updatedAt: '2026-09-08 15:00',
|
|
},
|
|
{
|
|
docId: 'doc-priv-zhang',
|
|
projectId: 1,
|
|
taskKey: 'memo_zhang',
|
|
title: '张工个人商务底线与让利谈判备忘',
|
|
stage: 'V1.0',
|
|
category: 'private',
|
|
creatorId: 1,
|
|
creatorName: '张工',
|
|
isFixedWorkflow: false,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
updatedAt: '2026-09-08 15:30',
|
|
},
|
|
{
|
|
docId: 'doc-priv-li',
|
|
projectId: 1,
|
|
taskKey: 'memo_li',
|
|
title: '李主管专属:团队工时饱和度与绩效评估',
|
|
stage: 'V0.5',
|
|
category: 'private',
|
|
creatorId: 2,
|
|
creatorName: '李主管',
|
|
isFixedWorkflow: false,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '李主管',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
updatedAt: '2026-09-08 15:40',
|
|
},
|
|
];
|
|
|
|
test('三层交付物多用户隐私绝对隔离测试 (Multi-user Strict Privacy Isolation)', () => {
|
|
// 1. 张工 (ID: 1) 登录:必须看到 2个工作流文档 + 1个共享文档 + 张工自己的私密文档 (共4个)
|
|
// 严禁看到李主管的私密文档!
|
|
const zhangView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 1);
|
|
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'));
|
|
assert.ok(zhangView.some((d) => d.docId === 'doc-sh-1'));
|
|
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个)
|
|
// 严禁看到张工的私密文档!
|
|
const liView = filterVisibleDeliverables(SAMPLE_DELIVERABLES, 2);
|
|
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'));
|
|
assert.ok(liView.some((d) => d.docId === 'doc-sh-1'));
|
|
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);
|
|
assert.equal(wangView.length, 3);
|
|
assert.ok(wangView.every((d) => d.category !== 'private'), '无私密文档的用户只看到公共受控与共享内容');
|
|
});
|
|
|
|
test('一键共享至项目参考库流转测试 (Promote to Shared Reference Test)', () => {
|
|
const privateDoc = SAMPLE_DELIVERABLES.find((d) => d.docId === 'doc-priv-zhang')!;
|
|
assert.equal(privateDoc.category, 'private');
|
|
|
|
// 张工点击「一键共享给项目组」
|
|
const nowStr = '2026-09-08 16:00';
|
|
const sharedDoc = promoteToShared(privateDoc, nowStr);
|
|
|
|
assert.equal(sharedDoc.category, 'shared');
|
|
assert.equal(sharedDoc.sharedAt, nowStr);
|
|
assert.equal(sharedDoc.isFixedWorkflow, false, '共享参考资料不可篡改为工作流文档');
|
|
|
|
// 模拟更新交付物列表
|
|
const updatedDeliverables = SAMPLE_DELIVERABLES.map((d) =>
|
|
d.docId === sharedDoc.docId ? sharedDoc : d
|
|
);
|
|
|
|
// 李主管 (ID: 2) 重新刷新视图:现在李主管可以查阅这份被共享出来的文档!
|
|
const liViewAfterShare = filterVisibleDeliverables(updatedDeliverables, 2);
|
|
assert.ok(
|
|
liViewAfterShare.some((d) => d.docId === 'doc-priv-zhang'),
|
|
'一旦创建者一键共享,项目组成员李主管立即具有查阅权限'
|
|
);
|
|
});
|
|
|
|
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);
|
|
|
|
// 1. 非创建者 (李主管 ID: 2) 尝试撤回:必须抛出权限越权异常
|
|
assert.throws(
|
|
() => revokeShareToPrivate(sharedDoc, 2),
|
|
/只有该共享文档的创建者本人方可撤回为私有/
|
|
);
|
|
|
|
// 2. 创建者 (张工 ID: 1) 正确执行撤回
|
|
const revokedDoc = revokeShareToPrivate(sharedDoc, 1);
|
|
assert.equal(revokedDoc.category, 'private');
|
|
assert.equal(revokedDoc.sharedAt, undefined);
|
|
|
|
// 3. 验证撤回后李主管 (ID: 2) 的视图已彻底不可见
|
|
const updatedDeliverables = SAMPLE_DELIVERABLES.map((d) =>
|
|
d.docId === revokedDoc.docId ? revokedDoc : d
|
|
);
|
|
const liViewAfterRevoke = filterVisibleDeliverables(updatedDeliverables, 2);
|
|
assert.ok(
|
|
!liViewAfterRevoke.some((d) => d.docId === 'doc-sh-1'),
|
|
'撤回私有后,其他成员立刻无法访问该文档'
|
|
);
|
|
});
|
|
|
|
test('受控工作流纯净性红线检验 (Workflow Purity Security Red Line)', () => {
|
|
const workflowDoc = SAMPLE_DELIVERABLES.find((d) => d.docId === 'doc-wf-1')!;
|
|
|
|
// 严禁将法定工作流受控文档篡改为普通共享参考
|
|
assert.throws(
|
|
() => promoteToShared(workflowDoc, '2026-09-08 16:30'),
|
|
/受控工作流交付物不可变更为项目共享类型/
|
|
);
|
|
});
|