From 98ebeb398c19d534b1d4b681b640daf4d79f6767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=A5=E9=BA=92?= Date: Fri, 28 Aug 2026 16:54:55 +0800 Subject: [PATCH] perf(test): run registry smoke help checks in process --- docs/agents/cli-e2e-tests.md | 20 ++++---- .../cli/tests/e2e/registry.smoke.e2e.test.ts | 48 ++++++++++++++----- packages/e2e/src/registry-smoke.ts | 22 ++++++++- packages/e2e/tests/registry-smoke.test.ts | 17 +++++++ .../tests/e2e/registry.smoke.e2e.test.ts | 28 ++++++++--- 5 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 packages/e2e/tests/registry-smoke.test.ts diff --git a/docs/agents/cli-e2e-tests.md b/docs/agents/cli-e2e-tests.md index 5449e1a..aa86c6c 100644 --- a/docs/agents/cli-e2e-tests.md +++ b/docs/agents/cli-e2e-tests.md @@ -2,16 +2,16 @@ ## 架构分层 -| 层级 | 路径 | 测什么 | -| --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) | -| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 | -| **Journey E2E** | `packages/commands/tests/e2e/knowledge/journeys` | 用户旅程全链路(跨命令回路 + 标记词召回闭环),全部 live gated;见 `journeys/README.md` | -| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path `--help`、分组 help、根 help | -| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | 从 `kscli/src/commands.ts` 推导 path/分组;identity(`--version`、`search --help` path) | -| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 | +| 层级 | 路径 | 测什么 | +| --------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------- | +| **共享基建** | `packages/e2e` | gating、子进程 runner、registry help 捕获、output、globalSetup(`private`,不发布) | +| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 | +| **Journey E2E** | `packages/commands/tests/e2e/knowledge/journeys` | 用户旅程全链路(跨命令回路 + 标记词召回闭环),全部 live gated;见 `journeys/README.md` | +| **bl smoke** | `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` | 产品 map 全部 path/分组的进程内 help;根 help、鉴权域等代表性子进程冒烟 | +| **kscli smoke** | `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts` | map 全部 path/分组的进程内 help;`--version`、`search --help` 等代表性子进程冒烟 | +| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 | -**依赖边界**:`e2e` → `core`;`commands/tests` → `e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src`。**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。 +**依赖边界**:`e2e` → `core`;`commands/tests` → `e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src` + `runtime` 公共 API。**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。 ## 触发条件 @@ -37,6 +37,8 @@ - bl:`runCli` from `packages/cli/tests/e2e/helpers.ts` - kscli:`runKscli` from `packages/kscli/tests/e2e/helpers.ts` +- 全量 leaf/group help 使用产品 `commands` 创建 `CommandRegistry`,先通过 `resolve([...path, "--help"])` 检查 help 路由,再用 `captureRegistryHelp` 检查完整 Usage;禁止在 `test.each(commandPaths/groupPaths)` 中逐条启动 `tsx` 子进程 +- 真实子进程只保留根 help/version、产品身份、代表性叶子 help/鉴权域和缺参退出码等 shell/stdio/env 契约 ### 共享 diff --git a/packages/cli/tests/e2e/registry.smoke.e2e.test.ts b/packages/cli/tests/e2e/registry.smoke.e2e.test.ts index aa87d00..b94b9ee 100644 --- a/packages/cli/tests/e2e/registry.smoke.e2e.test.ts +++ b/packages/cli/tests/e2e/registry.smoke.e2e.test.ts @@ -1,14 +1,26 @@ -import { describe, expect, test } from "vite-plus/test"; -import { deriveGroupPaths } from "e2e/registry-smoke"; +import { mkdtempSync, rmSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterAll, describe, expect, test } from "vite-plus/test"; +import { captureRegistryHelp, deriveGroupPaths } from "e2e/registry-smoke"; +import { CommandRegistry, resolve } from "bailian-cli-runtime"; import { commands } from "../../src/commands.ts"; import { runCli } from "./helpers.ts"; const commandPaths = Object.keys(commands).sort(); const groupPaths = deriveGroupPaths(commandPaths); +const registry = new CommandRegistry(commands, "bl"); +const isolatedConfigDir = mkdtempSync(join(tmpdir(), "bl-registry-smoke-")); + +afterAll(() => rmSync(isolatedConfigDir, { recursive: true, force: true })); + +function runCliSmoke(args: string[]) { + return runCli(args, { BAILIAN_CONFIG_DIR: isolatedConfigDir }); +} describe("e2e: bl registry smoke", () => { test("根帮助展示 bl、逐命令鉴权域与全局 flag", async () => { - const { stderr, exitCode } = await runCli(["--help"]); + const { stderr, exitCode } = await runCliSmoke(["--help"]); expect(exitCode, stderr).toBe(0); expect(stderr).toMatch(/\bbl\b/i); expect(stderr).not.toMatch(/COMMAND\s+AUTH\s+DESCRIPTION/); @@ -24,7 +36,7 @@ describe("e2e: bl registry smoke", () => { }); test("分组帮助按叶子命令展示不同鉴权域", async () => { - const { stderr, exitCode } = await runCli(["app", "--help"]); + const { stderr, exitCode } = await runCliSmoke(["app", "--help"]); expect(exitCode, stderr).toBe(0); expect(stderr).toMatch(/app call\s+\[API Key\]\s+Call a Bailian application/); expect(stderr).toMatch(/app list\s+\[Console\]\s+List Bailian applications/); @@ -36,13 +48,13 @@ describe("e2e: bl registry smoke", () => { [["token-plan", "list-seats"], "AK/SK"], [["config", "show"], "No Auth"], ] as const)("%s --help 明确展示鉴权域 %s", async (commandPath, authLabel) => { - const { stderr, exitCode } = await runCli([...commandPath, "--help"]); + const { stderr, exitCode } = await runCliSmoke([...commandPath, "--help"]); expect(exitCode, stderr).toBe(0); expect(stderr).toContain(`Authentication: ${authLabel}`); }); test("quota check --help:Flags 含 console 域鉴权 flag,Global Flags 全量列出", async () => { - const { stderr, exitCode } = await runCli(["quota", "check", "--help"]); + const { stderr, exitCode } = await runCliSmoke(["quota", "check", "--help"]); expect(exitCode, stderr).toBe(0); expect(stderr).toMatch(/Global Flags:/); expect(stderr).toMatch(/--console-region /); @@ -52,13 +64,25 @@ describe("e2e: bl registry smoke", () => { expect(stderr).not.toMatch(/API region \(default: cn-beijing\)/); }); - test.each(commandPaths)("已注册命令 %s --help 成功", async (path) => { - const { stderr, exitCode } = await runCli([...path.split(" "), "--help"]); - expect(exitCode, stderr).toBe(0); + test.each(commandPaths)("已注册命令 %s --help 成功", (path) => { + const commandPath = path.split(" "); + + expect(resolve([...commandPath, "--help"], registry)).toEqual({ + kind: "help", + path: commandPath, + }); + expect(captureRegistryHelp(registry, commandPath)).toContain(`Usage: bl ${path}`); }); - test.each(groupPaths)("命令分组 %s --help 成功", async (path) => { - const { stderr, exitCode } = await runCli([...path.split(" "), "--help"]); - expect(exitCode, stderr).toBe(0); + test.each(groupPaths)("命令分组 %s --help 成功", (path) => { + const commandPath = path.split(" "); + + expect(resolve([...commandPath, "--help"], registry)).toEqual({ + kind: "help", + path: commandPath, + }); + expect(captureRegistryHelp(registry, commandPath)).toContain( + `Usage: bl ${path} [flags]`, + ); }); }); diff --git a/packages/e2e/src/registry-smoke.ts b/packages/e2e/src/registry-smoke.ts index bed14cc..6b91aa7 100644 --- a/packages/e2e/src/registry-smoke.ts +++ b/packages/e2e/src/registry-smoke.ts @@ -6,8 +6,8 @@ export function deriveGroupPaths(commandPaths: string[]): string[] { const groups = new Set(); for (const path of commandPaths) { const parts = path.split(" "); - for (let i = 1; i < parts.length; i++) { - const prefix = parts.slice(0, i).join(" "); + for (let partIndex = 1; partIndex < parts.length; partIndex++) { + const prefix = parts.slice(0, partIndex).join(" "); const hasChildren = commandPaths.some( (candidate) => candidate.startsWith(`${prefix} `) && candidate !== prefix, ); @@ -16,3 +16,21 @@ export function deriveGroupPaths(commandPaths: string[]): string[] { } return [...groups].sort(); } + +interface RegistryHelpPrinter { + printHelp(commandPath: string[], output: NodeJS.WriteStream): void; +} + +export function captureRegistryHelp(printer: RegistryHelpPrinter, commandPath: string[]): string { + let helpOutput = ""; + const output = { + isTTY: false, + write(chunk: string): boolean { + helpOutput += chunk; + return true; + }, + } as NodeJS.WriteStream; + + printer.printHelp(commandPath, output); + return helpOutput; +} diff --git a/packages/e2e/tests/registry-smoke.test.ts b/packages/e2e/tests/registry-smoke.test.ts new file mode 100644 index 0000000..4209ac2 --- /dev/null +++ b/packages/e2e/tests/registry-smoke.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from "vite-plus/test"; +import { captureRegistryHelp } from "../src/registry-smoke.ts"; + +test("captureRegistryHelp 捕获指定命令路径的非 TTY 输出", () => { + const requestedPaths: string[][] = []; + const renderer = { + printHelp(commandPath: string[], output: NodeJS.WriteStream): void { + requestedPaths.push(commandPath); + output.write(`Usage: bl ${commandPath.join(" ")}`); + }, + }; + + const output = captureRegistryHelp(renderer, ["text", "chat"]); + + expect(output).toBe("Usage: bl text chat"); + expect(requestedPaths).toEqual([["text", "chat"]]); +}); diff --git a/packages/kscli/tests/e2e/registry.smoke.e2e.test.ts b/packages/kscli/tests/e2e/registry.smoke.e2e.test.ts index 892aa1f..8a0f808 100644 --- a/packages/kscli/tests/e2e/registry.smoke.e2e.test.ts +++ b/packages/kscli/tests/e2e/registry.smoke.e2e.test.ts @@ -1,11 +1,13 @@ import { describe, expect, test } from "vite-plus/test"; -import { deriveGroupPaths } from "e2e/registry-smoke"; +import { captureRegistryHelp, deriveGroupPaths } from "e2e/registry-smoke"; +import { CommandRegistry, resolve } from "bailian-cli-runtime"; import pkg from "../../package.json" with { type: "json" }; import { commands } from "../../src/commands.ts"; import { runKscli } from "./helpers.ts"; const commandPaths = Object.keys(commands).sort(); const groupPaths = deriveGroupPaths(commandPaths); +const registry = new CommandRegistry(commands, "kscli"); describe("e2e: kscli registry smoke", () => { test("根帮助展示 kscli 与全局 flag", async () => { @@ -38,13 +40,25 @@ describe("e2e: kscli registry smoke", () => { expect(stderr).toMatch(/--query|Missing required/i); }); - test.each(commandPaths)("已注册命令 %s --help 成功", async (path) => { - const { stderr, exitCode } = await runKscli([...path.split(" "), "--help"]); - expect(exitCode, stderr).toBe(0); + test.each(commandPaths)("已注册命令 %s --help 成功", (path) => { + const commandPath = path.split(" "); + + expect(resolve([...commandPath, "--help"], registry)).toEqual({ + kind: "help", + path: commandPath, + }); + expect(captureRegistryHelp(registry, commandPath)).toContain(`Usage: kscli ${path}`); }); - test.each(groupPaths)("命令分组 %s --help 成功", async (path) => { - const { stderr, exitCode } = await runKscli([...path.split(" "), "--help"]); - expect(exitCode, stderr).toBe(0); + test.each(groupPaths)("命令分组 %s --help 成功", (path) => { + const commandPath = path.split(" "); + + expect(resolve([...commandPath, "--help"], registry)).toEqual({ + kind: "help", + path: commandPath, + }); + expect(captureRegistryHelp(registry, commandPath)).toContain( + `Usage: kscli ${path} [flags]`, + ); }); });