# 错误码一致性测试设计(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.` 开头**的 4 条 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 行数 == 4(防止表格结构变化静默失效;新增/删除 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) - ❌ 其它文档表一致性检查