Skip to content

Commit 9c3803d

Browse files
docs: 指定测试计划
1 parent 5fda872 commit 9c3803d

7 files changed

Lines changed: 1229 additions & 416 deletions
Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
# Plan 10 — 修复 WEAK 评分测试文件
2+
3+
> 优先级:高 | 8 个文件 | 预估新增/修改 ~60 个测试用例
4+
5+
本计划修复 testing-spec.md 中评定为 WEAK 的 8 个测试文件的断言缺陷和覆盖缺口。
6+
7+
---
8+
9+
## 10.1 `src/utils/__tests__/format.test.ts`
10+
11+
**问题**`formatNumber``formatTokens``formatRelativeTime` 使用 `toContain` 代替精确匹配,无法检测格式回归。
12+
13+
### 修改清单
14+
15+
#### formatNumber — toContain → toBe
16+
17+
```typescript
18+
// 当前(弱)
19+
expect(formatNumber(1321)).toContain("k");
20+
expect(formatNumber(1500000)).toContain("m");
21+
22+
// 修复为
23+
expect(formatNumber(1321)).toBe("1.3k");
24+
expect(formatNumber(1500000)).toBe("1.5m");
25+
```
26+
27+
> 注意:`Intl.NumberFormat` 输出可能因 locale 不同。若 CI locale 不一致,改用 `toMatch(/^\d+(\.\d)?[km]$/)` 正则匹配。
28+
29+
#### formatTokens — 补精确断言
30+
31+
```typescript
32+
expect(formatTokens(1000)).toBe("1k");
33+
expect(formatTokens(1500)).toBe("1.5k");
34+
```
35+
36+
#### formatRelativeTime — toContain → toBe
37+
38+
```typescript
39+
// 当前(弱)
40+
expect(formatRelativeTime(diff, now)).toContain("30");
41+
expect(formatRelativeTime(diff, now)).toContain("ago");
42+
43+
// 修复为
44+
expect(formatRelativeTime(diff, now)).toBe("30s ago");
45+
```
46+
47+
#### 新增:formatDuration 进位边界
48+
49+
| 用例 | 输入 | 期望 |
50+
|------|------|------|
51+
| 59.5s 进位 | 59500ms | 至少含 `1m` |
52+
| 59m59s 进位 | 3599000ms | 至少含 `1h` |
53+
| sub-millisecond | 0.5ms | `"<1ms"``"0ms"` |
54+
55+
#### 新增:未测试函数
56+
57+
| 函数 | 最少用例 |
58+
|------|---------|
59+
| `formatRelativeTimeAgo` | 2(过去 / 未来) |
60+
| `formatLogMetadata` | 1(基本调用不抛错) |
61+
| `formatResetTime` | 2(有值 / null) |
62+
| `formatResetText` | 1(基本调用) |
63+
64+
---
65+
66+
## 10.2 `src/tools/shared/__tests__/gitOperationTracking.test.ts`
67+
68+
**问题**`detectGitOperation` 内部调用 `getCommitCounter()``getPrCounter()``logEvent()`,测试产生分析副作用。
69+
70+
### 修改清单
71+
72+
#### 添加 analytics mock
73+
74+
在文件顶部添加 `mock.module`
75+
76+
```typescript
77+
import { mock, afterAll, afterEach, beforeEach } from "bun:test";
78+
79+
mock.module("src/services/analytics/index.ts", () => ({
80+
logEvent: mock(() => {}),
81+
}));
82+
83+
mock.module("src/bootstrap/state.ts", () => ({
84+
getCommitCounter: mock(() => ({ increment: mock(() => {}) })),
85+
getPrCounter: mock(() => ({ increment: mock(() => {}) })),
86+
}));
87+
```
88+
89+
> 需验证 `detectGitOperation` 的实际导入路径,按需调整 mock 目标。
90+
91+
#### 新增:缺失的 GH PR actions
92+
93+
| 用例 | 输入 | 期望 |
94+
|------|------|------|
95+
| gh pr edit | `'gh pr edit 123 --title "fix"'` | `result.pr.number === 123` |
96+
| gh pr close | `'gh pr close 456'` | `result.pr.number === 456` |
97+
| gh pr ready | `'gh pr ready 789'` | `result.pr.number === 789` |
98+
| gh pr comment | `'gh pr comment 123 --body "done"'` | `result.pr.number === 123` |
99+
100+
#### 新增:parseGitCommitId 边界
101+
102+
| 用例 | 输入 | 期望 |
103+
|------|------|------|
104+
| 完整 40 字符 SHA | `'[abcdef0123456789abcdef0123456789abcdef01] ...'` | 返回完整 40 字符 |
105+
| 畸形括号输出 | `'create mode 100644 file.txt'` | 返回 `null` |
106+
107+
---
108+
109+
## 10.3 `src/utils/permissions/__tests__/PermissionMode.test.ts`
110+
111+
**问题**`isExternalPermissionMode` 在非 ant 环境永远返回 true,false 路径从未执行;mode 覆盖不完整。
112+
113+
### 修改清单
114+
115+
#### 补全 mode 覆盖
116+
117+
| 函数 | 缺失的 mode |
118+
|------|-------------|
119+
| `permissionModeTitle` | `bypassPermissions`, `dontAsk` |
120+
| `permissionModeShortTitle` | `dontAsk`, `acceptEdits` |
121+
| `getModeColor` | `dontAsk`, `acceptEdits`, `plan` |
122+
| `permissionModeFromString` | `acceptEdits`, `bypassPermissions` |
123+
| `toExternalPermissionMode` | `acceptEdits`, `bypassPermissions` |
124+
125+
#### 修复 isExternalPermissionMode
126+
127+
```typescript
128+
// 当前:只测了非 ant 环境(永远 true)
129+
// 需要新增 ant 环境测试
130+
describe("when USER_TYPE is 'ant'", () => {
131+
beforeEach(() => {
132+
process.env.USER_TYPE = "ant";
133+
});
134+
afterEach(() => {
135+
delete process.env.USER_TYPE;
136+
});
137+
138+
test("returns false for 'auto' in ant context", () => {
139+
expect(isExternalPermissionMode("auto")).toBe(false);
140+
});
141+
142+
test("returns false for 'bubble' in ant context", () => {
143+
expect(isExternalPermissionMode("bubble")).toBe(false);
144+
});
145+
146+
test("returns true for non-ant modes in ant context", () => {
147+
expect(isExternalPermissionMode("plan")).toBe(true);
148+
});
149+
});
150+
```
151+
152+
#### 新增:permissionModeSchema
153+
154+
| 用例 | 输入 | 期望 |
155+
|------|------|------|
156+
| 有效 mode | `'plan'` | `success: true` |
157+
| 无效 mode | `'invalid'` | `success: false` |
158+
159+
---
160+
161+
## 10.4 `src/utils/permissions/__tests__/dangerousPatterns.test.ts`
162+
163+
**问题**:纯数据 smoke test,无行为验证。
164+
165+
### 修改清单
166+
167+
#### 新增:重复值检查
168+
169+
```typescript
170+
test("CROSS_PLATFORM_CODE_EXEC has no duplicates", () => {
171+
const set = new Set(CROSS_PLATFORM_CODE_EXEC);
172+
expect(set.size).toBe(CROSS_PLATFORM_CODE_EXEC.length);
173+
});
174+
175+
test("DANGEROUS_BASH_PATTERNS has no duplicates", () => {
176+
const set = new Set(DANGEROUS_BASH_PATTERNS);
177+
expect(set.size).toBe(DANGEROUS_BASH_PATTERNS.length);
178+
});
179+
```
180+
181+
#### 新增:全量成员断言(用 Set 确保精确)
182+
183+
```typescript
184+
test("CROSS_PLATFORM_CODE_EXEC contains expected interpreters", () => {
185+
const expected = ["node", "python", "python3", "ruby", "perl", "php",
186+
"bun", "deno", "npx", "tsx"];
187+
const set = new Set(CROSS_PLATFORM_CODE_EXEC);
188+
for (const entry of expected) {
189+
expect(set.has(entry)).toBe(true);
190+
}
191+
});
192+
```
193+
194+
#### 新增:空字符串不匹配
195+
196+
```typescript
197+
test("empty string does not match any pattern", () => {
198+
for (const pattern of DANGEROUS_BASH_PATTERNS) {
199+
expect("".startsWith(pattern)).toBe(false);
200+
}
201+
});
202+
```
203+
204+
---
205+
206+
## 10.5 `src/utils/__tests__/zodToJsonSchema.test.ts`
207+
208+
**问题**:object 属性仅 `toBeDefined` 未验证类型结构;optional 字段未验证 absence。
209+
210+
### 修改清单
211+
212+
#### 修复 object schema 测试
213+
214+
```typescript
215+
// 当前(弱)
216+
expect(schema.properties!.name).toBeDefined();
217+
expect(schema.properties!.age).toBeDefined();
218+
219+
// 修复为
220+
expect(schema.properties!.name).toEqual({ type: "string" });
221+
expect(schema.properties!.age).toEqual({ type: "number" });
222+
```
223+
224+
#### 修复 optional 字段测试
225+
226+
```typescript
227+
test("optional field is not in required array", () => {
228+
const schema = zodToJsonSchema(z.object({
229+
required: z.string(),
230+
optional: z.string().optional(),
231+
}));
232+
expect(schema.required).toEqual(["required"]);
233+
expect(schema.required).not.toContain("optional");
234+
});
235+
```
236+
237+
#### 新增:缺失的 schema 类型
238+
239+
| 用例 | 输入 | 期望 |
240+
|------|------|------|
241+
| `z.literal("foo")` | `z.literal("foo")` | `{ const: "foo" }` |
242+
| `z.null()` | `z.null()` | `{ type: "null" }` |
243+
| `z.union()` | `z.union([z.string(), z.number()])` | `{ anyOf: [...] }` |
244+
| `z.record()` | `z.record(z.string(), z.number())` | `{ type: "object", additionalProperties: { type: "number" } }` |
245+
| `z.tuple()` | `z.tuple([z.string(), z.number()])` | `{ type: "array", items: [...], additionalItems: false }` |
246+
| 嵌套 object | `z.object({ a: z.object({ b: z.string() }) })` | 验证嵌套属性结构 |
247+
248+
---
249+
250+
## 10.6 `src/utils/__tests__/envValidation.test.ts`
251+
252+
**问题**`validateBoundedIntEnvVar` lower bound=100 时 value=1 返回 `status: "valid"`,疑似源码 bug。
253+
254+
### 修改清单
255+
256+
#### 验证 lower bound 行为
257+
258+
```typescript
259+
// 当前测试
260+
test("value of 1 with lower bound 100", () => {
261+
const result = validateBoundedIntEnvVar("1", { defaultValue: 100, upperLimit: 1000, lowerLimit: 100 });
262+
// 如果源码有 bug,这里应该暴露
263+
expect(result.effective).toBeGreaterThanOrEqual(100);
264+
expect(result.status).toBe(result.effective !== 100 ? "capped" : "valid");
265+
});
266+
```
267+
268+
#### 新增边界用例
269+
270+
| 用例 | value | lowerLimit | 期望 |
271+
|------|-------|------------|------|
272+
| 低于 lower bound | `"50"` | 100 | `effective: 100, status: "capped"` |
273+
| 等于 lower bound | `"100"` | 100 | `effective: 100, status: "valid"` |
274+
| 浮点截断 | `"50.7"` | 100 | `effective: 100`(parseInt 截断后 cap) |
275+
| 空白字符 | `" 500 "` | 1 | `effective: 500, status: "valid"` |
276+
| defaultValue 为 0 | `"0"` | 0 | 需确认 `parsed <= 0` 逻辑 |
277+
278+
> **行动**:先确认 `validateBoundedIntEnvVar` 源码中 lower bound 的实际执行路径。如果确实不生效,需先修源码再补测试。
279+
280+
---
281+
282+
## 10.7 `src/utils/__tests__/file.test.ts`
283+
284+
**问题**`addLineNumbers``toContain`,未验证完整格式。
285+
286+
### 修改清单
287+
288+
#### 修复 addLineNumbers 断言
289+
290+
```typescript
291+
// 当前(弱)
292+
expect(result).toContain("1");
293+
expect(result).toContain("hello");
294+
295+
// 修复为(需确定 isCompactLinePrefixEnabled 行为)
296+
// 假设 compact=false,格式为 " 1→hello"
297+
test("formats single line with tab prefix", () => {
298+
// 先确认环境,如果 compact 模式不确定,用正则
299+
expect(result).toMatch(/^\s*\d+[→\t]hello$/m);
300+
});
301+
```
302+
303+
#### 新增:stripLineNumberPrefix 边界
304+
305+
| 用例 | 输入 | 期望 |
306+
|------|------|------|
307+
| 纯数字行 | `"123"` | `""` |
308+
| 无内容前缀 | `"→"` | `""` |
309+
| compact 格式 `"1\thello"` | `"1\thello"` | `"hello"` |
310+
311+
#### 新增:pathsEqual 边界
312+
313+
| 用例 | a | b | 期望 |
314+
|------|---|---|------|
315+
| 尾部斜杠差异 | `"/a/b"` | `"/a/b/"` | `false` |
316+
| `..`| `"/a/../b"` | `"/b"` | 视实现而定 |
317+
318+
---
319+
320+
## 10.8 `src/utils/__tests__/notebook.test.ts`
321+
322+
**问题**`mapNotebookCellsToToolResult` 内容检查用 `toContain`,未验证 XML 格式。
323+
324+
### 修改清单
325+
326+
#### 修复 content 断言
327+
328+
```typescript
329+
// 当前(弱)
330+
expect(result).toContain("cell-0");
331+
expect(result).toContain("print('hello')");
332+
333+
// 修复为
334+
expect(result).toContain('<cell id="cell-0">');
335+
expect(result).toContain("</cell>");
336+
```
337+
338+
#### 新增:parseCellId 边界
339+
340+
| 用例 | 输入 | 期望 |
341+
|------|------|------|
342+
| 负数 | `"cell--1"` | `null` |
343+
| 前导零 | `"cell-007"` | `7` |
344+
| 极大数 | `"cell-999999999"` | `999999999` |
345+
346+
#### 新增:mapNotebookCellsToToolResult 边界
347+
348+
| 用例 | 输入 | 期望 |
349+
|------|------|------|
350+
| 空 data 数组 | `{ cells: [] }` | 空字符串或空结果 |
351+
| 无 cell_id | `{ cell_type: "code", source: "x" }` | fallback 到 `cell-${index}` |
352+
| error output | `{ output_type: "error", ename: "Error", evalue: "msg" }` | 包含 error 信息 |
353+
354+
---
355+
356+
## 验收标准
357+
358+
- [ ] `bun test` 全部通过
359+
- [ ] 8 个文件评分从 WEAK 提升至 ACCEPTABLE 或 GOOD
360+
- [ ] `toContain` 仅用于警告文本等确实不确定精确值的场景
361+
- [ ] envValidation bug 确认并修复(或确认非 bug 并更新测试)

0 commit comments

Comments
 (0)