96 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`