feat: add usage/quota/workspace cli command

This commit is contained in:
故璃
2026-06-10 16:04:39 +08:00
parent 006ea23a62
commit dd56b04569
21 changed files with 3156 additions and 33 deletions
+349
View File
@@ -0,0 +1,349 @@
import { describe, expect, test } from "vite-plus/test";
import { isBailianE2EEnabled, parseStdoutJson, runCli } from "./helpers.ts";
import { readConfigFile } from "bailian-cli-core";
function isConsoleE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true;
try {
const config = readConfigFile();
return typeof config.access_token === "string" && config.access_token.length > 0;
} catch {
return false;
}
}
describe("e2e: quota", () => {
test("quota list --help 正常退出", async () => {
const { stderr, exitCode } = await runCli(["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 runCli(["quota", "list", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("bl quota list");
expect(stderr).toContain("bl quota list --model qwen3.6-plus");
expect(stderr).toContain("bl quota list --all");
});
test("quota request --help 正常退出", async () => {
const { stderr, exitCode } = await runCli(["quota", "request", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("--model");
expect(stderr).toContain("--tpm");
expect(stderr).toContain("--yes");
});
test("quota history --help 正常退出", async () => {
const { stderr, exitCode } = await runCli(["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 runCli(["quota", "check", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("--model");
expect(stderr).toContain("--period");
expect(stderr).toContain("bl quota check");
});
test("quota check --period 0 报错最小值", async () => {
const { stderr, exitCode } = await runCli(["quota", "check", "--period", "0.5"]);
expect(exitCode).toBe(1);
expect(stderr).toContain("at least 1 minute");
});
});
describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
test("quota list --dry-run 输出请求参数", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"list",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: {
input?: { queryQpmInfo?: boolean; supports?: { selfServiceLimitIncrease?: boolean } };
};
}>(stdout);
expect(data.api).toContain("listFoundationModels");
expect(data.data?.input?.queryQpmInfo).toBe(true);
expect(data.data?.input?.supports?.selfServiceLimitIncrease).toBe(true);
});
test("quota list --dry-run --all 不传 supports 过滤", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"list",
"--all",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { input?: { supports?: unknown } };
}>(stdout);
expect(data.data?.input?.supports).toBeUndefined();
});
test("quota list 文本输出包含双行表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"list",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("模型");
expect(stdout).toContain("Model");
expect(stdout).toContain("RPM");
expect(stdout).toContain("TPM");
expect(stdout).toContain("可设上限 TPM");
expect(stdout).toContain("Max TPM");
});
test("quota list --model 指定模型返回结果", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"list",
"--model",
"qwen3.6-plus",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toMatch(/共 1 个模型/);
});
test("quota list --model 不存在的模型报错", async () => {
const { stderr, exitCode } = await runCli([
"quota",
"list",
"--model",
"nonexistent-model-xyz-99999",
"--output",
"text",
]);
expect(exitCode).toBe(1);
expect(stderr).toContain("no matching models found");
});
test("quota list JSON 输出包含 qpmInfo", async () => {
const { stdout, stderr, exitCode } = await runCli(["quota", "list", "--output", "json"]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<Array<{ model?: string; qpmInfo?: unknown }>>(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBeGreaterThan(0);
expect(data[0].model).toBeTypeOf("string");
expect(data[0].qpmInfo).toBeDefined();
});
test("quota request --dry-run 输出请求参数", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"request",
"--model",
"qwen3.6-plus",
"--tpm",
"6000000",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: { input?: { model?: string; limit?: { usage_limit?: number } } };
}>(stdout);
expect(data.api).toContain("updateFoundationModelLimits");
expect(data.data?.input?.model).toBe("qwen3.6-plus");
expect(data.data?.input?.limit?.usage_limit).toBeTypeOf("number");
});
test("quota request TPM 超范围报错", async () => {
const { stderr, exitCode } = await runCli([
"quota",
"request",
"--model",
"qwen3.6-plus",
"--tpm",
"999",
]);
expect(exitCode).toBe(1);
expect(stderr).toContain("out of range");
expect(stderr).toContain("Current");
expect(stderr).toContain("Range");
});
test("quota request 不支持提额的模型报错", async () => {
const { stderr, exitCode } = await runCli([
"quota",
"request",
"--model",
"nonexistent-model-xyz-99999",
"--tpm",
"100000",
]);
expect(exitCode).toBe(1);
expect(stderr).toContain("not found");
});
test("quota history --dry-run 输出请求参数", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"history",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: { input?: { pageNo?: number; pageSize?: number } };
}>(stdout);
expect(data.api).toContain("listModelLimitApplications");
expect(data.data?.input?.pageNo).toBe(1);
expect(data.data?.input?.pageSize).toBe(10);
});
test("quota check --dry-run 输出 API 信息", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ apis?: string[] }>(stdout);
expect(data.apis).toContain(
"zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels",
);
expect(data.apis).toContain("zeldaEasy.bailian-telemetry.monitor.getMonitorData");
});
test("quota check 文本输出包含双行表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("模型");
expect(stdout).toContain("Model");
expect(stdout).toContain("RPM 用量/限额");
expect(stdout).toContain("RPM Usage/Limit");
expect(stdout).toContain("TPM 用量/限额");
expect(stdout).toContain("状态");
});
test("quota check --model 指定单模型", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--model",
"qwen3.6-plus",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toMatch(/共 1 个模型/);
});
test("quota check --model 逗号分隔多模型", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--model",
"qwen3.6-plus,qwen-plus",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toContain("qwen-plus");
expect(stdout).toMatch(/共 2 个模型/);
});
test("quota check JSON 输出包含用量和限额字段", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--model",
"qwen3.6-plus",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<
Array<{
model?: string;
rpmUsage?: number;
rpmLimit?: number;
tpmUsage?: number;
tpmLimit?: number;
}>
>(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBe(1);
expect(data[0].model).toBe("qwen3.6-plus");
expect(data[0].rpmUsage).toBeTypeOf("number");
expect(data[0].rpmLimit).toBeTypeOf("number");
expect(data[0].tpmUsage).toBeTypeOf("number");
expect(data[0].tpmLimit).toBeTypeOf("number");
});
test("quota check 状态列显示正常/接近限流/已限流之一", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--model",
"qwen3.6-plus",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
const hasStatus =
stdout.includes("正常") || stdout.includes("接近限流") || stdout.includes("已限流");
expect(hasStatus).toBe(true);
});
test("quota history --dry-run --page 2 --page-size 20", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"history",
"--page",
"2",
"--page-size",
"20",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { input?: { pageNo?: number; pageSize?: number } };
}>(stdout);
expect(data.data?.input?.pageNo).toBe(2);
expect(data.data?.input?.pageSize).toBe(20);
});
});
@@ -0,0 +1,282 @@
import { describe, expect, test } from "vite-plus/test";
import { isBailianE2EEnabled, parseStdoutJson, runCli } from "./helpers.ts";
import { readConfigFile } from "bailian-cli-core";
function isConsoleE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true;
try {
const config = readConfigFile();
return typeof config.access_token === "string" && config.access_token.length > 0;
} catch {
return false;
}
}
describe("e2e: usage free", () => {
test("usage 分组展示子命令帮助且退出码为 0", async () => {
const { stdout, stderr, exitCode } = await runCli(["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 runCli(["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 runCli(["usage", "free", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("bl usage free");
expect(stderr).toContain("bl usage free --model qwen3-max");
expect(stderr).toContain("bl usage free --model qwen3-max,qwen-turbo");
});
});
describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
test("usage free --dry-run --model 输出请求参数不发起调用", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--dry-run",
"--model",
"qwen3-max",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: { queryFreeTierQuotaRequest?: { models?: string[] } };
}>(stdout);
expect(data.api).toContain("queryFreeTierQuota");
expect(data.data?.queryFreeTierQuotaRequest?.models).toEqual(["qwen3-max"]);
});
test("usage free --dry-run --model 逗号分隔多个模型", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--dry-run",
"--model",
"qwen3-max,qwen-turbo",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { queryFreeTierQuotaRequest?: { models?: string[] } };
}>(stdout);
expect(data.data?.queryFreeTierQuotaRequest?.models).toEqual(["qwen3-max", "qwen-turbo"]);
});
test("usage free --dry-run --model 重复模型名自动去重", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--dry-run",
"--model",
"qwen3-max,qwen3-max,qwen-turbo",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { queryFreeTierQuotaRequest?: { models?: string[] } };
}>(stdout);
expect(data.data?.queryFreeTierQuotaRequest?.models).toEqual(["qwen3-max", "qwen-turbo"]);
});
test("usage free --dry-run --model 逗号间有空格也能正确解析", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--dry-run",
"--model",
"qwen3-max, qwen-turbo",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { queryFreeTierQuotaRequest?: { models?: string[] } };
}>(stdout);
expect(data.data?.queryFreeTierQuotaRequest?.models).toEqual(["qwen3-max", "qwen-turbo"]);
});
test("usage free --dry-run 不指定 --model 传全量模型列表", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { queryFreeTierQuotaRequest?: { models?: string[] } };
}>(stdout);
const models = data.data?.queryFreeTierQuotaRequest?.models ?? [];
expect(models.length).toBeGreaterThan(0);
});
test("usage free --model 单模型查询返回 JSON 结果", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
code?: string;
successResponse?: boolean;
}>(stdout);
expect(data.code).toBe("200");
expect(data.successResponse).toBe(true);
});
test("usage free --model 单模型文本输出包含表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("Model");
expect(stdout).toContain("Type");
expect(stdout).toContain("Remaining/Total");
expect(stdout).toContain("Usage");
expect(stdout).toContain("Expires");
expect(stdout).toContain("Auto-Stop");
});
test("usage free --model 文本输出包含模型名", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3-max");
});
test("usage free --model 逗号分隔多模型文本输出包含所有模型", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max,qwen-turbo",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3-max");
expect(stdout).toContain("qwen-turbo");
});
test("usage free --model 文本输出包含正确的 Type 列", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("Text");
});
test("usage free --model quotaStatus 为 UNKNOWN 时 Auto-Stop 显示 Unsupported", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"wan2.7-image",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("Unsupported");
});
test("usage free --model quotaStatus 为 UNKNOWN 时额度显示为 -", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"wan2.7-image",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
const lines = stdout.split("\n").filter((line) => line.includes("wan2.7-image"));
expect(lines.length).toBe(1);
expect(lines[0]).toContain("Vision");
expect(lines[0]).toContain("Unsupported");
});
test("usage free --model 不存在的模型仍返回表格行", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"nonexistent-model-xyz-12345",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("nonexistent-model-xyz-12345");
});
test("usage free --model Auto-Stop 显示 ON、OFF 或 Unsupported", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
const hasAutoStop =
stdout.includes("ON") || stdout.includes("OFF") || stdout.includes("Unsupported");
expect(hasAutoStop).toBe(true);
});
test("usage free --model --region cn-beijing 指定区域查询", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--region",
"cn-beijing",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ code?: string }>(stdout);
expect(data.code).toBe("200");
});
});
@@ -0,0 +1,299 @@
import { describe, expect, test } from "vite-plus/test";
import { isBailianE2EEnabled, parseStdoutJson, runCli } from "./helpers.ts";
import { readConfigFile } from "bailian-cli-core";
function isConsoleE2EReady(): boolean {
if (!isBailianE2EEnabled()) return false;
if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true;
try {
const config = readConfigFile();
return typeof config.access_token === "string" && config.access_token.length > 0;
} catch {
return false;
}
}
function getStaticWorkspaceId(): string | undefined {
if (process.env.BAILIAN_WORKSPACE_ID?.trim()) return process.env.BAILIAN_WORKSPACE_ID.trim();
try {
const config = readConfigFile();
if (config.workspace_id) return config.workspace_id;
} catch {}
return undefined;
}
async function fetchDefaultWorkspaceId(): Promise<string> {
const staticId = getStaticWorkspaceId();
if (staticId) return staticId;
const { stdout } = await runCli(["workspace", "list", "--output", "json"]);
const result = JSON.parse(stdout);
const data = result?.data?.DataV2?.data?.data?.data ?? [];
const defaultWs = data.find((ws: { defaultAgent?: boolean }) => ws.defaultAgent);
if (defaultWs?.workspaceId) return defaultWs.workspaceId;
if (data.length > 0 && data[0].workspaceId) return data[0].workspaceId;
throw new Error("No workspace found for e2e tests");
}
describe("e2e: usage stats", () => {
test("usage stats --help 正常退出", async () => {
const { stderr, exitCode } = await runCli(["usage", "stats", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--model|--days|stats/i);
});
test("usage stats --help 包含所有示例", async () => {
const { stderr, exitCode } = await runCli(["usage", "stats", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("bl usage stats");
expect(stderr).toContain("bl usage stats --model qwen-turbo");
expect(stderr).toContain("bl usage stats --days 30");
});
test("usage stats --help 包含 --workspace-id 选项", async () => {
const { stderr, exitCode } = await runCli(["usage", "stats", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toContain("--workspace-id");
});
});
describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
let wsId: string;
test("获取默认 workspace-id", async () => {
wsId = await fetchDefaultWorkspaceId();
expect(wsId).toBeTypeOf("string");
expect(wsId.length).toBeGreaterThan(0);
});
test("usage stats --dry-run 概览模式输出请求参数", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: {
reqDTO?: {
startTime?: number;
endTime?: number;
modelCallSource?: string;
filterWorkspaceId?: string;
};
};
}>(stdout);
expect(data.api).toContain("getModelUsageStatistic");
expect(data.data?.reqDTO?.modelCallSource).toBe("Online");
expect(data.data?.reqDTO?.startTime).toBeTypeOf("number");
expect(data.data?.reqDTO?.endTime).toBeTypeOf("number");
expect(data.data?.reqDTO?.filterWorkspaceId).toBe(wsId);
});
test("usage stats --dry-run --days 30 时间跨度约 30 天", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--dry-run",
"--days",
"30",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { reqDTO?: { startTime?: number; endTime?: number } };
}>(stdout);
const span = (data.data?.reqDTO?.endTime ?? 0) - (data.data?.reqDTO?.startTime ?? 0);
const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
expect(span).toBeGreaterThan(thirtyDaysMs - 5000);
expect(span).toBeLessThan(thirtyDaysMs + 5000);
});
test("usage stats --dry-run --model 指定模型使用 list API", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--dry-run",
"--model",
"qwen-turbo",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
data?: { reqDTO?: { model?: string; filterWorkspaceId?: string } };
}>(stdout);
expect(data.api).toContain("listModelUsageStatisticData");
expect(data.data?.reqDTO?.model).toBe("qwen-turbo");
expect(data.data?.reqDTO?.filterWorkspaceId).toBe(wsId);
});
test("usage stats --dry-run --type Text 传递 obsModelType", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--dry-run",
"--type",
"Text",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
data?: { reqDTO?: { obsModelType?: string } };
}>(stdout);
expect(data.data?.reqDTO?.obsModelType).toBe("Text");
});
test("usage stats 概览模式返回 JSON 结果", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
code?: string;
successResponse?: boolean;
}>(stdout);
expect(data.code).toBe("200");
expect(data.successResponse).toBe(true);
});
test("usage stats 概览文本输出包含中英文表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("时间范围 Period:");
expect(stdout).toContain("调用模型数");
expect(stdout).toContain("Models Called");
expect(stdout).toContain("调用成功次数");
expect(stdout).toContain("Successful Calls");
});
test("usage stats 概览文本输出包含 Token 用量", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("总 Token");
expect(stdout).toContain("Total Tokens");
expect(stdout).toContain("[tokens]");
});
test("usage stats --model 单模型文本输出包含双行表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--model",
"qwen3.6-plus",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("模型");
expect(stdout).toContain("Model");
expect(stdout).toContain("调用次数");
expect(stdout).toContain("Calls");
expect(stdout).toContain("qwen3.6-plus");
});
test("usage stats --model 逗号分隔多模型返回多行", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--model",
"qwen3.6-plus,deepseek-v4-pro",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toContain("deepseek-v4-pro");
expect(stdout).toMatch(/共 2 个模型/);
});
test("usage stats --model 不存在的模型返回空表格", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--model",
"nonexistent-model-xyz-99999",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("No usage data found");
});
test("usage stats --days 1 短时间范围正常返回", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--days",
"1",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("(1 天)");
expect(stdout).toContain("调用模型数");
});
test("usage stats --type Vision 按类型过滤", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"stats",
"--workspace-id",
wsId,
"--type",
"Vision",
"--output",
"text",
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("时间范围 Period:");
});
});