docs: 错误码一致性测试设计(api §7 ↔ 异常树机器验证)

This commit is contained in:
lhl
2026-08-09 15:19:55 +08:00
parent f3a30256d5
commit 49f6ca80d8
@@ -0,0 +1,102 @@
# 错误码一致性测试设计(api-design §7 ↔ exceptions.py
> **状态:** 已批准(brainstorming + DX 审查吸收)
> **日期:** 2026-08-09
> **里程碑后备路径:** LLM 错误码对齐(error_code)收尾
## 1. 背景与目标
「LLM 错误码对齐」完成后,`api-design.md §7` 错误码表与 `src/genesis/inference/exceptions.py` 的异常树通过 `error_code` 类属性建立了一一对应关系(LLM_TIMEOUT ↔ LLMTimeoutError 等),但该对应关系目前**仅靠人工核对**维护:
- `test_inference_errors.py` 断言了异常类自身的 `error_code` 值(代码自证代码,防不了文档漂移)
- 文档添加/删除错误码行、或代码新增异常类时,没有测试会在两边失配时变红
**目标:** 将「api §7 = 唯一事实源」的核对从人工升级为机器验证——新增回归测试解析 `docs/api-design.md` §7 表格,断言与异常树双向一致,防文档↔代码漂移。
## 2. 范围与不做的事
**范围:**
- 仅验证 §7 表格中**来源列以 `exceptions.` 开头**的 5 条 LLM 错误码行(LLM_TIMEOUT / LLM_NETWORK_ERROR / LLM_NOT_CONFIGURED / LLM_PARSE_ERROR
- 双向验证:异常树 → 文档、文档 → 异常树
**不做(YAGNI):**
- 不验证非 LLM 错误码行(FILE_TYPE_INVALID、EMBEDDING_FAILED、INTERNAL_ERROR 等来源列为「—」,暂无代码映射)
- 不抽 src 生产模块(测试自包含,零生产代码改动)
- 不做 web-ui §8 端点表等其它文档一致性检查(有第二个需求再提炼共享模块)
## 3. 设计
### 3.1 落点
新建独立测试文件 `tests/test_api_design_consistency.py`(单一职责:文档 ↔ 代码一致性门禁),不污染现有 `test_inference_errors.py`
### 3.2 文件定位(沿用项目先例)
```python
API_DOC = Path(__file__).resolve().parents[1] / "docs/api-design.md"
```
(与 `tests/test_real_samples.py:7``SAMPLES` 定位方式一致;不依赖 CWD,pytest 任意目录启动可跑。)
### 3.3 解析函数(内联纯函数)
```python
def _extract_llm_error_rows(document: str) -> dict[str, str]:
"""解析 §7 表格中来源列以 exceptions. 开头的行,返回 {错误码: 来源类名}。"""
```
- 实现:逐行匹配表格行 ` | \`CODE\` | ... | exceptions.Xxx |`,提取错误码与来源类名
- 用途:返回 LLM 错误码 → 来源异常类名 的映射;非 LLM 行(来源 "—")被天然排除
### 3.4 测试用例(4 个)
| # | 用例 | 验证方向 | 断言要点 |
|---|------|---------|---------|
| 1 | `test_error_doc_lists_every_llm_error_code` | 异常树 → 文档 | 遍历 `LLMError.__subclasses__()`,每个子类 `error_code`(非 None)必须出现在文档映射中,且来源类名 == 子类名 |
| 2 | `test_error_doc_llm_rows_resolve_to_exception` | 文档 → 异常树 | 文档映射中每个错误码都能在 `LLMError.__subclasses__()` 中找到 `error_code` 相同、类名匹配的唯一类 |
| 3 | `test_error_doc_stable_llm_row_count` | 格式护栏 | 文档映射中 LLM 行数 == 5(防止表格结构变化静默失效;新增/删除 LLM 行需显式更新此数) |
| 4 | `test_error_doc_parse_guard_prevents_silent_noop` | 格式护栏 | 若解析结果为空(表格格式被改动导致正则失配),立即硬失败而非静默通过 |
### 3.5 失败可诊断(DX 修正 F1/F2
- 测试 1/2 断言失败时,输出**双向差异清单**:文档有而代码无的码、代码有而文档无的码,并附引导语「请同步 api-design.md §7 或 exceptions.py」
- 通过 pytest 的断言消息(`assert ... , "差异说明"`)实现,避免裸 `assert 'LLM_QUOTA' in dict` 的模糊失败
### 3.6 契约可读性(DX 修正 F4)
- 文件头部模块 docstring 写明:「此测试为 api-design §7 ↔ exceptions.py 的机器契约,变更错误码必须两处同步修改」
## 4. 错误处理
| 场景 | 行为 |
|------|------|
| 文档缺失 | `read_text()` 抛 FileNotFoundError → 测试失败(显式暴露) |
| 表格行解析失配(0 行) | 用例 4 硬失败,杜绝静默失效 |
| 新增异常未登记 / 文档多行无单码 | 用例 1/2 分别双硬失败并列出差异 |
| LLM 行数变化 | 用例 3 失败,提示显式更新计数 |
## 5. 测试策略与覆盖
- 本功能自身即测试;不新增生产代码,`fail_under=99` 红线不受触碰(其按 `src/` 生效,tests/ 命中不计)
- 全量回归保持 ≥ 128 passed(新增 4 用例后 ≥ 132
- 验证命令:`python -m pytest tests/test_api_design_consistency.py -v`(聚焦);`python -m pytest -q`(全量)
## 6. 验收标准
1. `pytest tests/test_api_design_consistency.py -v` 全绿(4 passed
2. 全量回归 `python -m pytest -q` ≥ 132 passed / 100.00% 覆盖
3. 手工破坏验证(可选):临时删除文档某 LLM 行 → 用例 1 变红且差异清单可读;恢复正常
4. 不修改任何现有源文件或测试文件
## 7. 影响面
- 新增 1 个测试文件(约 60-80 行)
- 修改 1 个文件:`_AI_USAGE_LOG.md`(追加日志行)
- 提交消息:`test: api §7 与异常树错误码一致性门禁测试(防文档漂移)`
## 8. 不做的事(YAGNI 最终确认)
- ❌ 非 LLM 行验证
- ❌ src 生产辅助模块
- ❌ Enum 化错误码(spec 已明确 str
- ❌ 其它文档表一致性检查