Files
Agent-System/web/src/engine/deliverableCategories.test.ts
T

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'),
/受控工作流交付物不可变更为项目共享类型/
);
});