mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
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.
This commit is contained in:
@@ -2,13 +2,13 @@
|
||||
|
||||
## 架构分层
|
||||
|
||||
| 层级 | 路径 | 测什么 |
|
||||
| --------------- | ----------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) |
|
||||
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);harness `binName: "bl"` |
|
||||
| **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` | 6 条 flat path `--help`;`search --help` 与 harness 一致 |
|
||||
| **runtime** | `packages/runtime/tests` | `proxy.e2e`、console 跨域 flag 拒绝 |
|
||||
| 层级 | 路径 | 测什么 |
|
||||
| --------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **共享基建** | `packages/e2e` | gating、子进程 runner、output、globalSetup(`private`,不发布) |
|
||||
| **命令 E2E** | `packages/commands/tests/e2e` | help、缺参、dry-run、live(gated);每用例最小路由 |
|
||||
| **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 拒绝 |
|
||||
|
||||
**依赖边界**:`e2e` → `core`;`commands/tests` → `e2e` + `commands/src`;产品 tests → `e2e` + 各自 `src`。**禁止**产品 import `commands/tests/**`(子进程 spawn harness 路径除外)。
|
||||
|
||||
@@ -17,18 +17,20 @@
|
||||
- 新增/修改 `packages/commands/src/commands` 下的 command 实现
|
||||
- 新增/修改 `packages/cli/src/commands.ts` 的 `bl` 命令路径 map
|
||||
- 新建或扩展 `packages/commands/tests/e2e/<topic>.e2e.test.ts`
|
||||
- 新增 bl 产品 path → 同步 `registry.smoke`(自动覆盖 leaf path)与 `harness/e2e-command-map.ts`
|
||||
- 新增 bl 产品 path → `registry.smoke` 自动覆盖 leaf path;commands topic 测试在 `topic-routes.ts` 补最小路由
|
||||
|
||||
跑测与环境变量见 `.cursor/skills/bailian-cli-e2e/SKILL.md`。
|
||||
|
||||
> **规则**:共享 command 行为在 `commands/tests/e2e`;产品 map、identity、CLI-only 命令留在对应产品 `tests/e2e`。
|
||||
|
||||
## 文件与工具
|
||||
|
||||
### commands E2E
|
||||
|
||||
- 路径:`packages/commands/tests/e2e/<kebab-topic>.e2e.test.ts`
|
||||
- 子进程:`runCommandE2e` from `./helpers.ts`(spawn `harness/main.ts`)
|
||||
- 子进程:`runCommandE2e(routes, args)` from `./helpers.ts`(spawn `harness/main.ts`,`routes` 为本 topic 最小 path → export 映射)
|
||||
- fixtures:`packages/commands/tests/e2e/fixtures/`
|
||||
- map:`harness/e2e-command-map.ts`(自维护,初始从 `cli/commands.ts` 复制)
|
||||
- 路由常量:`topic-routes.ts`(按 topic 维护,**非**全量产品 map)
|
||||
|
||||
### 产品 smoke
|
||||
|
||||
@@ -45,13 +47,12 @@
|
||||
## 双层 describe(固定结构)
|
||||
|
||||
```ts
|
||||
// 1) 不 skip:分组 + --help,无密钥、无真实 API
|
||||
// 1) 不 skip:--help,无密钥、无真实 API(分组 help 由 bl registry.smoke 覆盖)
|
||||
describe("e2e: <topic>", () => {
|
||||
test("<group> 分组展示子命令帮助且成功退出", ...);
|
||||
test("<subcommand> --help 正常退出", ...);
|
||||
});
|
||||
|
||||
// 2) skipIf:缺参 / dry-run / 真实集成;原有集成用例放最后、勿改逻辑
|
||||
// 2) skipIf:缺参 / dry-run / 真实集成
|
||||
describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
|
||||
test("缺少 --<flag> 时退出为用法错误 (2)", ...);
|
||||
test("<cmd> --dry-run ...", ...); // 若适用
|
||||
@@ -71,18 +72,15 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
|
||||
|
||||
## 用例类型
|
||||
|
||||
1. **分组 help**:`runCommandE2e(["image"])` → `exitCode === 0`,stdout+stderr 含子命令名
|
||||
2. **--help**:`runCommandE2e([..., "--help"])` → stderr 含主要 flags
|
||||
3. **缺参**:带一个无害全局 flag(如 `--quiet`)且不传 required flag → `exitCode === 2`,stderr 匹配 `--flag|Missing required argument`
|
||||
4. **--dry-run**:仅当实现在联网/上传/写盘**之前**返回;断言 stdout JSON/文本,不入网
|
||||
5. **真实集成**:保留既有用例名称与断言;放在 skip 块**末尾**
|
||||
1. **--help**:`runCommandE2e(ROUTES, [..., "--help"])` → stderr 含主要 flags
|
||||
2. **缺参**:带无害全局 flag(如 `--quiet`)且不传 required flag → `exitCode === 2`
|
||||
3. **--dry-run**:实现在联网/上传/写盘**之前**返回;断言 stdout JSON/文本
|
||||
4. **真实集成**:放在 skip 块**末尾**
|
||||
|
||||
## 契约与防漂移
|
||||
## 增删命令同步
|
||||
|
||||
- `harness/e2e-command-map.contract.test.ts`:path 唯一、合法 export、白名单、测试 argv 前缀在 map 中
|
||||
- Advisory(不阻塞 CI):`node tools/compare-e2e-command-map.ts` 对比 `cli/commands.ts` 与 harness map
|
||||
|
||||
增删命令须同步三处:**实现 export** + **`cli/commands.ts`** + **`e2e-command-map.ts`**(见 [command-add-remove.md](command-add-remove.md))。
|
||||
- **commands export** + **topic 路由**(`topic-routes.ts` 或测试文件内 `ROUTES`)+ **产品 map**(`cli/commands.ts` / `kscli/commands.ts`)
|
||||
- 分组 help 由产品 `registry.smoke` 负责,无需在 commands 重复
|
||||
|
||||
## 安全与例外
|
||||
|
||||
@@ -93,10 +91,10 @@ describe.skipIf(<ready>)("e2e: <topic>(DashScope …)", () => {
|
||||
|
||||
## 新增 command 检查清单
|
||||
|
||||
- [ ] `packages/commands/src/index.ts` 导出 + `packages/cli/src/commands.ts` 暴露路径 + `harness/e2e-command-map.ts` 登记
|
||||
- [ ] `packages/commands/src/index.ts` 导出 + `packages/cli/src/commands.ts` 暴露路径 + `topic-routes.ts` 补最小路由
|
||||
- [ ] `packages/commands/tests/e2e/<topic>.e2e.test.ts`(新建或扩展)
|
||||
- [ ] 若改了 `usageArgs` / `flags` / `exampleArgs`,跑 `pnpm --filter bailian-cli run generate:reference` 更新 `skills/bailian-cli/reference/` 并提交
|
||||
- [ ] 顶层:分组 help + 子命令 `--help`(多子命令则各一条 help)
|
||||
- [ ] 子命令 `--help`(分组 help 由 bl `registry.smoke` 覆盖)
|
||||
- [ ] skip 块:每个 required flag 缺参;可 dry-run 则加一条
|
||||
- [ ] 至少一条真实集成(或说明为何仅 smoke);不破坏已有集成用例顺序
|
||||
- [ ] `vp test packages/commands/tests/e2e/<file>` 通过
|
||||
@@ -108,20 +106,21 @@ pnpm --filter bailian-cli-commands exec vp test packages/commands/tests/e2e/text
|
||||
pnpm --filter bailian-cli exec vp test packages/cli/tests/e2e/registry.smoke.e2e.test.ts
|
||||
pnpm --filter knowledge-studio-cli exec vp test packages/kscli/tests/e2e/registry.smoke.e2e.test.ts
|
||||
pnpm --filter bailian-cli-runtime exec vp test packages/runtime/tests/proxy.e2e.test.ts
|
||||
node tools/compare-e2e-command-map.ts
|
||||
```
|
||||
|
||||
## 示例片段
|
||||
|
||||
```ts
|
||||
import { FOO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
test("foo bar 缺少 --prompt 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["foo", "bar", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(FOO_ROUTES, ["foo", "bar", "--quiet"]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--prompt|Missing required argument/i);
|
||||
});
|
||||
|
||||
test("foo bar --dry-run 仅输出计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FOO_ROUTES, [
|
||||
"foo",
|
||||
"bar",
|
||||
"--dry-run",
|
||||
|
||||
@@ -93,17 +93,16 @@ packages/commands/src/index.ts
|
||||
### D. 测试层
|
||||
|
||||
- [ ] 按 [cli-e2e-tests.md](cli-e2e-tests.md) 新建或更新 `packages/commands/tests/e2e/<topic>.e2e.test.ts`
|
||||
- [ ] 同步 `packages/commands/tests/e2e/harness/e2e-command-map.ts` 与 `E2E_COMMAND_EXPORTS`
|
||||
- [ ] `packages/cli/src/commands.ts` 增删 path 后跑 `node tools/compare-e2e-command-map.ts`(advisory)确认与 harness 一致
|
||||
- [ ] bl 产品 path 变更由 `packages/cli/tests/e2e/registry.smoke.e2e.test.ts` 覆盖;kscli 变更同步 `packages/kscli/tests/e2e/registry.smoke.e2e.test.ts`
|
||||
- [ ] 删除命令时一并删对应 commands e2e / README 示例 / reference 生成结果 / harness map 条目
|
||||
- [ ] 同步 `packages/commands/tests/e2e/topic-routes.ts`(该 topic 的最小 path → export 映射)
|
||||
- [ ] bl 产品 path 变更由 `registry.smoke` 自动覆盖;kscli 变更同步 `kscli/src/commands.ts` 与 `registry.smoke`
|
||||
- [ ] 删除命令时一并删对应 commands e2e / README 示例 / reference / topic 路由条目
|
||||
- [ ] 如果 shared command 在不同入口路径下复用,至少确保 commands e2e 覆盖 `bl` path;`kscli` 入口改动需补对应 smoke 或说明不测 flat path live
|
||||
|
||||
### E. 重命名特殊处理
|
||||
|
||||
- [ ] 全仓 grep **旧命令名字符串**,确保以下位置全部更新:
|
||||
- `packages/cli/src/commands.ts` map key
|
||||
- `packages/kscli/src/main.ts` map key(如适用)
|
||||
- `packages/kscli/src/commands.ts` map key(如适用)
|
||||
- 用户可见 hint / README / tests
|
||||
- `skills/bailian-cli/reference/`(重建后检查并提交)
|
||||
- [ ] 检查 `usageArgs` / `exampleArgs` 没有硬编码旧的 `bl <path>` 前缀
|
||||
@@ -114,8 +113,7 @@ packages/commands/src/index.ts
|
||||
pnpm run sync:skill-assets
|
||||
pnpm -F bailian-cli exec tsx src/main.ts <new-command> --help
|
||||
pnpm -F bailian-cli exec tsx src/main.ts
|
||||
vp test packages/cli/tests/e2e/<topic>.e2e.test.ts
|
||||
node tools/compare-e2e-command-map.ts
|
||||
vp test packages/commands/tests/e2e/<topic>.e2e.test.ts
|
||||
```
|
||||
|
||||
如改了 `kscli` 入口:
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { deriveGroupPaths } from "e2e/registry-smoke";
|
||||
import { commands } from "../../src/commands.ts";
|
||||
import { runCli } from "./helpers.ts";
|
||||
|
||||
/** 从命令 map 推导有子命令的分组路径 */
|
||||
function deriveGroupPaths(commandPaths: string[]): string[] {
|
||||
const groups = new Set<string>();
|
||||
for (const path of commandPaths) {
|
||||
const parts = path.split(" ");
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const prefix = parts.slice(0, i).join(" ");
|
||||
const hasChildren = commandPaths.some(
|
||||
(candidate) => candidate.startsWith(`${prefix} `) && candidate !== prefix,
|
||||
);
|
||||
if (hasChildren) groups.add(prefix);
|
||||
}
|
||||
}
|
||||
return [...groups].sort();
|
||||
}
|
||||
|
||||
const commandPaths = Object.keys(commands).sort();
|
||||
const groupPaths = deriveGroupPaths(commandPaths);
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { ADVISOR_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: advisor recommend", () => {
|
||||
test("advisor shows subcommand groups and exits successfully", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["advisor"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/advisor|recommend/i);
|
||||
});
|
||||
|
||||
test("advisor recommend --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["advisor", "recommend", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/recommend|--message|dry-run/i);
|
||||
});
|
||||
@@ -17,13 +16,17 @@ describe("e2e: advisor recommend", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () => {
|
||||
test("advisor recommend without --message errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["advisor", "recommend", "--quiet"]);
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--message|Usage:/i);
|
||||
});
|
||||
|
||||
test("advisor recommend --dry-run outputs intent analysis and candidates", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--dry-run",
|
||||
@@ -63,7 +66,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
|
||||
}, 60_000);
|
||||
|
||||
test("advisor recommend full flow returns results", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--message",
|
||||
@@ -96,7 +99,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
|
||||
// ---- Model preference: positive cases ----
|
||||
|
||||
test("scoped preference — intent contains modelPreference.mode=scoped when family is specified", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--dry-run",
|
||||
@@ -116,7 +119,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
|
||||
}, 60_000);
|
||||
|
||||
test("comparison preference — intent contains modelPreference.mode=comparison when comparing models", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--dry-run",
|
||||
@@ -136,7 +139,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
|
||||
}, 60_000);
|
||||
|
||||
test("excludes preference — intent detects modelPreference when excluding models", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--dry-run",
|
||||
@@ -166,7 +169,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
|
||||
// ---- Model preference: negative cases ----
|
||||
|
||||
test("no preference — intent has no modelPreference or mode=unconstrained for generic queries", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
|
||||
"advisor",
|
||||
"recommend",
|
||||
"--dry-run",
|
||||
|
||||
@@ -1,45 +1,39 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { AUTH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Auth 相关 E2E:只验证 CLI 进程能正常解析参数并退出。
|
||||
*/
|
||||
|
||||
describe("e2e: auth", () => {
|
||||
test("auth 分组展示子命令帮助且退出码为 0", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["auth"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/auth|Authentication|login|logout|status/i);
|
||||
});
|
||||
|
||||
test("auth login --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "login", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, ["auth", "login", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/login|api-key/i);
|
||||
expect(stderr).toMatch(/--console-site/);
|
||||
});
|
||||
|
||||
test("auth logout --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "logout", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, ["auth", "logout", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/logout|dry-run|yes/i);
|
||||
});
|
||||
|
||||
test("auth status --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "status", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, ["auth", "status", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/status|output/i);
|
||||
});
|
||||
|
||||
test("auth login 缺少 --api-key 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "login", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, ["auth", "login", "--quiet"]);
|
||||
expect(exitCode, stderr).toBe(2);
|
||||
expect(stderr).toMatch(/--api-key|Usage:/i);
|
||||
});
|
||||
|
||||
test("auth login --dry-run --api-key 不发起校验与落盘", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"login",
|
||||
"--dry-run",
|
||||
@@ -51,7 +45,7 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test("auth login --dry-run 覆盖全局参数 --output json --timeout", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"login",
|
||||
"--dry-run",
|
||||
@@ -67,7 +61,12 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test("auth login 缺少密钥且 --output json 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "login", "--output", "json"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"login",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
const err = JSON.parse(stderr.trim()) as { error?: { code?: number; message?: string } };
|
||||
expect(err.error?.code).toBe(2);
|
||||
@@ -75,14 +74,18 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test("auth logout --dry-run 不写入配置", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["auth", "logout", "--dry-run"]);
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"logout",
|
||||
"--dry-run",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("No changes made.");
|
||||
expect(stderr).not.toContain("Cleared api_key");
|
||||
});
|
||||
|
||||
test("auth logout --dry-run --quiet", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"logout",
|
||||
"--dry-run",
|
||||
@@ -93,7 +96,7 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test("auth logout --dry-run --output json(不清除密钥)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"logout",
|
||||
"--dry-run",
|
||||
@@ -106,7 +109,7 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test.skipIf(!isDashScopeE2EReady())("auth status 文本输出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"status",
|
||||
"--output",
|
||||
@@ -119,7 +122,7 @@ describe("e2e: auth", () => {
|
||||
});
|
||||
|
||||
test.skipIf(!isDashScopeE2EReady())("auth status --output json", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"status",
|
||||
"--output",
|
||||
@@ -138,6 +141,7 @@ describe("e2e: auth", () => {
|
||||
"auth status --output json --quiet(base_url 经 env 指定;凭证域 flag 对 status 不可见)",
|
||||
async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
AUTH_ROUTES,
|
||||
["auth", "status", "--output", "json", "--quiet"],
|
||||
{ DASHSCOPE_BASE_URL: "https://dashscope.aliyuncs.com" },
|
||||
);
|
||||
@@ -149,7 +153,7 @@ describe("e2e: auth", () => {
|
||||
);
|
||||
|
||||
test("auth status 不接受凭证域覆盖 flag(--base-url 报 Unknown flag)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(AUTH_ROUTES, [
|
||||
"auth",
|
||||
"status",
|
||||
"--base-url",
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { CONFIG_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Config 相关 E2E
|
||||
*/
|
||||
|
||||
describe("e2e: config", () => {
|
||||
test("config 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["config"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/config|show|set/i);
|
||||
});
|
||||
|
||||
test("config show --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["config", "show", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, ["config", "show", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/show|config/i);
|
||||
});
|
||||
|
||||
test("config set --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["config", "set", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, ["config", "set", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/set|--key|--value/i);
|
||||
});
|
||||
|
||||
test("config show --output json", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"show",
|
||||
"--output",
|
||||
@@ -44,7 +38,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config show --output text", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"show",
|
||||
"--output",
|
||||
@@ -55,13 +49,13 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set 缺少 --key / --value 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["config", "set", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, ["config", "set", "--quiet"]);
|
||||
expect(exitCode, stderr).toBe(2);
|
||||
expect(stderr).toMatch(/--key|--value|Usage:/i);
|
||||
});
|
||||
|
||||
test("config set 非法 key 时退出为用法错误", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--key",
|
||||
@@ -74,7 +68,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set 非法 output", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--key",
|
||||
@@ -87,7 +81,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set 非法 timeout", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--key",
|
||||
@@ -100,7 +94,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set --dry-run 不落盘(仅输出 would_set)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--dry-run",
|
||||
@@ -117,7 +111,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set --dry-run 支持连字符别名 key", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--dry-run",
|
||||
@@ -134,7 +128,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set --dry-run 支持 AccessKey 短字段别名", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--dry-run",
|
||||
@@ -151,7 +145,7 @@ describe("e2e: config", () => {
|
||||
});
|
||||
|
||||
test("config set 不接受旧 OpenAPI AccessKey 字段名", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(CONFIG_ROUTES, [
|
||||
"config",
|
||||
"set",
|
||||
"--key",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { CONSOLE_FLAGS_DRY_RUN_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
type ConsoleDryRunMeta = {
|
||||
consoleRegion?: string;
|
||||
@@ -9,7 +10,11 @@ type ConsoleDryRunMeta = {
|
||||
|
||||
describe("e2e: console global flags (dry-run)", () => {
|
||||
test("console call --help 不暴露命令级 region/site,示例使用 --console-region", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["console", "call", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"console",
|
||||
"call",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--api <api>/);
|
||||
expect(stderr).toMatch(/--data <json>/);
|
||||
@@ -19,7 +24,11 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("auth login --help:自有 flag 含 --console-site,不含其余 console 域 flag", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["auth", "login", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"auth",
|
||||
"login",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--api-key <key>/);
|
||||
expect(stderr).toMatch(/--base-url <url>/);
|
||||
@@ -29,7 +38,7 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("console call --dry-run 默认 consoleRegion 为 cn-beijing", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"console",
|
||||
"call",
|
||||
"--api",
|
||||
@@ -47,7 +56,7 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("console call --dry-run --console-region / --console-site / --console-switch-agent 透传", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"console",
|
||||
"call",
|
||||
"--api",
|
||||
@@ -72,7 +81,7 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("console call 拒绝未知全局 flag --region", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"console",
|
||||
"call",
|
||||
"--api",
|
||||
@@ -88,7 +97,7 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("mcp list --dry-run --console-region 透传", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"mcp",
|
||||
"list",
|
||||
"--dry-run",
|
||||
@@ -103,7 +112,7 @@ describe("e2e: console global flags (dry-run)", () => {
|
||||
});
|
||||
|
||||
test("quota check --dry-run --console-region 透传", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(CONSOLE_FLAGS_DRY_RUN_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--dry-run",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { join } from "path";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e, e2eFixturesDir } from "./helpers.ts";
|
||||
import { DATASET_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Dataset (fine-tune file) E2E.
|
||||
@@ -16,22 +17,19 @@ import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e, e2eFixturesDir } f
|
||||
*/
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
test("dataset --help 列出子命令", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["dataset"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/upload|list|get|delete|validate/);
|
||||
});
|
||||
|
||||
test("dataset upload --help 正常退出并展示 --file", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["dataset", "upload", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"upload",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--file|jsonl/i);
|
||||
});
|
||||
|
||||
test("dataset validate 通过合法 JSONL", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -47,7 +45,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset validate 拒绝 pretty-printed JSON 并以非零码退出", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-invalid.jsonl");
|
||||
const { stdout, exitCode } = await runCommandE2e([
|
||||
const { stdout, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -66,7 +64,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset upload --no-validate --dry-run 跳过本地校验", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-invalid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"upload",
|
||||
"--file",
|
||||
@@ -86,7 +84,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
// No --schema: a record carrying chosen/rejected is auto-detected as DPO
|
||||
// and the valid fixture passes.
|
||||
const file = join(e2eFixturesDir, ".dataset-dpo-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -104,7 +102,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
// No --schema: a record carrying `text` (and no `messages`) is auto-detected
|
||||
// as CPT and the valid fixture passes.
|
||||
const file = join(e2eFixturesDir, ".dataset-cpt-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -120,7 +118,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset validate --schema cpt 拒绝缺失 text 的记录", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-valid.jsonl"); // SFT {messages}, no text
|
||||
const { stdout, exitCode } = await runCommandE2e([
|
||||
const { stdout, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -140,7 +138,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset validate --schema dpo 拒绝缺失 rejected 的记录", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-dpo-invalid.jsonl");
|
||||
const { stdout, exitCode } = await runCommandE2e([
|
||||
const { stdout, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -161,7 +159,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
test("dataset validate --schema chatml 忽略 chosen/rejected(不报 DPO 错误)", async () => {
|
||||
// Same invalid-DPO file, but --schema chatml must not run DPO checks.
|
||||
const file = join(e2eFixturesDir, ".dataset-dpo-invalid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -179,7 +177,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset validate --schema <bad> 以非零码退出", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -195,7 +193,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
test("dataset upload --dry-run 转发 --schema", async () => {
|
||||
const file = join(e2eFixturesDir, ".dataset-dpo-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"upload",
|
||||
"--file",
|
||||
@@ -215,7 +213,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (DashScope)", () => {
|
||||
test("dataset list --output json 返回结构化结果", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
|
||||
"dataset",
|
||||
"list",
|
||||
"--page-size",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { DEPLOY_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Deploy E2E.
|
||||
@@ -14,21 +15,14 @@ import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.t
|
||||
*/
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
test("deploy 列出子命令", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["deploy"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/create|list|get|delete|update|scale|models/);
|
||||
});
|
||||
|
||||
test("deploy create --help 正常退出并展示必填项", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["deploy", "create", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, ["deploy", "create", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--name/i);
|
||||
});
|
||||
|
||||
test("deploy create --dry-run 构造 lora 部署请求体", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -57,7 +51,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
});
|
||||
|
||||
test("deploy scale --dry-run 转发 capacity", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
"scale",
|
||||
"--deployed-model",
|
||||
@@ -80,7 +74,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
});
|
||||
|
||||
test("deploy update --dry-run 转发 rate limits", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
"update",
|
||||
"--deployed-model",
|
||||
@@ -104,7 +98,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
});
|
||||
|
||||
test("deploy scale --dry-run 缺少 capacity/input-tpm/output-tpm 时报错", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
"scale",
|
||||
"--deployed-model",
|
||||
@@ -124,7 +118,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
|
||||
["models", ["--source", "custom"]],
|
||||
["delete", ["--deployed-model", "dep-xxx"]],
|
||||
])("deploy %s --dry-run 发出结构化动作", async (sub, extra) => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
sub,
|
||||
...extra,
|
||||
@@ -147,7 +141,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (DashScope)", () => {
|
||||
* 而非进程崩溃),即视为通过。
|
||||
*/
|
||||
test("deploy list --output json 优雅返回(空账号或鉴权失败均通过)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
|
||||
"deploy",
|
||||
"list",
|
||||
"--page-size",
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { join } from "path";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e, e2eFixturesDir } from "./helpers.ts";
|
||||
import { FILE_UPLOAD_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* File upload E2E
|
||||
*/
|
||||
|
||||
describe("e2e: file upload", () => {
|
||||
test("file 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["file"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/file|upload/i);
|
||||
});
|
||||
|
||||
test("file upload --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["file", "upload", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(FILE_UPLOAD_ROUTES, [
|
||||
"file",
|
||||
"upload",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/upload|--file|--model/i);
|
||||
});
|
||||
@@ -23,7 +21,7 @@ describe("e2e: file upload", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: file upload(DashScope)", () => {
|
||||
test("file upload 缺少 --file 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(FILE_UPLOAD_ROUTES, [
|
||||
"file",
|
||||
"upload",
|
||||
"--model",
|
||||
@@ -35,14 +33,19 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: file upload(DashScope)", () =>
|
||||
|
||||
test("file upload 缺少 --model 时报用法错误并退出 (2)", async () => {
|
||||
const testFile = join(e2eFixturesDir, ".smoke-32.png");
|
||||
const { stderr, exitCode } = await runCommandE2e(["file", "upload", "--file", testFile]);
|
||||
const { stderr, exitCode } = await runCommandE2e(FILE_UPLOAD_ROUTES, [
|
||||
"file",
|
||||
"upload",
|
||||
"--file",
|
||||
testFile,
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--model|Usage:/i);
|
||||
});
|
||||
|
||||
test("上传文件成功返回oss临时 URL", async () => {
|
||||
const testFile = join(e2eFixturesDir, ".smoke-32.png");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FILE_UPLOAD_ROUTES, [
|
||||
"file",
|
||||
"upload",
|
||||
"--file",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { join } from "path";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e, e2eFixturesDir } from "./helpers.ts";
|
||||
import { FINETUNE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Fine-tune E2E.
|
||||
@@ -15,21 +16,18 @@ import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e, e2eFixturesDir } f
|
||||
*/
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
test("finetune 列出子命令", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["finetune"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/create|list|get|cancel|delete|logs|checkpoints|export|watch|capability/);
|
||||
});
|
||||
|
||||
test("finetune create --help 正常退出并展示必填项", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["finetune", "create", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--datasets/i);
|
||||
});
|
||||
|
||||
test("finetune create --dry-run 构造 SFT 默认请求体", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -62,7 +60,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
});
|
||||
|
||||
test("finetune create --dry-run 转发训练类型与超参", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -115,7 +113,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
});
|
||||
|
||||
test("finetune create --training-type 拒绝不支持的训练类型值", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -133,7 +131,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
|
||||
test("finetune create --dry-run 把本地路径标记为 pending 上传且不发起网络请求", async () => {
|
||||
const localPath = join(e2eFixturesDir, ".dataset-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -163,7 +161,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
});
|
||||
|
||||
test("finetune create --datasets 为空时拒绝", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -183,7 +181,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
// so this is fully offline (no key, no network) — the proof is that the
|
||||
// error is the gate message AND no "Uploaded …" line ever appears.
|
||||
const localPath = join(e2eFixturesDir, ".dataset-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -204,7 +202,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
// Even with --batch-size 1 (server clamps to 8), 3 samples <= 8 still trips
|
||||
// the gate — confirms the gate uses the clamped/effective batch, not the raw.
|
||||
const localPath = join(e2eFixturesDir, ".dataset-valid.jsonl");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -231,7 +229,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
["watch", ["--job-id", "ft-xxx"]],
|
||||
["capability", ["--model", "qwen3-8b"]],
|
||||
])("finetune %s --dry-run 发出结构化动作", async (sub, extra) => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
sub,
|
||||
...extra,
|
||||
@@ -245,7 +243,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
|
||||
});
|
||||
|
||||
test("finetune create --dry-run 解析多 datasets 中的空白", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"create",
|
||||
"--model",
|
||||
@@ -273,7 +271,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (DashScope)", () => {
|
||||
* 而非进程崩溃),即视为通过。
|
||||
*/
|
||||
test("finetune list --output json 优雅返回(空账号或鉴权失败均通过)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
|
||||
"finetune",
|
||||
"list",
|
||||
"--page-size",
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { readdirSync, readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import * as commandExports from "bailian-cli-commands";
|
||||
import { E2E_COMMAND_EXPORTS, e2eCommandMap } from "./e2e-command-map.ts";
|
||||
|
||||
const e2eDir = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const exportedCommands = new Set(
|
||||
Object.values(commandExports).filter(
|
||||
(value) => typeof value === "object" && value !== null && "run" in value,
|
||||
),
|
||||
);
|
||||
|
||||
/** 从 runCommandE2e([...]) 调用中提取命令 path 前缀 */
|
||||
function extractCommandPathsFromSource(source: string): string[] {
|
||||
const paths: string[] = [];
|
||||
const callPattern = /runCommandE2e\(\[([\s\S]*?)\]/g;
|
||||
|
||||
for (const match of source.matchAll(callPattern)) {
|
||||
const arrayBody = match[1] ?? "";
|
||||
const tokens: string[] = [];
|
||||
const stringPattern = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/g;
|
||||
for (const tokenMatch of arrayBody.matchAll(stringPattern)) {
|
||||
const token = tokenMatch[1] ?? tokenMatch[2] ?? "";
|
||||
if (token.startsWith("--")) break;
|
||||
tokens.push(token);
|
||||
}
|
||||
if (tokens.length > 0) paths.push(tokens.join(" "));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
describe("e2e-command-map contract", () => {
|
||||
test("path 唯一", () => {
|
||||
const paths = Object.keys(e2eCommandMap);
|
||||
expect(new Set(paths).size).toBe(paths.length);
|
||||
});
|
||||
|
||||
test("value 均为 bailian-cli-commands 合法 export", () => {
|
||||
for (const [path, command] of Object.entries(e2eCommandMap)) {
|
||||
expect(exportedCommands.has(command), `invalid export for path "${path}"`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("E2E_COMMAND_EXPORTS 白名单 ⊆ map values", () => {
|
||||
const mapValues = new Set(Object.values(e2eCommandMap));
|
||||
for (const command of E2E_COMMAND_EXPORTS) {
|
||||
expect(mapValues.has(command)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("各 e2e 测试 argv 前缀在 map 中有对应 path", () => {
|
||||
const mapPaths = new Set(Object.keys(e2eCommandMap));
|
||||
const testFiles = readdirSync(e2eDir).filter(
|
||||
(name) => name.endsWith(".e2e.test.ts") && name !== "proxy.e2e.test.ts",
|
||||
);
|
||||
|
||||
const missing: string[] = [];
|
||||
for (const file of testFiles) {
|
||||
const source = readFileSync(join(e2eDir, file), "utf8");
|
||||
for (const path of extractCommandPathsFromSource(source)) {
|
||||
if (!mapPaths.has(path)) missing.push(`${file}: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,233 +0,0 @@
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import {
|
||||
authLogin,
|
||||
authStatus,
|
||||
authLogout,
|
||||
textChat,
|
||||
textOmni,
|
||||
imageGenerate,
|
||||
imageEdit,
|
||||
videoGenerate,
|
||||
videoEdit,
|
||||
videoRef,
|
||||
videoTaskGet,
|
||||
videoDownload,
|
||||
visionDescribe,
|
||||
configShow,
|
||||
configSet,
|
||||
update,
|
||||
appCall,
|
||||
appList,
|
||||
memoryAdd,
|
||||
memorySearch,
|
||||
memoryList,
|
||||
memoryUpdate,
|
||||
memoryDelete,
|
||||
memoryProfileCreate,
|
||||
memoryProfileGet,
|
||||
knowledgeRetrieve,
|
||||
knowledgeSearch,
|
||||
knowledgeChat,
|
||||
mcpCall,
|
||||
mcpList,
|
||||
mcpTools,
|
||||
searchWeb,
|
||||
speechSynthesize,
|
||||
speechRecognize,
|
||||
fileUpload,
|
||||
consoleCall,
|
||||
usageFree,
|
||||
usageFreetier,
|
||||
usageStats,
|
||||
pipelineRun,
|
||||
pipelineValidate,
|
||||
advisorRecommend,
|
||||
workspaceList,
|
||||
quotaList,
|
||||
quotaRequest,
|
||||
quotaHistory,
|
||||
quotaCheck,
|
||||
datasetUpload,
|
||||
datasetList,
|
||||
datasetGet,
|
||||
datasetDelete,
|
||||
datasetValidate,
|
||||
finetuneCreate,
|
||||
finetuneList,
|
||||
finetuneGet,
|
||||
finetuneCancel,
|
||||
finetuneDelete,
|
||||
finetuneLogs,
|
||||
finetuneCheckpoints,
|
||||
finetuneExport,
|
||||
finetuneWatch,
|
||||
finetuneCapability,
|
||||
deployCreate,
|
||||
deployList,
|
||||
deployGet,
|
||||
deployModels,
|
||||
deployScale,
|
||||
deployUpdate,
|
||||
deployDelete,
|
||||
tokenPlanListSeats,
|
||||
tokenPlanCreateKey,
|
||||
tokenPlanAssignSeats,
|
||||
tokenPlanAddMember,
|
||||
} from "bailian-cli-commands";
|
||||
|
||||
/**
|
||||
* E2E 专用全量路由表(与产品 map 独立维护)。
|
||||
* 新增/删改命令时须同步:commands 实现、产品 map、本文件。
|
||||
*/
|
||||
export const e2eCommandMap: Record<string, AnyCommand> = {
|
||||
"auth login": authLogin,
|
||||
"auth status": authStatus,
|
||||
"auth logout": authLogout,
|
||||
"text chat": textChat,
|
||||
omni: textOmni,
|
||||
"image generate": imageGenerate,
|
||||
"image edit": imageEdit,
|
||||
"video generate": videoGenerate,
|
||||
"video edit": videoEdit,
|
||||
"video ref": videoRef,
|
||||
"video task get": videoTaskGet,
|
||||
"video download": videoDownload,
|
||||
"vision describe": visionDescribe,
|
||||
"config show": configShow,
|
||||
"config set": configSet,
|
||||
update,
|
||||
"app call": appCall,
|
||||
"app list": appList,
|
||||
"memory add": memoryAdd,
|
||||
"memory search": memorySearch,
|
||||
"memory list": memoryList,
|
||||
"memory update": memoryUpdate,
|
||||
"memory delete": memoryDelete,
|
||||
"memory profile create": memoryProfileCreate,
|
||||
"memory profile get": memoryProfileGet,
|
||||
"knowledge retrieve": knowledgeRetrieve,
|
||||
"knowledge search": knowledgeSearch,
|
||||
"knowledge chat": knowledgeChat,
|
||||
"mcp call": mcpCall,
|
||||
"mcp list": mcpList,
|
||||
"mcp tools": mcpTools,
|
||||
"search web": searchWeb,
|
||||
"speech synthesize": speechSynthesize,
|
||||
"speech recognize": speechRecognize,
|
||||
"file upload": fileUpload,
|
||||
"console call": consoleCall,
|
||||
"usage free": usageFree,
|
||||
"usage freetier": usageFreetier,
|
||||
"usage stats": usageStats,
|
||||
"pipeline run": pipelineRun,
|
||||
"pipeline validate": pipelineValidate,
|
||||
"advisor recommend": advisorRecommend,
|
||||
"workspace list": workspaceList,
|
||||
"quota list": quotaList,
|
||||
"quota request": quotaRequest,
|
||||
"quota history": quotaHistory,
|
||||
"quota check": quotaCheck,
|
||||
"dataset upload": datasetUpload,
|
||||
"dataset list": datasetList,
|
||||
"dataset get": datasetGet,
|
||||
"dataset delete": datasetDelete,
|
||||
"dataset validate": datasetValidate,
|
||||
"finetune create": finetuneCreate,
|
||||
"finetune list": finetuneList,
|
||||
"finetune get": finetuneGet,
|
||||
"finetune cancel": finetuneCancel,
|
||||
"finetune delete": finetuneDelete,
|
||||
"finetune logs": finetuneLogs,
|
||||
"finetune checkpoints": finetuneCheckpoints,
|
||||
"finetune export": finetuneExport,
|
||||
"finetune watch": finetuneWatch,
|
||||
"finetune capability": finetuneCapability,
|
||||
"deploy create": deployCreate,
|
||||
"deploy list": deployList,
|
||||
"deploy get": deployGet,
|
||||
"deploy models": deployModels,
|
||||
"deploy scale": deployScale,
|
||||
"deploy update": deployUpdate,
|
||||
"deploy delete": deployDelete,
|
||||
"token-plan list-seats": tokenPlanListSeats,
|
||||
"token-plan create-key": tokenPlanCreateKey,
|
||||
"token-plan assign-seats": tokenPlanAssignSeats,
|
||||
"token-plan add-member": tokenPlanAddMember,
|
||||
};
|
||||
|
||||
/** 须在 e2eCommandMap 中出现至少一次的 commands export(契约测试用) */
|
||||
export const E2E_COMMAND_EXPORTS = [
|
||||
authLogin,
|
||||
authStatus,
|
||||
authLogout,
|
||||
textChat,
|
||||
textOmni,
|
||||
imageGenerate,
|
||||
imageEdit,
|
||||
videoGenerate,
|
||||
videoEdit,
|
||||
videoRef,
|
||||
videoTaskGet,
|
||||
videoDownload,
|
||||
visionDescribe,
|
||||
configShow,
|
||||
configSet,
|
||||
update,
|
||||
appCall,
|
||||
appList,
|
||||
memoryAdd,
|
||||
memorySearch,
|
||||
memoryList,
|
||||
memoryUpdate,
|
||||
memoryDelete,
|
||||
memoryProfileCreate,
|
||||
memoryProfileGet,
|
||||
knowledgeRetrieve,
|
||||
knowledgeSearch,
|
||||
knowledgeChat,
|
||||
mcpCall,
|
||||
mcpList,
|
||||
mcpTools,
|
||||
searchWeb,
|
||||
speechSynthesize,
|
||||
speechRecognize,
|
||||
fileUpload,
|
||||
consoleCall,
|
||||
usageFree,
|
||||
usageFreetier,
|
||||
usageStats,
|
||||
pipelineRun,
|
||||
pipelineValidate,
|
||||
advisorRecommend,
|
||||
workspaceList,
|
||||
quotaList,
|
||||
quotaRequest,
|
||||
quotaHistory,
|
||||
quotaCheck,
|
||||
datasetUpload,
|
||||
datasetList,
|
||||
datasetGet,
|
||||
datasetDelete,
|
||||
datasetValidate,
|
||||
finetuneCreate,
|
||||
finetuneList,
|
||||
finetuneGet,
|
||||
finetuneCancel,
|
||||
finetuneDelete,
|
||||
finetuneLogs,
|
||||
finetuneCheckpoints,
|
||||
finetuneExport,
|
||||
finetuneWatch,
|
||||
finetuneCapability,
|
||||
deployCreate,
|
||||
deployList,
|
||||
deployGet,
|
||||
deployModels,
|
||||
deployScale,
|
||||
deployUpdate,
|
||||
deployDelete,
|
||||
tokenPlanListSeats,
|
||||
tokenPlanCreateKey,
|
||||
tokenPlanAssignSeats,
|
||||
tokenPlanAddMember,
|
||||
] as const;
|
||||
@@ -0,0 +1,13 @@
|
||||
import pkg from "../../../package.json" with { type: "json" };
|
||||
|
||||
/**
|
||||
* commands E2E harness 产品身份。
|
||||
* version 取自 commands 包(与 monorepo 同步);npmPackage 使用非发布名以阻断
|
||||
* versionCheckStage 的 registry 查询与 CI 中的 auto-update。
|
||||
*/
|
||||
export const E2E_HARNESS_IDENTITY = {
|
||||
binName: "bl",
|
||||
version: pkg.version,
|
||||
clientName: "commands-e2e",
|
||||
npmPackage: "bailian-cli-commands-e2e-harness",
|
||||
} as const;
|
||||
@@ -1,10 +1,29 @@
|
||||
import * as cmd from "bailian-cli-commands";
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import { createCli } from "bailian-cli-runtime";
|
||||
import pkg from "../../../../cli/package.json" with { type: "json" };
|
||||
import { e2eCommandMap } from "./e2e-command-map.ts";
|
||||
import { E2E_HARNESS_IDENTITY } from "./identity.ts";
|
||||
|
||||
void createCli(e2eCommandMap, {
|
||||
binName: "bl",
|
||||
version: pkg.version,
|
||||
clientName: "commands-e2e",
|
||||
npmPackage: "bailian-cli-commands",
|
||||
}).run();
|
||||
interface RouteSpec {
|
||||
path: string;
|
||||
export: string;
|
||||
}
|
||||
|
||||
function buildRoutesFromEnv(): Record<string, AnyCommand> {
|
||||
const raw = process.env.BAILIAN_E2E_ROUTES;
|
||||
if (!raw?.trim()) {
|
||||
throw new Error("BAILIAN_E2E_ROUTES is required for commands E2E harness");
|
||||
}
|
||||
const spec = JSON.parse(raw) as RouteSpec[];
|
||||
const routes: Record<string, AnyCommand> = {};
|
||||
const lib = cmd as Record<string, unknown>;
|
||||
for (const { path, export: exportName } of spec) {
|
||||
const command = lib[exportName];
|
||||
if (typeof command !== "object" || command === null || !("run" in command)) {
|
||||
throw new Error(`Unknown bailian-cli-commands export: ${exportName}`);
|
||||
}
|
||||
routes[path] = command as AnyCommand;
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
void createCli(buildRoutesFromEnv(), E2E_HARNESS_IDENTITY).run();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
parseStdoutJson,
|
||||
} from "e2e/output";
|
||||
import { runNodeMain, type RunCliResult } from "e2e/runner";
|
||||
import type { E2eRouteExports } from "./topic-routes.ts";
|
||||
|
||||
export {
|
||||
cliTimeoutPrefix,
|
||||
@@ -39,13 +40,26 @@ export const commandsPackageRoot = join(e2eDir, "..", "..");
|
||||
/** E2E fixtures 目录 */
|
||||
export const e2eFixturesDir = join(e2eDir, "fixtures");
|
||||
|
||||
/** 通过 harness 子进程执行 E2E 命令 */
|
||||
function serializeRoutes(routes: E2eRouteExports): string {
|
||||
return JSON.stringify(
|
||||
Object.entries(routes).map(([path, exportName]) => ({ path, export: exportName })),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 harness 子进程执行命令 E2E。
|
||||
* `routes` 为本用例所需的最小 path → export 映射,不维护全量产品 map。
|
||||
*/
|
||||
export async function runCommandE2e(
|
||||
routes: E2eRouteExports,
|
||||
args: string[],
|
||||
envOverrides: NodeJS.ProcessEnv = {},
|
||||
): Promise<RunCliResult> {
|
||||
return runNodeMain(harnessMainTs, args, {
|
||||
cwd: commandsPackageRoot,
|
||||
env: envOverrides,
|
||||
env: {
|
||||
BAILIAN_E2E_ROUTES: serializeRoutes(routes),
|
||||
...envOverrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,27 +9,21 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { IMAGE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Image edit E2E
|
||||
*/
|
||||
|
||||
describe("e2e: image edit", () => {
|
||||
test("image 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["image"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/image|generate|edit/i);
|
||||
});
|
||||
|
||||
test("image edit --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["image", "edit", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, ["image", "edit", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/edit|--image|--prompt|--async|--concurrent/i);
|
||||
});
|
||||
|
||||
test("image edit --dry-run 接受 async 模型的 --async 与 --concurrent", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"edit",
|
||||
"--dry-run",
|
||||
@@ -56,21 +50,31 @@ describe("e2e: image edit", () => {
|
||||
|
||||
describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())("e2e: image edit", () => {
|
||||
test("image edit 缺少 --image 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["image", "edit", "--prompt", "仅提示词"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"edit",
|
||||
"--prompt",
|
||||
"仅提示词",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--image|Usage:/i);
|
||||
});
|
||||
|
||||
test("image edit 缺少 --prompt 时报用法错误并退出 (2)", async () => {
|
||||
const testPng = join(e2eFixturesDir, ".smoke-32.png");
|
||||
const { stderr, exitCode } = await runCommandE2e(["image", "edit", "--image", testPng]);
|
||||
const { stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"edit",
|
||||
"--image",
|
||||
testPng,
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--prompt|Usage:/i);
|
||||
});
|
||||
|
||||
test("【qwen-image-2.0】图片编辑", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const gen = await runCommandE2e([
|
||||
const gen = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
@@ -90,7 +94,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())("e2e: ima
|
||||
|
||||
expect(imagePath).toBeTruthy();
|
||||
|
||||
const ed = await runCommandE2e([
|
||||
const ed = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"edit",
|
||||
"--model",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { IMAGE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Image generate:先做 help / 分组等常规检测(不依赖密钥、不调生成接口)。
|
||||
@@ -15,15 +16,8 @@ import {
|
||||
*/
|
||||
|
||||
describe("e2e: image generate", () => {
|
||||
test("image 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["image"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/image|generate|edit/i);
|
||||
});
|
||||
|
||||
test("image generate --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["image", "generate", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, ["image", "generate", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/generate|--prompt|--model/i);
|
||||
});
|
||||
@@ -33,7 +27,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: image generate",
|
||||
() => {
|
||||
test("image generate 缺少 --prompt 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
@@ -45,7 +39,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
test("【qwen-image-2.0】图片生成", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(IMAGE_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isChatE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { KNOWLEDGE_CHAT_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
interface ContentPart {
|
||||
type: string;
|
||||
@@ -24,7 +25,11 @@ interface DryRunBody {
|
||||
|
||||
describe("e2e: knowledge chat", () => {
|
||||
test("knowledge chat --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "chat", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--message/i);
|
||||
expect(stderr).toMatch(/--agent-id/i);
|
||||
@@ -32,7 +37,7 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("缺少 --message 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--agent-id",
|
||||
@@ -43,13 +48,19 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("缺少 --agent-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "chat", "--message", "Hello"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
"Hello",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--agent-id|Usage:/i);
|
||||
});
|
||||
|
||||
test("缺少 --workspace-id 时非零退出并提示", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_CHAT_ROUTES,
|
||||
// 假 key + 隔离配置目录:避免本机 config 的 workspace_id/api_key 漏入
|
||||
[
|
||||
"knowledge",
|
||||
@@ -70,7 +81,7 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("--dry-run 输出 endpoint 和 request body", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--dry-run",
|
||||
@@ -93,7 +104,7 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("--dry-run 多轮消息解析 role:content 前缀", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--dry-run",
|
||||
@@ -123,7 +134,7 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("--dry-run + --image 输出多模态 content 数组", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--dry-run",
|
||||
@@ -152,7 +163,7 @@ describe("e2e: knowledge chat", () => {
|
||||
});
|
||||
|
||||
test("--dry-run + --image 无 --message 自动创建空 user message", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--dry-run",
|
||||
@@ -194,7 +205,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
|
||||
|
||||
test("chat (JSON mode) returns answer", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -215,7 +226,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
});
|
||||
|
||||
test("chat (text mode) returns plain text", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -233,7 +244,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
});
|
||||
|
||||
test("chat (stream, JSON mode) collects and returns answer", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -254,7 +265,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
});
|
||||
|
||||
test("chat (stream, text mode) outputs streaming text", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -272,7 +283,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
});
|
||||
|
||||
test("chat with multi-turn messages returns context-aware answer", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -296,7 +307,7 @@ describe.skipIf(!isChatE2EReady())("e2e: knowledge chat (live)", () => {
|
||||
});
|
||||
|
||||
test("chat with invalid agent_id fails gracefully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHAT_ROUTES, [
|
||||
"knowledge",
|
||||
"chat",
|
||||
"--message",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isSearchE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { KNOWLEDGE_SEARCH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
interface DryRunBody {
|
||||
endpoint?: string;
|
||||
@@ -13,7 +14,11 @@ interface DryRunBody {
|
||||
|
||||
describe("e2e: knowledge search", () => {
|
||||
test("knowledge search --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "search", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--query/i);
|
||||
expect(stderr).toMatch(/--agent-id/i);
|
||||
@@ -23,7 +28,7 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("缺少 --query 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--agent-id",
|
||||
@@ -34,13 +39,19 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("缺少 --agent-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "search", "--query", "test"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--query",
|
||||
"test",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--agent-id|Usage:/i);
|
||||
});
|
||||
|
||||
test("缺少 --workspace-id 时非零退出并提示", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_SEARCH_ROUTES,
|
||||
// 假 key + 隔离配置目录:避免本机 config 的 workspace_id/api_key 漏入
|
||||
[
|
||||
"knowledge",
|
||||
@@ -61,7 +72,7 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("--dry-run 输出 endpoint 和 request body", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--dry-run",
|
||||
@@ -83,7 +94,7 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("--dry-run + --image 输出 images", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--dry-run",
|
||||
@@ -109,7 +120,7 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("--dry-run + --query-history 输出用户对话历史", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--dry-run",
|
||||
@@ -133,7 +144,7 @@ describe("e2e: knowledge search", () => {
|
||||
});
|
||||
|
||||
test("--dry-run + --query-history 无效 JSON 非零退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--dry-run",
|
||||
@@ -173,7 +184,7 @@ describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
|
||||
const workspaceId = process.env.BAILIAN_WORKSPACE_ID!;
|
||||
|
||||
test("search returns results in JSON mode", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--query",
|
||||
@@ -201,7 +212,7 @@ describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
|
||||
});
|
||||
|
||||
test("search returns results in text mode", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--query",
|
||||
@@ -219,7 +230,7 @@ describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
|
||||
});
|
||||
|
||||
test("search with --query-history returns results", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--query",
|
||||
@@ -241,7 +252,7 @@ describe.skipIf(!isSearchE2EReady())("e2e: knowledge search (live)", () => {
|
||||
});
|
||||
|
||||
test("search with invalid agent_id fails gracefully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SEARCH_ROUTES, [
|
||||
"knowledge",
|
||||
"search",
|
||||
"--query",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { KNOWLEDGE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
// ---- Types ----
|
||||
|
||||
@@ -20,15 +21,12 @@ interface DryRunBody {
|
||||
// ---- Help & missing args (no credentials needed) ----
|
||||
|
||||
describe("e2e: knowledge retrieve", () => {
|
||||
test("knowledge 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["knowledge"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/knowledge|retrieve/i);
|
||||
});
|
||||
|
||||
test("knowledge retrieve --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "retrieve", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_ROUTES, [
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--index-id/i);
|
||||
expect(stderr).toMatch(/--query/i);
|
||||
@@ -37,13 +35,18 @@ describe("e2e: knowledge retrieve", () => {
|
||||
});
|
||||
|
||||
test("缺少 --index-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["knowledge", "retrieve", "--query", "test"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_ROUTES, [
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
"--query",
|
||||
"test",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--index-id|Usage:/i);
|
||||
});
|
||||
|
||||
test("缺少 --query 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_ROUTES, [
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
"--index-id",
|
||||
@@ -59,6 +62,7 @@ describe("e2e: knowledge retrieve", () => {
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: knowledge retrieve errors", () => {
|
||||
test("无任何凭证时提示缺少密钥并非零退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_ROUTES,
|
||||
["knowledge", "retrieve", "--index-id", "idx_test", "--query", "test", "--output", "json"],
|
||||
{
|
||||
DASHSCOPE_API_KEY: "",
|
||||
@@ -76,6 +80,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: knowledge retrieve errors", () =>
|
||||
describe("e2e: knowledge retrieve dry-run", () => {
|
||||
test("--dry-run 输出 endpoint 和 snake_case body", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_ROUTES,
|
||||
[
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
@@ -98,6 +103,7 @@ describe("e2e: knowledge retrieve dry-run", () => {
|
||||
|
||||
test("--dry-run + --top-k 转发到 rerank_top_n 并输出废弃警告", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_ROUTES,
|
||||
[
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
@@ -121,6 +127,7 @@ describe("e2e: knowledge retrieve dry-run", () => {
|
||||
|
||||
test("--dry-run + --rerank-top-n 优先于 --top-k", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_ROUTES,
|
||||
[
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
@@ -145,6 +152,7 @@ describe("e2e: knowledge retrieve dry-run", () => {
|
||||
|
||||
test("--dry-run + rerank 参数完整输出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
KNOWLEDGE_ROUTES,
|
||||
[
|
||||
"knowledge",
|
||||
"retrieve",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { MCP_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* `bl mcp` E2E.
|
||||
@@ -16,40 +17,32 @@ import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.t
|
||||
*/
|
||||
|
||||
describe("e2e: mcp", () => {
|
||||
test("mcp 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["mcp"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/mcp/i);
|
||||
expect(out).toMatch(/list|tools|call/i);
|
||||
});
|
||||
|
||||
test("mcp list --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "list", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "list", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/list|--name|--type|--page/i);
|
||||
});
|
||||
|
||||
test("mcp tools --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "tools", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "tools", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/tools|--server|--url/i);
|
||||
});
|
||||
|
||||
test("mcp call --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "call", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "call", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/call|--target|--arg|--json/i);
|
||||
});
|
||||
|
||||
test("mcp list --help 不暴露 --all 入口(市场全量已下线)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "list", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "list", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).not.toMatch(/--all/);
|
||||
});
|
||||
|
||||
test("mcp list --dry-run 仅打印计划且固定 activated=1", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"list",
|
||||
"--dry-run",
|
||||
@@ -87,7 +80,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp list --dry-run 自定义 --console-region 透传", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"list",
|
||||
"--dry-run",
|
||||
@@ -102,7 +95,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp tools --server <code> --dry-run 输出 /api/v1/mcps/<code>/mcp 形态 URL", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"tools",
|
||||
"--server",
|
||||
@@ -121,7 +114,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp tools --url 覆盖 baseUrl 约定", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"tools",
|
||||
"--server",
|
||||
@@ -139,13 +132,13 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp tools 缺少 --server 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "tools", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "tools", "--quiet"]);
|
||||
expect(exitCode, stderr).toBe(2);
|
||||
expect(stderr).toMatch(/--server|Usage:/i);
|
||||
});
|
||||
|
||||
test("mcp call --target <server.tool> --dry-run 输出工具调用计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"call",
|
||||
"--target",
|
||||
@@ -171,7 +164,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp call --json 与 --arg 合并(arg 覆盖 json),--query 等价 arg.query", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"call",
|
||||
"--target",
|
||||
@@ -207,7 +200,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp call --target 缺少 . 时报错且非零退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"call",
|
||||
"--target",
|
||||
@@ -220,7 +213,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp call --arg 非 K=V 形式时报错且非零退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"call",
|
||||
"--target",
|
||||
@@ -235,7 +228,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp call --json 无效 JSON 报错且非零退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"call",
|
||||
"--target",
|
||||
@@ -250,7 +243,7 @@ describe("e2e: mcp", () => {
|
||||
});
|
||||
|
||||
test("mcp call 缺少 --target 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["mcp", "call", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MCP_ROUTES, ["mcp", "call", "--quiet"]);
|
||||
expect(exitCode, stderr).toBe(2);
|
||||
expect(stderr).toMatch(/--target|Usage:/i);
|
||||
});
|
||||
@@ -261,7 +254,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: mcp (live)", () => {
|
||||
// Regression: bailianMcpUrl previously added an `AliyunBailianMCP_` prefix,
|
||||
// which made every real call 500. This test asserts the convention-built URL
|
||||
// (no --url override) actually reaches a live MCP server end-to-end.
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MCP_ROUTES, [
|
||||
"mcp",
|
||||
"tools",
|
||||
"--server",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { MEMORY_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
interface MemoryAddBody {
|
||||
memory_ids?: string[];
|
||||
@@ -36,33 +37,31 @@ function memoryLibraryCliArgs(): string[] {
|
||||
*/
|
||||
|
||||
describe("e2e: memory", () => {
|
||||
test("memory 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["memory"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/memory|add|list|search|update|delete|profile/i);
|
||||
});
|
||||
|
||||
test("memory add --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["memory", "add", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, ["memory", "add", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/add|--user-id|--content|messages/i);
|
||||
});
|
||||
|
||||
test("memory list --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["memory", "list", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, ["memory", "list", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/list|--user-id|memory-library/i);
|
||||
});
|
||||
|
||||
test("memory search --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["memory", "search", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, ["memory", "search", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/search|--query|user-id/i);
|
||||
});
|
||||
|
||||
test("memory profile create --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["memory", "profile", "create", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"profile",
|
||||
"create",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/profile|create|user-id/i);
|
||||
});
|
||||
@@ -75,7 +74,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: memory CRUD + search",
|
||||
() => {
|
||||
test("memory add 缺少 --user-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"add",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -88,7 +87,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
test("memory add 缺少 --messages 与 --content 时报错正常退出", async () => {
|
||||
const userId = process.env.BAILIAN_E2E_MEMORY_USER_ID?.trim() || DEFAULT_E2E_MEMORY_USER_ID;
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"add",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -101,7 +100,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
test("memory add --dry-run 仅输出计划且不入网", async () => {
|
||||
const userId = process.env.BAILIAN_E2E_MEMORY_USER_ID?.trim() || DEFAULT_E2E_MEMORY_USER_ID;
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"add",
|
||||
"--dry-run",
|
||||
@@ -126,7 +125,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
const contentA = "CLI vp test:记忆写入(可删)";
|
||||
const contentB = "CLI vp test:记忆已更新";
|
||||
|
||||
const addRes = await runCommandE2e([
|
||||
const addRes = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"add",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -141,7 +140,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
const added = parseStdoutJson<MemoryAddBody & { request_id?: string }>(addRes.stdout);
|
||||
expect(added.request_id?.length ?? 0, addRes.stdout + addRes.stderr).toBeGreaterThan(0);
|
||||
|
||||
const listRes = await runCommandE2e([
|
||||
const listRes = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"list",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -162,7 +161,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
const nodeId = listed.memory_nodes![0]!.memory_node_id.trim();
|
||||
expect(nodeId.length).toBeGreaterThan(0);
|
||||
|
||||
const searchRes = await runCommandE2e([
|
||||
const searchRes = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"search",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -179,7 +178,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
const searched = parseStdoutJson<MemorySearchBody>(searchRes.stdout);
|
||||
expect(searched.memory_nodes?.length ?? 0).toBeGreaterThan(0);
|
||||
|
||||
const updRes = await runCommandE2e([
|
||||
const updRes = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"update",
|
||||
...memoryLibraryCliArgs(),
|
||||
@@ -194,7 +193,7 @@ describe.skipIf(!isBailianE2EEnabled() || !isDashScopeE2EReady())(
|
||||
]);
|
||||
expect(updRes.exitCode, updRes.stderr).toBe(0);
|
||||
|
||||
const delRes = await runCommandE2e([
|
||||
const delRes = await runCommandE2e(MEMORY_ROUTES, [
|
||||
"memory",
|
||||
"delete",
|
||||
...memoryLibraryCliArgs(),
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { OMNI_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: omni", () => {
|
||||
test("omni --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["omni", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(OMNI_ROUTES, ["omni", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/omni|--message|--audio|text-only/i);
|
||||
});
|
||||
@@ -21,7 +22,10 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: omni(DashScope 媒体)",
|
||||
() => {
|
||||
test("omni --list-voices 输出音色列表并退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["omni", "--list-voices"]);
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(OMNI_ROUTES, [
|
||||
"omni",
|
||||
"--list-voices",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toMatch(/Omni output voices:/);
|
||||
expect(stdout).toMatch(/Tina/);
|
||||
@@ -30,13 +34,17 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("omni 缺少 --message 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["omni", "--model", "qwen3.5-omni-flash"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(OMNI_ROUTES, [
|
||||
"omni",
|
||||
"--model",
|
||||
"qwen3.5-omni-flash",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--message|Usage:/i);
|
||||
});
|
||||
|
||||
test("omni --audio 无法识别扩展名时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(OMNI_ROUTES, [
|
||||
"omni",
|
||||
"--model",
|
||||
"qwen3.5-omni-flash",
|
||||
@@ -51,7 +59,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("omni --dry-run --audio 构造 input_audio 而非 audio_url", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(OMNI_ROUTES, [
|
||||
"omni",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
@@ -91,7 +99,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
const clipText = "端到端Omni音频测试";
|
||||
const clipWav = join(outDir, "e2e-omni-input.wav");
|
||||
|
||||
const syn = await runCommandE2e([
|
||||
const syn = await runCommandE2e(OMNI_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
@@ -109,7 +117,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
]);
|
||||
expect(syn.exitCode, syn.stderr).toBe(0);
|
||||
|
||||
const omni = await runCommandE2e([
|
||||
const omni = await runCommandE2e(OMNI_ROUTES, [
|
||||
"omni",
|
||||
"--model",
|
||||
"qwen3.5-omni-flash",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { PIPELINE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: pipeline", () => {
|
||||
let tempDir: string;
|
||||
@@ -63,30 +64,29 @@ describe("e2e: pipeline", () => {
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("pipeline 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["pipeline"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/pipeline|run|validate/i);
|
||||
expect(out).toMatch(/Minimal workflow\.yaml|text\/chat|bl pipeline run workflow\.yaml/i);
|
||||
expect(out).toMatch(/Say hello in one short sentence|--dry-run --output json/i);
|
||||
});
|
||||
|
||||
test("pipeline run --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["pipeline", "run", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/pipeline run|--input|--input-file|--events|--concurrency/i);
|
||||
expect(stderr).not.toMatch(/--session-(?:dir|id)/i);
|
||||
});
|
||||
|
||||
test("pipeline validate --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["pipeline", "validate", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"validate",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/pipeline validate|workflow\.json|output json/i);
|
||||
});
|
||||
|
||||
test("pipeline validate --output json 校验合法 workflow", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -102,6 +102,7 @@ describe("e2e: pipeline", () => {
|
||||
|
||||
test("pipeline validate 使用 config 输出格式", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
PIPELINE_ROUTES,
|
||||
["pipeline", "validate", "--file", chatBasicPath],
|
||||
{
|
||||
DASHSCOPE_OUTPUT: "text",
|
||||
@@ -112,7 +113,7 @@ describe("e2e: pipeline", () => {
|
||||
});
|
||||
|
||||
test("pipeline validate 拒绝非法依赖 workflow", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"validate",
|
||||
"--file",
|
||||
@@ -128,13 +129,17 @@ describe("e2e: pipeline", () => {
|
||||
});
|
||||
|
||||
test("pipeline run 缺少 --file 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["pipeline", "run", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(2);
|
||||
expect(stderr).toMatch(/Usage: bl pipeline run --file <path>|--file/i);
|
||||
});
|
||||
|
||||
test("pipeline run --dry-run --output json 仅输出计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--file",
|
||||
@@ -168,6 +173,7 @@ describe("e2e: pipeline", () => {
|
||||
|
||||
test("pipeline run 使用 config 输出格式", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
PIPELINE_ROUTES,
|
||||
["pipeline", "run", "--file", chatBasicPath, "--input", '{"message":"hello"}', "--dry-run"],
|
||||
{ DASHSCOPE_OUTPUT: "text" },
|
||||
);
|
||||
@@ -177,7 +183,7 @@ describe("e2e: pipeline", () => {
|
||||
});
|
||||
|
||||
test("pipeline run --verbose 打印总步数和当前步骤序号", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--file",
|
||||
@@ -193,7 +199,7 @@ describe("e2e: pipeline", () => {
|
||||
});
|
||||
|
||||
test("pipeline run --events jsonl 在 dry-run 下输出生命周期事件", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--file",
|
||||
@@ -221,7 +227,7 @@ describe("e2e: pipeline", () => {
|
||||
});
|
||||
|
||||
test("pipeline run 拒绝未知 events format", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(PIPELINE_ROUTES, [
|
||||
"pipeline",
|
||||
"run",
|
||||
"--file",
|
||||
|
||||
@@ -5,17 +5,18 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { QUOTA_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: quota", () => {
|
||||
test("quota list --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "list", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "list", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--model");
|
||||
expect(stderr).toContain("--all");
|
||||
});
|
||||
|
||||
test("quota list --help 包含所有示例", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "list", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "list", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("bl quota list");
|
||||
expect(stderr).toContain("bl quota list --model qwen3.6-plus");
|
||||
@@ -23,21 +24,21 @@ describe("e2e: quota", () => {
|
||||
});
|
||||
|
||||
test("quota request --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "request", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "request", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--model");
|
||||
expect(stderr).toContain("--tpm");
|
||||
});
|
||||
|
||||
test("quota history --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "history", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "history", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--page");
|
||||
expect(stderr).toContain("--model");
|
||||
});
|
||||
|
||||
test("quota check --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "check", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "check", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--model");
|
||||
expect(stderr).toContain("--period");
|
||||
@@ -45,7 +46,12 @@ describe("e2e: quota", () => {
|
||||
});
|
||||
|
||||
test("quota check --period 0 报错最小值", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["quota", "check", "--period", "0.5"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--period",
|
||||
"0.5",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toContain("at least 1 minute");
|
||||
});
|
||||
@@ -53,7 +59,7 @@ describe("e2e: quota", () => {
|
||||
|
||||
describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
test("quota list --dry-run 输出请求参数", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"list",
|
||||
"--dry-run",
|
||||
@@ -73,7 +79,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota list --dry-run --all 不传 supports 过滤", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"list",
|
||||
"--all",
|
||||
@@ -89,13 +95,13 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota list 文本输出包含英文表头", async () => {
|
||||
const result = await runCommandE2e(["quota", "list", "--output", "text"]);
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, ["quota", "list", "--output", "text"]);
|
||||
if (isConsoleAuthFailure(result)) return;
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
});
|
||||
|
||||
test("quota list --model 指定模型返回结果", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"list",
|
||||
"--model",
|
||||
@@ -108,7 +114,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota list --model 不存在的模型报错", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"list",
|
||||
"--model",
|
||||
@@ -122,13 +128,13 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota list JSON 输出包含 model/rpm/tpm/maxTPM", async () => {
|
||||
const result = await runCommandE2e(["quota", "list", "--output", "json"]);
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, ["quota", "list", "--output", "json"]);
|
||||
if (isConsoleAuthFailure(result)) return;
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
});
|
||||
|
||||
test("quota request --dry-run 输出请求参数", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"request",
|
||||
"--model",
|
||||
@@ -150,7 +156,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota request TPM 超范围报错", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"request",
|
||||
"--model",
|
||||
@@ -166,7 +172,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota request 不支持提额的模型报错", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"request",
|
||||
"--model",
|
||||
@@ -180,7 +186,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota history --dry-run 输出请求参数", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"history",
|
||||
"--dry-run",
|
||||
@@ -198,7 +204,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check --dry-run 输出 API 信息", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--dry-run",
|
||||
@@ -215,7 +221,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check --dry-run --console-region 透传", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--dry-run",
|
||||
@@ -230,13 +236,13 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check 文本输出包含英文表头", async () => {
|
||||
const result = await runCommandE2e(["quota", "check", "--output", "text"]);
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, ["quota", "check", "--output", "text"]);
|
||||
if (isConsoleAuthFailure(result)) return;
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
});
|
||||
|
||||
test("quota check --model 指定单模型", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--model",
|
||||
@@ -249,7 +255,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check --model 逗号分隔多模型", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--model",
|
||||
@@ -262,7 +268,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota check JSON 输出包含用量和限额字段", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"check",
|
||||
"--model",
|
||||
@@ -275,7 +281,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
});
|
||||
|
||||
test("quota history --dry-run --page 2 --page-size 20", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
|
||||
"quota",
|
||||
"history",
|
||||
"--page",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { SEARCH_WEB_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
function pagesFromSearchWebStdout(stdout: string): Array<{ title?: string; url?: string }> {
|
||||
const envelope = parseStdoutJson<{ content?: Array<{ type?: string; text?: string }> }>(stdout);
|
||||
@@ -15,21 +16,19 @@ function pagesFromSearchWebStdout(stdout: string): Array<{ title?: string; url?:
|
||||
*/
|
||||
|
||||
describe("e2e: search web", () => {
|
||||
test("search 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["search"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/search|web/i);
|
||||
});
|
||||
|
||||
test("search web --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["search", "web", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SEARCH_WEB_ROUTES, [
|
||||
"search",
|
||||
"web",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/web|--query|list-tools|count/i);
|
||||
});
|
||||
|
||||
test("search web --dry-run --list-tools 无需 --query 也无需凭证即可干跑", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
SEARCH_WEB_ROUTES,
|
||||
["search", "web", "--dry-run", "--list-tools", "--output", "json"],
|
||||
{
|
||||
DASHSCOPE_API_KEY: undefined,
|
||||
@@ -44,13 +43,17 @@ describe("e2e: search web", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: search web", () => {
|
||||
test("search web 缺少 --query 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["search", "web", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SEARCH_WEB_ROUTES, [
|
||||
"search",
|
||||
"web",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--query|Usage:/i);
|
||||
});
|
||||
|
||||
test("search web --dry-run 仅输出计划且不调 MCP", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SEARCH_WEB_ROUTES, [
|
||||
"search",
|
||||
"web",
|
||||
"--dry-run",
|
||||
@@ -74,7 +77,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: search web", () => {
|
||||
});
|
||||
|
||||
test("联网搜索返回 JSON 且含搜索结果", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SEARCH_WEB_ROUTES, [
|
||||
"search",
|
||||
"web",
|
||||
"--query",
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, runCommandE2e } from "./helpers.ts";
|
||||
import { SPEECH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Speech list-voices E2E
|
||||
*/
|
||||
|
||||
describe("e2e: speech list-voices", () => {
|
||||
test("speech 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["speech"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/speech|synthesize|recognize/i);
|
||||
});
|
||||
|
||||
test("speech synthesize --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "synthesize", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/synthesize|--text|--voice|list-voices|model/i);
|
||||
});
|
||||
|
||||
test("speech recognize --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "recognize", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"recognize",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/recognize|--url|audio|model/i);
|
||||
});
|
||||
@@ -28,13 +30,17 @@ describe("e2e: speech list-voices", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: speech list-voices", () => {
|
||||
test("speech synthesize 缺少 --text 且非 --list-voices 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "synthesize", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--text|Usage:/i);
|
||||
});
|
||||
|
||||
test("【cosyvoice-v3-flash】获取音色列表", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--list-voices",
|
||||
|
||||
@@ -9,20 +9,19 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { SPEECH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Speech recognize:help / 分组不依赖密钥;识别流程需媒体 E2E + DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: speech recognize", () => {
|
||||
test("speech 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["speech"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/speech|synthesize|recognize/i);
|
||||
});
|
||||
|
||||
test("speech recognize --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "recognize", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"recognize",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/recognize|--url|model|audio/i);
|
||||
});
|
||||
@@ -32,7 +31,11 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: speech recognize(DashScope 媒体)",
|
||||
() => {
|
||||
test("speech recognize 缺少 --url 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "recognize", "--quiet"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"recognize",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--url|Usage:/i);
|
||||
});
|
||||
@@ -40,7 +43,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
test("【fun-asr】语音识别", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const outMp3 = join(outDir, "e2e-tts.mp3");
|
||||
const syn = await runCommandE2e([
|
||||
const syn = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
@@ -60,7 +63,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
expect(audioUrl?.startsWith("http")).toBe(true);
|
||||
|
||||
const asrJson = join(outDir, "e2e-asr.json");
|
||||
const rec = await runCommandE2e([
|
||||
const rec = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"recognize",
|
||||
"--model",
|
||||
|
||||
@@ -8,20 +8,19 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { SPEECH_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Speech synthesize:help / 分组不依赖密钥;合成本地需媒体 E2E + DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: speech synthesize", () => {
|
||||
test("speech 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["speech"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/speech|synthesize|recognize/i);
|
||||
});
|
||||
|
||||
test("speech synthesize --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["speech", "synthesize", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/synthesize|--text|--voice|model/i);
|
||||
});
|
||||
@@ -31,7 +30,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: speech synthesize(DashScope 媒体)",
|
||||
() => {
|
||||
test("speech synthesize 缺少 --text 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
@@ -44,7 +43,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("speech synthesize --dry-run 仅输出 request 且不调 TTS", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--dry-run",
|
||||
@@ -68,7 +67,7 @@ describe.skipIf(!isBailianE2EMediaEnabled() || !isDashScopeE2EReady())(
|
||||
test("【cosyvoice-v3-flash】语音合成", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const outMp3 = join(outDir, "e2e-tts.mp3");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SPEECH_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
||||
import { TEXT_CHAT_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Text chat:help / 分组不依赖密钥;对话需 DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: text chat", () => {
|
||||
test("text 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["text"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/text|chat/i);
|
||||
});
|
||||
|
||||
test("text chat --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["text", "chat", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(TEXT_CHAT_ROUTES, ["text", "chat", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/chat|--message|model|stream/i);
|
||||
});
|
||||
@@ -21,13 +16,18 @@ describe("e2e: text chat", () => {
|
||||
|
||||
describe.skipIf(!isDashScopeE2EReady())("e2e: text chat(DashScope)", () => {
|
||||
test("text chat 缺少 --message 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["text", "chat", "--model", "qwen3.7-max"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(TEXT_CHAT_ROUTES, [
|
||||
"text",
|
||||
"chat",
|
||||
"--model",
|
||||
"qwen3.7-max",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--message|Usage:/i);
|
||||
});
|
||||
|
||||
test("text chat --dry-run 仅输出 request 且不调对话接口", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(TEXT_CHAT_ROUTES, [
|
||||
"text",
|
||||
"chat",
|
||||
"--dry-run",
|
||||
@@ -49,7 +49,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: text chat(DashScope)", () => {
|
||||
});
|
||||
|
||||
test("【qwen3.7-max】文本对话", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(TEXT_CHAT_ROUTES, [
|
||||
"text",
|
||||
"chat",
|
||||
"--model",
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 各 topic E2E 的最小路由(path → bailian-cli-commands export 名)。
|
||||
* 仅包含该 topic 测试会调用的 path,不维护全量产品 map。
|
||||
*/
|
||||
export type E2eRouteExports = Record<string, string>;
|
||||
|
||||
export const AUTH_ROUTES: E2eRouteExports = {
|
||||
"auth login": "authLogin",
|
||||
"auth status": "authStatus",
|
||||
"auth logout": "authLogout",
|
||||
};
|
||||
|
||||
export const TEXT_CHAT_ROUTES: E2eRouteExports = { "text chat": "textChat" };
|
||||
|
||||
export const CONFIG_ROUTES: E2eRouteExports = {
|
||||
"config show": "configShow",
|
||||
"config set": "configSet",
|
||||
};
|
||||
|
||||
export const MEMORY_ROUTES: E2eRouteExports = {
|
||||
"memory add": "memoryAdd",
|
||||
"memory search": "memorySearch",
|
||||
"memory list": "memoryList",
|
||||
"memory update": "memoryUpdate",
|
||||
"memory delete": "memoryDelete",
|
||||
"memory profile create": "memoryProfileCreate",
|
||||
"memory profile get": "memoryProfileGet",
|
||||
};
|
||||
|
||||
export const KNOWLEDGE_ROUTES: E2eRouteExports = {
|
||||
"knowledge retrieve": "knowledgeRetrieve",
|
||||
"knowledge search": "knowledgeSearch",
|
||||
"knowledge chat": "knowledgeChat",
|
||||
};
|
||||
|
||||
export const KNOWLEDGE_SEARCH_ROUTES: E2eRouteExports = {
|
||||
"knowledge search": "knowledgeSearch",
|
||||
};
|
||||
|
||||
export const KNOWLEDGE_CHAT_ROUTES: E2eRouteExports = {
|
||||
"knowledge chat": "knowledgeChat",
|
||||
};
|
||||
|
||||
export const IMAGE_ROUTES: E2eRouteExports = {
|
||||
"image generate": "imageGenerate",
|
||||
"image edit": "imageEdit",
|
||||
};
|
||||
|
||||
export const VIDEO_ROUTES: E2eRouteExports = {
|
||||
"video generate": "videoGenerate",
|
||||
"video edit": "videoEdit",
|
||||
"video ref": "videoRef",
|
||||
"video task get": "videoTaskGet",
|
||||
"video download": "videoDownload",
|
||||
};
|
||||
|
||||
export const SPEECH_ROUTES: E2eRouteExports = {
|
||||
"speech synthesize": "speechSynthesize",
|
||||
"speech recognize": "speechRecognize",
|
||||
};
|
||||
|
||||
export const MCP_ROUTES: E2eRouteExports = {
|
||||
"mcp call": "mcpCall",
|
||||
"mcp list": "mcpList",
|
||||
"mcp tools": "mcpTools",
|
||||
};
|
||||
|
||||
export const SEARCH_WEB_ROUTES: E2eRouteExports = { "search web": "searchWeb" };
|
||||
|
||||
export const PIPELINE_ROUTES: E2eRouteExports = {
|
||||
"pipeline run": "pipelineRun",
|
||||
"pipeline validate": "pipelineValidate",
|
||||
};
|
||||
|
||||
export const OMNI_ROUTES: E2eRouteExports = { omni: "textOmni" };
|
||||
|
||||
export const FILE_UPLOAD_ROUTES: E2eRouteExports = { "file upload": "fileUpload" };
|
||||
|
||||
export const ADVISOR_ROUTES: E2eRouteExports = { "advisor recommend": "advisorRecommend" };
|
||||
|
||||
export const QUOTA_ROUTES: E2eRouteExports = {
|
||||
"quota list": "quotaList",
|
||||
"quota request": "quotaRequest",
|
||||
"quota history": "quotaHistory",
|
||||
"quota check": "quotaCheck",
|
||||
};
|
||||
|
||||
export const USAGE_ROUTES: E2eRouteExports = {
|
||||
"usage free": "usageFree",
|
||||
"usage freetier": "usageFreetier",
|
||||
"usage stats": "usageStats",
|
||||
};
|
||||
|
||||
export const DEPLOY_ROUTES: E2eRouteExports = {
|
||||
"deploy create": "deployCreate",
|
||||
"deploy list": "deployList",
|
||||
"deploy get": "deployGet",
|
||||
"deploy models": "deployModels",
|
||||
"deploy scale": "deployScale",
|
||||
"deploy update": "deployUpdate",
|
||||
"deploy delete": "deployDelete",
|
||||
};
|
||||
|
||||
export const DATASET_ROUTES: E2eRouteExports = {
|
||||
"dataset upload": "datasetUpload",
|
||||
"dataset list": "datasetList",
|
||||
"dataset get": "datasetGet",
|
||||
"dataset delete": "datasetDelete",
|
||||
"dataset validate": "datasetValidate",
|
||||
};
|
||||
|
||||
export const FINETUNE_ROUTES: E2eRouteExports = {
|
||||
"finetune create": "finetuneCreate",
|
||||
"finetune list": "finetuneList",
|
||||
"finetune get": "finetuneGet",
|
||||
"finetune cancel": "finetuneCancel",
|
||||
"finetune delete": "finetuneDelete",
|
||||
"finetune logs": "finetuneLogs",
|
||||
"finetune checkpoints": "finetuneCheckpoints",
|
||||
"finetune export": "finetuneExport",
|
||||
"finetune watch": "finetuneWatch",
|
||||
"finetune capability": "finetuneCapability",
|
||||
};
|
||||
|
||||
export const CONSOLE_FLAGS_DRY_RUN_ROUTES: E2eRouteExports = {
|
||||
"console call": "consoleCall",
|
||||
"mcp list": "mcpList",
|
||||
"quota check": "quotaCheck",
|
||||
};
|
||||
@@ -5,23 +5,17 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { USAGE_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: usage free", () => {
|
||||
test("usage 分组展示子命令帮助且退出码为 0", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["usage"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const out = `${stdout}\n${stderr}`;
|
||||
expect(out).toMatch(/usage|free|freetier/i);
|
||||
});
|
||||
|
||||
test("usage free --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["usage", "free", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, ["usage", "free", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|quota|free-tier/i);
|
||||
});
|
||||
|
||||
test("usage free --help 包含所有示例", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["usage", "free", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, ["usage", "free", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("bl usage free");
|
||||
expect(stderr).toContain("bl usage free --model qwen3-max");
|
||||
@@ -31,7 +25,7 @@ describe("e2e: usage free", () => {
|
||||
|
||||
describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
test("usage free --dry-run --model 输出请求参数不发起调用", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--dry-run",
|
||||
@@ -50,7 +44,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --dry-run --model 逗号分隔多个模型", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--dry-run",
|
||||
@@ -67,7 +61,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --dry-run --model 重复模型名自动去重", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--dry-run",
|
||||
@@ -84,7 +78,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --dry-run --model 逗号间有空格也能正确解析", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--dry-run",
|
||||
@@ -101,7 +95,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --dry-run 不指定 --model 传全量模型列表", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--dry-run",
|
||||
@@ -112,7 +106,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 单模型查询返回 JSON 结果", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -125,7 +119,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 单模型文本输出包含表头", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -138,7 +132,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 文本输出包含模型名", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -151,7 +145,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 逗号分隔多模型文本输出包含所有模型", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -164,7 +158,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 文本输出包含正确的 Type 列", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -177,7 +171,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model quotaStatus 为 UNKNOWN 时 Auto-Stop 显示 Unsupported", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -190,7 +184,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model quotaStatus 为 UNKNOWN 时额度显示为 -", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -203,7 +197,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model 不存在的模型仍返回表格行", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -216,7 +210,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model Auto-Stop 显示 ON、OFF 或 Unsupported", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
@@ -229,7 +223,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage free --model --console-region cn-beijing 指定区域查询", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"free",
|
||||
"--model",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { USAGE_ROUTES } from "./topic-routes.ts";
|
||||
import { readConfigFile } from "bailian-cli-core";
|
||||
|
||||
function getStaticWorkspaceId(): string | undefined {
|
||||
@@ -25,7 +26,7 @@ async function fetchDefaultWorkspaceId(): Promise<string> {
|
||||
const staticId = getStaticWorkspaceId();
|
||||
if (staticId) return staticId;
|
||||
|
||||
const result = await runCommandE2e(["workspace", "list", "--output", "json"]);
|
||||
const result = await runCommandE2e(USAGE_ROUTES, ["workspace", "list", "--output", "json"]);
|
||||
if (isConsoleAuthFailure(result) || result.exitCode !== 0) return FALLBACK_WORKSPACE_ID;
|
||||
try {
|
||||
const parsed = JSON.parse(result.stdout);
|
||||
@@ -41,13 +42,13 @@ async function fetchDefaultWorkspaceId(): Promise<string> {
|
||||
|
||||
describe("e2e: usage stats", () => {
|
||||
test("usage stats --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["usage", "stats", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, ["usage", "stats", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--model|--days|stats/i);
|
||||
});
|
||||
|
||||
test("usage stats --help 包含所有示例", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["usage", "stats", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, ["usage", "stats", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("bl usage stats");
|
||||
expect(stderr).toContain("bl usage stats --model qwen-turbo");
|
||||
@@ -55,7 +56,7 @@ describe("e2e: usage stats", () => {
|
||||
});
|
||||
|
||||
test("usage stats --help 包含 --workspace-id 选项", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["usage", "stats", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, ["usage", "stats", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--workspace-id");
|
||||
});
|
||||
@@ -71,7 +72,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --dry-run 概览模式输出请求参数", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -100,7 +101,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --dry-run --days 30 时间跨度约 30 天", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -122,7 +123,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --dry-run --model 指定模型使用 list API", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -144,7 +145,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --dry-run --type Text 传递 obsModelType", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -163,7 +164,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats 概览模式返回 JSON 结果", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -176,7 +177,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats 概览文本输出包含英文标签", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -189,7 +190,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats 概览文本输出包含 Token 用量", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -202,7 +203,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --model 单模型文本输出包含英文表头", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -217,7 +218,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --model 逗号分隔多模型返回多行", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -232,7 +233,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --model 不存在的模型返回空表格", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -247,7 +248,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --days 1 短时间范围正常返回", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
@@ -262,7 +263,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
});
|
||||
|
||||
test("usage stats --type Vision 按类型过滤", async () => {
|
||||
const result = await runCommandE2e([
|
||||
const result = await runCommandE2e(USAGE_ROUTES, [
|
||||
"usage",
|
||||
"stats",
|
||||
"--workspace-id",
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/** dry-run 占位 UUID */
|
||||
const PLACEHOLDER_TASK_ID = "00000000-0000-4000-8000-000000000001";
|
||||
@@ -20,14 +21,8 @@ const PLACEHOLDER_TASK_ID = "00000000-0000-4000-8000-000000000001";
|
||||
*/
|
||||
|
||||
describe("e2e: video download", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video download --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "download", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, ["video", "download", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/download|--task-id|--out/i);
|
||||
});
|
||||
@@ -37,7 +32,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: video download(DashScope 视频)",
|
||||
() => {
|
||||
test("video download 缺少 --task-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"download",
|
||||
"--out",
|
||||
@@ -48,7 +43,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("video download 缺少 --out 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"download",
|
||||
"--task-id",
|
||||
@@ -61,7 +56,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
test("video download --dry-run 仅输出计划且不下载", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const fakeOut = join(outDir, "e2e-dry-not-written.mp4");
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"download",
|
||||
"--dry-run",
|
||||
@@ -83,7 +78,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const genMp4 = join(outDir, "e2e-gen-for-download.mp4");
|
||||
|
||||
const gen = await runCommandE2e([
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -111,7 +106,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
expect(existsSync(genMp4)).toBe(true);
|
||||
|
||||
const downloadMp4 = join(outDir, "e2e-download.mp4");
|
||||
const dl = await runCommandE2e([
|
||||
const dl = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"download",
|
||||
...cliTimeoutPrefix(),
|
||||
|
||||
@@ -9,26 +9,21 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Video edit E2E
|
||||
*/
|
||||
|
||||
describe("e2e: video edit", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video edit --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "edit", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, ["video", "edit", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/edit|--video|--prompt|model|--async|--concurrent/i);
|
||||
});
|
||||
|
||||
test("video edit --dry-run 接受 --async 与 --concurrent", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"edit",
|
||||
"--dry-run",
|
||||
@@ -54,7 +49,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: video edit(DashScope 视频)",
|
||||
() => {
|
||||
test("video edit 缺少 --video 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"edit",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -71,7 +66,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const t2vPath = join(outDir, "e2e-video-t2v.mp4");
|
||||
|
||||
const t2v = await runCommandE2e([
|
||||
const t2v = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -88,7 +83,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const t2vData = parseStdoutJson<{ status?: string; video_url?: string }>(t2v.stdout);
|
||||
expect(t2vData.status).toBe("SUCCEEDED");
|
||||
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"edit",
|
||||
...cliTimeoutPrefix(),
|
||||
|
||||
@@ -9,20 +9,15 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Video generate (i2v):help / 分组不依赖密钥;长任务需视频 E2E + DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: video generate (i2v)", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video generate --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "generate", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, ["video", "generate", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/generate|--prompt|--image|model/i);
|
||||
});
|
||||
@@ -32,7 +27,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: video generate (i2v)(DashScope 视频)",
|
||||
() => {
|
||||
test("video generate 缺少 --prompt 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -46,7 +41,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("video generate --dry-run(无 --image)仅输出 request(t2v 路径不调上传)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -69,7 +64,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
test("【happyhorse-1.1-i2v】图片生成视频", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const png = join(outDir, "e2e-gen.png");
|
||||
const gen = await runCommandE2e([
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
@@ -87,7 +82,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const genData = parseStdoutJson<{ saved?: string[] }>(gen.stdout);
|
||||
const imagePath = genData.saved?.[0] ?? png;
|
||||
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
|
||||
@@ -9,20 +9,15 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Video generate (t2v):help / 分组不依赖密钥;长任务需视频 E2E + DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: video generate (t2v)", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video generate --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "generate", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, ["video", "generate", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/generate|--prompt|--model|download|image/i);
|
||||
});
|
||||
@@ -32,7 +27,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: video generate (t2v)(DashScope 视频)",
|
||||
() => {
|
||||
test("video generate 缺少 --prompt 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -44,7 +39,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("video generate --dry-run(无 --image)仅输出 request 且不调生成接口", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
@@ -66,7 +61,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
test("【happyhorse-1.1-t2v】文本生成视频", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
|
||||
@@ -9,26 +9,21 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
/**
|
||||
* Video ref (r2v):help / 分组不依赖密钥;参考生成需视频 E2E + DashScope。
|
||||
*/
|
||||
|
||||
describe("e2e: video ref (r2v)", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video ref --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "ref", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, ["video", "ref", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/ref|--prompt|--image|model|--async|--concurrent/i);
|
||||
});
|
||||
|
||||
test("video ref --dry-run 接受 --async 与 --concurrent", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
"--dry-run",
|
||||
@@ -54,7 +49,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"e2e: video ref (r2v)(DashScope 视频)",
|
||||
() => {
|
||||
test("video ref 缺少 --prompt 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -68,7 +63,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("video ref 缺少 --image 与 --ref-video 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
@@ -83,7 +78,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
|
||||
test("【happyhorse-1.1-r2v】视频参考生成", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const gen = await runCommandE2e([
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
@@ -102,7 +97,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const imagePath = genData.saved?.[0];
|
||||
expect(imagePath).toBeTruthy();
|
||||
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
parseStdoutJson,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { VIDEO_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
const taskId = process.env.BAILIAN_E2E_VIDEO_TASK_ID?.trim();
|
||||
|
||||
@@ -13,14 +14,13 @@ const taskId = process.env.BAILIAN_E2E_VIDEO_TASK_ID?.trim();
|
||||
*/
|
||||
|
||||
describe("e2e: video task get", () => {
|
||||
test("video 分组展示子命令帮助且成功退出", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(["video"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/video|generate|edit|ref|task|download/i);
|
||||
});
|
||||
|
||||
test("video task get --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(["video", "task", "get", "--help"]);
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"task",
|
||||
"get",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/task|get|--task-id/i);
|
||||
});
|
||||
@@ -30,7 +30,7 @@ describe.skipIf(!isBailianE2EEnabled() || !taskId || !isDashScopeE2EReady())(
|
||||
"e2e: video task get(DashScope)",
|
||||
() => {
|
||||
test("video task get 缺少 --task-id 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"task",
|
||||
"get",
|
||||
@@ -44,7 +44,7 @@ describe.skipIf(!isBailianE2EEnabled() || !taskId || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("video task get --dry-run 仅回显 task_id 且不调任务接口", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"task",
|
||||
"get",
|
||||
@@ -60,7 +60,7 @@ describe.skipIf(!isBailianE2EEnabled() || !taskId || !isDashScopeE2EReady())(
|
||||
});
|
||||
|
||||
test("根据 task_id 查询任务状态", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e([
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"task",
|
||||
"get",
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"./runner": "./src/run-subprocess.ts",
|
||||
"./output": "./src/output.ts",
|
||||
"./global-setup": "./src/global-setup.ts",
|
||||
"./monorepo-root": "./src/monorepo-root.ts"
|
||||
"./monorepo-root": "./src/monorepo-root.ts",
|
||||
"./registry-smoke": "./src/registry-smoke.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"bailian-cli-core": "workspace:*"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 从产品 commands map 的 path key 推导有子命令的分组前缀。
|
||||
* 供 cli / kscli registry smoke 共用。
|
||||
*/
|
||||
export function deriveGroupPaths(commandPaths: string[]): string[] {
|
||||
const groups = new Set<string>();
|
||||
for (const path of commandPaths) {
|
||||
const parts = path.split(" ");
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const prefix = parts.slice(0, i).join(" ");
|
||||
const hasChildren = commandPaths.some(
|
||||
(candidate) => candidate.startsWith(`${prefix} `) && candidate !== prefix,
|
||||
);
|
||||
if (hasChildren) groups.add(prefix);
|
||||
}
|
||||
}
|
||||
return [...groups].sort();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import {
|
||||
configShow,
|
||||
configSet,
|
||||
update,
|
||||
knowledgeRetrieve,
|
||||
knowledgeSearch,
|
||||
knowledgeChat,
|
||||
} from "bailian-cli-commands";
|
||||
|
||||
// kscli (Knowledge Studio CLI): lightweight RAG product. Ships config/update
|
||||
// plus the knowledge commands, remapped to flat paths. Routing is driven
|
||||
// entirely by these keys, and usage/examples/errors render the path from the
|
||||
// key — so the same shared command shows `kscli search` here and
|
||||
// `bl knowledge search` in bl.
|
||||
export const commands: Record<string, AnyCommand> = {
|
||||
"config show": configShow,
|
||||
"config set": configSet,
|
||||
update,
|
||||
retrieve: knowledgeRetrieve,
|
||||
search: knowledgeSearch,
|
||||
chat: knowledgeChat,
|
||||
};
|
||||
@@ -1,29 +1,7 @@
|
||||
import { createCli } from "bailian-cli-runtime";
|
||||
import type { AnyCommand } from "bailian-cli-core";
|
||||
import {
|
||||
configShow,
|
||||
configSet,
|
||||
update,
|
||||
knowledgeRetrieve,
|
||||
knowledgeSearch,
|
||||
knowledgeChat,
|
||||
} from "bailian-cli-commands";
|
||||
import { commands } from "./commands.ts";
|
||||
import pkg from "../package.json" with { type: "json" };
|
||||
|
||||
// kscli (Knowledge Studio CLI): lightweight RAG product. Ships config/update
|
||||
// plus the knowledge commands, remapped to flat paths. Routing is driven
|
||||
// entirely by these keys, and usage/examples/errors render the path from the
|
||||
// key — so the same shared command shows `kscli search` here and
|
||||
// `bl knowledge search` in bl.
|
||||
const commands: Record<string, AnyCommand> = {
|
||||
"config show": configShow,
|
||||
"config set": configSet,
|
||||
update,
|
||||
retrieve: knowledgeRetrieve,
|
||||
search: knowledgeSearch,
|
||||
chat: knowledgeChat,
|
||||
};
|
||||
|
||||
void createCli(commands, {
|
||||
binName: "kscli",
|
||||
version: pkg.version,
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { runNodeMain } from "e2e/runner";
|
||||
import { deriveGroupPaths } from "e2e/registry-smoke";
|
||||
import pkg from "../../package.json" with { type: "json" };
|
||||
import { commands } from "../../src/commands.ts";
|
||||
import { runKscli } from "./helpers.ts";
|
||||
|
||||
const kscliPaths = ["config show", "config set", "update", "retrieve", "search", "chat"] as const;
|
||||
|
||||
const harnessMainTs = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
"../../../commands/tests/e2e/harness/main.ts",
|
||||
);
|
||||
const commandsPackageRoot = join(dirname(fileURLToPath(import.meta.url)), "../../../commands");
|
||||
|
||||
/** 提取 Flags + Global Flags 段,忽略 Notes/Examples 中的产品 bin 差异 */
|
||||
function extractFlagSections(text: string): string {
|
||||
const start = text.indexOf("Flags:");
|
||||
if (start < 0) return text.trim();
|
||||
const notesIdx = text.indexOf("\nNotes:", start);
|
||||
const examplesIdx = text.indexOf("\nExamples:", start);
|
||||
const endCandidates = [notesIdx, examplesIdx].filter((idx) => idx >= 0);
|
||||
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : text.length;
|
||||
return text.slice(start, end).trim();
|
||||
}
|
||||
const commandPaths = Object.keys(commands).sort();
|
||||
const groupPaths = deriveGroupPaths(commandPaths);
|
||||
|
||||
describe("e2e: kscli registry smoke", () => {
|
||||
test.each(kscliPaths)("已注册命令 %s --help 成功", async (path) => {
|
||||
test("根帮助展示 kscli 与全局 flag", async () => {
|
||||
const { stderr, exitCode } = await runKscli(["--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/\bkscli\b/i);
|
||||
expect(stderr).toMatch(/--base-url/);
|
||||
expect(stderr).not.toMatch(/^\s*--region\s/m);
|
||||
});
|
||||
|
||||
test("--version 输出产品名与版本", async () => {
|
||||
const { stdout, exitCode } = await runKscli(["--version"]);
|
||||
expect(exitCode).toBe(0);
|
||||
expect(stdout.trim()).toBe(`kscli ${pkg.version}`);
|
||||
});
|
||||
|
||||
test("search --help 展示 kscli 路径与 knowledge 必填 flag", async () => {
|
||||
const { stderr, exitCode } = await runKscli(["search", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/kscli search/i);
|
||||
expect(stderr).toMatch(/--query/i);
|
||||
expect(stderr).toMatch(/--agent-id/i);
|
||||
expect(stderr).toMatch(/--workspace-id/i);
|
||||
expect(stderr).not.toMatch(/bl knowledge search/i);
|
||||
});
|
||||
|
||||
test("search 缺少 --query 时报用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runKscli(["search", "--agent-id", "aid_test"]);
|
||||
expect(exitCode).toBe(2);
|
||||
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("search --help flag 与 harness knowledge search --help 一致", async () => {
|
||||
const [kscliHelp, harnessHelp] = await Promise.all([
|
||||
runKscli(["search", "--help"]),
|
||||
runNodeMain(harnessMainTs, ["knowledge", "search", "--help"], {
|
||||
cwd: commandsPackageRoot,
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(kscliHelp.exitCode, kscliHelp.stderr).toBe(0);
|
||||
expect(harnessHelp.exitCode, harnessHelp.stderr).toBe(0);
|
||||
|
||||
expect(extractFlagSections(kscliHelp.stderr)).toBe(extractFlagSections(harnessHelp.stderr));
|
||||
test.each(groupPaths)("命令分组 %s --help 成功", async (path) => {
|
||||
const { stderr, exitCode } = await runKscli([...path.split(" "), "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { runCommandE2e } from "../../commands/tests/e2e/helpers.ts";
|
||||
import { runNodeMain } from "e2e/runner";
|
||||
|
||||
const runtimeRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const harnessMainTs = join(runtimeRoot, "tests/harness/cross-domain-main.ts");
|
||||
|
||||
async function runRuntimeHarness(args: string[]) {
|
||||
return runNodeMain(harnessMainTs, args, { cwd: runtimeRoot });
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域 flag 拒绝:runtime 凭证域与命令 flag 解析边界。
|
||||
* 跨域 flag 拒绝:runtime 凭证域与命令 flag 解析边界(synthetic command,不依赖 commands)。
|
||||
*/
|
||||
|
||||
describe("e2e: console global flags (cross-domain rejection)", () => {
|
||||
test("跨域 flag 拒绝:model 命令传 --console-region 报 Unknown flag", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runRuntimeHarness([
|
||||
"text",
|
||||
"chat",
|
||||
"--message",
|
||||
@@ -21,7 +29,7 @@ describe("e2e: console global flags (cross-domain rejection)", () => {
|
||||
});
|
||||
|
||||
test("跨域 flag 拒绝:console 命令传 --api-key 报 Unknown flag", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e([
|
||||
const { stderr, exitCode } = await runRuntimeHarness([
|
||||
"mcp",
|
||||
"list",
|
||||
"--api-key",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { defineCommand } from "bailian-cli-core";
|
||||
import { createCli } from "bailian-cli-runtime";
|
||||
import pkg from "../../package.json" with { type: "json" };
|
||||
|
||||
const noop = async () => {};
|
||||
|
||||
/** apiKey 域 stub:用于跨域 flag 拒绝测试 */
|
||||
const textChat = defineCommand({
|
||||
description: "runtime e2e stub",
|
||||
auth: "apiKey",
|
||||
flags: {
|
||||
message: { type: "string", valueHint: "<m>", description: "message" },
|
||||
},
|
||||
run: noop,
|
||||
});
|
||||
|
||||
/** console 域 stub:用于跨域 flag 拒绝测试 */
|
||||
const mcpList = defineCommand({
|
||||
description: "runtime e2e stub",
|
||||
auth: "console",
|
||||
flags: {},
|
||||
run: noop,
|
||||
});
|
||||
|
||||
void createCli(
|
||||
{ "text chat": textChat, "mcp list": mcpList },
|
||||
{
|
||||
binName: "bl",
|
||||
version: pkg.version,
|
||||
clientName: "runtime-e2e",
|
||||
npmPackage: "bailian-cli-runtime-e2e-harness",
|
||||
},
|
||||
).run();
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Advisory:对比 bl 产品 map 与 E2E harness map 是否指向同一 command 实例。
|
||||
* 不一致时打印 warning,不阻塞测试。
|
||||
*/
|
||||
import { commands as cliCommands } from "../packages/cli/src/commands.ts";
|
||||
import { e2eCommandMap } from "../packages/commands/tests/e2e/harness/e2e-command-map.ts";
|
||||
|
||||
const cliPaths = new Set(Object.keys(cliCommands));
|
||||
const e2ePaths = new Set(Object.keys(e2eCommandMap));
|
||||
|
||||
let hasWarning = false;
|
||||
|
||||
for (const path of cliPaths) {
|
||||
if (!e2ePaths.has(path)) {
|
||||
console.warn(`[advisory] cli map path missing in e2e map: ${path}`);
|
||||
hasWarning = true;
|
||||
} else if (cliCommands[path] !== e2eCommandMap[path]) {
|
||||
console.warn(`[advisory] command instance mismatch for path: ${path}`);
|
||||
hasWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const path of e2ePaths) {
|
||||
if (!cliPaths.has(path)) {
|
||||
console.warn(`[advisory] e2e map path missing in cli map: ${path}`);
|
||||
hasWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasWarning) {
|
||||
console.log("[advisory] cli commands map and e2e harness map are aligned.");
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
Reference in New Issue
Block a user