Files
modelstudioai__cli/packages/cli/tests/e2e/helpers.ts
T
zeyu.fz 6317da8454 feat(cli): 重构知识库检索命令,支持API-KEY和AK/SK鉴权
- 增加API-KEY鉴权路径,采用DashScope协议(snake_case)请求后端接口
- 保留AK/SK鉴权路径,但打印废弃警告,采用PascalCase请求后端
- 命令参数调整,新增dense-similarity-top-k、sparse-similarity-top-k等API-KEY专用选项
- 废弃部分旧参数如顶层top-k,提醒用户改用rerank-top-n
- 统一输出格式以及静默模式下文本结果的打印逻辑优化
- 添加相关类型定义,完善请求与响应结构的类型支持
- CLI端增加dry-run模式,展示实际请求参数与地址
- E2E测试覆盖API-KEY和AK/SK两条路径,包含帮助提示、错误场景及关键参数测试
- 更新依赖的核心包导出与接口,新增knowledgeRetrieveEndpoint方法接口调用
2026-06-08 18:43:50 +08:00

184 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { execFile } from "child_process";
import { mkdirSync, readFileSync } from "fs";
import { promisify } from "util";
import { basename, dirname, join } from "path";
import { fileURLToPath } from "url";
import { readConfigFile } from "bailian-cli-core";
const execFileAsync = promisify(execFile);
/**
* Vitest `global-setup.ts` 写入 `test/output/` 下本文件名,供各 worker 进程读取同一会话 id。
* (仅模块内变量无法跨 Vitest 多进程 worker 共享。)
*/
export const E2E_RUN_SESSION_FILENAME = ".e2e-run-session";
/**
* 单次 `vp test` / Vitest 运行共用的 E2E 输出会话目录名(惰性缓存于当前进程)。
*/
let e2eOutputSessionId: string | undefined;
/** `packages/cli` 根目录(含 `src/main.ts`) */
export const cliPackageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
const mainTs = join(cliPackageRoot, "src", "main.ts");
/** Monorepo 根(含根 `package.json`) */
export function monorepoRoot(): string {
return join(cliPackageRoot, "..", "..");
}
function readE2eRunSessionFromOutputDir(): string | undefined {
try {
const p = join(monorepoRoot(), "test", "output", E2E_RUN_SESSION_FILENAME);
const t = readFileSync(p, "utf8").trim();
return t.length > 0 ? t : undefined;
} catch {
return undefined;
}
}
function getE2eOutputSessionId(): string {
if (!e2eOutputSessionId) {
const fromEnv = process.env.BAILIAN_E2E_RUN_ID?.trim();
if (fromEnv) {
e2eOutputSessionId = fromEnv.replace(/[^a-zA-Z0-9._-]+/g, "-");
} else {
const fromFile = readE2eRunSessionFromOutputDir();
if (fromFile) {
e2eOutputSessionId = fromFile.replace(/[^a-zA-Z0-9._-]+/g, "-");
} else {
e2eOutputSessionId = `e2e-run-${Date.now()}-${process.pid}`;
}
}
}
return e2eOutputSessionId;
}
/**
* 在 `test/output/<会话>/` 下创建用例子目录。
* 会话 id 优先 `BAILIAN_E2E_RUN_ID`,否则读 Vitest globalSetup 写入的 `test/output/.e2e-run-session`,
* 再否则回退为单进程 id(非 Vitest 直接跑用例时)。
* 若已设 `BAILIAN_E2E_OUT` 则直接使用(不再套会话目录)。
*/
export function makeE2eOutputDir(label: string): string {
const fromEnv = process.env.BAILIAN_E2E_OUT?.trim();
if (fromEnv) {
mkdirSync(fromEnv, { recursive: true });
return fromEnv;
}
const safe = label.replace(/[^a-zA-Z0-9._-]+/g, "-");
const sessionDir = join(monorepoRoot(), "test", "output", getE2eOutputSessionId());
mkdirSync(sessionDir, { recursive: true });
const dir = join(sessionDir, `e2e-vp-${safe}-${Date.now()}`);
mkdirSync(dir, { recursive: true });
return dir;
}
/** 全局 `--timeout` 秒数(视频等长任务) */
export function cliTimeoutSeconds(): string {
return process.env.BAILIAN_E2E_TIMEOUT_SEC?.trim() || "3600";
}
export function cliTimeoutPrefix(): string[] {
return ["--timeout", cliTimeoutSeconds()];
}
/** 显式开启后才跑真实网络 E2E,避免默认 `vp test` 依赖密钥或打外网 */
export function isBailianE2EEnabled(): boolean {
return process.env.BAILIAN_E2E === "1";
}
/** 可调 DashScope 的 API Key:环境变量优先,否则读 ~/.bailian/config.json */
export function isDashScopeE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (process.env.DASHSCOPE_API_KEY?.trim()) return true;
try {
const f = readConfigFile();
return typeof f.api_key === "string" && f.api_key.length > 0;
} catch {
return false;
}
}
/** 语音与图像(可设 `BAILIAN_E2E_MEDIA=0` 在仅跑文本/记忆/知识库时跳过) */
export function isBailianE2EMediaEnabled(): boolean {
if (process.env.BAILIAN_E2E_MEDIA === "0") return false;
return isBailianE2EEnabled();
}
/** 文生视频 / 图生视频 / 参考视频 / 视频编辑(耗时长,默认关闭) */
export function isBailianE2EVideoEnabled(): boolean {
return isBailianE2EEnabled() && process.env.BAILIAN_E2E_VIDEO === "1";
}
/** 从 `import.meta.url` 生成 OUT 子目录标签,避免并行用例目录冲突 */
export function e2eLabelFromMetaUrl(metaUrl: string): string {
return basename(fileURLToPath(metaUrl), ".ts").replace(/\.e2e\.test$/, "");
}
/** 知识库用例:须显式索引 ID + API-KEY 或 AK/SK */
export function isKnowledgeE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (!process.env.BAILIAN_E2E_INDEX_ID) return false;
const hasApiKey = isDashScopeE2EReady();
const hasAkSk =
!!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID && !!process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
return hasApiKey || hasAkSk;
}
export function isKnowledgeAkSkReady(): boolean {
return (
isBailianE2EEnabled() &&
!!process.env.ALIBABA_CLOUD_ACCESS_KEY_ID &&
!!process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET &&
!!process.env.BAILIAN_E2E_INDEX_ID
);
}
export interface RunCliResult {
stdout: string;
stderr: string;
exitCode: number;
}
/**
* 子进程执行 CLI(等价于在 `packages/cli` 下 `node src/main.ts ...`)。
* request_id 等诊断信息在 stderr;`--output json` 时 JSON 在 stdout。
*/
export async function runCli(
args: string[],
envOverrides: NodeJS.ProcessEnv = {},
): Promise<RunCliResult> {
try {
const { stdout, stderr } = await execFileAsync("node", [mainTs, ...args], {
cwd: cliPackageRoot,
encoding: "utf8",
maxBuffer: 32 * 1024 * 1024,
env: {
...process.env,
NODE_NO_WARNINGS: "1",
DO_NOT_TRACK: "1",
...envOverrides,
},
});
return { stdout: stdout ?? "", stderr: stderr ?? "", exitCode: 0 };
} catch (err: unknown) {
const e = err as {
stdout?: string;
stderr?: string;
code?: number;
};
return {
stdout: e.stdout ?? "",
stderr: e.stderr ?? "",
exitCode: typeof e.code === "number" ? e.code : 1,
};
}
}
export function parseStdoutJson<T = unknown>(stdout: string): T {
const t = stdout.trim();
return JSON.parse(t) as T;
}