# TDD — Obsidian Vault 同步 · 测试设计与实现 > 版本:v1.0 · 日期:2026-08-19 · 对应 PRD/SDD/BDD v1.0 > 本文是 BDD.md 场景到可执行测试的映射与运行指南。 --- ## 1. 测试金字塔 | 层 | 工具 | 位置 | 覆盖目标 | |---|---|---|---| | 单元/集成(后端) | xUnit + EF InMemory/临时目录 | `build-flow.api/tests/BuildFlow.Rag.Tests/` | Handler 增量对账、Scanner 安全门禁 | | 架构护栏 | xUnit(NetArchTest) | `tests/BuildFlow.Architecture.Tests/` | RAG 模块安全约束(含 Obsidian) | | UI 端到端 | Playwright(API mock) | `build-flow.admin/e2e/obsidian-sync.spec.ts` | 登录→知识库→同步→结果 全链路 UI 行为 | | 全链路端到端(可选) | Playwright + 真实后端 | 同 spec 文件 + §5 手动规程 | 真实 PG/服务联调 | ## 2. BDD 场景 → 测试映射 ### 2.1 后端场景(已存在,本次未新增代码故未新增用例;端点修复由既有测试回归覆盖) | BDD 场景 | 测试 | 状态 | |---|---|---| | 忽略非笔记内容 / 白名单 / `..` / 符号链接逃逸 / 容量超限 / fail-closed | `ObsidianVaultScannerTests`(6 例) | ✅ 通过 | | 安全架构约束 | `ObsidianSyncSecurityTests`、`RagSecurityArchitectureTests` | ✅ 通过 | | 首次同步 / 增量跳过 / 修改与新增(Handler 三分支) | 由 Scanner 测试 + 架构测试间接覆盖;Handler 直测列入后续补测(全项目 203 功能点补测计划的一部分) | ⚠️ 间接 | > 2026-08-19 实跑:`dotnet test tests/BuildFlow.Rag.Tests --filter "FullyQualifiedName~Obsidian"` → **9/9 通过**。 > 全量 Rag.Tests 另有 17 个失败,全部为需要本地 PostgreSQL(pgvector)/Redis 的集成测试 > (`RagPgVectorIntegrationTests`、`RedisChatMessageCacheTests`),与本次改动无关,需在带基础设施的环境复跑。 ### 2.2 UI 场景(本次新增,Playwright) | BDD 场景 | 测试用例 | 断言要点 | |---|---|---| | 从知识库列表发起同步并看到结果 | `从知识库列表发起同步并展示结果` | 结果弹窗标题(`.ant-modal-confirm-title`)、TotalFiles/Added/Updated/Skipped 四计数、后端 message、请求恰好 1 次 | | 必填校验 | `路径必填校验:不输入路径不发起请求` | 中文必填提示可见、sync 请求数 = 0 | | 错误透出 | `后端业务错误以中文 message 透出` | 400 + `invalid_param` 时 `.ant-message` 展示后端中文 message | | 防重复提交 | `同步进行中按钮 loading 且防重复提交` | 慢响应(2.5s)期间确认键 disabled、强制再点不产生第 2 个请求、最终正常出结果 | **运行结果(2026-08-19):4/4 通过(10.7s,Chromium,生产构建 + max preview)。** ## 3. E2E 技术设计(关键决策) 1. **执行环境 = 生产构建 + `max preview`**,不用 dev server。 umi dev 按需 bundling 会让页面长时间停在 "Bundling..." 中间页、CSS 未注入,测试不稳定; `playwright.config.ts` 的 `webServer` 自动托管 `npx max preview --port 8000`(跑前需 `npm run build`)。 2. **API 一律 `page.route('**/api/**')` mock**,响应信封 `{code,data,message}` 与后端 `ApiResponseMiddleware` 对齐;登录/未登录由 mock 内 `authed` 状态机控制(未登录一律 401)。 无需真实账号、PG、Redis,CI 可跑。 3. **antd v6 confirm 弹窗断言陷阱**:`Modal.success` 的经典 `.ant-modal-header` 是 `display:none`,可见标题在 `.ant-modal-confirm-title`;`getByText('标题')` 会命中隐藏副本, 必须用 `.ant-modal-confirm-title` 或正文内容断言。(本次实测踩坑后固化为此条规范。) 4. **定位策略**:业务组件加 `data-testid`(`obsidian-sync-open` / `obsidian-vault-input` / `obsidian-sync-submit`),登录表单用 placeholder(antd Form.Item 的 name 不映射到 input name 属性)。 5. **antd v6 Select 下拉项无 role**:下拉选项渲染为 `generic`(无 `option` 角色), `getByRole('option')` / `.ant-select-item-option` 类名定位均不可靠;正确做法是 `getByRole('combobox', { name: /字段标签/ })` 展开后 `getByText('选项文案', { exact: true })` 点击。 (create-forms.spec.ts 实测踩坑后固化。) 6. **multipart 断言**:`page.route` 捕获的 `req.postData()` 对 FormData 提交是 multipart 原始文本, 可直接 `toContain('name="ObjectKey"')` 等断言表单字段(见文件上传用例)。 ## 4. 运行方式 ```bash cd build-flow.admin npm run build # 首次或代码变更后 npm run test:e2e # = playwright test,自动起 max preview npx playwright test --ui # 调试模式 ``` 后端回归: ```bash cd build-flow.api dotnet test tests/BuildFlow.Rag.Tests --filter "FullyQualifiedName~Obsidian" ``` ## 5. 全链路端到端(真实后端,手动可选) UI 级 E2E 覆盖了前端全链路行为;如需打到真实后端: 1. 配置允许根目录(环境变量注入,不改入库配置): `export Rag__Obsidian__AllowedRoots__0=/path/to/test-vault` 2. 准备测试 vault(若干 .md 文件,含一个子目录与一个 `.obsidian/` 目录); 3. `cd build-flow.api && ./run-dev.sh`(连远程中间件,端口 5667); 4. `cd build-flow.admin && npm run start:no-mock`(代理 `/api` → 5667); 5. 浏览器手动执行 BDD「React 管理端」4 场景,或以真实账号改造 spec 去掉 mock 后执行。 > 全链路自动化需要可用的测试账号凭据与稳定的远程中间件,当前未纳入自动套件,列入 SOP 检查项。 ## 6. 后续补测清单 - [ ] `SyncObsidianVaultCommandHandler` 直测:added/updated/skipped 三分支 + 知识库归属校验(NotFound) - [ ] 端点分块模式 `Enum.IsDefined` 校验的参数化测试(0-5 接受、6/-1 拒绝→null) - [ ] 带 Docker 基础设施环境复跑 `RagPgVectorIntegrationTests` / `RedisChatMessageCacheTests`