96 lines
5.8 KiB
Markdown
96 lines
5.8 KiB
Markdown
# 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`
|