Skip to content

Commit d646a4e

Browse files
committed
refactor(e2e): update test routes and streamline command handling
- Replaced hardcoded command paths with dynamic route mappings in E2E tests. - Enhanced documentation for command testing, specifying minimum route requirements. - Consolidated command path management into `topic-routes.ts` for better maintainability. - Updated various E2E test cases to utilize the new routing structure, ensuring consistency across tests. - Removed redundant helper functions and improved test clarity by directly referencing route exports.
1 parent 91a7106 commit d646a4e

48 files changed

Lines changed: 797 additions & 889 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/agents/cli-e2e-tests.md

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
## 架构分层
44

5-
| 层级 | 路径 | 测什么 |
6-
| --------------- | ----------------------------------------------------- | --------------------------------------------------------------- |
7-
| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) |
8-
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);harness `binName: "bl"` |
9-
| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path `--help`、分组 help、根 help |
10-
| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | 6 条 flat path `--help``search --help` 与 harness 一致 |
11-
| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 |
5+
| 层级 | 路径 | 测什么 |
6+
| --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
7+
| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) |
8+
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 |
9+
| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path `--help`、分组 help、根 help |
10+
| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | `kscli/src/commands.ts` 推导 path/分组;identity(`--version``search --help` path) |
11+
| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 |
1212

1313
**依赖边界**`e2e``core``commands/tests``e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src`**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。
1414

@@ -17,18 +17,20 @@
1717
- 新增/修改 `packages/commands/src/commands` 下的 command 实现
1818
- 新增/修改 `packages/cli/src/commands.ts``bl` 命令路径 map
1919
- 新建或扩展 `packages/commands/tests/e2e/<topic>.e2e.test.ts`
20-
- 新增 bl 产品 path → 同步 `registry.smoke`自动覆盖 leaf path)与 `harness/e2e-command-map.ts`
20+
- 新增 bl 产品 path → `registry.smoke` 自动覆盖 leaf path;commands topic 测试在 `topic-routes.ts` 补最小路由
2121

2222
跑测与环境变量见 `.cursor/skills/bailian-cli-e2e/SKILL.md`
2323

24+
> **规则**:共享 command 行为在 `commands/tests/e2e`;产品 map、identity、CLI-only 命令留在对应产品 `tests/e2e`
25+
2426
## 文件与工具
2527

2628
### commands E2E
2729

2830
- 路径:`packages/commands/tests/e2e/<kebab-topic>.e2e.test.ts`
29-
- 子进程:`runCommandE2e` from `./helpers.ts`(spawn `harness/main.ts`
31+
- 子进程:`runCommandE2e(routes, args)` from `./helpers.ts`(spawn `harness/main.ts``routes` 为本 topic 最小 path → export 映射
3032
- fixtures:`packages/commands/tests/e2e/fixtures/`
31-
- map:`harness/e2e-command-map.ts`自维护,初始从 `cli/commands.ts` 复制
33+
- 路由常量:`topic-routes.ts`按 topic 维护,****全量产品 map
3234

3335
### 产品 smoke
3436

@@ -45,13 +47,12 @@
4547
## 双层 describe(固定结构)
4648

4749
```ts
48-
// 1) 不 skip:分组 + --help,无密钥、无真实 API
50+
// 1) 不 skip:--help,无密钥、无真实 API(分组 help 由 bl registry.smoke 覆盖)
4951
describe("e2e: <topic>", () => {
50-
test("<group> 分组展示子命令帮助且成功退出", ...);
5152
test("<subcommand> --help 正常退出", ...);
5253
});
5354

54-
// 2) skipIf:缺参 / dry-run / 真实集成;原有集成用例放最后、勿改逻辑
55+
// 2) skipIf:缺参 / dry-run / 真实集成
5556
describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
5657
test("缺少 --<flag> 时退出为用法错误 (2)", ...);
5758
test("<cmd> --dry-run ...", ...); // 若适用
@@ -71,18 +72,15 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
7172

7273
## 用例类型
7374

74-
1. **分组 help**`runCommandE2e(["image"])``exitCode === 0`,stdout+stderr 含子命令名
75-
2. **--help**`runCommandE2e([..., "--help"])` → stderr 含主要 flags
76-
3. **缺参**:带一个无害全局 flag(如 `--quiet`)且不传 required flag → `exitCode === 2`,stderr 匹配 `--flag|Missing required argument`
77-
4. **--dry-run**:仅当实现在联网/上传/写盘**之前**返回;断言 stdout JSON/文本,不入网
78-
5. **真实集成**:保留既有用例名称与断言;放在 skip 块**末尾**
79-
80-
## 契约与防漂移
75+
1. **--help**`runCommandE2e(ROUTES, [..., "--help"])` → stderr 含主要 flags
76+
2. **缺参**:带无害全局 flag(如 `--quiet`)且不传 required flag → `exitCode === 2`
77+
3. **--dry-run**:实现在联网/上传/写盘**之前**返回;断言 stdout JSON/文本
78+
4. **真实集成**:放在 skip 块**末尾**
8179

82-
- `harness/e2e-command-map.contract.test.ts`:path 唯一、合法 export、白名单、测试 argv 前缀在 map 中
83-
- Advisory(不阻塞 CI):`node tools/compare-e2e-command-map.ts` 对比 `cli/commands.ts` 与 harness map
80+
## 增删命令同步
8481

85-
增删命令须同步三处:**实现 export** + **`cli/commands.ts`** + **`e2e-command-map.ts`**(见 [command-add-remove.md](command-add-remove.md))。
82+
- **commands export** + **topic 路由**`topic-routes.ts` 或测试文件内 `ROUTES`)+ **产品 map**`cli/commands.ts` / `kscli/commands.ts`
83+
- 分组 help 由产品 `registry.smoke` 负责,无需在 commands 重复
8684

8785
## 安全与例外
8886

@@ -93,10 +91,10 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
9391

9492
## 新增 command 检查清单
9593

96-
- [ ] `packages/commands/src/index.ts` 导出 + `packages/cli/src/commands.ts` 暴露路径 + `harness/e2e-command-map.ts` 登记
94+
- [ ] `packages/commands/src/index.ts` 导出 + `packages/cli/src/commands.ts` 暴露路径 + `topic-routes.ts` 补最小路由
9795
- [ ] `packages/commands/tests/e2e/<topic>.e2e.test.ts`(新建或扩展)
9896
- [ ] 若改了 `usageArgs` / `flags` / `exampleArgs`,跑 `pnpm --filter bailian-cli run generate:reference` 更新 `skills/bailian-cli/reference/` 并提交
99-
- [ ] 顶层:分组 help + 子命令 `--help`多子命令则各一条 help)
97+
- [ ] 子命令 `--help`分组 help 由 bl `registry.smoke` 覆盖
10098
- [ ] skip 块:每个 required flag 缺参;可 dry-run 则加一条
10199
- [ ] 至少一条真实集成(或说明为何仅 smoke);不破坏已有集成用例顺序
102100
- [ ] `vp test packages/commands/tests/e2e/<file>` 通过
@@ -108,20 +106,21 @@ pnpm --filter bailian-cli-commands exec vp test packages/commands/tests/e2e/text
108106
pnpm --filter bailian-cli exec vp test packages/cli/tests/e2e/registry.smoke.e2e.test.ts
109107
pnpm --filter knowledge-studio-cli exec vp test packages/kscli/tests/e2e/registry.smoke.e2e.test.ts
110108
pnpm --filter bailian-cli-runtime exec vp test packages/runtime/tests/proxy.e2e.test.ts
111-
node tools/compare-e2e-command-map.ts
112109
```
113110

114111
## 示例片段
115112

116113
```ts
114+
import { FOO_ROUTES } from "./topic-routes.ts";
115+
117116
test("foo bar 缺少 --prompt 时退出为用法错误 (2)", async () => {
118-
const { stderr, exitCode } = await runCommandE2e(["foo", "bar", "--quiet"]);
117+
const { stderr, exitCode } = await runCommandE2e(FOO_ROUTES, ["foo", "bar", "--quiet"]);
119118
expect(exitCode).toBe(2);
120119
expect(stderr).toMatch(/--prompt|Missing required argument/i);
121120
});
122121

123122
test("foo bar --dry-run 仅输出计划", async () => {
124-
const { stdout, stderr, exitCode } = await runCommandE2e([
123+
const { stdout, stderr, exitCode } = await runCommandE2e(FOO_ROUTES, [
125124
"foo",
126125
"bar",
127126
"--dry-run",

docs/agents/command-add-remove.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,16 @@ packages/commands/src/index.ts
9393
### D. 测试层
9494

9595
- [ ][cli-e2e-tests.md](cli-e2e-tests.md) 新建或更新 `packages/commands/tests/e2e/<topic>.e2e.test.ts`
96-
- [ ] 同步 `packages/commands/tests/e2e/harness/e2e-command-map.ts``E2E_COMMAND_EXPORTS`
97-
- [ ] `packages/cli/src/commands.ts` 增删 path 后跑 `node tools/compare-e2e-command-map.ts`(advisory)确认与 harness 一致
98-
- [ ] bl 产品 path 变更由 `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` 覆盖;kscli 变更同步 `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts`
99-
- [ ] 删除命令时一并删对应 commands e2e / README 示例 / reference 生成结果 / harness map 条目
96+
- [ ] 同步 `packages/commands/tests/e2e/topic-routes.ts`(该 topic 的最小 path → export 映射)
97+
- [ ] bl 产品 path 变更由 `registry.smoke` 自动覆盖;kscli 变更同步 `kscli/src/commands.ts``registry.smoke`
98+
- [ ] 删除命令时一并删对应 commands e2e / README 示例 / reference / topic 路由条目
10099
- [ ] 如果 shared command 在不同入口路径下复用,至少确保 commands e2e 覆盖 `bl` path;`kscli` 入口改动需补对应 smoke 或说明不测 flat path live
101100

102101
### E. 重命名特殊处理
103102

104103
- [ ] 全仓 grep **旧命令名字符串**,确保以下位置全部更新:
105104
- `packages/cli/src/commands.ts` map key
106-
- `packages/kscli/src/main.ts` map key(如适用)
105+
- `packages/kscli/src/commands.ts` map key(如适用)
107106
- 用户可见 hint / README / tests
108107
- `skills/bailian-cli/reference/`(重建后检查并提交)
109108
- [ ] 检查 `usageArgs` / `exampleArgs` 没有硬编码旧的 `bl <path>` 前缀
@@ -114,8 +113,7 @@ packages/commands/src/index.ts
114113
pnpm run sync:skill-assets
115114
pnpm -F bailian-cli exec tsx src/main.ts <new-command> --help
116115
pnpm -F bailian-cli exec tsx src/main.ts
117-
vp test packages/cli/tests/e2e/<topic>.e2e.test.ts
118-
node tools/compare-e2e-command-map.ts
116+
vp test packages/commands/tests/e2e/<topic>.e2e.test.ts
119117
```
120118

121119
如改了 `kscli` 入口:

packages/cli/tests/e2e/registry.smoke.e2e.test.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,8 @@
11
import { describe, expect, test } from "vite-plus/test";
2+
import { deriveGroupPaths } from "e2e/registry-smoke";
23
import { commands } from "../../src/commands.ts";
34
import { runCli } from "./helpers.ts";
45

5-
/** 从命令 map 推导有子命令的分组路径 */
6-
function deriveGroupPaths(commandPaths: string[]): string[] {
7-
const groups = new Set<string>();
8-
for (const path of commandPaths) {
9-
const parts = path.split(" ");
10-
for (let i = 1; i < parts.length; i++) {
11-
const prefix = parts.slice(0, i).join(" ");
12-
const hasChildren = commandPaths.some(
13-
(candidate) => candidate.startsWith(`${prefix} `) && candidate !== prefix,
14-
);
15-
if (hasChildren) groups.add(prefix);
16-
}
17-
}
18-
return [...groups].sort();
19-
}
20-
216
const commandPaths = Object.keys(commands).sort();
227
const groupPaths = deriveGroupPaths(commandPaths);
238

packages/commands/tests/e2e/advisor-recommend.e2e.test.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
import { describe, expect, test } from "vite-plus/test";
22
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
3+
import { ADVISOR_ROUTES } from "./topic-routes.ts";
34

45
describe("e2e: advisor recommend", () => {
5-
test("advisor shows subcommand groups and exits successfully", async () => {
6-
const { stdout, stderr, exitCode } = await runCommandE2e(["advisor"]);
7-
expect(exitCode, stderr).toBe(0);
8-
expect(`${stdout}\n${stderr}`).toMatch(/advisor|recommend/i);
9-
});
10-
116
test("advisor recommend --help exits successfully", async () => {
12-
const { stderr, exitCode } = await runCommandE2e(["advisor", "recommend", "--help"]);
7+
const { stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
8+
"advisor",
9+
"recommend",
10+
"--help",
11+
]);
1312
expect(exitCode, stderr).toBe(0);
1413
expect(stderr).toMatch(/recommend|--message|dry-run/i);
1514
});
1615
});
1716

1817
describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () => {
1918
test("advisor recommend without --message errors as usage error (2)", async () => {
20-
const { stdout, stderr, exitCode } = await runCommandE2e(["advisor", "recommend", "--quiet"]);
19+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
20+
"advisor",
21+
"recommend",
22+
"--quiet",
23+
]);
2124
expect(exitCode).toBe(2);
2225
expect(`${stdout}\n${stderr}`).toMatch(/--message|Usage:/i);
2326
});
2427

2528
test("advisor recommend --dry-run outputs intent analysis and candidates", async () => {
26-
const { stdout, stderr, exitCode } = await runCommandE2e([
29+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
2730
"advisor",
2831
"recommend",
2932
"--dry-run",
@@ -63,7 +66,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
6366
}, 60_000);
6467

6568
test("advisor recommend full flow returns results", async () => {
66-
const { stdout, stderr, exitCode } = await runCommandE2e([
69+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
6770
"advisor",
6871
"recommend",
6972
"--message",
@@ -96,7 +99,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
9699
// ---- Model preference: positive cases ----
97100

98101
test("scoped preference — intent contains modelPreference.mode=scoped when family is specified", async () => {
99-
const { stdout, stderr, exitCode } = await runCommandE2e([
102+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
100103
"advisor",
101104
"recommend",
102105
"--dry-run",
@@ -116,7 +119,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
116119
}, 60_000);
117120

118121
test("comparison preference — intent contains modelPreference.mode=comparison when comparing models", async () => {
119-
const { stdout, stderr, exitCode } = await runCommandE2e([
122+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
120123
"advisor",
121124
"recommend",
122125
"--dry-run",
@@ -136,7 +139,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
136139
}, 60_000);
137140

138141
test("excludes preference — intent detects modelPreference when excluding models", async () => {
139-
const { stdout, stderr, exitCode } = await runCommandE2e([
142+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
140143
"advisor",
141144
"recommend",
142145
"--dry-run",
@@ -166,7 +169,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
166169
// ---- Model preference: negative cases ----
167170

168171
test("no preference — intent has no modelPreference or mode=unconstrained for generic queries", async () => {
169-
const { stdout, stderr, exitCode } = await runCommandE2e([
172+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
170173
"advisor",
171174
"recommend",
172175
"--dry-run",

0 commit comments

Comments
 (0)