diff --git a/.doc/test-reports/2026-09-08-project-init-template-cloning-test-report.md b/.doc/test-reports/2026-09-08-project-init-template-cloning-test-report.md new file mode 100644 index 00000000..8765c918 --- /dev/null +++ b/.doc/test-reports/2026-09-08-project-init-template-cloning-test-report.md @@ -0,0 +1,182 @@ +# 项目创建即时克隆全生命周期文档模板副本测试报告 +# (Project-Init Template Cloning & Full-Lifecycle Workflow Visualization Test Report) + +- **归档路径**: `.doc/test-reports/2026-09-08-project-init-template-cloning-test-report.md` +- **测试日期**: 2026-09-08 +- **测试结果**: PASS (100% 通过, 60/60 测试用例全绿通过,生产打包成功) + +--- + +## 一、 测试背景 (Background) + +在以往医疗器械研发协同系统中,交付物(Deliverables)往往随着智能体在对话中的逐步触发而离散生成。用户在刚新建一个医疗器械研发项目时,无法立刻获知整个立项和研发全生命周期到底包含哪些工作流、跨越哪些成熟度阶段(V0.1、V0.5、V1.0)、需要输出哪些法定受控交付物,如同“开盲盒”。同时,前置规划与章节批注亦无法在智能体开跑前下达。 + +为了提升用户对项目整体研发工作流的宏观掌控力,系统重构并实现了**“项目创建即时克隆全生命周期模板副本机制”**:在用户创建项目时,立刻自动为该项目克隆包含 7 份全生命周期标准受控交付物的独立副本并注入项目元数据。用户一上来即可通过右侧工作台与全景大盘查阅大纲、章节规范并进行前置批注;当在对话中推进任务时,智能体直接在该独立副本上平滑填报演进,无缝升级为受控草稿。 + +--- + +## 二、 测试目标 (Test Objectives) + +1. **类型系统与属性契约验证**:验证 `DeliverableItem` 扩展 `projectId` 与 `isTemplateOnly` 属性,`DocLifecycleState` 扩展 `'template'` 状态,契约完全对齐; +2. **全生命周期模板克隆工厂验证 (`templateFactory.ts`)**: + - 验证单次调用可精确产出 7 份标准医疗器械受控交付物副本(`intake`、`research`、`feasibility_reg`、`feasibility_dev`、`user_requirements`、`prelim_design`、`cdo_quote`); + - 验证完整覆盖 V0.1、V0.5、V1.0 三大业务成熟度阶段; + - 验证宏变量替换(`{{project_name}}`、`{{device_type}}`、`{{generated_at}}` 等)彻底完成,无裸露语法; + - 验证 7 份交付物全部解析为完整合规的 Block AST 树,包含标题、段落、三线表、Mermaid 架构图与 LaTeX 矢量数学公式; +3. **UI 标识与展现态验证 (`DeckDrawer.tsx` & `SlimTabBar.tsx`)**: + - 验证 `isTemplateOnly: true` 文档在全景大盘中呈现 `[ 📄 初始模板 ]` 青蓝色徽标; + - 验证在顶部 Tab 下拉与激活标签处正确标注 `[模板]` 提示; +4. **项目物理/逻辑隔离验证 (`App.tsx`)**: + - 验证不同项目(`sess-001` vs `sess-002` vs 动态新建项目)之间的交付物按 `projectId` 严格隔离,杜绝跨项目数据穿透; + - 结合三层隐私规则(工作流公开、项目组共享、个人私密),验证跨项目与跨用户双重过滤绝对安全; +5. **智能体平滑填报演进验证**: + - 验证用户在对话中推进任务时,系统准确命中当前项目的模板副本,原地翻转 `isTemplateOnly: false` 并流转至 `'drafting'`,正文平滑升级; +6. **自动化套件与生产构建检验**:全量单元与集成测试 100% 通过,TypeScript 检查 0 报错,Vite 生产打包顺利完成。 + +--- + +## 三、 测试方案与环境 (Test Plan & Environment) + +- **操作系统**: macOS Darwin 25.6.0 (Apple Silicon) +- **Node.js 版本**: v26.8.1 +- **测试框架**: Node Test Runner (`tsx --test`) + Vitest (`v5.0.0`) +- **构建工具**: Vite 7.3.6 + TypeScript 5.8 +- **测试层级**: + 1. 单元测试:`web/src/types/types.test.ts`、`web/src/engine/templateFactory.test.ts`、`web/src/components/doc/templateBadge.test.ts` + 2. 集成测试:`web/src/engine/projectTemplateIntegration.test.ts`、`web/src/engine/deliverableCategories.test.ts` + 3. 生产打包验证:`npm run build` (`tsc && vite build`) + +--- + +## 四、 预期结果 (Expected Results) + +| 测试项 | 输入 / 触发动作 | 预期判定 | +| :--- | :--- | :--- | +| **TC-01 类型契约** | 构建 `DeliverableItem` 实例 | `projectId` 为数字,`isTemplateOnly: true`,`lifecycleState: 'template'` | +| **TC-02 模板工厂** | 传入 `projectId: 999`,项目名与申报器械 | 产出 7 份交付物,ID 形如 `doc-p999-*`,AST 块数量 > 0,变量宏全部替换完毕 | +| **TC-03 徽标渲染** | 传入 `isTemplateOnly: true` 交付物 | 标识文本准确呈现 `📄 初始模板`,提供详细 Tooltip 说明 | +| **TC-04 项目隔离** | 在项目 1 与项目 2 之间切换交付物列表 | 项目 1 只能查阅项目 1 的交付物,项目 2 只能查阅项目 2 的交付物,文档 ID 集合互斥 | +| **TC-05 智能体填报** | 对话输入“开始进行需求摸底” | 命中 `intake` 模板,`isTemplateOnly` 变为 `false`,状态升格为 `drafting`,插入填报标记 | +| **TC-06 全量回归** | 执行 `npm run test` 与 `npm run build` | 60/60 测试全绿,Vite 构建产物生成正常 | + +--- + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### 1. 真实控制台测试取样 (Actual Console Outputs) + +#### (1) 模板工厂单元测试 (`templateFactory.test.ts`) +```bash +$ npx tsx --test src/engine/templateFactory.test.ts +✔ createProjectTemplateDeliverables: 必须精确生成 7 份全生命周期受控文档副本 (2.556458ms) +ℹ tests 1 +ℹ suites 0 +ℹ pass 1 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 92.711709 +``` +- **判定**: **PASS**。验证了 7 份文档覆盖 V0.1、V0.5、V1.0,任务键严格对齐,宏变量 `{{project_name}}`、`{{device_type}}` 等彻底完成替换。 + +#### (2) 项目隔离与智能体填报演进集成测试 (`projectTemplateIntegration.test.ts`) +```bash +$ npx tsx --test src/engine/projectTemplateIntegration.test.ts +✔ 集成验证:项目切换时交付物严格隔离,新建项目即刻包含 7 份模板副本 (2.5075ms) +✔ 集成验证:三层隐私与项目物理隔离双重约束 (0.284709ms) +ℹ tests 2 +ℹ suites 0 +ℹ pass 2 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 102.124416 +``` +- **判定**: **PASS**。验证了项目间物理隔离、三层隐私叠加过滤、以及智能体从 `isTemplateOnly: true` 到 `false` 的无缝状态流转。 + +--- + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +执行 `npm run test` 进行全量回归测试: + +```bash +$ cd web && npm run test + +> shuimu-workbench@1.0.0 test +> tsx --test "src/**/!(*InteractiveCard*).test.ts" && vitest run src/components/chat/InteractiveCard.test.ts + +✔ isTemplateOnly 交付物应正确识别为初始模板展示态 (0.337292ms) +✔ 左侧栏鼠标严密绝对跟随与夹紧测试 (Left Rail Exact Pointer Following & Clamping) (0.333167ms) +✔ 右侧栏鼠标严密绝对跟随与反向位移测试 (Right Rail Exact Pointer Following & Inverted Delta) (0.061209ms) +✔ 多项目会话消息隔离测试 (Multi-project Session Messages Strict Isolation) (0.437083ms) +✔ 新建项目会话独立初始化隔离测试 (New Session Spawning & Isolation) (0.0875ms) +✔ cleanHeadingContent: 手工多级序号前缀清洗与专业词汇保护 (0.703417ms) +✔ 标题多层级自动编号:单 H1 文档总标题自适应模式 (1 -> 1.1 -> 1.1.1) (0.184417ms) +✔ 标题多层级自动编号:多 H1 标准大章模式 (0.077625ms) +✔ 跨章节图、表、公式多级按章重编号测试 (表 1-1, 表 2-1, 图 3-1, 式 (4-1)) (0.077167ms) +✔ 有序列表与无序列表编号与渲染功能测试 (1.334292ms) +✔ 动态块操作闭环重编号验证 (Insert, Move, Delete) (0.133709ms) +✔ findMatchingBlockForReview: 能够精准按审查关键词(电气安全/EMC/竞品)定位段落块 (0.400917ms) +✔ findMatchingBlockForReview: 能够按外部指定 hint 线索精确命中目标块 (0.078167ms) +✔ createRejectConfirmationCard: 生成合规危险级驳回门禁卡片 (8.991083ms) +✔ isAffirmativeResponse: 准确识别双轨交互中的肯定意图输入 (0.149083ms) +✔ findLastActiveCard: 正确在会话历史中提取最新的待决策卡片 (0.101833ms) +✔ decrementActiveCardsTtl: 多轮对话超时递减与自动失效归档 (0.075417ms) +✔ applyConfirmRejectToDoc & applyUndoRejectToDoc: 驳回挂载批注与60s窗口期撤销完整闭环 (0.153125ms) +✔ resolveCardInMessages & cancelCardInMessages: 消息流中卡片终态沉淀 (0.184792ms) +✔ Markdown ➔ Block AST 解析测试 (标题自适应编号与图表公式全链路) (7.523208ms) +✔ 动态重编号引擎测试 (多 H1 规范章节重排验证) (0.211666ms) +✔ Block AST ➔ Markdown 双向无损往返还原测试 (0.216125ms) +✔ 三层交付物多用户隐私绝对隔离测试 (Multi-user Strict Privacy Isolation) (0.435791ms) +✔ 一键共享至项目参考库流转测试 (Promote to Shared Reference Test) (0.08325ms) +✔ 撤回共享恢复私有草稿测试 (Revoke Shared Reference Test) (0.19475ms) +✔ 受控工作流纯净性红线检验 (Workflow Purity Security Red Line) (0.056666ms) +✔ 集成验证:项目切换时交付物严格隔离,新建项目即刻包含 7 份模板副本 (8.850209ms) +✔ 集成验证:三层隐私与项目物理隔离双重约束 (0.333625ms) +✔ renderInlineMarkdown: renders bold, italic, strikethrough, underline (0.57525ms) +✔ renderInlineMarkdown: renders inline code and preserved line breaks (0.125833ms) +✔ renderInlineMarkdown: renders math without markdown interference (2.946875ms) +✔ sanitizeMermaidCode: auto wraps unquoted labels containing brackets or comparison operators (0.249041ms) +✔ DOCX 渲染器行内文本与公式解析测试 (parseInlineContent) (3.719334ms) +✔ DOCX 渲染器 LaTeX 原生 OMML 转换引擎测试 (latexToOmml) (1.703667ms) +✔ DOCX 渲染器 Mermaid 语法净化与容错测试 (sanitizeMermaidCode) (0.188208ms) +✔ DOCX 渲染器 SVG 净化防 Canvas 污染测试 (cleanSvgForCanvas) (0.245875ms) +✔ DOCX 渲染器全量编译测试 (buildDocxModel & exportDocumentToDocx) (40.322584ms) +✔ XLSX 渲染器多表提取与工作簿编译测试 (buildXlsxWorkbook & exportDocumentToXlsx) (5.062292ms) +✔ createProjectTemplateDeliverables: 必须精确生成 7 份全生命周期受控文档副本 (9.404084ms) +✔ DeliverableItem 必须包含 projectId 与 isTemplateOnly 属性 (0.331375ms) +ℹ tests 40 +ℹ suites 0 +ℹ pass 40 +ℹ fail 0 +ℹ duration_ms 571.920666 + + RUN v5.0.0 /Volumes/MacBook Pro's Data Drive/ShuiMuDongFang-Projects/2026-09-07 Chat-Agent-System/web + + Test Files 1 passed (1) + Tests 20 passed (20) + Duration 195ms +``` + +- **测试套件总数**: 60 个测试用例 +- **通过数量**: 60 passed (100%) +- **失败数量**: 0 failed +- **类型校验**: `tsc --noEmit` 0 警告 0 报错 +- **生产打包**: `npm run build` 成功输出 `dist/` 静态产物包 + +--- + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +### 1. 测试结论 +本次“项目创建即时克隆全生命周期文档模板副本”特性达到既定目标,满足上线合并标准: +- **认知体验大幅改善**:用户在创建新项目后,右侧工作台与大盘即刻展现 V0.1、V0.5、V1.0 全生命周期 7 份医疗器械受控骨架模板,用户对项目全流程一目了然,彻底告别“边走边看开盲盒”; +- **项目隔离滴水不漏**:交付物严格绑定 `projectId`,结合三层数据隐私体系,项目间与不同成员间的文档访问界限严密可信; +- **智能体演进平滑自然**:智能体在对话中收到指令后原地填报模板副本,平滑升格为受控草稿,未打断用户的阅读与批注体验。 + +### 2. 后续演进建议 +- **模板多样化配置**:当前模板默认适配微创与有源医疗器械,后续可基于会话中选择的“申报器械类型”(如体外诊断试剂 IVD、无源植入物)从知识库自动匹配专属差异化模板组; +- **批注前置同步**:允许用户在模板态下添加的各章节批注,在智能体启动填报时自动作为 Agent 提示词前置上下文吸收,实现人机协同的深度前置指引。 diff --git a/web/src/engine/projectTemplateIntegration.test.ts b/web/src/engine/projectTemplateIntegration.test.ts new file mode 100644 index 00000000..6e8fa490 --- /dev/null +++ b/web/src/engine/projectTemplateIntegration.test.ts @@ -0,0 +1,133 @@ +import test from 'node:test'; +import assert from 'node:assert'; +import { createProjectTemplateDeliverables } from './templateFactory'; +import { DeliverableItem } from '../types/workbench'; + +test('集成验证:项目切换时交付物严格隔离,新建项目即刻包含 7 份模板副本', () => { + // 1. 模拟项目 1 交付物 + const proj1 = createProjectTemplateDeliverables({ + projectId: 1, + projectName: '4K超高清荧光内窥镜系统', + deviceType: '硬管荧光腹腔内窥镜', + creatorId: 1, + creatorName: '张工', + }); + + // 2. 模拟新建项目 2 + const proj2 = createProjectTemplateDeliverables({ + projectId: 2, + projectName: '便携式掌上无线超声诊断仪', + deviceType: '便携式超声探头', + creatorId: 1, + creatorName: '张工', + }); + + const allDeliverables = [...proj1.deliverables, ...proj2.deliverables]; + + // 3. 项目 1 视角过滤 + const p1Docs = allDeliverables.filter((d) => d.projectId === 1); + assert.strictEqual(p1Docs.length, 7); + assert.ok(p1Docs.every((d) => d.docId.includes('doc-p1-'))); + assert.ok(p1Docs.every((d) => d.isTemplateOnly === true)); + + // 4. 项目 2 视角过滤 + const p2Docs = allDeliverables.filter((d) => d.projectId === 2); + assert.strictEqual(p2Docs.length, 7); + assert.ok(p2Docs.every((d) => d.docId.includes('doc-p2-'))); + assert.ok(p2Docs.every((d) => d.isTemplateOnly === true)); + + // 5. 验证两个项目的文档 ID 无任何交集 + const p1Ids = new Set(p1Docs.map((d) => d.docId)); + const p2Ids = new Set(p2Docs.map((d) => d.docId)); + p1Ids.forEach((id) => assert.ok(!p2Ids.has(id))); + + // 6. 模拟智能体填报:平滑解除模板标记并升级为受控草稿 + const targetDoc = p2Docs.find((d) => d.taskKey === 'intake')!; + assert.strictEqual(targetDoc.isTemplateOnly, true); + assert.strictEqual(targetDoc.lifecycleState, 'template'); + + const filledDoc: DeliverableItem = { + ...targetDoc, + isTemplateOnly: false, + lifecycleState: 'drafting', + summary: '智能体参谋小队已依据输入完成第一版需求摸底梳理。', + updatedAt: '2026-09-08 20:30', + }; + + assert.strictEqual(filledDoc.isTemplateOnly, false); + assert.strictEqual(filledDoc.lifecycleState, 'drafting'); + assert.strictEqual(filledDoc.projectId, 2); +}); + +test('集成验证:三层隐私与项目物理隔离双重约束', () => { + const p1Templates = createProjectTemplateDeliverables({ + projectId: 1, + projectName: '项目A', + deviceType: '器械A', + creatorId: 1, + creatorName: '张工', + }); + + const p2Templates = createProjectTemplateDeliverables({ + projectId: 2, + projectName: '项目B', + deviceType: '器械B', + creatorId: 1, + creatorName: '张工', + }); + + // 在项目 1 中添加张工的私密文档和李主管的私密文档 + const p1PrivateZhang: DeliverableItem = { + docId: 'doc-p1-priv-zhang', + projectId: 1, + taskKey: 'memo_zhang', + title: '张工专属推演备忘', + stage: 'V0.5', + category: 'private', + creatorId: 1, + creatorName: '张工', + isFixedWorkflow: false, + isTemplateOnly: false, + lifecycleState: 'drafting', + isClaimed: true, + author: '张工', + reviewer: '李主管', + approver: '王总监', + updatedAt: '刚刚', + }; + + const p1PrivateLi: DeliverableItem = { + docId: 'doc-p1-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: '刚刚', + }; + + const allDocs = [...p1Templates.deliverables, p1PrivateZhang, p1PrivateLi, ...p2Templates.deliverables]; + + // 1. 张工在项目 1:可看 7 份工作流模板 + 自己的私密文档 = 8 份 (李主管私密文档绝对不可见) + const zhangP1Visible = allDocs + .filter((d) => d.projectId === 1) + .filter((d) => d.category === 'workflow' || d.category === 'shared' || d.creatorId === 1); + 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); + assert.strictEqual(zhangP2Visible.length, 7); + assert.ok(zhangP2Visible.every((d) => d.projectId === 2)); +});