docs: 指定测试计划
This commit is contained in:
parent
5fda87246d
commit
9c3803d16b
361
docs/test-plans/10-fix-weak-tests.md
Normal file
361
docs/test-plans/10-fix-weak-tests.md
Normal file
@ -0,0 +1,361 @@
|
||||
# Plan 10 — 修复 WEAK 评分测试文件
|
||||
|
||||
> 优先级:高 | 8 个文件 | 预估新增/修改 ~60 个测试用例
|
||||
|
||||
本计划修复 testing-spec.md 中评定为 WEAK 的 8 个测试文件的断言缺陷和覆盖缺口。
|
||||
|
||||
---
|
||||
|
||||
## 10.1 `src/utils/__tests__/format.test.ts`
|
||||
|
||||
**问题**:`formatNumber`、`formatTokens`、`formatRelativeTime` 使用 `toContain` 代替精确匹配,无法检测格式回归。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### formatNumber — toContain → toBe
|
||||
|
||||
```typescript
|
||||
// 当前(弱)
|
||||
expect(formatNumber(1321)).toContain("k");
|
||||
expect(formatNumber(1500000)).toContain("m");
|
||||
|
||||
// 修复为
|
||||
expect(formatNumber(1321)).toBe("1.3k");
|
||||
expect(formatNumber(1500000)).toBe("1.5m");
|
||||
```
|
||||
|
||||
> 注意:`Intl.NumberFormat` 输出可能因 locale 不同。若 CI locale 不一致,改用 `toMatch(/^\d+(\.\d)?[km]$/)` 正则匹配。
|
||||
|
||||
#### formatTokens — 补精确断言
|
||||
|
||||
```typescript
|
||||
expect(formatTokens(1000)).toBe("1k");
|
||||
expect(formatTokens(1500)).toBe("1.5k");
|
||||
```
|
||||
|
||||
#### formatRelativeTime — toContain → toBe
|
||||
|
||||
```typescript
|
||||
// 当前(弱)
|
||||
expect(formatRelativeTime(diff, now)).toContain("30");
|
||||
expect(formatRelativeTime(diff, now)).toContain("ago");
|
||||
|
||||
// 修复为
|
||||
expect(formatRelativeTime(diff, now)).toBe("30s ago");
|
||||
```
|
||||
|
||||
#### 新增:formatDuration 进位边界
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 59.5s 进位 | 59500ms | 至少含 `1m` |
|
||||
| 59m59s 进位 | 3599000ms | 至少含 `1h` |
|
||||
| sub-millisecond | 0.5ms | `"<1ms"` 或 `"0ms"` |
|
||||
|
||||
#### 新增:未测试函数
|
||||
|
||||
| 函数 | 最少用例 |
|
||||
|------|---------|
|
||||
| `formatRelativeTimeAgo` | 2(过去 / 未来) |
|
||||
| `formatLogMetadata` | 1(基本调用不抛错) |
|
||||
| `formatResetTime` | 2(有值 / null) |
|
||||
| `formatResetText` | 1(基本调用) |
|
||||
|
||||
---
|
||||
|
||||
## 10.2 `src/tools/shared/__tests__/gitOperationTracking.test.ts`
|
||||
|
||||
**问题**:`detectGitOperation` 内部调用 `getCommitCounter()`、`getPrCounter()`、`logEvent()`,测试产生分析副作用。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 添加 analytics mock
|
||||
|
||||
在文件顶部添加 `mock.module`:
|
||||
|
||||
```typescript
|
||||
import { mock, afterAll, afterEach, beforeEach } from "bun:test";
|
||||
|
||||
mock.module("src/services/analytics/index.ts", () => ({
|
||||
logEvent: mock(() => {}),
|
||||
}));
|
||||
|
||||
mock.module("src/bootstrap/state.ts", () => ({
|
||||
getCommitCounter: mock(() => ({ increment: mock(() => {}) })),
|
||||
getPrCounter: mock(() => ({ increment: mock(() => {}) })),
|
||||
}));
|
||||
```
|
||||
|
||||
> 需验证 `detectGitOperation` 的实际导入路径,按需调整 mock 目标。
|
||||
|
||||
#### 新增:缺失的 GH PR actions
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| gh pr edit | `'gh pr edit 123 --title "fix"'` | `result.pr.number === 123` |
|
||||
| gh pr close | `'gh pr close 456'` | `result.pr.number === 456` |
|
||||
| gh pr ready | `'gh pr ready 789'` | `result.pr.number === 789` |
|
||||
| gh pr comment | `'gh pr comment 123 --body "done"'` | `result.pr.number === 123` |
|
||||
|
||||
#### 新增:parseGitCommitId 边界
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 完整 40 字符 SHA | `'[abcdef0123456789abcdef0123456789abcdef01] ...'` | 返回完整 40 字符 |
|
||||
| 畸形括号输出 | `'create mode 100644 file.txt'` | 返回 `null` |
|
||||
|
||||
---
|
||||
|
||||
## 10.3 `src/utils/permissions/__tests__/PermissionMode.test.ts`
|
||||
|
||||
**问题**:`isExternalPermissionMode` 在非 ant 环境永远返回 true,false 路径从未执行;mode 覆盖不完整。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 补全 mode 覆盖
|
||||
|
||||
| 函数 | 缺失的 mode |
|
||||
|------|-------------|
|
||||
| `permissionModeTitle` | `bypassPermissions`, `dontAsk` |
|
||||
| `permissionModeShortTitle` | `dontAsk`, `acceptEdits` |
|
||||
| `getModeColor` | `dontAsk`, `acceptEdits`, `plan` |
|
||||
| `permissionModeFromString` | `acceptEdits`, `bypassPermissions` |
|
||||
| `toExternalPermissionMode` | `acceptEdits`, `bypassPermissions` |
|
||||
|
||||
#### 修复 isExternalPermissionMode
|
||||
|
||||
```typescript
|
||||
// 当前:只测了非 ant 环境(永远 true)
|
||||
// 需要新增 ant 环境测试
|
||||
describe("when USER_TYPE is 'ant'", () => {
|
||||
beforeEach(() => {
|
||||
process.env.USER_TYPE = "ant";
|
||||
});
|
||||
afterEach(() => {
|
||||
delete process.env.USER_TYPE;
|
||||
});
|
||||
|
||||
test("returns false for 'auto' in ant context", () => {
|
||||
expect(isExternalPermissionMode("auto")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns false for 'bubble' in ant context", () => {
|
||||
expect(isExternalPermissionMode("bubble")).toBe(false);
|
||||
});
|
||||
|
||||
test("returns true for non-ant modes in ant context", () => {
|
||||
expect(isExternalPermissionMode("plan")).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增:permissionModeSchema
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 有效 mode | `'plan'` | `success: true` |
|
||||
| 无效 mode | `'invalid'` | `success: false` |
|
||||
|
||||
---
|
||||
|
||||
## 10.4 `src/utils/permissions/__tests__/dangerousPatterns.test.ts`
|
||||
|
||||
**问题**:纯数据 smoke test,无行为验证。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 新增:重复值检查
|
||||
|
||||
```typescript
|
||||
test("CROSS_PLATFORM_CODE_EXEC has no duplicates", () => {
|
||||
const set = new Set(CROSS_PLATFORM_CODE_EXEC);
|
||||
expect(set.size).toBe(CROSS_PLATFORM_CODE_EXEC.length);
|
||||
});
|
||||
|
||||
test("DANGEROUS_BASH_PATTERNS has no duplicates", () => {
|
||||
const set = new Set(DANGEROUS_BASH_PATTERNS);
|
||||
expect(set.size).toBe(DANGEROUS_BASH_PATTERNS.length);
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增:全量成员断言(用 Set 确保精确)
|
||||
|
||||
```typescript
|
||||
test("CROSS_PLATFORM_CODE_EXEC contains expected interpreters", () => {
|
||||
const expected = ["node", "python", "python3", "ruby", "perl", "php",
|
||||
"bun", "deno", "npx", "tsx"];
|
||||
const set = new Set(CROSS_PLATFORM_CODE_EXEC);
|
||||
for (const entry of expected) {
|
||||
expect(set.has(entry)).toBe(true);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增:空字符串不匹配
|
||||
|
||||
```typescript
|
||||
test("empty string does not match any pattern", () => {
|
||||
for (const pattern of DANGEROUS_BASH_PATTERNS) {
|
||||
expect("".startsWith(pattern)).toBe(false);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10.5 `src/utils/__tests__/zodToJsonSchema.test.ts`
|
||||
|
||||
**问题**:object 属性仅 `toBeDefined` 未验证类型结构;optional 字段未验证 absence。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 修复 object schema 测试
|
||||
|
||||
```typescript
|
||||
// 当前(弱)
|
||||
expect(schema.properties!.name).toBeDefined();
|
||||
expect(schema.properties!.age).toBeDefined();
|
||||
|
||||
// 修复为
|
||||
expect(schema.properties!.name).toEqual({ type: "string" });
|
||||
expect(schema.properties!.age).toEqual({ type: "number" });
|
||||
```
|
||||
|
||||
#### 修复 optional 字段测试
|
||||
|
||||
```typescript
|
||||
test("optional field is not in required array", () => {
|
||||
const schema = zodToJsonSchema(z.object({
|
||||
required: z.string(),
|
||||
optional: z.string().optional(),
|
||||
}));
|
||||
expect(schema.required).toEqual(["required"]);
|
||||
expect(schema.required).not.toContain("optional");
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增:缺失的 schema 类型
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| `z.literal("foo")` | `z.literal("foo")` | `{ const: "foo" }` |
|
||||
| `z.null()` | `z.null()` | `{ type: "null" }` |
|
||||
| `z.union()` | `z.union([z.string(), z.number()])` | `{ anyOf: [...] }` |
|
||||
| `z.record()` | `z.record(z.string(), z.number())` | `{ type: "object", additionalProperties: { type: "number" } }` |
|
||||
| `z.tuple()` | `z.tuple([z.string(), z.number()])` | `{ type: "array", items: [...], additionalItems: false }` |
|
||||
| 嵌套 object | `z.object({ a: z.object({ b: z.string() }) })` | 验证嵌套属性结构 |
|
||||
|
||||
---
|
||||
|
||||
## 10.6 `src/utils/__tests__/envValidation.test.ts`
|
||||
|
||||
**问题**:`validateBoundedIntEnvVar` lower bound=100 时 value=1 返回 `status: "valid"`,疑似源码 bug。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 验证 lower bound 行为
|
||||
|
||||
```typescript
|
||||
// 当前测试
|
||||
test("value of 1 with lower bound 100", () => {
|
||||
const result = validateBoundedIntEnvVar("1", { defaultValue: 100, upperLimit: 1000, lowerLimit: 100 });
|
||||
// 如果源码有 bug,这里应该暴露
|
||||
expect(result.effective).toBeGreaterThanOrEqual(100);
|
||||
expect(result.status).toBe(result.effective !== 100 ? "capped" : "valid");
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增边界用例
|
||||
|
||||
| 用例 | value | lowerLimit | 期望 |
|
||||
|------|-------|------------|------|
|
||||
| 低于 lower bound | `"50"` | 100 | `effective: 100, status: "capped"` |
|
||||
| 等于 lower bound | `"100"` | 100 | `effective: 100, status: "valid"` |
|
||||
| 浮点截断 | `"50.7"` | 100 | `effective: 100`(parseInt 截断后 cap) |
|
||||
| 空白字符 | `" 500 "` | 1 | `effective: 500, status: "valid"` |
|
||||
| defaultValue 为 0 | `"0"` | 0 | 需确认 `parsed <= 0` 逻辑 |
|
||||
|
||||
> **行动**:先确认 `validateBoundedIntEnvVar` 源码中 lower bound 的实际执行路径。如果确实不生效,需先修源码再补测试。
|
||||
|
||||
---
|
||||
|
||||
## 10.7 `src/utils/__tests__/file.test.ts`
|
||||
|
||||
**问题**:`addLineNumbers` 仅 `toContain`,未验证完整格式。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 修复 addLineNumbers 断言
|
||||
|
||||
```typescript
|
||||
// 当前(弱)
|
||||
expect(result).toContain("1");
|
||||
expect(result).toContain("hello");
|
||||
|
||||
// 修复为(需确定 isCompactLinePrefixEnabled 行为)
|
||||
// 假设 compact=false,格式为 " 1→hello"
|
||||
test("formats single line with tab prefix", () => {
|
||||
// 先确认环境,如果 compact 模式不确定,用正则
|
||||
expect(result).toMatch(/^\s*\d+[→\t]hello$/m);
|
||||
});
|
||||
```
|
||||
|
||||
#### 新增:stripLineNumberPrefix 边界
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 纯数字行 | `"123"` | `""` |
|
||||
| 无内容前缀 | `"→"` | `""` |
|
||||
| compact 格式 `"1\thello"` | `"1\thello"` | `"hello"` |
|
||||
|
||||
#### 新增:pathsEqual 边界
|
||||
|
||||
| 用例 | a | b | 期望 |
|
||||
|------|---|---|------|
|
||||
| 尾部斜杠差异 | `"/a/b"` | `"/a/b/"` | `false` |
|
||||
| `..` 段 | `"/a/../b"` | `"/b"` | 视实现而定 |
|
||||
|
||||
---
|
||||
|
||||
## 10.8 `src/utils/__tests__/notebook.test.ts`
|
||||
|
||||
**问题**:`mapNotebookCellsToToolResult` 内容检查用 `toContain`,未验证 XML 格式。
|
||||
|
||||
### 修改清单
|
||||
|
||||
#### 修复 content 断言
|
||||
|
||||
```typescript
|
||||
// 当前(弱)
|
||||
expect(result).toContain("cell-0");
|
||||
expect(result).toContain("print('hello')");
|
||||
|
||||
// 修复为
|
||||
expect(result).toContain('<cell id="cell-0">');
|
||||
expect(result).toContain("</cell>");
|
||||
```
|
||||
|
||||
#### 新增:parseCellId 边界
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 负数 | `"cell--1"` | `null` |
|
||||
| 前导零 | `"cell-007"` | `7` |
|
||||
| 极大数 | `"cell-999999999"` | `999999999` |
|
||||
|
||||
#### 新增:mapNotebookCellsToToolResult 边界
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| 空 data 数组 | `{ cells: [] }` | 空字符串或空结果 |
|
||||
| 无 cell_id | `{ cell_type: "code", source: "x" }` | fallback 到 `cell-${index}` |
|
||||
| error output | `{ output_type: "error", ename: "Error", evalue: "msg" }` | 包含 error 信息 |
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `bun test` 全部通过
|
||||
- [ ] 8 个文件评分从 WEAK 提升至 ACCEPTABLE 或 GOOD
|
||||
- [ ] `toContain` 仅用于警告文本等确实不确定精确值的场景
|
||||
- [ ] envValidation bug 确认并修复(或确认非 bug 并更新测试)
|
||||
177
docs/test-plans/11-strengthen-acceptable-tests.md
Normal file
177
docs/test-plans/11-strengthen-acceptable-tests.md
Normal file
@ -0,0 +1,177 @@
|
||||
# Plan 11 — 加强 ACCEPTABLE 评分测试
|
||||
|
||||
> 优先级:中 | ~15 个文件 | 预估新增 ~80 个测试用例
|
||||
|
||||
本计划对 ACCEPTABLE 评分文件中的具体缺陷进行定向加强。每个条目只列出需要改动的部分,不做全量重写。
|
||||
|
||||
---
|
||||
|
||||
## 11.1 `src/utils/__tests__/diff.test.ts`
|
||||
|
||||
| 改动 | 当前 | 改为 |
|
||||
|------|------|------|
|
||||
| `getPatchFromContents` 断言 | `hunks.length > 0` | 验证具体 `+`/`-` 行内容 |
|
||||
| `$` 字符转义 | 未测试 | 新增含 `$` 的内容测试 |
|
||||
| `ignoreWhitespace` 选项 | 未测试 | 新增 `ignoreWhitespace: true` 用例 |
|
||||
| 删除全部内容 | 未测试 | `newContent: ""` |
|
||||
| 多 hunk 偏移 | `adjustHunkLineNumbers` 仅单 hunk | 新增多 hunk 同数组测试 |
|
||||
|
||||
---
|
||||
|
||||
## 11.2 `src/utils/__tests__/path.test.ts`
|
||||
|
||||
当前仅覆盖 2/5+ 导出函数。新增:
|
||||
|
||||
| 函数 | 最少用例 | 关键边界 |
|
||||
|------|---------|---------|
|
||||
| `expandPath` | 6 | `~/` 展开、绝对路径直通、相对路径、空串、含 null 字节、`~user` 格式 |
|
||||
| `toRelativePath` | 3 | 同级文件、子目录、父目录 |
|
||||
| `sanitizePath` | 3 | 正常路径、含 `..` 段、空串 |
|
||||
|
||||
`containsPathTraversal` 补充:
|
||||
- URL 编码 `%2e%2e%2f`(确认不匹配,记录为非需求)
|
||||
- 混合分隔符 `foo/..\bar`
|
||||
|
||||
`normalizePathForConfigKey` 补充:
|
||||
- 混合分隔符 `foo/bar\baz`
|
||||
- 冗余分隔符 `foo//bar`
|
||||
- Windows 盘符 `C:\foo\bar`
|
||||
|
||||
---
|
||||
|
||||
## 11.3 `src/utils/__tests__/uuid.test.ts`
|
||||
|
||||
| 改动 | 说明 |
|
||||
|------|------|
|
||||
| 大写测试断言强化 | `not.toBeNull()` → 验证标准化输出(小写+连字符格式) |
|
||||
| 新增 `createAgentId` | 3 用例:无 label / 有 label / 输出格式正则 `/^a[a-z]*-[a-f0-9]{16}$/` |
|
||||
| 前后空白 | `" 550e8400-... "` 期望 `null` |
|
||||
|
||||
---
|
||||
|
||||
## 11.4 `src/utils/__tests__/semver.test.ts`
|
||||
|
||||
| 用例 | 输入 | 期望 |
|
||||
|------|------|------|
|
||||
| pre-release 比较 | `gt("1.0.0", "1.0.0-alpha")` | `true` |
|
||||
| pre-release 间比较 | `order("1.0.0-alpha", "1.0.0-beta")` | `-1` |
|
||||
| tilde range | `satisfies("1.2.5", "~1.2.3")` | `true` |
|
||||
| `*` 通配符 | `satisfies("2.0.0", "*")` | `true` |
|
||||
| 畸形版本 | `order("abc", "1.0.0")` | 确认不抛错 |
|
||||
| `0.0.0` | `gt("0.0.0", "0.0.0")` | `false` |
|
||||
|
||||
---
|
||||
|
||||
## 11.5 `src/utils/__tests__/hash.test.ts`
|
||||
|
||||
| 改动 | 当前 | 改为 |
|
||||
|------|------|------|
|
||||
| djb2 32 位检查 | `hash \| 0`(恒 true) | `Number.isSafeInteger(hash) && Math.abs(hash) <= 0x7FFFFFFF` |
|
||||
| hashContent 空串 | 未测试 | 新增 |
|
||||
| hashContent 格式 | 未验证输出为数字串 | `toMatch(/^\d+$/)` |
|
||||
| hashPair 空串 | 未测试 | `hashPair("", "b")`, `hashPair("", "")` |
|
||||
| 已知答案 | 无 | 断言 `djb2Hash("hello")` 为特定值(需先在控制台运行一次确定) |
|
||||
|
||||
---
|
||||
|
||||
## 11.6 `src/utils/__tests__/claudemd.test.ts`
|
||||
|
||||
当前仅覆盖 3 个辅助函数。新增:
|
||||
|
||||
| 用例 | 函数 | 说明 |
|
||||
|------|------|------|
|
||||
| 未闭合注释 | `stripHtmlComments` | `"<!-- no close some text"` → 原样返回 |
|
||||
| 跨行注释 | `stripHtmlComments` | `"<!--\nmulti\nline\n-->text"` → `"text"` |
|
||||
| 同行注释+内容 | `stripHtmlComments` | `"<!-- note -->some text"` → `"some text"` |
|
||||
| 内联代码中的注释 | `stripHtmlComments` | `` `<!-- kept -->` `` → 保留 |
|
||||
| 大小写不敏感 | `isMemoryFilePath` | `"claude.md"`, `"CLAUDE.MD"` |
|
||||
| 非 .md 规则文件 | `isMemoryFilePath` | `.claude/rules/foo.txt` → `false` |
|
||||
| 空数组 | `getLargeMemoryFiles` | `[]` → `[]` |
|
||||
|
||||
---
|
||||
|
||||
## 11.7 `src/tools/FileEditTool/__tests__/utils.test.ts`
|
||||
|
||||
| 函数 | 新增用例 |
|
||||
|------|---------|
|
||||
| `normalizeQuotes` | 混合引号 `"`she said 'hello'"` |
|
||||
| `stripTrailingWhitespace` | CR-only `\r`、无尾部换行、全空白串 |
|
||||
| `findActualString` | 空 content、Unicode content |
|
||||
| `preserveQuoteStyle` | 单引号、缩写中的撇号(如 `it's`)、空串 |
|
||||
| `applyEditToFile` | `replaceAll=true` 零匹配、`oldString` 无尾部 `\n`、多行内容 |
|
||||
|
||||
---
|
||||
|
||||
## 11.8 `src/utils/model/__tests__/providers.test.ts`
|
||||
|
||||
| 改动 | 说明 |
|
||||
|------|------|
|
||||
| 删除 `originalEnv` | 未使用,消除死代码 |
|
||||
| env 恢复改为快照 | `beforeEach` 保存 `process.env`,`afterEach` 恢复 |
|
||||
| 新增三变量同时设置 | bedrock + vertex + foundry 全部为 `"1"`,验证优先级 |
|
||||
| 新增非 `"1"` 值 | `"true"`, `"0"`, `""` |
|
||||
| `isFirstPartyAnthropicBaseUrl` | URL 含路径 `/v1`、含尾斜杠、非 HTTPS |
|
||||
|
||||
---
|
||||
|
||||
## 11.9 `src/utils/__tests__/hyperlink.test.ts`
|
||||
|
||||
| 用例 | 说明 |
|
||||
|------|------|
|
||||
| 空 URL | `createHyperlink("http://x.com", "", { supported: true })` 不抛错 |
|
||||
| undefined supportsHyperlinks | 选项未传时走默认检测 |
|
||||
| 非 ant staging URL | `USER_TYPE !== "ant"` 时 staging 返回 `false` |
|
||||
|
||||
---
|
||||
|
||||
## 11.10 `src/utils/__tests__/objectGroupBy.test.ts`
|
||||
|
||||
| 用例 | 说明 |
|
||||
|------|------|
|
||||
| key 返回 undefined | `(_, i) => undefined` → 全部归入 `undefined` 组 |
|
||||
| key 为特殊字符 | `({ name }) => name` 含空格/中文 |
|
||||
|
||||
---
|
||||
|
||||
## 11.11 `src/utils/__tests__/CircularBuffer.test.ts`
|
||||
|
||||
| 用例 | 说明 |
|
||||
|------|------|
|
||||
| capacity=1 | 添加 2 个元素,仅保留最后一个 |
|
||||
| 空 buffer 调用 getRecent | 返回空数组 |
|
||||
| getRecent(0) | 返回空数组 |
|
||||
|
||||
---
|
||||
|
||||
## 11.12 `src/utils/__tests__/contentArray.test.ts`
|
||||
|
||||
| 用例 | 说明 |
|
||||
|------|------|
|
||||
| 混合交替 | `[tool_result, text, tool_result]` — 验证插入到正确位置 |
|
||||
|
||||
---
|
||||
|
||||
## 11.13 `src/utils/__tests__/argumentSubstitution.test.ts`
|
||||
|
||||
| 用例 | 说明 |
|
||||
|------|------|
|
||||
| 转义引号 | `"he said \"hello\""` |
|
||||
| 越界索引 | `$ARGUMENTS[99]`(参数不够时) |
|
||||
| 多占位符 | `"cmd $0 $1 $0"` |
|
||||
|
||||
---
|
||||
|
||||
## 11.14 `src/utils/__tests__/messages.test.ts`
|
||||
|
||||
| 改动 | 说明 |
|
||||
|------|------|
|
||||
| `normalizeMessages` 断言加强 | 验证拆分后的消息内容,不只是长度 |
|
||||
| `isNotEmptyMessage` 空白 | `[{ type: "text", text: " " }]` |
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `bun test` 全部通过
|
||||
- [ ] 目标文件评分从 ACCEPTABLE 提升至 GOOD
|
||||
- [ ] 无 `toContain` 用于精确值检查的场景
|
||||
145
docs/test-plans/12-mock-reliability.md
Normal file
145
docs/test-plans/12-mock-reliability.md
Normal file
@ -0,0 +1,145 @@
|
||||
# Plan 12 — Mock 可靠性修复
|
||||
|
||||
> 优先级:高 | 影响 4 个测试文件 | 预估修改 ~15 处
|
||||
|
||||
本计划修复测试中 mock 相关的副作用、状态泄漏和虚假测试。
|
||||
|
||||
---
|
||||
|
||||
## 12.1 `gitOperationTracking.test.ts` — 消除分析副作用
|
||||
|
||||
**当前问题**:`detectGitOperation` 内部调用 `logEvent()`、`getCommitCounter().increment()`、`getPrCounter().increment()`,每次测试运行都触发真实分析代码。
|
||||
|
||||
**修复步骤**:
|
||||
|
||||
1. 读取 `src/tools/shared/gitOperationTracking.ts`,确认 analytics 导入路径
|
||||
2. 在测试文件顶部添加 `mock.module`:
|
||||
|
||||
```typescript
|
||||
import { mock } from "bun:test";
|
||||
|
||||
mock.module("src/services/analytics/index.ts", () => ({
|
||||
logEvent: mock(() => {}),
|
||||
// 按需补充其他导出
|
||||
}));
|
||||
```
|
||||
|
||||
3. 如果 `getCommitCounter` / `getPrCounter` 来自 `src/bootstrap/state.ts`:
|
||||
|
||||
```typescript
|
||||
mock.module("src/bootstrap/state.ts", () => ({
|
||||
getCommitCounter: mock(() => ({ increment: mock(() => {}) })),
|
||||
getPrCounter: mock(() => ({ increment: mock(() => {}) })),
|
||||
// 保留其他被测函数实际需要的导出
|
||||
}));
|
||||
```
|
||||
|
||||
4. 使用 `await import()` 模式加载被测模块
|
||||
5. 运行测试验证无副作用
|
||||
|
||||
**风险**:`mock.module` 会替换整个模块。如果 `detectGitOperation` 还需要其他来自这些模块的导出,需在 mock 工厂中提供。
|
||||
|
||||
---
|
||||
|
||||
## 12.2 `PermissionMode.test.ts` — 修复 `isExternalPermissionMode` 虚假测试
|
||||
|
||||
**当前问题**:`isExternalPermissionMode` 依赖 `process.env.USER_TYPE`。非 ant 环境下所有 mode 都返回 true,测试从未覆盖 false 分支。
|
||||
|
||||
**修复步骤**:
|
||||
|
||||
1. 新增 ant 环境测试组(见 Plan 10.3 详细用例)
|
||||
2. 使用 `beforeEach`/`afterEach` 管理 `process.env.USER_TYPE`
|
||||
|
||||
```typescript
|
||||
describe("when USER_TYPE is 'ant'", () => {
|
||||
const originalUserType = process.env.USER_TYPE;
|
||||
beforeEach(() => { process.env.USER_TYPE = "ant"; });
|
||||
afterEach(() => {
|
||||
if (originalUserType !== undefined) {
|
||||
process.env.USER_TYPE = originalUserType;
|
||||
} else {
|
||||
delete process.env.USER_TYPE;
|
||||
}
|
||||
});
|
||||
|
||||
test("returns false for 'auto'", () => {
|
||||
expect(isExternalPermissionMode("auto")).toBe(false);
|
||||
});
|
||||
test("returns false for 'bubble'", () => {
|
||||
expect(isExternalPermissionMode("bubble")).toBe(false);
|
||||
});
|
||||
test("returns true for 'plan'", () => {
|
||||
expect(isExternalPermissionMode("plan")).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
3. 验证新增测试确实执行 false 路径
|
||||
|
||||
---
|
||||
|
||||
## 12.3 `providers.test.ts` — 环境变量快照恢复
|
||||
|
||||
**当前问题**:
|
||||
- `originalEnv` 声明后未使用
|
||||
- `afterEach` 仅删除已知 3 个 key,如果源码新增 env var,测试间状态泄漏
|
||||
|
||||
**修复步骤**:
|
||||
|
||||
```typescript
|
||||
let savedEnv: Record<string, string | undefined>;
|
||||
|
||||
beforeEach(() => {
|
||||
savedEnv = {};
|
||||
for (const key of Object.keys(process.env)) {
|
||||
savedEnv[key] = process.env[key];
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 删除所有当前 env,恢复快照
|
||||
for (const key of Object.keys(process.env)) {
|
||||
delete process.env[key];
|
||||
}
|
||||
for (const [key, value] of Object.entries(savedEnv)) {
|
||||
if (value !== undefined) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> 简化方案:只保存/恢复相关 key 列表 `["CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY", "ANTHROPIC_BASE_URL", "USER_TYPE"]`,但需注释说明新增 env var 时需同步更新。
|
||||
|
||||
---
|
||||
|
||||
## 12.4 `envUtils.test.ts` — 验证环境变量恢复完整性
|
||||
|
||||
**当前状态**:已有 `afterEach` 恢复。需审查:
|
||||
|
||||
1. 确认所有 `describe` 块中的 `afterEach` 都完整恢复了修改的 env var
|
||||
2. 确认 `process.argv` 修改也被恢复(`getClaudeConfigHomeDir` 测试修改了 argv)
|
||||
3. 新增:`afterEach` 中断言无意外 env 泄漏(可选,CI-only)
|
||||
|
||||
---
|
||||
|
||||
## 12.5 `sleep.test.ts` / `memoize.test.ts` — 时间敏感测试加固
|
||||
|
||||
**当前状态**:已有合理 margin。可选加固:
|
||||
|
||||
| 文件 | 用例 | 当前 | 加固 |
|
||||
|------|------|------|------|
|
||||
| `sleep.test.ts` | `resolves after timeout` | `sleep(50)`, check `>= 40ms` | 增大 margin:`sleep(50)`, check `>= 30ms` |
|
||||
| `memoize.test.ts` | stale serve & refresh | TTL=1ms, wait 10ms | 增大 margin:TTL=5ms, wait 50ms |
|
||||
|
||||
> 仅在 CI 出现 flaky 时执行此加固。
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `gitOperationTracking.test.ts` 无分析副作用(可通过在 mock 中增加 `expect(logEvent).toHaveBeenCalledTimes(N)` 验证)
|
||||
- [ ] `PermissionMode.test.ts` 的 `isExternalPermissionMode` 覆盖 true + false 分支
|
||||
- [ ] `providers.test.ts` 的 `originalEnv` 死代码已删除
|
||||
- [ ] 所有修改 env 的测试文件恢复完整
|
||||
- [ ] `bun test` 全部通过
|
||||
71
docs/test-plans/13-cjk-truncate-tests.md
Normal file
71
docs/test-plans/13-cjk-truncate-tests.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Plan 13 — truncate CJK/Emoji 补充测试
|
||||
|
||||
> 优先级:中 | 1 个文件 | 预估新增 ~15 个测试用例
|
||||
|
||||
`truncate.ts` 使用 `stringWidth` 和 grapheme segmentation 实现宽度感知截断,但现有测试仅覆盖 ASCII。这是核心场景缺失。
|
||||
|
||||
---
|
||||
|
||||
## 被测函数
|
||||
|
||||
- `truncateToWidth(text, maxWidth)` — 尾部截断加 `…`
|
||||
- `truncateStartToWidth(text, maxWidth)` — 头部截断加 `…`
|
||||
- `truncateToWidthNoEllipsis(text, maxWidth)` — 尾部截断无省略号
|
||||
- `truncatePathMiddle(path, maxLength)` — 路径中间截断
|
||||
- `wrapText(text, maxWidth)` — 按宽度换行
|
||||
|
||||
---
|
||||
|
||||
## 新增用例
|
||||
|
||||
### CJK 全角字符
|
||||
|
||||
| 用例 | 函数 | 输入 | maxWidth | 期望行为 |
|
||||
|------|------|------|----------|----------|
|
||||
| 纯中文截断 | `truncateToWidth` | `"你好世界"` | 4 | `"你好…"` (每个中文字占 2 宽度) |
|
||||
| 中英混合 | `truncateToWidth` | `"hello你好"` | 8 | `"hello你…"` |
|
||||
| 全角不截断 | `truncateToWidth` | `"你好"` | 4 | `"你好"` (恰好 4) |
|
||||
| emoji 单字符 | `truncateToWidth` | `"👋"` | 2 | `"👋"` (emoji 通常 2 宽度) |
|
||||
| emoji 截断 | `truncateToWidth` | `"hello 👋 world"` | 8 | 确认宽度计算正确 |
|
||||
| 头部中文 | `truncateStartToWidth` | `"你好世界"` | 4 | `"…界"` |
|
||||
| 无省略中文 | `truncateToWidthNoEllipsis` | `"你好世界"` | 4 | `"你好"` |
|
||||
|
||||
> **注意**:`stringWidth` 对 CJK/emoji 的宽度计算取决于具体实现。先在 REPL 中运行确认实际宽度再写断言:
|
||||
> ```typescript
|
||||
> import { stringWidth } from "src/utils/truncate.ts";
|
||||
> console.log(stringWidth("你好")); // 确认是 4 还是 2
|
||||
> console.log(stringWidth("👋")); // 确认 emoji 宽度
|
||||
> ```
|
||||
|
||||
### 路径中间截断补充
|
||||
|
||||
| 用例 | 输入 | maxLength | 期望 |
|
||||
|------|------|-----------|------|
|
||||
| 文件名超长 | `"/very/long/path/to/MyComponent.tsx"` | 10 | 含 `…` 且以 `.tsx` 结尾 |
|
||||
| 无斜杠短串 | `"abc"` | 1 | 确认行为不抛错 |
|
||||
| maxLength 极小 | `"/a/b"` | 1 | 确认不抛错 |
|
||||
| maxLength=4 | `"/a/b/c.ts"` | 4 | 确认行为 |
|
||||
|
||||
### wrapText 补充
|
||||
|
||||
| 用例 | 输入 | maxWidth | 期望 |
|
||||
|------|------|----------|------|
|
||||
| 含换行符 | `"hello\nworld"` | 10 | 保留原有换行 |
|
||||
| 宽度=0 | `"hello"` | 0 | 空串或原串(确认不抛错) |
|
||||
|
||||
---
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 在 REPL 中确认 `stringWidth` 对 CJK/emoji 的实际返回值
|
||||
2. 按实际值编写精确断言
|
||||
3. 如果 `stringWidth` 依赖 ICU 或平台特性,添加平台检查(`process.platform !== "win32"` 跳过条件)
|
||||
4. 运行测试
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] 至少 5 个 CJK/emoji 相关测试通过
|
||||
- [ ] 断言基于实际 `stringWidth` 返回值,非猜测
|
||||
- [ ] `bun test` 全部通过
|
||||
191
docs/test-plans/14-integration-tests.md
Normal file
191
docs/test-plans/14-integration-tests.md
Normal file
@ -0,0 +1,191 @@
|
||||
# Plan 14 — 集成测试搭建
|
||||
|
||||
> 优先级:中 | 新建 ~3 个测试文件 | 预估 ~30 个测试用例
|
||||
|
||||
当前 `tests/integration/` 目录为空,spec 设计的三个集成测试均未创建。本计划搭建 mock 基础设施并实现核心集成测试。
|
||||
|
||||
---
|
||||
|
||||
## 14.1 搭建 `tests/mocks/` 基础设施
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── mocks/
|
||||
│ ├── api-responses.ts # Claude API mock 响应
|
||||
│ ├── file-system.ts # 临时文件系统工具
|
||||
│ └── fixtures/
|
||||
│ ├── sample-claudemd.md # CLAUDE.md 样本
|
||||
│ └── sample-messages.json # 消息样本
|
||||
├── integration/
|
||||
│ ├── tool-chain.test.ts
|
||||
│ ├── context-build.test.ts
|
||||
│ └── message-pipeline.test.ts
|
||||
└── helpers/
|
||||
└── setup.ts # 共享 beforeAll/afterAll
|
||||
```
|
||||
|
||||
### `tests/mocks/file-system.ts`
|
||||
|
||||
```typescript
|
||||
import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
export async function createTempDir(prefix = "claude-test-"): Promise<string> {
|
||||
const dir = await mkdtemp(join(tmpdir(), prefix));
|
||||
return dir;
|
||||
}
|
||||
|
||||
export async function cleanupTempDir(dir: string): Promise<void> {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
export async function writeTempFile(dir: string, name: string, content: string): Promise<string> {
|
||||
const path = join(dir, name);
|
||||
await writeFile(path, content, "utf-8");
|
||||
return path;
|
||||
}
|
||||
```
|
||||
|
||||
### `tests/mocks/fixtures/sample-claudemd.md`
|
||||
|
||||
```markdown
|
||||
# Project Instructions
|
||||
|
||||
This is a sample CLAUDE.md file for testing.
|
||||
```
|
||||
|
||||
### `tests/mocks/api-responses.ts`
|
||||
|
||||
```typescript
|
||||
export const mockStreamResponse = {
|
||||
type: "message_start" as const,
|
||||
message: {
|
||||
id: "msg_mock_001",
|
||||
type: "message" as const,
|
||||
role: "assistant",
|
||||
content: [],
|
||||
model: "claude-sonnet-4-20250514",
|
||||
stop_reason: null,
|
||||
stop_sequence: null,
|
||||
usage: { input_tokens: 100, output_tokens: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
export const mockTextBlock = {
|
||||
type: "content_block_start" as const,
|
||||
index: 0,
|
||||
content_block: { type: "text" as const, text: "Mock response" },
|
||||
};
|
||||
|
||||
export const mockToolUseBlock = {
|
||||
type: "content_block_start" as const,
|
||||
index: 1,
|
||||
content_block: {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_mock_001",
|
||||
name: "Read",
|
||||
input: { file_path: "/tmp/test.txt" },
|
||||
},
|
||||
};
|
||||
|
||||
export const mockEndEvent = {
|
||||
type: "message_stop" as const,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14.2 `tests/integration/tool-chain.test.ts`
|
||||
|
||||
**目标**:验证 Tool 注册 → 发现 → 权限检查链路。
|
||||
|
||||
### 前置条件
|
||||
|
||||
`src/tools.ts` 的 `getAllBaseTools` / `getTools` 导入链过重。策略:
|
||||
- 尝试直接 import 并 mock 最重依赖
|
||||
- 若不可行,改为测试 `src/Tool.ts` 的 `findToolByName` + 手动构造 tool 列表
|
||||
|
||||
### 用例
|
||||
|
||||
| # | 用例 | 验证点 |
|
||||
|---|------|--------|
|
||||
| 1 | `findToolByName("Bash")` 在已注册列表中查找 | 返回正确的 tool 定义 |
|
||||
| 2 | `findToolByName("NonExistent")` | 返回 `undefined` |
|
||||
| 3 | `findToolByName` 大小写不敏感 | `"bash"` 也能找到 |
|
||||
| 4 | `filterToolsByDenyRules` 拒绝特定工具 | 被拒绝工具不在结果中 |
|
||||
| 5 | `parseToolPreset("default")` 返回已知列表 | 包含核心 tools |
|
||||
| 6 | `buildTool` 构建的 tool 可被 `findToolByName` 发现 | 端到端验证 |
|
||||
|
||||
> 如果 `getAllBaseTools` 确实不可导入,改用 mock tool list 替代。
|
||||
|
||||
---
|
||||
|
||||
## 14.3 `tests/integration/context-build.test.ts`
|
||||
|
||||
**目标**:验证系统提示组装流程(CLAUDE.md 加载 + git status + 日期注入)。
|
||||
|
||||
### 前置条件
|
||||
|
||||
`src/context.ts` 依赖链极重。策略:
|
||||
- Mock `src/bootstrap/state.ts`(提供 cwd、projectRoot)
|
||||
- Mock `src/utils/git.ts`(提供 git status)
|
||||
- 使用真实 `src/utils/claudemd.ts` + 临时文件
|
||||
|
||||
### 用例
|
||||
|
||||
| # | 用例 | 验证点 |
|
||||
|---|------|--------|
|
||||
| 1 | 基本 context 构建 | 返回值包含系统提示字符串 |
|
||||
| 2 | CLAUDE.md 内容出现在 context 中 | `stripHtmlComments` 后的内容被包含 |
|
||||
| 3 | 多层目录 CLAUDE.md 合并 | 父目录 + 子目录 CLAUDE.md 都被加载 |
|
||||
| 4 | 无 CLAUDE.md 时不报错 | context 正常返回,无 crash |
|
||||
| 5 | git status 为 null | context 正常构建(测试环境中 git 不可用时) |
|
||||
|
||||
> **风险评估**:如果 mock `context.ts` 的依赖链成本过高,退化为测试 `buildEffectiveSystemPrompt`(已在 systemPrompt.test.ts 中完成),记录为已知限制。
|
||||
|
||||
---
|
||||
|
||||
## 14.4 `tests/integration/message-pipeline.test.ts`
|
||||
|
||||
**目标**:验证用户输入 → 消息格式化 → API 请求构建。
|
||||
|
||||
### 前置条件
|
||||
|
||||
`src/services/api/claude.ts` 构建最终 API 请求。策略:
|
||||
- Mock Anthropic SDK 的 streaming endpoint
|
||||
- 验证请求参数结构
|
||||
|
||||
### 用例
|
||||
|
||||
| # | 用例 | 验证点 |
|
||||
|---|------|--------|
|
||||
| 1 | 文本消息格式化 | `createUserMessage` 生成正确 role+content |
|
||||
| 2 | tool_result 消息格式化 | 包含 tool_use_id 和 content |
|
||||
| 3 | 多轮消息序列化 | messages 数组保持顺序 |
|
||||
| 4 | 系统提示注入到请求 | API 请求的 system 字段非空 |
|
||||
| 5 | 消息 normalize 后格式一致 | `normalizeMessages` 输出结构正确 |
|
||||
|
||||
> **现实评估**:消息格式化的大部分已在 `messages.test.ts` 覆盖。API 请求构建需要 mock SDK,复杂度高。如果投入产出比低,仅实现用例 1-3 和 5,用例 4 标记为 stretch goal。
|
||||
|
||||
---
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 创建 `tests/mocks/` 目录和基础文件
|
||||
2. 实现 `tool-chain.test.ts`(最低风险,最高价值)
|
||||
3. 评估 `context-build.test.ts` 可行性,决定是否实施
|
||||
4. 实现 `message-pipeline.test.ts`(可降级为单元测试)
|
||||
5. 更新 `testing-spec.md` 状态
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] `tests/mocks/` 基础设施可用
|
||||
- [ ] 至少 `tool-chain.test.ts` 实现并通过
|
||||
- [ ] 集成测试独立于单元测试运行:`bun test tests/integration/`
|
||||
- [ ] 所有集成测试使用 `createTempDir` + `cleanupTempDir`,不留文件系统残留
|
||||
- [ ] `bun test` 全部通过
|
||||
67
docs/test-plans/15-cli-coverage-baseline.md
Normal file
67
docs/test-plans/15-cli-coverage-baseline.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Plan 15 — CLI 参数测试 + 覆盖率基线
|
||||
|
||||
> 优先级:低 | 预估 ~15 个测试用例
|
||||
|
||||
---
|
||||
|
||||
## 15.1 `src/main.tsx` CLI 参数测试
|
||||
|
||||
**目标**:覆盖 Commander.js 配置的参数解析和模式切换。
|
||||
|
||||
### 前置条件
|
||||
|
||||
`src/main.tsx` 的 Commander 实例通常在模块顶层创建。测试策略:
|
||||
- 直接构造 Commander 实例或 mock `main.tsx` 的 program 导出
|
||||
- 使用 `parseArgs` 而非 `parse`(不触发 `process.exit`)
|
||||
|
||||
### 用例
|
||||
|
||||
| # | 用例 | 输入 | 期望 |
|
||||
|---|------|------|------|
|
||||
| 1 | 默认模式 | `[]` | 模式为 REPL |
|
||||
| 2 | pipe 模式 | `["-p"]` | 模式为 pipe |
|
||||
| 3 | pipe 带输入 | `["-p", "say hello"]` | 输入为 `"say hello"` |
|
||||
| 4 | print 模式 | `["--print", "hello"]` | 等效于 pipe |
|
||||
| 5 | verbose | `["-v"]` | verbose 标志为 true |
|
||||
| 6 | model 选择 | `["--model", "claude-opus-4-6"]` | model 值正确传递 |
|
||||
| 7 | system prompt | `["--system-prompt", "custom"]` | system prompt 被设置 |
|
||||
| 8 | help | `["--help"]` | 显示帮助信息,不报错 |
|
||||
| 9 | version | `["--version"]` | 显示版本号 |
|
||||
| 10 | unknown flag | `["--nonexistent"]` | 不报错(Commander 允许未知参数时) |
|
||||
|
||||
> **风险**:`main.tsx` 可能执行初始化逻辑(auth、analytics),需要在 mock 环境中运行。如果复杂度过高,降级为只测试参数解析部分。
|
||||
|
||||
---
|
||||
|
||||
## 15.2 覆盖率基线
|
||||
|
||||
### 运行命令
|
||||
|
||||
```bash
|
||||
bun test --coverage 2>&1 | tail -50
|
||||
```
|
||||
|
||||
### 记录内容
|
||||
|
||||
| 模块 | 当前覆盖率 | 目标 |
|
||||
|------|-----------|------|
|
||||
| `src/utils/` | 待测量 | >= 80% |
|
||||
| `src/utils/permissions/` | 待测量 | >= 60% |
|
||||
| `src/utils/model/` | 待测量 | >= 60% |
|
||||
| `src/Tool.ts` + `src/tools.ts` | 待测量 | >= 80% |
|
||||
| `src/utils/claudemd.ts` | 待测量 | >= 40%(核心逻辑难测) |
|
||||
| 整体 | 待测量 | 不设强制指标 |
|
||||
|
||||
### 后续行动
|
||||
|
||||
- 将基线数据填入 `testing-spec.md` §4
|
||||
- 识别覆盖率最低的 10 个文件,排入后续测试计划
|
||||
- 如 `bun test --coverage` 输出不可用(Bun 版本限制),改用手动计算已测/总导出函数比
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
- [ ] CLI 参数至少覆盖 5 个核心 flag
|
||||
- [ ] 覆盖率基线数据记录到 testing-spec.md
|
||||
- [ ] `bun test` 全部通过
|
||||
@ -1,455 +1,256 @@
|
||||
# Testing Specification
|
||||
|
||||
本文档定义了 claude-code 项目的测试规范,作为编写和维护测试代码的统一标准。
|
||||
本文档定义 claude-code 项目的测试规范、当前覆盖状态和改进计划。
|
||||
|
||||
## 1. 测试目标
|
||||
## 1. 技术栈
|
||||
|
||||
| 目标 | 说明 |
|
||||
|------|------|
|
||||
| **防止回归** | 确保已有功能不被新改动破坏,每次 PR 必须通过全部测试 |
|
||||
| **验证核心流程** | 覆盖 CLI 核心交互流程:Tool 调用链、Context 构建、消息处理 |
|
||||
| **文档化行为** | 通过测试用例记录各模块的预期行为,作为活文档供开发者参考 |
|
||||
| 项 | 选型 |
|
||||
|----|------|
|
||||
| 测试框架 | `bun:test` |
|
||||
| 断言/Mock | `bun:test` 内置 |
|
||||
| 覆盖率 | `bun test --coverage` |
|
||||
| CI | GitHub Actions,push/PR 到 main 自动运行 |
|
||||
|
||||
## 2. 技术栈
|
||||
|
||||
| 项 | 选型 | 说明 |
|
||||
|----|------|------|
|
||||
| 测试框架 | `bun:test` | Bun 内置,零配置,与运行时一致 |
|
||||
| 断言库 | `bun:test` 内置 `expect` | 兼容 Jest `expect` API |
|
||||
| Mock | `bun:test` 内置 `mock`/`spyOn` | 配合手动 mock fixtures |
|
||||
| 覆盖率 | `bun test --coverage` | 内置覆盖率报告 |
|
||||
|
||||
## 3. 测试层次
|
||||
## 2. 测试层次
|
||||
|
||||
本项目采用 **单元测试 + 集成测试** 两层结构,不做 E2E 或快照测试。
|
||||
|
||||
### 3.1 单元测试
|
||||
- **单元测试** — 纯函数、工具类、解析器。文件就近放置于 `src/**/__tests__/`。
|
||||
- **集成测试** — 多模块协作流程。集中于 `tests/integration/`。
|
||||
|
||||
- **对象**:纯函数、工具类、解析器、独立模块
|
||||
- **特征**:无外部依赖、执行快、可并行
|
||||
- **示例场景**:
|
||||
- `src/utils/array.ts` — 数组操作函数
|
||||
- `src/utils/path.ts` — 路径解析
|
||||
- `src/utils/diff.ts` — diff 算法
|
||||
- `src/utils/permissions/` — 权限判断逻辑
|
||||
- `src/utils/model/` — 模型选择与 provider 路由
|
||||
- Tool 的 `inputSchema` 校验逻辑
|
||||
|
||||
### 3.2 集成测试
|
||||
|
||||
- **对象**:多模块协作流程
|
||||
- **特征**:可能需要 mock 外部服务(API、文件系统),测试模块间协作
|
||||
- **示例场景**:
|
||||
- Tool 调用链:`tools.ts` 注册 → `findToolByName` → tool `call()` 执行
|
||||
- Context 构建:`context.ts` 组装系统提示(CLAUDE.md 加载 + git status + 日期)
|
||||
- 消息处理管线:用户输入 → 消息格式化 → API 请求构建
|
||||
|
||||
## 4. 文件结构
|
||||
|
||||
采用 **混合模式**:单元测试就近放置,集成测试集中管理。
|
||||
## 3. 文件结构与命名
|
||||
|
||||
```
|
||||
src/
|
||||
├── utils/
|
||||
│ ├── array.ts
|
||||
│ ├── __tests__/ # 单元测试:就近放置
|
||||
│ │ ├── array.test.ts
|
||||
│ │ ├── set.test.ts
|
||||
│ │ └── path.test.ts
|
||||
│ ├── model/
|
||||
│ │ ├── providers.ts
|
||||
│ │ └── __tests__/
|
||||
│ │ └── providers.test.ts
|
||||
│ └── permissions/
|
||||
│ ├── index.ts
|
||||
│ └── __tests__/
|
||||
│ └── permissions.test.ts
|
||||
├── tools/
|
||||
│ ├── BashTool/
|
||||
│ │ ├── index.ts
|
||||
│ │ └── __tests__/
|
||||
│ │ └── BashTool.test.ts
|
||||
│ └── FileEditTool/
|
||||
│ ├── index.ts
|
||||
│ └── __tests__/
|
||||
│ └── FileEditTool.test.ts
|
||||
tests/ # 集成测试:集中管理
|
||||
├── integration/
|
||||
│ ├── tool-chain.test.ts
|
||||
│ ├── context-build.test.ts
|
||||
│ └── message-pipeline.test.ts
|
||||
├── mocks/ # 通用 mock / fixtures
|
||||
│ ├── api-responses.ts # Claude API mock 响应
|
||||
│ ├── file-system.ts # 文件系统 mock 工具
|
||||
│ └── fixtures/
|
||||
│ ├── sample-claudemd.md
|
||||
│ └── sample-messages.json
|
||||
└── helpers/ # 测试辅助函数
|
||||
└── setup.ts
|
||||
├── utils/__tests__/ # 纯函数单元测试
|
||||
├── tools/<Tool>/__tests__/ # Tool 单元测试
|
||||
├── services/mcp/__tests__/ # MCP 单元测试
|
||||
├── utils/permissions/__tests__/
|
||||
├── utils/model/__tests__/
|
||||
├── utils/settings/__tests__/
|
||||
├── utils/shell/__tests__/
|
||||
├── utils/git/__tests__/
|
||||
└── __tests__/ # 顶层模块测试 (Tool.ts, tools.ts)
|
||||
tests/
|
||||
├── integration/ # 集成测试(尚未创建)
|
||||
├── mocks/ # 共享 mock/fixture(尚未创建)
|
||||
└── helpers/ # 测试辅助函数
|
||||
```
|
||||
|
||||
### 命名规则
|
||||
- 测试文件:`<module>.test.ts`
|
||||
- 命名风格:`describe("functionName")` + `test("行为描述")`,英文
|
||||
- 编写原则:Arrange-Act-Assert、单一职责、独立性、边界覆盖
|
||||
|
||||
| 项 | 规则 |
|
||||
|----|------|
|
||||
| 测试文件 | `<module-name>.test.ts` |
|
||||
| 测试目录 | `__tests__/`(单元)、`tests/integration/`(集成) |
|
||||
| Fixture 文件 | `tests/mocks/fixtures/` 下按用途命名 |
|
||||
| Helper 文件 | `tests/helpers/` 下按功能命名 |
|
||||
## 4. 当前覆盖状态
|
||||
|
||||
## 5. 命名与编写规范
|
||||
> 更新日期:2026-04-02 | **1177 tests, 64 files, 0 fail, 837ms**
|
||||
|
||||
### 5.1 命名风格
|
||||
### 4.1 可靠度评分
|
||||
|
||||
使用 `describe` + `it`/`test` 英文描述:
|
||||
每个测试文件按断言深度、边界覆盖、mock 质量、测试独立性综合评定:
|
||||
|
||||
```typescript
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
describe("findToolByName", () => {
|
||||
test("returns the tool when name matches exactly", () => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test("returns undefined when no tool matches", () => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test("is case-insensitive for tool name lookup", () => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 5.2 describe 块组织原则
|
||||
|
||||
- 顶层 `describe` 对应被测函数/类/模块名
|
||||
- 可嵌套 `describe` 对分支场景分组(如 `describe("when input is empty", ...)`)
|
||||
- 每个 `test` 应测试一个行为,命名采用 **"动作 + 预期结果"** 格式
|
||||
|
||||
### 5.3 编写原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
| 等级 | 含义 |
|
||||
|------|------|
|
||||
| **Arrange-Act-Assert** | 每个测试分三段:准备数据、执行操作、验证结果 |
|
||||
| **单一职责** | 一个 `test` 只验证一个行为 |
|
||||
| **独立性** | 测试之间无顺序依赖,无共享可变状态 |
|
||||
| **可读性优先** | 测试代码是文档,宁可重复也不过度抽象 |
|
||||
| **边界覆盖** | 空值、边界值、异常输入必须覆盖 |
|
||||
| **GOOD** | 断言精确(exact match),边界充分,结构清晰 |
|
||||
| **ACCEPTABLE** | 正常路径覆盖完整,部分边界或断言可加强 |
|
||||
| **WEAK** | 存在明显缺陷:断言过弱、重要边界缺失、或有脆弱性风险 |
|
||||
|
||||
### 5.4 异步测试
|
||||
### 4.2 按模块分布
|
||||
|
||||
```typescript
|
||||
test("reads file content correctly", async () => {
|
||||
const content = await readFile("/tmp/test.txt");
|
||||
expect(content).toContain("expected");
|
||||
});
|
||||
```
|
||||
#### P0 — 核心模块
|
||||
|
||||
## 6. Mock 策略
|
||||
| 文件 | Tests | 评分 | 覆盖范围 | 主要不足 |
|
||||
|------|-------|------|----------|----------|
|
||||
| `src/__tests__/Tool.test.ts` | 20 | GOOD | buildTool, toolMatchesName, findToolByName, filterToolProgressMessages | — |
|
||||
| `src/__tests__/tools.test.ts` | 9 | ACCEPTABLE | parseToolPreset, filterToolsByDenyRules | 预设覆盖仅测 "default";有冗余用例 |
|
||||
| `src/tools/FileEditTool/__tests__/utils.test.ts` | 22 | ACCEPTABLE | normalizeQuotes, applyEditToFile, preserveQuoteStyle | `findActualString` 断言过弱(`not.toBeNull`);`preserveQuoteStyle` 仅 2 用例 |
|
||||
| `src/tools/shared/__tests__/gitOperationTracking.test.ts` | 14 | WEAK | parseGitCommitId, detectGitOperation | **未 mock analytics 依赖**,测试产生副作用;6 个 GH PR action 仅测 2 个 |
|
||||
| `src/tools/BashTool/__tests__/destructiveCommandWarning.test.ts` | 21 | ACCEPTABLE | git/rm/SQL/k8s/terraform 危险模式 | safe commands 4 断言合一;缺少 `rm -rf /`、`DROP DATABASE`、管道命令 |
|
||||
| `src/tools/BashTool/__tests__/commandSemantics.test.ts` | 10 | ACCEPTABLE | grep/diff/test/rg/find 退出码语义 | mock `splitCommand_DEPRECATED` 与实现可能分歧;覆盖可更全面 |
|
||||
|
||||
采用 **混合管理**:通用 mock 集中于 `tests/mocks/`,专用 mock 就近定义。
|
||||
**Utils 纯函数(19 文件):**
|
||||
|
||||
### 6.1 Claude API Mock(集中管理)
|
||||
| 文件 | Tests | 评分 | 覆盖范围 | 主要不足 |
|
||||
|------|-------|------|----------|----------|
|
||||
| `utils/__tests__/array.test.ts` | 12 | GOOD | intersperse, count, uniq | — |
|
||||
| `utils/__tests__/set.test.ts` | 11 | GOOD | difference, intersects, every, union | — |
|
||||
| `utils/__tests__/xml.test.ts` | 9 | GOOD | escapeXml, escapeXmlAttr | 缺 null/undefined 输入测试 |
|
||||
| `utils/__tests__/hash.test.ts` | 12 | ACCEPTABLE | djb2Hash, hashContent, hashPair | `hashContent`/`hashPair` 无已知答案断言(仅测确定性) |
|
||||
| `utils/__tests__/stringUtils.test.ts` | 30 | GOOD | 10 个函数全覆盖,含 Unicode 边界 | — |
|
||||
| `utils/__tests__/semver.test.ts` | 16 | ACCEPTABLE | gt/gte/lt/lte/satisfies/order | 缺 pre-release、tilde range、畸形版本串 |
|
||||
| `utils/__tests__/uuid.test.ts` | 6 | ACCEPTABLE | validateUuid | 大写测试仅 `not.toBeNull`,未验证标准化输出 |
|
||||
| `utils/__tests__/format.test.ts` | 20 | WEAK | formatFileSize, formatDuration, formatNumber 等 | **多处 `toContain` 应为 `toBe`**:formatNumber/formatTokens/formatRelativeTime 仅检查子串 |
|
||||
| `utils/__tests__/frontmatterParser.test.ts` | 22 | GOOD | parseFrontmatter, splitPathInFrontmatter, parsePositiveIntFromFrontmatter | — |
|
||||
| `utils/__tests__/file.test.ts` | 13 | ACCEPTABLE | convertLeadingTabsToSpaces, addLineNumbers, stripLineNumberPrefix | `addLineNumbers` 仅 `toContain`;缺 Windows 路径分隔符测试 |
|
||||
| `utils/__tests__/glob.test.ts` | 6 | ACCEPTABLE | extractGlobBaseDirectory | 缺绝对路径、根 `/`、Windows 路径 |
|
||||
| `utils/__tests__/diff.test.ts` | 8 | ACCEPTABLE | adjustHunkLineNumbers, getPatchFromContents | `getPatchFromContents` 仅检查结构,未验证 diff 内容正确性 |
|
||||
| `utils/__tests__/json.test.ts` | 15 | GOOD | safeParseJSON, parseJSONL, addItemToJSONCArray | — |
|
||||
| `utils/__tests__/truncate.test.ts` | 18 | ACCEPTABLE | truncateToWidth, wrapText, truncatePathMiddle | **缺 CJK/emoji/wide-char 测试**(这是宽度感知实现的核心场景) |
|
||||
| `utils/__tests__/path.test.ts` | 15 | ACCEPTABLE | containsPathTraversal, normalizePathForConfigKey | 仅覆盖 2/5+ 导出函数 |
|
||||
| `utils/__tests__/tokens.test.ts` | 18 | GOOD | getTokenCountFromUsage, doesMostRecentAssistantMessageExceed200k 等 | — |
|
||||
|
||||
所有 API 测试全部使用 mock,不调用真实 API。
|
||||
**Context 构建(2 文件):**
|
||||
|
||||
```typescript
|
||||
// tests/mocks/api-responses.ts
|
||||
export const mockStreamResponse = {
|
||||
type: "message_start",
|
||||
message: {
|
||||
id: "msg_mock_001",
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [],
|
||||
model: "claude-sonnet-4-20250514",
|
||||
// ...
|
||||
},
|
||||
};
|
||||
| 文件 | Tests | 评分 | 覆盖范围 | 主要不足 |
|
||||
|------|-------|------|----------|----------|
|
||||
| `utils/__tests__/claudemd.test.ts` | 14 | ACCEPTABLE | stripHtmlComments, isMemoryFilePath, getLargeMemoryFiles | **仅测 3 个辅助函数**,核心发现/加载/`@include` 指令/memoization 未覆盖 |
|
||||
| `utils/__tests__/systemPrompt.test.ts` | 8 | GOOD | buildEffectiveSystemPrompt | — |
|
||||
|
||||
export const mockToolUseResponse = {
|
||||
type: "content_block_start",
|
||||
content_block: {
|
||||
type: "tool_use",
|
||||
id: "toolu_mock_001",
|
||||
name: "Read",
|
||||
input: { file_path: "/tmp/test.txt" },
|
||||
},
|
||||
};
|
||||
```
|
||||
#### P1 — 重要模块
|
||||
|
||||
### 6.2 模块级 Mock(就近定义)
|
||||
| 文件 | Tests | 评分 | 覆盖范围 | 主要不足 |
|
||||
|------|-------|------|----------|----------|
|
||||
| `permissions/__tests__/permissionRuleParser.test.ts` | 16 | GOOD | escape/unescape 规则,roundtrip 完整性 | — |
|
||||
| `permissions/__tests__/permissions.test.ts` | 12 | ACCEPTABLE | getDenyRuleForTool, getAskRuleForTool, filterDeniedAgents | `as any` cast;缺 MCP tool deny 测试 |
|
||||
| `permissions/__tests__/shellRuleMatching.test.ts` | 19 | GOOD | 通配符、转义、正则特殊字符 | — |
|
||||
| `permissions/__tests__/PermissionMode.test.ts` | 18 | WEAK | permissionModeFromString, isExternalPermissionMode 等 | **`isExternalPermissionMode` false 路径从未执行**;mode 覆盖不完整(5 选 3) |
|
||||
| `permissions/__tests__/dangerousPatterns.test.ts` | 7 | WEAK | CROSS_PLATFORM_CODE_EXEC, DANGEROUS_BASH_PATTERNS | 纯数据 smoke test,无行为测试;不验证数组无重复 |
|
||||
| `model/__tests__/aliases.test.ts` | 15 | ACCEPTABLE | isModelAlias, isModelFamilyAlias | 缺 null/undefined/空串输入 |
|
||||
| `model/__tests__/model.test.ts` | 13 | ACCEPTABLE | firstPartyNameToCanonical | 缺空串、非标准日期后缀 |
|
||||
| `model/__tests__/providers.test.ts` | 9 | ACCEPTABLE | getAPIProvider, isFirstPartyAnthropicBaseUrl | `originalEnv` 声明未使用;env 恢复不完整 |
|
||||
| `utils/__tests__/messages.test.ts` | 36 | GOOD | createAssistantMessage, createUserMessage, extractTag 等 16 个 describe | `normalizeMessages` 仅检查长度未验证内容 |
|
||||
|
||||
```typescript
|
||||
import { mock } from "bun:test";
|
||||
#### P2 — 补充模块
|
||||
|
||||
// mock 整个模块
|
||||
mock.module("src/services/api/claude.ts", () => ({
|
||||
createApiClient: () => ({
|
||||
stream: mock(() => mockStreamResponse),
|
||||
}),
|
||||
}));
|
||||
```
|
||||
| 文件 | Tests | 评分 | 覆盖范围 | 主要不足 |
|
||||
|------|-------|------|----------|----------|
|
||||
| `utils/__tests__/cron.test.ts` | 31 | GOOD | parseCronExpression, computeNextCronRun, cronToHuman | 缺月边界、闰年 |
|
||||
| `utils/__tests__/git.test.ts` | 15 | ACCEPTABLE | normalizeGitRemoteUrl (SSH/HTTPS/ssh://) | 缺 git://、file://、端口号 |
|
||||
| `settings/__tests__/config.test.ts` | 38 | GOOD | SettingsSchema, type guards, validateSettingsFileContent, formatZodError | 缺 DeniedMcpServerEntrySchema |
|
||||
|
||||
### 6.3 文件系统 Mock
|
||||
#### P3-P6 — 扩展覆盖(27 文件)
|
||||
|
||||
对于需要文件系统交互的测试,使用临时目录:
|
||||
| 文件 | Tests | 评分 | 备注 |
|
||||
|------|-------|------|------|
|
||||
| `utils/__tests__/errors.test.ts` | 33 | GOOD | — |
|
||||
| `utils/__tests__/envUtils.test.ts` | 33 | GOOD | env 保存/恢复规范 |
|
||||
| `utils/__tests__/effort.test.ts` | 30 | GOOD | 5 个 mock 模块,边界完整 |
|
||||
| `utils/__tests__/argumentSubstitution.test.ts` | 22 | ACCEPTABLE | 缺转义引号、越界索引 |
|
||||
| `utils/__tests__/sanitization.test.ts` | 14 | ACCEPTABLE | — |
|
||||
| `utils/__tests__/sleep.test.ts` | 14 | GOOD | 时间相关测试,margin 充足 |
|
||||
| `utils/__tests__/CircularBuffer.test.ts` | 11 | ACCEPTABLE | 缺 capacity=1、空 buffer getRecent |
|
||||
| `utils/__tests__/memoize.test.ts` | 18 | GOOD | 缓存 hit/stale/LRU 全覆盖 |
|
||||
| `utils/__tests__/tokenBudget.test.ts` | 21 | GOOD | — |
|
||||
| `utils/__tests__/displayTags.test.ts` | 17 | GOOD | — |
|
||||
| `utils/__tests__/taggedId.test.ts` | 10 | GOOD | — |
|
||||
| `utils/__tests__/controlMessageCompat.test.ts` | 15 | GOOD | — |
|
||||
| `utils/__tests__/gitConfigParser.test.ts` | 21 | GOOD | — |
|
||||
| `utils/__tests__/windowsPaths.test.ts` | 19 | GOOD | 双向 round-trip 测试 |
|
||||
| `utils/__tests__/envExpansion.test.ts` | 15 | GOOD | — |
|
||||
| `utils/__tests__/formatBriefTimestamp.test.ts` | 10 | GOOD | 固定 now 时间戳,确定性 |
|
||||
| `utils/__tests__/notebook.test.ts` | 9 | ACCEPTABLE | 合并断言偏弱 |
|
||||
| `utils/__tests__/hyperlink.test.ts` | 10 | ACCEPTABLE | 空串测试行为注释混乱 |
|
||||
| `utils/__tests__/zodToJsonSchema.test.ts` | 9 | WEAK | **object 属性仅 `toBeDefined` 未验证类型**;optional 字段未验证 absence |
|
||||
| `utils/__tests__/objectGroupBy.test.ts` | 5 | ACCEPTABLE | 极简,缺 undefined key 测试 |
|
||||
| `utils/__tests__/contentArray.test.ts` | 6 | ACCEPTABLE | 缺混合 tool_result+text 交替 |
|
||||
| `utils/__tests__/slashCommandParsing.test.ts` | 8 | GOOD | — |
|
||||
| `utils/__tests__/groupToolUses.test.ts` | 10 | GOOD | — |
|
||||
| `utils/__tests__/shell/__tests__/outputLimits.test.ts` | 7 | ACCEPTABLE | — |
|
||||
| `utils/__tests__/envValidation.test.ts` | 9 | ACCEPTABLE | **可能存在 bug**:lower bound=100 但 value=1 报 valid |
|
||||
| `utils/git/__tests__/gitConfigParser.test.ts` | 20 | GOOD | — |
|
||||
| `services/mcp/__tests__/mcpStringUtils.test.ts` | 16 | GOOD | — |
|
||||
| `services/mcp/__tests__/normalization.test.ts` | 10 | GOOD | — |
|
||||
|
||||
```typescript
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, beforeAll } from "bun:test";
|
||||
### 4.3 评分汇总
|
||||
|
||||
let tempDir: string;
|
||||
| 等级 | 文件数 | 占比 |
|
||||
|------|--------|------|
|
||||
| **GOOD** | 30 | 47% |
|
||||
| **ACCEPTABLE** | 26 | 41% |
|
||||
| **WEAK** | 8 | 12% |
|
||||
|
||||
beforeAll(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "claude-test-"));
|
||||
});
|
||||
## 5. 系统性问题
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(tempDir, { recursive: true });
|
||||
});
|
||||
```
|
||||
### 5.1 断言过弱(Smell: `toContain` 代替精确匹配)
|
||||
|
||||
## 7. 优先测试模块
|
||||
以下文件的部分测试使用 `toContain` 或 `not.toBeNull` 检查结果,当实现返回包含目标子串的任何字符串时测试仍通过,无法检测格式错误:
|
||||
|
||||
按优先级从高到低排列,括号内为目标覆盖率:
|
||||
| 文件 | 受影响函数 | 建议 |
|
||||
|------|-----------|------|
|
||||
| `format.test.ts` | formatNumber, formatTokens, formatRelativeTime | 改为 `toBe` 精确匹配 |
|
||||
| `file.test.ts` | addLineNumbers | 断言完整输出格式 |
|
||||
| `diff.test.ts` | getPatchFromContents | 验证 hunk 内容正确性 |
|
||||
| `notebook.test.ts` | mapNotebookCellsToToolResult | 验证合并后内容 |
|
||||
| `uuid.test.ts` | validateUuid (uppercase) | 断言标准化后的精确值 |
|
||||
|
||||
### P0 — 核心(行覆盖率 >= 80%)
|
||||
### 5.2 集成测试空白
|
||||
|
||||
| 模块 | 路径 | 测试重点 |
|
||||
|------|------|----------|
|
||||
| **Tool 系统** | `src/tools/`, `src/Tool.ts`, `src/tools.ts` | tool 注册/发现、inputSchema 校验、call() 执行与错误处理 |
|
||||
| **工具函数** | `src/utils/` 下纯函数 | 各种 utility 的正确性与边界情况 |
|
||||
| **Context 构建** | `src/context.ts`, `src/utils/claudemd.ts` | 系统提示拼装、CLAUDE.md 发现与加载、context 内容完整性 |
|
||||
Spec 定义的三个集成测试均未创建:
|
||||
|
||||
### P1 — 重要(行覆盖率 >= 60%)
|
||||
|
||||
| 模块 | 路径 | 测试重点 |
|
||||
|------|------|----------|
|
||||
| **权限系统** | `src/utils/permissions/` | 权限模式判断、tool 许可/拒绝逻辑 |
|
||||
| **模型路由** | `src/utils/model/` | provider 选择、模型名映射、fallback 逻辑 |
|
||||
| **消息处理** | `src/types/message.ts`, `src/utils/messages.ts` | 消息类型构造、格式化、过滤 |
|
||||
| **CLI 参数** | `src/main.tsx` 中的 Commander 配置 | 参数解析、模式切换(REPL/pipe) |
|
||||
|
||||
### P2 — 补充
|
||||
|
||||
| 模块 | 路径 | 测试重点 |
|
||||
|------|------|----------|
|
||||
| **Cron 调度** | `src/utils/cron*.ts` | cron 表达式解析、任务调度逻辑 |
|
||||
| **Git 工具** | `src/utils/git.ts` | git 命令构造、输出解析 |
|
||||
| **Config** | `src/utils/config.ts`, `src/utils/settings/` | 配置加载、合并、默认值 |
|
||||
|
||||
## 8. 覆盖率要求
|
||||
|
||||
| 范围 | 目标 | 说明 |
|
||||
| 计划 | 状态 | 依赖 |
|
||||
|------|------|------|
|
||||
| P0 核心模块 | **>= 80%** 行覆盖率 | Tool 系统、工具函数、Context 构建 |
|
||||
| P1 重要模块 | **>= 60%** 行覆盖率 | 权限、模型路由、消息处理 |
|
||||
| 整体 | 不设强制指标 | 逐步提升,不追求数字 |
|
||||
| `tests/integration/tool-chain.test.ts` | 未创建 | 需 mock tools.ts 完整注册链 |
|
||||
| `tests/integration/context-build.test.ts` | 未创建 | 需 mock context.ts 重依赖链 |
|
||||
| `tests/integration/message-pipeline.test.ts` | 未创建 | 需 mock API 层 |
|
||||
|
||||
运行覆盖率报告:
|
||||
`tests/mocks/` 目录也不存在,无共享 mock/fixture 基础设施。
|
||||
|
||||
```bash
|
||||
bun test --coverage
|
||||
```
|
||||
### 5.3 Mock 相关
|
||||
|
||||
## 9. CI 集成
|
||||
| 问题 | 影响文件 | 说明 |
|
||||
|------|----------|------|
|
||||
| 未 mock 重依赖 | `gitOperationTracking.test.ts` | `detectGitOperation` 内部调用 analytics,测试产生副作用 |
|
||||
| `isExternalPermissionMode` 永远 true | `PermissionMode.test.ts` | false 路径从未被执行,测试形同虚设 |
|
||||
| env 恢复不完整 | `providers.test.ts` | 仅删除已知 key,新增 env var 会导致测试泄漏 |
|
||||
|
||||
已有 GitHub Actions 配置(`.github/workflows/ci.yml`),`bun test` 步骤已就位。
|
||||
### 5.4 潜在 Bug
|
||||
|
||||
### CI 中测试的运行条件
|
||||
|
||||
- **push** 到 `main` 或 `feature/*` 分支时自动运行
|
||||
- **pull_request** 到 `main` 分支时自动运行
|
||||
- 测试失败将阻止合并
|
||||
|
||||
### 本地运行
|
||||
|
||||
```bash
|
||||
# 运行全部测试
|
||||
bun test
|
||||
|
||||
# 运行特定文件
|
||||
bun test src/utils/__tests__/array.test.ts
|
||||
|
||||
# 运行匹配模式
|
||||
bun test --filter "findToolByName"
|
||||
|
||||
# 带覆盖率
|
||||
bun test --coverage
|
||||
|
||||
# watch 模式(开发时)
|
||||
bun test --watch
|
||||
```
|
||||
|
||||
## 10. 编写测试 Checklist
|
||||
|
||||
每次新增或修改测试时,确认以下事项:
|
||||
|
||||
- [ ] 测试文件位置正确(单元 → `__tests__/`,集成 → `tests/integration/`)
|
||||
- [ ] 命名遵循 `describe` + `test` 英文格式
|
||||
- [ ] 每个 test 只验证一个行为
|
||||
- [ ] 覆盖了正常路径、边界情况和错误情况
|
||||
- [ ] 无硬编码的绝对路径或系统特定值
|
||||
- [ ] Mock 使用得当(通用 → `tests/mocks/`,专用 → 就近)
|
||||
- [ ] 测试可独立运行,无顺序依赖
|
||||
- [ ] `bun test` 本地全部通过后再提交
|
||||
|
||||
## 11. 当前测试覆盖状态
|
||||
|
||||
> 更新日期:2026-04-02 | 总计:**1177 tests, 64 files, 0 failures**
|
||||
|
||||
### P0 — 核心模块
|
||||
|
||||
| 测试计划 | 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|----------|--------|----------|
|
||||
| 01 - Tool 系统 | `src/__tests__/Tool.test.ts` | 25 | buildTool, toolMatchesName, findToolByName, getEmptyToolPermissionContext, filterToolProgressMessages |
|
||||
| | `src/__tests__/tools.test.ts` | 10 | parseToolPreset, filterToolsByDenyRules |
|
||||
| | `src/tools/shared/__tests__/gitOperationTracking.test.ts` | 16 | parseGitCommitId, detectGitOperation |
|
||||
| | `src/tools/FileEditTool/__tests__/utils.test.ts` | 24 | normalizeQuotes, stripTrailingWhitespace, findActualString, preserveQuoteStyle, applyEditToFile |
|
||||
| 02 - Utils 纯函数 | `src/utils/__tests__/array.test.ts` | 12 | intersperse, count, uniq |
|
||||
| | `src/utils/__tests__/set.test.ts` | 12 | difference, intersects, every, union |
|
||||
| | `src/utils/__tests__/xml.test.ts` | 9 | escapeXml, escapeXmlAttr |
|
||||
| | `src/utils/__tests__/hash.test.ts` | 12 | djb2Hash, hashContent, hashPair |
|
||||
| | `src/utils/__tests__/stringUtils.test.ts` | 35 | escapeRegExp, capitalize, plural, firstLineOf, countCharInString, normalizeFullWidthDigits/Space, safeJoinLines, EndTruncatingAccumulator, truncateToLines |
|
||||
| | `src/utils/__tests__/semver.test.ts` | 21 | gt, gte, lt, lte, satisfies, order |
|
||||
| | `src/utils/__tests__/uuid.test.ts` | 6 | validateUuid |
|
||||
| | `src/utils/__tests__/format.test.ts` | 24 | formatFileSize, formatSecondsShort, formatDuration, formatNumber, formatTokens, formatRelativeTime |
|
||||
| | `src/utils/__tests__/frontmatterParser.test.ts` | 28 | parseFrontmatter, splitPathInFrontmatter, parsePositiveIntFromFrontmatter, parseBooleanFrontmatter, parseShellFrontmatter |
|
||||
| | `src/utils/__tests__/file.test.ts` | 17 | convertLeadingTabsToSpaces, addLineNumbers, stripLineNumberPrefix, normalizePathForComparison, pathsEqual |
|
||||
| | `src/utils/__tests__/glob.test.ts` | 6 | extractGlobBaseDirectory |
|
||||
| | `src/utils/__tests__/diff.test.ts` | 8 | adjustHunkLineNumbers, getPatchFromContents |
|
||||
| | `src/utils/__tests__/json.test.ts` | 27 | safeParseJSON, safeParseJSONC, parseJSONL, addItemToJSONCArray (mock log.ts) |
|
||||
| | `src/utils/__tests__/truncate.test.ts` | 24 | truncateToWidth, truncateStartToWidth, truncateToWidthNoEllipsis, truncatePathMiddle, truncate, wrapText |
|
||||
| | `src/utils/__tests__/path.test.ts` | 15 | containsPathTraversal, normalizePathForConfigKey |
|
||||
| | `src/utils/__tests__/tokens.test.ts` | 22 | getTokenCountFromUsage, getTokenUsage, tokenCountFromLastAPIResponse, messageTokenCountFromLastAPIResponse, getCurrentUsage, doesMostRecentAssistantMessageExceed200k, getAssistantMessageContentLength (mock log.ts, tokenEstimation, slowOperations) |
|
||||
| 03 - Context 构建 | `src/utils/__tests__/claudemd.test.ts` | 16 | stripHtmlComments, isMemoryFilePath, getLargeMemoryFiles |
|
||||
| | `src/utils/__tests__/systemPrompt.test.ts` | 9 | buildEffectiveSystemPrompt |
|
||||
|
||||
### P1 — 重要模块
|
||||
|
||||
| 测试计划 | 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|----------|--------|----------|
|
||||
| 04 - 权限系统 | `src/utils/permissions/__tests__/permissionRuleParser.test.ts` | 25 | escapeRuleContent, unescapeRuleContent, permissionRuleValueFromString, permissionRuleValueToString, normalizeLegacyToolName |
|
||||
| | `src/utils/permissions/__tests__/permissions.test.ts` | 13 | getDenyRuleForTool, getAskRuleForTool, getDenyRuleForAgent, filterDeniedAgents (mock log.ts, slowOperations) |
|
||||
| 05 - 模型路由 | `src/utils/model/__tests__/aliases.test.ts` | 16 | isModelAlias, isModelFamilyAlias |
|
||||
| | `src/utils/model/__tests__/model.test.ts` | 14 | firstPartyNameToCanonical |
|
||||
| | `src/utils/model/__tests__/providers.test.ts` | 10 | getAPIProvider, isFirstPartyAnthropicBaseUrl |
|
||||
| 06 - 消息处理 | `src/utils/__tests__/messages.test.ts` | 56 | createAssistantMessage, createUserMessage, isSyntheticMessage, getLastAssistantMessage, hasToolCallsInLastAssistantTurn, extractTag, isNotEmptyMessage, normalizeMessages, deriveUUID, isClassifierDenial 等 |
|
||||
|
||||
### P2 — 补充模块
|
||||
|
||||
| 测试计划 | 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|----------|--------|----------|
|
||||
| 07 - Cron 调度 | `src/utils/__tests__/cron.test.ts` | 38 | parseCronExpression, computeNextCronRun, cronToHuman |
|
||||
| 08 - Git 工具 | `src/utils/__tests__/git.test.ts` | 18 | normalizeGitRemoteUrl (SSH/HTTPS/ssh:///代理URL/大小写规范化) |
|
||||
| 09 - 配置与设置 | `src/utils/settings/__tests__/config.test.ts` | 62 | SettingsSchema, PermissionsSchema, AllowedMcpServerEntrySchema, MCP 类型守卫, 设置常量函数, filterInvalidPermissionRules, validateSettingsFileContent, formatZodError |
|
||||
|
||||
### P3 — Phase 1 纯函数扩展
|
||||
|
||||
| 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|--------|----------|
|
||||
| `src/utils/__tests__/errors.test.ts` | 28 | ClaudeError, AbortError, ConfigParseError, ShellError, TelemetrySafeError, isAbortError, hasExactErrorMessage, toError, errorMessage, getErrnoCode, isENOENT, getErrnoPath, shortErrorStack, isFsInaccessible, classifyAxiosError |
|
||||
| `src/utils/permissions/__tests__/shellRuleMatching.test.ts` | 22 | permissionRuleExtractPrefix, hasWildcards, matchWildcardPattern, parsePermissionRule, suggestionForExactCommand, suggestionForPrefix |
|
||||
| `src/utils/__tests__/argumentSubstitution.test.ts` | 18 | parseArguments, parseArgumentNames, generateProgressiveArgumentHint, substituteArguments |
|
||||
| `src/utils/__tests__/CircularBuffer.test.ts` | 12 | CircularBuffer class: add, addAll, getRecent, toArray, clear, length |
|
||||
| `src/utils/__tests__/sanitization.test.ts` | 14 | partiallySanitizeUnicode, recursivelySanitizeUnicode |
|
||||
| `src/utils/__tests__/slashCommandParsing.test.ts` | 8 | parseSlashCommand |
|
||||
| `src/utils/__tests__/contentArray.test.ts` | 6 | insertBlockAfterToolResults |
|
||||
| `src/utils/__tests__/objectGroupBy.test.ts` | 5 | objectGroupBy |
|
||||
|
||||
### P4 — Phase 2 轻 Mock 扩展
|
||||
|
||||
| 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|--------|----------|
|
||||
| `src/utils/__tests__/envUtils.test.ts` | 34 | isEnvTruthy, isEnvDefinedFalsy, parseEnvVars, hasNodeOption, getAWSRegion, getDefaultVertexRegion, getVertexRegionForModel, isBareMode, shouldMaintainProjectWorkingDir, getClaudeConfigHomeDir |
|
||||
| `src/utils/__tests__/sleep.test.ts` | 14 | sleep (abort, throwOnAbort, abortError), withTimeout, sequential |
|
||||
| `src/utils/__tests__/memoize.test.ts` | 16 | memoizeWithTTL, memoizeWithTTLAsync (dedup/cache/clear), memoizeWithLRU (eviction/cache methods) |
|
||||
| `src/utils/__tests__/groupToolUses.test.ts` | 10 | applyGrouping (verbose, grouping, result collection, mixed messages) |
|
||||
| `src/utils/permissions/__tests__/dangerousPatterns.test.ts` | 7 | CROSS_PLATFORM_CODE_EXEC, DANGEROUS_BASH_PATTERNS 常量验证 |
|
||||
| `src/utils/shell/__tests__/outputLimits.test.ts` | 7 | getMaxOutputLength, BASH_MAX_OUTPUT_UPPER_LIMIT, BASH_MAX_OUTPUT_DEFAULT |
|
||||
|
||||
### P5 — Phase 3 补全 + Phase 4 工具模块
|
||||
|
||||
| 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|--------|----------|
|
||||
| `src/utils/__tests__/zodToJsonSchema.test.ts` | 9 | zodToJsonSchema (string/number/object/enum/optional/array/boolean + caching) |
|
||||
| `src/utils/permissions/__tests__/PermissionMode.test.ts` | 19 | PERMISSION_MODES, permissionModeFromString, permissionModeTitle, permissionModeShortTitle, permissionModeSymbol, getModeColor, isDefaultMode, toExternalPermissionMode, isExternalPermissionMode |
|
||||
| `src/utils/__tests__/envValidation.test.ts` | 9 | validateBoundedIntEnvVar (default/valid/capped/invalid/boundary) |
|
||||
| `src/services/mcp/__tests__/mcpStringUtils.test.ts` | 18 | mcpInfoFromString, getMcpPrefix, buildMcpToolName, getMcpDisplayName, getToolNameForPermissionCheck, extractMcpToolDisplayName |
|
||||
| `src/tools/BashTool/__tests__/destructiveCommandWarning.test.ts` | 22 | getDestructiveCommandWarning (git/rm/database/infrastructure patterns) |
|
||||
| `src/tools/BashTool/__tests__/commandSemantics.test.ts` | 11 | interpretCommandResult (grep/diff/test/rg/find exit code semantics) |
|
||||
|
||||
### P6 — Phase 5 扩展覆盖
|
||||
|
||||
| 测试文件 | 测试数 | 覆盖范围 |
|
||||
|----------|--------|----------|
|
||||
| `src/utils/__tests__/tokenBudget.test.ts` | 20 | parseTokenBudget, findTokenBudgetPositions, getBudgetContinuationMessage |
|
||||
| `src/utils/__tests__/displayTags.test.ts` | 17 | stripDisplayTags, stripDisplayTagsAllowEmpty, stripIdeContextTags |
|
||||
| `src/utils/__tests__/taggedId.test.ts` | 10 | toTaggedId (prefix/uniqueness/format) |
|
||||
| `src/utils/__tests__/controlMessageCompat.test.ts` | 15 | normalizeControlMessageKeys (snake_case→camelCase 转换) |
|
||||
| `src/services/mcp/__tests__/normalization.test.ts` | 11 | normalizeNameForMCP (特殊字符/截断/空字符串/Unicode) |
|
||||
| `src/services/mcp/__tests__/envExpansion.test.ts` | 14 | expandEnvVarsInString ($VAR/${VAR}/嵌套/未定义/转义) |
|
||||
| `src/utils/git/__tests__/gitConfigParser.test.ts` | 20 | parseConfigString (key=value/section/subsection/多行/注释/引号) |
|
||||
| `src/utils/__tests__/formatBriefTimestamp.test.ts` | 10 | formatBriefTimestamp (秒/分/时/天/周/月/年) |
|
||||
| `src/utils/__tests__/hyperlink.test.ts` | 10 | createHyperlink (OSC 8 序列/file:///path/fallback) |
|
||||
| `src/utils/__tests__/windowsPaths.test.ts` | 20 | windowsPathToPosixPath, posixPathToWindowsPath (驱动器/UNC/相对路径) |
|
||||
| `src/utils/__tests__/notebook.test.ts` | 14 | parseCellId, mapNotebookCellsToToolResult (code/markdown/output) |
|
||||
| `src/utils/__tests__/effort.test.ts` | 38 | isEffortLevel, parseEffortValue, isValidNumericEffort, convertEffortValueToLevel, getEffortLevelDescription, resolvePickerEffortPersistence |
|
||||
|
||||
### 已知限制
|
||||
|
||||
以下模块因 Bun 运行时限制或极重依赖链,暂时无法或不适合测试:
|
||||
|
||||
| 模块 | 问题 | 说明 |
|
||||
| 文件 | 函数 | 问题 |
|
||||
|------|------|------|
|
||||
| `Bun.JSONL.parseChunk` | 处理畸形行时无限挂起 | Bun 1.3.10 bug,错误恢复循环卡死;已跳过 parseJSONL 畸形行测试 |
|
||||
| `src/tools.ts` 部分函数 | `getAllBaseTools`/`getTools` 加载全量 tool | 导入链过重,mock 难度大 |
|
||||
| `src/tools/shared/spawnMultiAgent.ts` | 依赖 bootstrap/state + AppState + 50+ 模块 | mock 成本极高,投入产出比低 |
|
||||
| `src/utils/messages.ts` 部分函数 | `withMemoryCorrectionHint` 等 | 依赖 `getFeatureValue_CACHED_MAY_BE_STALE` |
|
||||
| `envValidation.test.ts` | validateBoundedIntEnvVar | 测试断言 lower bound=100 时 value=1 返回 `status: "valid"`,与函数名语义矛盾,可能是源码 bug 或测试逻辑错误 |
|
||||
|
||||
### Mock 策略总结
|
||||
### 5.5 已知限制
|
||||
|
||||
通过 `mock.module()` + `await import()` 模式成功解锁了以下重依赖模块的测试:
|
||||
| 模块 | 问题 |
|
||||
|------|------|
|
||||
| `Bun.JSONL.parseChunk` | 畸形行时无限挂起(Bun 1.3.10 bug) |
|
||||
| `context.ts` 核心逻辑 | 依赖 bootstrap/state + git + 50+ 模块,mock 不可行 |
|
||||
| `tools.ts` (getAllBaseTools) | 导入链过重 |
|
||||
| `spawnMultiAgent.ts` | 50+ 依赖 |
|
||||
| `messages.ts` 部分函数 | 依赖 `getFeatureValue_CACHED_MAY_BE_STALE` |
|
||||
| UI 组件 (`screens/`, `components/`) | 需 Ink 渲染测试环境 |
|
||||
|
||||
### 5.6 Mock 模式
|
||||
|
||||
通过 `mock.module()` + `await import()` 解锁重依赖模块:
|
||||
|
||||
| 被 Mock 模块 | 解锁的测试 |
|
||||
|-------------|-----------|
|
||||
| `src/utils/log.ts` | json.ts, tokens.ts, FileEditTool/utils.ts, permissions.ts, memoize.ts, PermissionMode.ts |
|
||||
| `src/services/tokenEstimation.ts` | tokens.ts |
|
||||
| `src/utils/slowOperations.ts` | tokens.ts, permissions.ts, memoize.ts, PermissionMode.ts |
|
||||
| `src/utils/debug.ts` | envValidation.ts, outputLimits.ts |
|
||||
| `src/utils/bash/commands.ts` | commandSemantics.ts |
|
||||
| `src/utils/thinking.js` | effort.ts |
|
||||
| `src/utils/settings/settings.js` | effort.ts |
|
||||
| `src/utils/auth.js` | effort.ts |
|
||||
| `src/services/analytics/growthbook.js` | effort.ts, tokenBudget.ts |
|
||||
| `src/utils/model/modelSupportOverrides.js` | effort.ts |
|
||||
| `src/utils/log.ts` | json, tokens, FileEditTool/utils, permissions, memoize, PermissionMode |
|
||||
| `src/services/tokenEstimation.ts` | tokens |
|
||||
| `src/utils/slowOperations.ts` | tokens, permissions, memoize, PermissionMode |
|
||||
| `src/utils/debug.ts` | envValidation, outputLimits |
|
||||
| `src/utils/bash/commands.ts` | commandSemantics |
|
||||
| `src/utils/thinking.js` | effort |
|
||||
| `src/utils/settings/settings.js` | effort |
|
||||
| `src/utils/auth.js` | effort |
|
||||
| `src/services/analytics/growthbook.js` | effort, tokenBudget |
|
||||
|
||||
**关键约束**:`mock.module()` 必须在每个测试文件中内联调用,不能从共享 helper 导入(Bun 在 mock 生效前就解析了 helper 的导入)。
|
||||
**约束**:`mock.module()` 必须在每个测试文件内联调用,不能从共享 helper 导入。
|
||||
|
||||
## 12. 后续测试覆盖计划
|
||||
## 6. 改进计划
|
||||
|
||||
> **已完成** — Phase 1-4 增加 321 tests (647 → 968),Phase 5 增加 209 tests (968 → 1177)
|
||||
>
|
||||
> Phase 1-4 全部完成,详见上方 P3-P5 表格。
|
||||
> Phase 5 新增 12 个测试文件覆盖:effort、tokenBudget、displayTags、taggedId、controlMessageCompat、MCP normalization/envExpansion、gitConfigParser、formatBriefTimestamp、hyperlink、windowsPaths、notebook,详见 P6 表格。
|
||||
> 实际调整:Phase 3 中 `context.ts` 因极重依赖链(bootstrap/state + claudemd + git 等)且 `getGitStatus` 在 test 环境直接返回 null,替换为 `envValidation.ts`(更实用);Phase 4 中 GlobTool 纯函数不足,替换为 `commandSemantics.ts` + `destructiveCommandWarning.ts`。
|
||||
### 优先级排序
|
||||
|
||||
### 不纳入计划的模块
|
||||
| 优先级 | 任务 | 预期效果 |
|
||||
|--------|------|----------|
|
||||
| **高** | 修复 8 个 WEAK 文件的断言缺陷 | 消除假阳性风险 |
|
||||
| **高** | 补 `gitOperationTracking.test.ts` 的 analytics mock | 消除测试副作用 |
|
||||
| **高** | 验证 `envValidation.test.ts` 潜在 bug | 排除源码缺陷 |
|
||||
| **中** | 搭建 `tests/mocks/` 基础设施 | 为集成测试铺路 |
|
||||
| **中** | 编写 `tests/integration/tool-chain.test.ts` | 覆盖 Tool 注册→发现→执行链路 |
|
||||
| **中** | 补 `truncate.test.ts` CJK/emoji 测试 | 覆盖核心场景 |
|
||||
| **低** | 补 `claudemd.test.ts` 核心逻辑 | 提升 P0 模块覆盖率 |
|
||||
| **低** | 补 CLI 参数测试 (`main.tsx`) | 完成 P1 覆盖 |
|
||||
| **低** | 运行 `bun test --coverage` 建立基线 | 量化覆盖率 |
|
||||
|
||||
### 不纳入计划
|
||||
|
||||
| 模块 | 原因 |
|
||||
|------|------|
|
||||
| `query.ts` / `QueryEngine.ts` | 核心循环,需集成测试环境 |
|
||||
| `query.ts` / `QueryEngine.ts` | 核心循环,需完整集成环境 |
|
||||
| `services/api/claude.ts` | 需 mock SDK 流式响应 |
|
||||
| `spawnMultiAgent.ts` | 50+ 依赖,mock 不可行 |
|
||||
| `spawnMultiAgent.ts` | 50+ 依赖 |
|
||||
| `modelCost.ts` | 依赖 bootstrap/state + analytics |
|
||||
| `mcp/dateTimeParser.ts` | 调用 Haiku API |
|
||||
| `screens/` / `components/` | UI 组件,需 Ink 渲染测试 |
|
||||
|
||||
## 13. 参考
|
||||
|
||||
- [Bun Test 文档](https://bun.sh/docs/cli/test)
|
||||
- 现有测试示例:`src/utils/__tests__/set.test.ts`, `src/utils/__tests__/array.test.ts`
|
||||
| `screens/` / `components/` | 需 Ink 渲染测试 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user