Commit 86c5c15a2fb5801b188aec304b1980ebd74bf74f
1 parent
78be2e6f
graphlemon: benchmark artifact for task b243dfe8-4da8-49bd-895b-8b9e9ed17280
Showing
1 changed file
with
345 additions
and
0 deletions
.graphlemon/runs/b243dfe8-4da8-49bd-895b-8b9e9ed17280/benchmark.md
0 → 100644
| 1 | +# Benchmark:Skill 详情页展示真实内容(`v_skill_audit_log` 契约收口) | ||
| 2 | + | ||
| 3 | +对应 SDD:《SDD:Skill 详情页展示真实内容(`v_skill_audit_log` 契约收口)》,Unit DU-01。 | ||
| 4 | + | ||
| 5 | +本文档定义 DU-01 的可验证验收场景,覆盖三层:`src/skill_query.py::get_skill_content()` | ||
| 6 | +单元层、`src/backend_server.py` HTTP 路由层、`frontend/app.js` 手工验证层,以及既有测试的回归 | ||
| 7 | +范围。每个场景给出**具体输入、预期行为、可观察的通过标准**(返回值/状态码/DOM 内容/异常类 | ||
| 8 | +型),不是场景标题的罗列。 | ||
| 9 | + | ||
| 10 | +--- | ||
| 11 | + | ||
| 12 | +## 0. 测试环境与前置数据约定 | ||
| 13 | + | ||
| 14 | +所有单元层 / 路由层场景假定以下共享 fixture(除非场景自身另行说明覆盖值): | ||
| 15 | + | ||
| 16 | +- 临时目录 `repo_root/`,其下 `repo_root/skills/skill-1/SKILL.md`,内容为已知字符串 | ||
| 17 | + `"# Skill One\n\nBody text.\n"`。 | ||
| 18 | +- SQLite `skill` 表插入一行: | ||
| 19 | + | ||
| 20 | + ``` | ||
| 21 | + skill_id='skill-1', owner_org_node='coder', file_path='skills/skill-1', | ||
| 22 | + current_git_commit_sha='unknown', source='migrated_from_hermes', | ||
| 23 | + migrated_from_profile='code', status='active' | ||
| 24 | + ``` | ||
| 25 | + | ||
| 26 | +- `skill_iteration` 表为空(对应 PRD 背景中"当前没有迭代记录"这句话本身正确的前提)。 | ||
| 27 | + | ||
| 28 | +每个场景会在此基线上做局部覆盖(如删除文件、篡改 `file_path`、增加支持文件等),并在场景描 | ||
| 29 | +述中明确写出与基线的差异。 | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +## 1. 单元层场景:`src/skill_query.py::get_skill_content()` | ||
| 34 | + | ||
| 35 | +### U1(happy path)正常内容读取,无支持文件 | ||
| 36 | + | ||
| 37 | +- **输入**:基线 fixture,调用 `get_skill_content(conn, "skill-1", repo_path=repo_root)`。 | ||
| 38 | +- **预期行为**:函数正常返回,不抛任何异常。 | ||
| 39 | +- **通过标准**(逐字段断言): | ||
| 40 | + ```python | ||
| 41 | + result = get_skill_content(conn, "skill-1", repo_path=repo_root) | ||
| 42 | + assert result == { | ||
| 43 | + "skill_id": "skill-1", | ||
| 44 | + "owner_org_node": "coder", | ||
| 45 | + "file_path": "skills/skill-1", | ||
| 46 | + "current_git_commit_sha": "unknown", | ||
| 47 | + "source": "migrated_from_hermes", | ||
| 48 | + "migrated_from_profile": "code", | ||
| 49 | + "status": "active", | ||
| 50 | + "content": "# Skill One\n\nBody text.\n", | ||
| 51 | + "supporting_files": [], | ||
| 52 | + } | ||
| 53 | + ``` | ||
| 54 | +- **对应测试**:`GetSkillContentTest.test_returns_real_file_content`(重写)。 | ||
| 55 | + | ||
| 56 | +### U2(happy path)支持文件枚举,多层子目录 | ||
| 57 | + | ||
| 58 | +- **输入**:在基线基础上,额外创建: | ||
| 59 | + - `repo_root/skills/skill-1/references/foo.md` | ||
| 60 | + - `repo_root/skills/skill-1/scripts/bar.py` | ||
| 61 | + - `repo_root/skills/skill-1/SKILL.md` 内容不变。 | ||
| 62 | +- **预期行为**:`content` 字段仍是 `SKILL.md` 全文;`supporting_files` 列出另外两个文件,使用 | ||
| 63 | + 相对 `skill_dir` 的相对路径,且**升序排序**、**不包含 `SKILL.md` 自身**。 | ||
| 64 | +- **通过标准**: | ||
| 65 | + ```python | ||
| 66 | + result = get_skill_content(conn, "skill-1", repo_path=repo_root) | ||
| 67 | + assert result["content"] == "# Skill One\n\nBody text.\n" | ||
| 68 | + assert result["supporting_files"] == ["references/foo.md", "scripts/bar.py"] | ||
| 69 | + assert "SKILL.md" not in result["supporting_files"] | ||
| 70 | + ``` | ||
| 71 | +- **对应测试**:新增/重写 `GetSkillContentTest.test_lists_supporting_files_sorted`。 | ||
| 72 | + | ||
| 73 | +### U3(boundary)目录下只有 `SKILL.md`,无其他文件 | ||
| 74 | + | ||
| 75 | +- **输入**:基线 fixture(无额外文件)。 | ||
| 76 | +- **预期行为**:`supporting_files == []`(空列表,不是 `None`,不是缺字段)。 | ||
| 77 | +- **通过标准**:`isinstance(result["supporting_files"], list) and result["supporting_files"] == []`。 | ||
| 78 | +- **对应测试**:并入 U1 断言即可覆盖,无需单独用例。 | ||
| 79 | + | ||
| 80 | +### U4(failure→非异常语义)`SKILL.md` 磁盘文件缺失,DB 行仍存在 | ||
| 81 | + | ||
| 82 | +- **输入**:DB 中 `skill-1` 行照常存在,但删除/从不创建 | ||
| 83 | + `repo_root/skills/skill-1/SKILL.md`(`repo_root/skills/skill-1/` 目录本身也不存在,或存在但 | ||
| 84 | + 为空目录——两种子场景都要覆盖)。 | ||
| 85 | +- **预期行为**:**不抛出任何异常**(不是 `SkillContentMissingError`,该类已被删除)。函数正常 | ||
| 86 | + 返回一个 dict,`content` 字段为 `None`。 | ||
| 87 | +- **通过标准**: | ||
| 88 | + ```python | ||
| 89 | + result = get_skill_content(conn, "skill-1", repo_path=repo_root) | ||
| 90 | + assert result["content"] is None | ||
| 91 | + assert result["supporting_files"] == [] # 目录不存在时 os.walk 返回空,不报错 | ||
| 92 | + assert result["skill_id"] == "skill-1" # 其余字段仍从 DB 正常返回 | ||
| 93 | + ``` | ||
| 94 | +- **对应测试**:`GetSkillContentTest.test_missing_file_returns_none_content`(重写,原断言 | ||
| 95 | + `assertRaises(SkillContentMissingError)` 改为断言 `content is None` 且不抛异常)。 | ||
| 96 | + | ||
| 97 | +### U5(failure)`skill_id` 在 `skill` 表中不存在 | ||
| 98 | + | ||
| 99 | +- **输入**:`get_skill_content(conn, "skill-does-not-exist", repo_path=repo_root)`。 | ||
| 100 | +- **预期行为**:抛出 `SkillNotFoundError`,且**不做任何文件系统访问**(DB 查询先于文件路径解 | ||
| 101 | + 析)。 | ||
| 102 | +- **通过标准**: | ||
| 103 | + ```python | ||
| 104 | + with self.assertRaises(skill_query.SkillNotFoundError): | ||
| 105 | + get_skill_content(conn, "skill-does-not-exist", repo_path=repo_root) | ||
| 106 | + ``` | ||
| 107 | +- **对应测试**:既有 `GetSkillContentTest.test_nonexistent_skill_raises_not_found`(保留,行为 | ||
| 108 | + 未变,仅确认契约变更没有波及此分支)。 | ||
| 109 | + | ||
| 110 | +### U6(failure/security)路径穿越——`file_path` 含相对 `../` 跳出 `skills/` 目录 | ||
| 111 | + | ||
| 112 | +- **输入**:DB 中插入一行 `skill_id='evil-1'`,`file_path='skills/../../etc/passwd'`(或等价的 | ||
| 113 | + `../` 相对路径写法,具体依 `_skill_file_path()` 的既有解析规则构造)。调用 | ||
| 114 | + `get_skill_content(conn, "evil-1", repo_path=repo_root)`。 | ||
| 115 | +- **预期行为**:抛出 `SkillPathTraversalError`(不是 `SkillContentMissingError`——该类型已删 | ||
| 116 | + 除)。**在异常抛出前,函数从未调用 `open()`**——用哨兵验证:在越界目标路径预先放置一个内容 | ||
| 117 | + 可识别的哨兵文件(例如 repo 外层一个 `sentinel.txt` 写入 `"SENTINEL-SHOULD-NEVER-BE-READ"`), | ||
| 118 | + 断言异常抛出且该哨兵内容从未出现在任何返回值或日志中。 | ||
| 119 | +- **通过标准**: | ||
| 120 | + ```python | ||
| 121 | + with self.assertRaises(skill_query.SkillPathTraversalError): | ||
| 122 | + get_skill_content(conn, "evil-1", repo_path=repo_root) | ||
| 123 | + # 哨兵文件从未被打开:可通过 mock open() 断言 open 从未以哨兵路径为参数被调用, | ||
| 124 | + # 或通过文件系统 side-channel(哨兵文件 mtime/访问标记不变)间接验证 | ||
| 125 | + ``` | ||
| 126 | +- **对应测试**:`GetSkillContentTest.test_traversal_via_relative_dotdot_in_file_path`(重写, | ||
| 127 | + 异常类型改为 `SkillPathTraversalError`)。 | ||
| 128 | + | ||
| 129 | +### U7(failure/security)路径穿越——`file_path` 通过目录组件字面量跳出(如 `skills-evil/`) | ||
| 130 | + | ||
| 131 | +- **输入**:DB 中插入一行 `skill_id='evil-2'`,`file_path='skills-evil/leak'`(`realpath` 前缀 | ||
| 132 | + 匹配上容易因字符串前缀误判为在 `skills_root` 内,例如 `"...skills"` 是 `"...skills-evil"` 的 | ||
| 133 | + 字符串前缀但不是路径前缀——用以验证用的是 `os.path.commonpath`,不是朴素的 `str.startswith`)。 | ||
| 134 | +- **预期行为**:抛出 `SkillPathTraversalError`。 | ||
| 135 | +- **通过标准**: | ||
| 136 | + ```python | ||
| 137 | + with self.assertRaises(skill_query.SkillPathTraversalError): | ||
| 138 | + get_skill_content(conn, "evil-2", repo_path=repo_root) | ||
| 139 | + ``` | ||
| 140 | +- **对应测试**:`GetSkillContentTest.test_traversal_via_directory_component_returns_path_traversal_error` | ||
| 141 | + (重写,函数名/断言从 `..._returns_content_missing` 改为反映新异常类型)。 | ||
| 142 | + | ||
| 143 | +### U8(boundary)`SkillContentMissingError` 符号已被移除 | ||
| 144 | + | ||
| 145 | +- **输入**:静态检查,非运行时用例。 | ||
| 146 | +- **预期行为**:`src/skill_query.py` 中不再存在 `SkillContentMissingError` 类定义; | ||
| 147 | + `src/backend_server.py` 中不再有引用该符号的 `except` 分支。 | ||
| 148 | +- **通过标准**:`grep -rn SkillContentMissingError src/` 返回空结果。 | ||
| 149 | +- **验证方式**:Code Review / grep,非单元测试断言(记录在 DoD 清单中)。 | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## 2. 路由层场景:`GET /api/skills/:skill_id`(`SkillRouteTest`) | ||
| 154 | + | ||
| 155 | +共享前置:`backend_server` 测试基线起一个真实 `HTTPServer` 线程或使用既有测试基建(与 | ||
| 156 | +`SkillRouteTest` 现有其他用例一致的启动方式),DB fixture 同第 0 节。 | ||
| 157 | + | ||
| 158 | +### R1(happy path)正常内容,200 | ||
| 159 | + | ||
| 160 | +- **请求**:`GET /api/skills/skill-1`(基线 fixture,含 U2 的两个支持文件)。 | ||
| 161 | +- **预期状态码**:`200`。 | ||
| 162 | +- **预期响应体**(JSON,逐字段): | ||
| 163 | + ```json | ||
| 164 | + { | ||
| 165 | + "skill_id": "skill-1", | ||
| 166 | + "owner_org_node": "coder", | ||
| 167 | + "file_path": "skills/skill-1", | ||
| 168 | + "current_git_commit_sha": "unknown", | ||
| 169 | + "source": "migrated_from_hermes", | ||
| 170 | + "migrated_from_profile": "code", | ||
| 171 | + "status": "active", | ||
| 172 | + "content": "# Skill One\n\nBody text.\n", | ||
| 173 | + "supporting_files": ["references/foo.md", "scripts/bar.py"] | ||
| 174 | + } | ||
| 175 | + ``` | ||
| 176 | +- **通过标准**:状态码等值比较 + 响应体逐 key 相等比较(不是子集匹配,字段名必须恰好是 | ||
| 177 | + `supporting_files`,不是 `support_files`)。 | ||
| 178 | +- **对应测试**:`SkillRouteTest.test_content_returns_real_content`(重写,URL 从 | ||
| 179 | + `/api/skills/skill-1/content` 改为 `/api/skills/skill-1`,字段名同步改)。 | ||
| 180 | + | ||
| 181 | +### R2(boundary)磁盘文件缺失,200 + `content: null` | ||
| 182 | + | ||
| 183 | +- **请求**:`GET /api/skills/skill-1`,DB 行存在但 `SKILL.md` 文件已删除(同 U4 场景)。 | ||
| 184 | +- **预期状态码**:`200`(**不是** 404——这是本次契约变更的核心行为差异之一)。 | ||
| 185 | +- **预期响应体**: | ||
| 186 | + ```json | ||
| 187 | + {"skill_id": "skill-1", "...": "...", "content": null, "supporting_files": []} | ||
| 188 | + ``` | ||
| 189 | +- **通过标准**:`resp.status == 200` 且 `json.loads(resp.body)["content"] is None`。 | ||
| 190 | +- **对应测试**:新增 `SkillRouteTest.test_content_missing_file_returns_200_null`(路由层此前 | ||
| 191 | + 完全没有覆盖越界/缺失场景,仅 `skill_query` 单元层有——本用例是 SDD §6 明确要求新增,不是重 | ||
| 192 | + 写)。 | ||
| 193 | + | ||
| 194 | +### R3(failure)skill 不存在,404 | ||
| 195 | + | ||
| 196 | +- **请求**:`GET /api/skills/does-not-exist`。 | ||
| 197 | +- **预期状态码**:`404`。 | ||
| 198 | +- **预期响应体**:`{"error": "NOT_FOUND"}`。 | ||
| 199 | +- **通过标准**:状态码 + body 精确匹配。 | ||
| 200 | +- **对应测试**:`SkillRouteTest.test_content_nonexistent_skill_returns_404`(重写,URL 改为裸 | ||
| 201 | + `skill_id`)。 | ||
| 202 | + | ||
| 203 | +### R4(failure/security)路径穿越,400 | ||
| 204 | + | ||
| 205 | +- **请求**:`GET /api/skills/evil-1`,DB 中该行 `file_path` 为越界值(同 U6 fixture)。 | ||
| 206 | +- **预期状态码**:`400`(**不是** 404,也不是 500)。 | ||
| 207 | +- **预期响应体**:`{"error": "INVALID_SKILL_ID"}`。 | ||
| 208 | +- **通过标准**:状态码 + body 精确匹配;额外断言响应体中不含任何越界目标路径的文件内容片段。 | ||
| 209 | +- **对应测试**:新增 `SkillRouteTest.test_content_path_traversal_returns_400`(路由层新增, | ||
| 210 | + SDD §6 明确要求)。 | ||
| 211 | + | ||
| 212 | +### R5(boundary)路由匹配优先级——`/audit` 后缀不被裸 `skill_id` 分支误吞 | ||
| 213 | + | ||
| 214 | +- **请求**:`GET /api/skills/skill-1/audit`(既有的迭代审计接口)。 | ||
| 215 | +- **预期行为**:命中既有 `_handle_skill_audit_log`,**不**命中新的裸 `skill_id` 分支(新分支要 | ||
| 216 | + 求 `"/" not in skill_id`,`"skill-1/audit"` 含 `/`,天然不满足,但需要显式测试验证路由顺序 | ||
| 217 | + 没有被写反)。 | ||
| 218 | +- **预期状态码/响应体**:与改动前完全一致——`skill_iteration` 表为空时返回 `200` + `[]`。 | ||
| 219 | +- **通过标准**:`resp.status == 200 and json.loads(resp.body) == []`。 | ||
| 220 | +- **对应测试**:既有 `SkillRouteTest.test_audit_log_empty_returns_empty_list`(原样保留,作为 | ||
| 221 | + 路由顺序回归证据,不修改)。 | ||
| 222 | + | ||
| 223 | +### R6(boundary)路由匹配优先级——`/diff` 端点不受影响 | ||
| 224 | + | ||
| 225 | +- **请求**:`GET /api/skills/diff?...`(既有 diff 接口的合法查询)。 | ||
| 226 | +- **预期行为**:命中既有 `_handle_skill_diff`分支(在裸 `skill_id` 分支**之前**注册),行为与 | ||
| 227 | + 改动前完全一致。 | ||
| 228 | +- **通过标准**:既有 `GetDiffTest`/`SkillRouteTest.test_diff_*` 全部保持通过,不需新增用例。 | ||
| 229 | + | ||
| 230 | +### R7(failure)`POST /api/skills/:skill_id/rollback` 不受本次改动影响 | ||
| 231 | + | ||
| 232 | +- **请求**:既有 `test_rollback_*` 用例的请求不变。 | ||
| 233 | +- **预期行为**:`do_POST` 分支未改动,行为完全一致。 | ||
| 234 | +- **通过标准**:既有 `SkillRouteTest.test_rollback_*` 全部保持通过,不需新增用例。 | ||
| 235 | + | ||
| 236 | +--- | ||
| 237 | + | ||
| 238 | +## 3. 前端手工验证场景(`frontend/app.js`,无自动化测试基建,与 PRD §8 一致) | ||
| 239 | + | ||
| 240 | +环境:`python3 -m src.backend_server --port 8420`,浏览器打开 | ||
| 241 | +`http://127.0.0.1:8420/#/skills`。 | ||
| 242 | + | ||
| 243 | +### F1(happy path)点击已迁移 Skill,两面板并列显示 | ||
| 244 | + | ||
| 245 | +- **操作**:在 `#/skills` 列表点击任一真实存在磁盘文件的 Skill(如迁移后的 `coder`/`planner` | ||
| 246 | + 等真实 skill_id)。 | ||
| 247 | +- **预期页面**:跳转到 `#/skills/:id/audit`;页面同时可见两个独立面板: | ||
| 248 | + 1. "Skill 内容"面板:`<pre class="code">` 元素内是该 Skill `SKILL.md` 的完整原始文本(可 | ||
| 249 | + 用浏览器"查看源代码"/`document.getElementById("skill_content")` 核对文本与磁盘文件字节 | ||
| 250 | + 一致,包括换行)。 | ||
| 251 | + 2. "迭代记录"面板:显示"该 Skill 尚无自主迭代记录"(因为 `skill_iteration` 表为空,这是 | ||
| 252 | + PRD 背景中确认为正确的既有行为,不应被本次改动改变)。 | ||
| 253 | +- **通过标准**:两个面板同时非空/各自呈现预期内容,其中一个面板的渲染逻辑失败不影响另一个( | ||
| 254 | + 可通过临时让 `/audit` 请求失败——如断网模拟——验证"Skill 内容"面板仍正常显示)。 | ||
| 255 | + | ||
| 256 | +### F2(boundary)无第三方渲染依赖 | ||
| 257 | + | ||
| 258 | +- **操作**:打开浏览器开发者工具 Network 面板,刷新 `#/skills/:id/audit` 页面。 | ||
| 259 | +- **预期行为**:页面加载的脚本资源里不出现任何 Markdown 渲染库或代码高亮库(如 | ||
| 260 | + `marked.js`/`highlight.js`/`prismjs` 等);"Skill 内容"面板的 DOM 元素是纯 `<pre>` 文本节 | ||
| 261 | + 点,未被转换为渲染后的 HTML 结构(如 `<h1>`/`<code class="language-*">`)。 | ||
| 262 | +- **通过标准**:Network 面板资源列表 + `document.getElementById("skill_content").innerHTML` | ||
| 263 | + 中除转义后的原始 Markdown 文本外不含渲染产物标签。 | ||
| 264 | + | ||
| 265 | +### F3(failure)磁盘文件缺失时的降级显示 | ||
| 266 | + | ||
| 267 | +- **操作**:临时删除某个已迁移 Skill 目录下的 `SKILL.md`(测试环境操作,验证后恢复),刷新对 | ||
| 268 | + 应详情页。 | ||
| 269 | +- **预期页面**:"Skill 内容"面板显示文案"(内容文件不可用)",不抛 JS 异常,不出现空白面 | ||
| 270 | + 板或浏览器控制台报错;"迭代记录"面板不受影响,正常渲染。 | ||
| 271 | +- **通过标准**:浏览器控制台无未捕获异常;"Skill 内容"面板文本节点内容精确等于 | ||
| 272 | + "(内容文件不可用)"。 | ||
| 273 | + | ||
| 274 | +### F4(failure)Skill 完全不存在时的整页错误态 | ||
| 275 | + | ||
| 276 | +- **操作**:直接在地址栏输入 `http://127.0.0.1:8420/#/skills/does-not-exist/audit`。 | ||
| 277 | +- **预期页面**:页面顶部出现统一错误横幅,文案含"该 Skill 不存在";"Skill 内容"面板与"迭 | ||
| 278 | + 代记录"面板均不渲染任何内容(不是各自显示两条不一致的错误提示)。 | ||
| 279 | +- **通过标准**:`#skill_audit_error` 元素 `display` 不为 `none` 且文本含"不存在"; | ||
| 280 | + `#skill_content`、`#skill_audit_list` 的 `innerHTML` 均为空字符串。 | ||
| 281 | + | ||
| 282 | +### F5(boundary)支持文件清单展示 | ||
| 283 | + | ||
| 284 | +- **前置**:为某个测试用 Skill 目录额外放置 `references/example.md`。 | ||
| 285 | +- **操作**:打开该 Skill 详情页。 | ||
| 286 | +- **预期页面**:"Skill 内容"面板在正文 `<pre>` 之外(之前或之后,与现有 DOM 结构一致的位 | ||
| 287 | + 置)显示一行提示,形如"支持文件:references/example.md";若无支持文件的 Skill 则显示"无 | ||
| 288 | + 支持文件"。 | ||
| 289 | +- **通过标准**:对应 hint 文本节点内容与磁盘实际支持文件列表一致;不存在支持文件的 Skill 显 | ||
| 290 | + 示"无支持文件"而不是空白或报错。 | ||
| 291 | + | ||
| 292 | +--- | ||
| 293 | + | ||
| 294 | +## 4. 回归范围:既有测试必须保持通过,断言不得修改 | ||
| 295 | + | ||
| 296 | +以下测试类/方法不在本次契约变更范围内,运行 `python3 -m unittest src.test_skill_query -v` 与 | ||
| 297 | +`python3 -m unittest src.test_backend_server.SkillRouteTest -v` 时必须保持全绿,且其断言内容 | ||
| 298 | +(包括请求 URL、期望字段名、期望状态码)**逐字不变**: | ||
| 299 | + | ||
| 300 | +- `src/test_skill_query.py`:`ListSkillsTest` 全部用例、`GetSkillAuditLogTest` 全部用例、 | ||
| 301 | + `GetDiffTest` 全部用例。 | ||
| 302 | +- `src/test_backend_server.py::SkillRouteTest`:`test_list_returns_seeded_skill`、 | ||
| 303 | + `test_audit_log_*`、`test_diff_*`、`test_full_update_diff_rollback_cycle`、 | ||
| 304 | + `test_rollback_*`。 | ||
| 305 | + | ||
| 306 | +**通过标准**:`git diff` 中上述测试方法体不出现在改动范围内(除非是纯粹的、不改变断言语义的 | ||
| 307 | +重排/import 调整)。若 diff 触碰了这些方法的断言内容,视为违反 SDD §0 裁决,判定为不通过。 | ||
| 308 | + | ||
| 309 | +--- | ||
| 310 | + | ||
| 311 | +## 5. 全量回归 | ||
| 312 | + | ||
| 313 | +- **命令**:`python3 -m unittest discover -s src`。 | ||
| 314 | +- **通过标准**:全部用例通过(0 failure,0 error),特别关注 | ||
| 315 | + `src/test_agent_profile.py`、`src/test_skill_update.py` 等间接依赖 `skill_query`/ | ||
| 316 | + `skill_update` 模块符号的测试文件不因 `SkillContentMissingError` 删除而出现 | ||
| 317 | + `ImportError`/`AttributeError`。 | ||
| 318 | + | ||
| 319 | +--- | ||
| 320 | + | ||
| 321 | +## 6. 验收标准总表(PRD AC 逐条 → 本文档场景编号) | ||
| 322 | + | ||
| 323 | +| PRD AC | 描述 | 覆盖场景 | | ||
| 324 | +|---|---|---| | ||
| 325 | +| AC1 | 真实内容以 `<pre>` 展示 | F1、F2、R1 | | ||
| 326 | +| AC2 | 变更审计面板行为不变 | R5、F1(迭代记录部分)| | ||
| 327 | +| AC3 | 两面板并列、互不依赖 | F1、F3(局部失败不影响另一面板)| | ||
| 328 | +| AC4 | 文件缺失显示"内容文件不可用",不 500 | U4、R2、F3 | | ||
| 329 | +| AC5 | skill 不存在 → 整页错误态,两面板都不渲染,404 | U5、R3、F4 | | ||
| 330 | +| AC6 | 越界 `file_path` 不读出目录外内容,400 非 500 | U6、U7、R4 | | ||
| 331 | +| AC7 | 既有测试全通过、断言不被无理由篡改 | 第 4 节回归范围 | | ||
| 332 | +| AC8 | 前端无新第三方依赖 | F2 | | ||
| 333 | + | ||
| 334 | +--- | ||
| 335 | + | ||
| 336 | +## 7. Definition of Done 检查清单(可勾选执行) | ||
| 337 | + | ||
| 338 | +- [ ] U1–U8 全部通过(`GetSkillContentTest` 重写后的类)。 | ||
| 339 | +- [ ] R1–R7 全部通过(`SkillRouteTest` 重写 + 新增用例)。 | ||
| 340 | +- [ ] `grep -rn SkillContentMissingError src/` 输出为空。 | ||
| 341 | +- [ ] `python3 -m unittest src.test_skill_query -v` 全绿。 | ||
| 342 | +- [ ] `python3 -m unittest src.test_backend_server.SkillRouteTest -v` 全绿。 | ||
| 343 | +- [ ] `python3 -m unittest discover -s src` 全绿。 | ||
| 344 | +- [ ] F1–F5 手工验证全部通过(浏览器实测,非自动化)。 | ||
| 345 | +- [ ] 第 4 节列出的既有测试方法体逐字未改动(`git diff` 复核)。 |