Skip to content

Commit 98ebeb3

Browse files
committed
perf(test): run registry smoke help checks in process
1 parent 5c96077 commit 98ebeb3

5 files changed

Lines changed: 105 additions & 30 deletions

File tree

docs/agents/cli-e2e-tests.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
## 架构分层
44

5-
| 层级 | 路径 | 测什么 |
6-
| --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
7-
| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) |
8-
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 |
9-
| **Journey E2E** | `packages/commands/tests/e2e/knowledge/journeys` | 用户旅程全链路(跨命令回路 + 标记词召回闭环),全部 live gated;见 `journeys/README.md` |
10-
| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path `--help`、分组 help、根 help |
11-
| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | `kscli/src/commands.ts` 推导 path/分组;identity(`--version``search --help` path) |
12-
| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 |
5+
| 层级 | 路径 | 测什么 |
6+
| --------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------- |
7+
| **共享基建** | `packages/e2e` | gating、子进程 runner、registry help 捕获、output、globalSetup(`private`,不发布) |
8+
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 |
9+
| **Journey E2E** | `packages/commands/tests/e2e/knowledge/journeys` | 用户旅程全链路(跨命令回路 + 标记词召回闭环),全部 live gated;见 `journeys/README.md` |
10+
| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path/分组的进程内 help根 help、鉴权域等代表性子进程冒烟 |
11+
| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | map 全部 path/分组的进程内 help;`--version``search --help` 等代表性子进程冒烟 |
12+
| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 |
1313

14-
**依赖边界**`e2e``core``commands/tests``e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src`**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。
14+
**依赖边界**`e2e``core``commands/tests``e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src` + `runtime` 公共 API**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。
1515

1616
## 触发条件
1717

@@ -37,6 +37,8 @@
3737

3838
- bl:`runCli` from `packages/cli/tests/e2e/helpers.ts`
3939
- kscli:`runKscli` from `packages/kscli/tests/e2e/helpers.ts`
40+
- 全量 leaf/group help 使用产品 `commands` 创建 `CommandRegistry`,先通过 `resolve([...path, "--help"])` 检查 help 路由,再用 `captureRegistryHelp` 检查完整 Usage;禁止在 `test.each(commandPaths/groupPaths)` 中逐条启动 `tsx` 子进程
41+
- 真实子进程只保留根 help/version、产品身份、代表性叶子 help/鉴权域和缺参退出码等 shell/stdio/env 契约
4042

4143
### 共享
4244

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

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
import { describe, expect, test } from "vite-plus/test";
2-
import { deriveGroupPaths } from "e2e/registry-smoke";
1+
import { mkdtempSync, rmSync } from "fs";
2+
import { tmpdir } from "os";
3+
import { join } from "path";
4+
import { afterAll, describe, expect, test } from "vite-plus/test";
5+
import { captureRegistryHelp, deriveGroupPaths } from "e2e/registry-smoke";
6+
import { CommandRegistry, resolve } from "bailian-cli-runtime";
37
import { commands } from "../../src/commands.ts";
48
import { runCli } from "./helpers.ts";
59

610
const commandPaths = Object.keys(commands).sort();
711
const groupPaths = deriveGroupPaths(commandPaths);
12+
const registry = new CommandRegistry(commands, "bl");
13+
const isolatedConfigDir = mkdtempSync(join(tmpdir(), "bl-registry-smoke-"));
14+
15+
afterAll(() => rmSync(isolatedConfigDir, { recursive: true, force: true }));
16+
17+
function runCliSmoke(args: string[]) {
18+
return runCli(args, { BAILIAN_CONFIG_DIR: isolatedConfigDir });
19+
}
820

921
describe("e2e: bl registry smoke", () => {
1022
test("根帮助展示 bl、逐命令鉴权域与全局 flag", async () => {
11-
const { stderr, exitCode } = await runCli(["--help"]);
23+
const { stderr, exitCode } = await runCliSmoke(["--help"]);
1224
expect(exitCode, stderr).toBe(0);
1325
expect(stderr).toMatch(/\bbl\b/i);
1426
expect(stderr).not.toMatch(/COMMAND\s+AUTH\s+DESCRIPTION/);
@@ -24,7 +36,7 @@ describe("e2e: bl registry smoke", () => {
2436
});
2537

2638
test("分组帮助按叶子命令展示不同鉴权域", async () => {
27-
const { stderr, exitCode } = await runCli(["app", "--help"]);
39+
const { stderr, exitCode } = await runCliSmoke(["app", "--help"]);
2840
expect(exitCode, stderr).toBe(0);
2941
expect(stderr).toMatch(/app call\s+\[API Key\]\s+Call a Bailian application/);
3042
expect(stderr).toMatch(/app list\s+\[Console\]\s+List Bailian applications/);
@@ -36,13 +48,13 @@ describe("e2e: bl registry smoke", () => {
3648
[["token-plan", "list-seats"], "AK/SK"],
3749
[["config", "show"], "No Auth"],
3850
] as const)("%s --help 明确展示鉴权域 %s", async (commandPath, authLabel) => {
39-
const { stderr, exitCode } = await runCli([...commandPath, "--help"]);
51+
const { stderr, exitCode } = await runCliSmoke([...commandPath, "--help"]);
4052
expect(exitCode, stderr).toBe(0);
4153
expect(stderr).toContain(`Authentication: ${authLabel}`);
4254
});
4355

4456
test("quota check --help:Flags 含 console 域鉴权 flag,Global Flags 全量列出", async () => {
45-
const { stderr, exitCode } = await runCli(["quota", "check", "--help"]);
57+
const { stderr, exitCode } = await runCliSmoke(["quota", "check", "--help"]);
4658
expect(exitCode, stderr).toBe(0);
4759
expect(stderr).toMatch(/Global Flags:/);
4860
expect(stderr).toMatch(/--console-region <region>/);
@@ -52,13 +64,25 @@ describe("e2e: bl registry smoke", () => {
5264
expect(stderr).not.toMatch(/API region \(default: cn-beijing\)/);
5365
});
5466

55-
test.each(commandPaths)("已注册命令 %s --help 成功", async (path) => {
56-
const { stderr, exitCode } = await runCli([...path.split(" "), "--help"]);
57-
expect(exitCode, stderr).toBe(0);
67+
test.each(commandPaths)("已注册命令 %s --help 成功", (path) => {
68+
const commandPath = path.split(" ");
69+
70+
expect(resolve([...commandPath, "--help"], registry)).toEqual({
71+
kind: "help",
72+
path: commandPath,
73+
});
74+
expect(captureRegistryHelp(registry, commandPath)).toContain(`Usage: bl ${path}`);
5875
});
5976

60-
test.each(groupPaths)("命令分组 %s --help 成功", async (path) => {
61-
const { stderr, exitCode } = await runCli([...path.split(" "), "--help"]);
62-
expect(exitCode, stderr).toBe(0);
77+
test.each(groupPaths)("命令分组 %s --help 成功", (path) => {
78+
const commandPath = path.split(" ");
79+
80+
expect(resolve([...commandPath, "--help"], registry)).toEqual({
81+
kind: "help",
82+
path: commandPath,
83+
});
84+
expect(captureRegistryHelp(registry, commandPath)).toContain(
85+
`Usage: bl ${path} <command> [flags]`,
86+
);
6387
});
6488
});

packages/e2e/src/registry-smoke.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export function deriveGroupPaths(commandPaths: string[]): string[] {
66
const groups = new Set<string>();
77
for (const path of commandPaths) {
88
const parts = path.split(" ");
9-
for (let i = 1; i < parts.length; i++) {
10-
const prefix = parts.slice(0, i).join(" ");
9+
for (let partIndex = 1; partIndex < parts.length; partIndex++) {
10+
const prefix = parts.slice(0, partIndex).join(" ");
1111
const hasChildren = commandPaths.some(
1212
(candidate) => candidate.startsWith(`${prefix} `) && candidate !== prefix,
1313
);
@@ -16,3 +16,21 @@ export function deriveGroupPaths(commandPaths: string[]): string[] {
1616
}
1717
return [...groups].sort();
1818
}
19+
20+
interface RegistryHelpPrinter {
21+
printHelp(commandPath: string[], output: NodeJS.WriteStream): void;
22+
}
23+
24+
export function captureRegistryHelp(printer: RegistryHelpPrinter, commandPath: string[]): string {
25+
let helpOutput = "";
26+
const output = {
27+
isTTY: false,
28+
write(chunk: string): boolean {
29+
helpOutput += chunk;
30+
return true;
31+
},
32+
} as NodeJS.WriteStream;
33+
34+
printer.printHelp(commandPath, output);
35+
return helpOutput;
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { expect, test } from "vite-plus/test";
2+
import { captureRegistryHelp } from "../src/registry-smoke.ts";
3+
4+
test("captureRegistryHelp 捕获指定命令路径的非 TTY 输出", () => {
5+
const requestedPaths: string[][] = [];
6+
const renderer = {
7+
printHelp(commandPath: string[], output: NodeJS.WriteStream): void {
8+
requestedPaths.push(commandPath);
9+
output.write(`Usage: bl ${commandPath.join(" ")}`);
10+
},
11+
};
12+
13+
const output = captureRegistryHelp(renderer, ["text", "chat"]);
14+
15+
expect(output).toBe("Usage: bl text chat");
16+
expect(requestedPaths).toEqual([["text", "chat"]]);
17+
});

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { describe, expect, test } from "vite-plus/test";
2-
import { deriveGroupPaths } from "e2e/registry-smoke";
2+
import { captureRegistryHelp, deriveGroupPaths } from "e2e/registry-smoke";
3+
import { CommandRegistry, resolve } from "bailian-cli-runtime";
34
import pkg from "../../package.json" with { type: "json" };
45
import { commands } from "../../src/commands.ts";
56
import { runKscli } from "./helpers.ts";
67

78
const commandPaths = Object.keys(commands).sort();
89
const groupPaths = deriveGroupPaths(commandPaths);
10+
const registry = new CommandRegistry(commands, "kscli");
911

1012
describe("e2e: kscli registry smoke", () => {
1113
test("根帮助展示 kscli 与全局 flag", async () => {
@@ -38,13 +40,25 @@ describe("e2e: kscli registry smoke", () => {
3840
expect(stderr).toMatch(/--query|Missing required/i);
3941
});
4042

41-
test.each(commandPaths)("已注册命令 %s --help 成功", async (path) => {
42-
const { stderr, exitCode } = await runKscli([...path.split(" "), "--help"]);
43-
expect(exitCode, stderr).toBe(0);
43+
test.each(commandPaths)("已注册命令 %s --help 成功", (path) => {
44+
const commandPath = path.split(" ");
45+
46+
expect(resolve([...commandPath, "--help"], registry)).toEqual({
47+
kind: "help",
48+
path: commandPath,
49+
});
50+
expect(captureRegistryHelp(registry, commandPath)).toContain(`Usage: kscli ${path}`);
4451
});
4552

46-
test.each(groupPaths)("命令分组 %s --help 成功", async (path) => {
47-
const { stderr, exitCode } = await runKscli([...path.split(" "), "--help"]);
48-
expect(exitCode, stderr).toBe(0);
53+
test.each(groupPaths)("命令分组 %s --help 成功", (path) => {
54+
const commandPath = path.split(" ");
55+
56+
expect(resolve([...commandPath, "--help"], registry)).toEqual({
57+
kind: "help",
58+
path: commandPath,
59+
});
60+
expect(captureRegistryHelp(registry, commandPath)).toContain(
61+
`Usage: kscli ${path} <command> [flags]`,
62+
);
4963
});
5064
});

0 commit comments

Comments
 (0)