feat: opt json output & reduce Chinese table name

This commit is contained in:
故璃
2026-06-16 11:39:16 +08:00
parent 3f29f93ef5
commit 14fc293ef6
9 changed files with 227 additions and 165 deletions
+11 -18
View File
@@ -64,9 +64,9 @@ function formatRatio(usage: number, limit: number): string {
function getStatus(usage: number, limit: number): string {
if (limit <= 0) return "-";
const pct = (usage / limit) * 100;
if (pct >= 100) return "已限流";
if (pct >= 80) return "接近限流";
return "正常";
if (pct >= 100) return "Throttled";
if (pct >= 80) return "Near Limit";
return "Normal";
}
function getNestedRecord(
@@ -193,7 +193,6 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
const yellow = noColor ? (t: string) => t : (t: string) => `\x1b[33m${t}\x1b[0m`;
const red = noColor ? (t: string) => t : (t: string) => `\x1b[31m${t}\x1b[0m`;
const headersCn = ["模型", "RPM 用量/限额", "TPM 用量/限额", "状态"];
const headersEn = ["Model", "RPM Usage/Limit", "TPM Usage/Limit", "Status"];
const tableRows = rows.map((r) => {
@@ -215,36 +214,30 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
return;
}
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...tableRows.map((r) => displayWidth(r.cells[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...tableRows.map((r) => displayWidth(r.cells[col]))),
);
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(headerLine + "\n");
process.stdout.write(separator + "\n");
const statusCol = 3;
for (const r of tableRows) {
const cells = r.cells.map((cell, col) => {
if (col === statusCol) {
if (cell === "已限流") return red(padEnd(cell, widths[col]));
if (cell === "接近限流") return yellow(padEnd(cell, widths[col]));
if (cell === "正常") return green(padEnd(cell, widths[col]));
if (cell === "Throttled") return red(padEnd(cell, widths[col]));
if (cell === "Near Limit") return yellow(padEnd(cell, widths[col]));
if (cell === "Normal") return green(padEnd(cell, widths[col]));
}
return padEnd(cell, widths[col]);
});
process.stdout.write(cells.join(" ") + "\n");
}
process.stdout.write(dim(`\n共 ${rows.length} 个模型 (Total: ${rows.length})`) + "\n");
process.stdout.write(dim(`\nTotal: ${rows.length} models`) + "\n");
}
export default defineCommand({
+15 -17
View File
@@ -65,7 +65,6 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
const headersCn = ["模型", "Token 账号限流", "申请时间"];
const headersEn = ["Model", "Token Limit", "Applied At"];
const rows = records.map((r) => [
@@ -74,27 +73,21 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
formatDateTime(r.gmtCreate),
]);
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...rows.map((row) => displayWidth(row[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
);
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(headerLine + "\n");
process.stdout.write(separator + "\n");
for (const row of rows) {
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
}
process.stdout.write(dim(`\n共 ${total} 条记录 (Total: ${total})`) + "\n");
process.stdout.write(dim(`\nTotal: ${total} records`) + "\n");
}
export default defineCommand({
@@ -161,11 +154,6 @@ export default defineCommand({
throw err;
}
if (format === "json") {
emitResult(result, format);
return;
}
const resp = extractResponseData(result as Record<string, unknown>);
let records = (resp.records as LimitApplicationItem[]) ?? [];
const total = (resp.items as number) ?? records.length;
@@ -174,6 +162,16 @@ export default defineCommand({
records = records.filter((r) => r.deployedModel === modelFilter);
}
if (format === "json") {
const items = records.map((r) => ({
model: r.deployedModel,
tokenLimit: r.usageLimit,
appliedAt: formatDateTime(r.gmtCreate),
}));
emitResult({ records: items, total: modelFilter ? records.length : total }, format);
return;
}
if (records.length === 0) {
process.stdout.write("No quota change history found.\n");
return;
+24 -13
View File
@@ -108,7 +108,6 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
const headersCn = ["模型", "RPM", "TPM", "可设上限 TPM"];
const headersEn = ["Model", "Req/min", "Token/min", "Max TPM"];
const rows = models.map((m) => {
@@ -135,27 +134,21 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
return;
}
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...rows.map((row) => displayWidth(row[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
);
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(headerLine + "\n");
process.stdout.write(separator + "\n");
for (const row of rows) {
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
}
process.stdout.write(dim(`\n共 ${models.length} 个模型 (Total: ${models.length})`) + "\n");
process.stdout.write(dim(`\nTotal: ${models.length} models`) + "\n");
}
export default defineCommand({
@@ -221,7 +214,25 @@ export default defineCommand({
}
if (format === "json") {
emitResult(models, format);
const items = models.map((m) => {
const qpm = m.qpmInfo;
const modelDefault = qpm?.["model-default"];
const userSpec = qpm?.["user-spec"];
const defaultRPM = calculateRPM(modelDefault);
const defaultTPM = calculateTPM(modelDefault);
const currentRPM = calculateRPM(userSpec, modelDefault?.count_limit_period) || defaultRPM;
const currentTPM = calculateTPM(userSpec, modelDefault?.usage_limit_period) || defaultTPM;
const maxTPM = defaultTPM * 2;
return {
model: m.model,
rpm: currentRPM > 0 ? currentRPM : null,
tpm: currentTPM > 0 ? currentTPM : null,
maxTPM: maxTPM > 0 ? maxTPM : null,
};
});
emitResult(items, format);
return;
}
+37 -19
View File
@@ -78,7 +78,6 @@ function printTable(
typeMap: Map<string, string>,
noColor: boolean,
): void {
const headersCn = ["模型", "类型", "剩余/总量", "使用率", "过期时间", "用完即停"];
const headersEn = ["Model", "Type", "Remaining/Total", "Usage", "Expires", "Auto-Stop"];
const rows = quotas.map((quota) => {
@@ -102,12 +101,8 @@ function printTable(
];
});
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...rows.map((row) => displayWidth(row[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
);
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
@@ -115,12 +110,10 @@ function printTable(
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
const yellow = noColor ? (text: string) => text : (text: string) => `\x1b[33m${text}\x1b[0m`;
const autoStopCol = headersCn.length - 1;
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const autoStopCol = headersEn.length - 1;
const enLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(separator + "\n");
@@ -296,11 +289,6 @@ export default defineCommand({
}),
]);
if (format === "json") {
emitResult(quotaResult, format);
return;
}
const allQuotas = extractQuotas(quotaResult);
let quotas = modelFlag
? allQuotas
@@ -321,14 +309,44 @@ export default defineCommand({
quotas.sort((a, b) => (a.quotaValidityPeriod ?? 0) - (b.quotaValidityPeriod ?? 0));
}
const stopStatuses = extractFreeTierOnlyStatuses(stopResult);
const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly]));
if (format === "json") {
const items = quotas.map((quota) => {
const hasQuota = quota.quotaInitTotal != null && quota.quotaTotal != null;
const used = hasQuota ? quota.quotaInitTotal - quota.quotaTotal : 0;
const stopStatus = stopMap.get(quota.model);
const autoStop =
quota.quotaStatus === "UNKNOWN"
? "unsupported"
: stopStatus === true
? true
: stopStatus === false
? false
: null;
return {
model: quota.model,
type: typeMap.get(quota.model) || null,
remaining: hasQuota ? quota.quotaTotal : null,
total: hasQuota ? quota.quotaInitTotal : null,
usagePercent:
hasQuota && quota.quotaInitTotal > 0
? Math.round((used / quota.quotaInitTotal) * 1000) / 10
: null,
expires: quota.quotaValidityPeriod ? formatDate(quota.quotaValidityPeriod) : null,
autoStop,
};
});
emitResult(items, format);
return;
}
if (quotas.length === 0) {
process.stdout.write("No free-tier quota found.\n");
return;
}
const stopStatuses = extractFreeTierOnlyStatuses(stopResult);
const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly]));
printTable(quotas, stopMap, typeMap, config.noColor);
},
});
+71 -47
View File
@@ -155,33 +155,32 @@ function resolveUsageMap(item: ModelStatisticItem): Record<string, number> {
}
interface UsageLabel {
cn: string;
en: string;
unit?: string;
}
const USAGE_KEY_LABELS: Record<string, UsageLabel> = {
total_token: { cn: "总 Token", en: "Total Tokens", unit: "tokens" },
input_token: { cn: "输入 Token", en: "Input Tokens", unit: "tokens" },
output_token: { cn: "输出 Token", en: "Output Tokens", unit: "tokens" },
input_token_cache: { cn: "缓存 Token", en: "Cached Tokens", unit: "tokens" },
input_token_cache_read: { cn: "缓存读取", en: "Cache Read", unit: "tokens" },
input_token_cache_creation: { cn: "缓存创建", en: "Cache Creation", unit: "tokens" },
thinking_input_token: { cn: "思考输入", en: "Thinking Input", unit: "tokens" },
thinking_output_token: { cn: "思考输出", en: "Thinking Output", unit: "tokens" },
text_input_token: { cn: "文本输入", en: "Text Input", unit: "tokens" },
purein_text_output_token: { cn: "文本输出", en: "Text Output", unit: "tokens" },
embedding_token: { cn: "向量", en: "Embedding", unit: "tokens" },
image_number: { cn: "图片数", en: "Images", unit: "张" },
video_duration: { cn: "视频时长", en: "Video Duration", unit: "秒" },
content_duration: { cn: "音频时长", en: "Audio Duration", unit: "秒" },
tts_text_number: { cn: "语音合成", en: "TTS Chars", unit: "字符" },
total_token_avg: { cn: "平均 Token/次", en: "Avg Tokens/Req" },
total_token: { en: "Total Tokens", unit: "tokens" },
input_token: { en: "Input Tokens", unit: "tokens" },
output_token: { en: "Output Tokens", unit: "tokens" },
input_token_cache: { en: "Cached Tokens", unit: "tokens" },
input_token_cache_read: { en: "Cache Read", unit: "tokens" },
input_token_cache_creation: { en: "Cache Creation", unit: "tokens" },
thinking_input_token: { en: "Thinking Input", unit: "tokens" },
thinking_output_token: { en: "Thinking Output", unit: "tokens" },
text_input_token: { en: "Text Input", unit: "tokens" },
purein_text_output_token: { en: "Text Output", unit: "tokens" },
embedding_token: { en: "Embedding", unit: "tokens" },
image_number: { en: "Images", unit: "images" },
video_duration: { en: "Video Duration", unit: "seconds" },
content_duration: { en: "Audio Duration", unit: "seconds" },
tts_text_number: { en: "TTS Chars", unit: "chars" },
total_token_avg: { en: "Avg Tokens/Req" },
};
function formatLabel(label: UsageLabel): string {
const unitSuffix = label.unit ? ` [${label.unit}]` : "";
return `${label.cn} (${label.en})${unitSuffix}`;
return `${label.en}${unitSuffix}`;
}
function printOverview(
@@ -195,12 +194,12 @@ function printOverview(
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
process.stdout.write(
`${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`,
`${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
);
const rows: [string, string][] = [
["调用模型数 (Models Called)", formatNumber(stat.modelCount ?? 0)],
["调用成功次数 (Successful Calls)", formatNumber(stat.callSuccessCount ?? 0)],
["Models Called", formatNumber(stat.modelCount ?? 0)],
["Successful Calls", formatNumber(stat.callSuccessCount ?? 0)],
];
for (const usage of stat.usages ?? []) {
@@ -226,7 +225,7 @@ function printModelTable(
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
process.stdout.write(
`${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`,
`${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
);
if (items.length === 0) {
@@ -257,15 +256,6 @@ function printModelTable(
return (idxA === -1 ? 999 : idxA) - (idxB === -1 ? 999 : idxB);
});
const headersCn = [
"模型",
"调用次数",
...orderedKeys.map((key) => {
const label = USAGE_KEY_LABELS[key];
if (!label) return key;
return label.unit ? `${label.cn} [${label.unit}]` : label.cn;
}),
];
const headersEn = [
"Model",
"Calls",
@@ -280,20 +270,14 @@ function printModelTable(
}),
]);
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...rows.map((row) => displayWidth(row[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
);
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(headerLine + "\n");
process.stdout.write(separator + "\n");
for (const row of rows) {
@@ -301,7 +285,7 @@ function printModelTable(
process.stdout.write(cells.join(" ") + "\n");
}
process.stdout.write(dim(`\n共 ${items.length} 个模型 (Total: ${items.length})`) + "\n");
process.stdout.write(dim(`\nTotal: ${items.length} models`) + "\n");
}
export default defineCommand({
@@ -391,16 +375,31 @@ export default defineCommand({
);
const allItems: ModelStatisticItem[] = [];
const jsonResults: unknown[] = [];
for (const result of results) {
if (!result) continue;
jsonResults.push(result);
const listData = extractListData(result);
allItems.push(...listData.list);
}
if (format === "json") {
emitResult(jsonResults.length === 1 ? jsonResults[0] : jsonResults, format);
const items = allItems.map((item) => {
const usage = resolveUsageMap(item);
const clean: Record<string, unknown> = {
model: item.model,
successfulCalls: item.callSuccessCount ?? 0,
};
for (const [key, val] of Object.entries(usage)) {
clean[key] = val;
}
return clean;
});
emitResult(
{
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
items,
},
format,
);
return;
}
@@ -425,12 +424,37 @@ export default defineCommand({
process.exit(1);
}
const stat = extractOverviewData(result);
if (format === "json") {
emitResult(result, format);
if (!stat) {
emitResult(
{
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
modelsCalled: 0,
successfulCalls: 0,
},
format,
);
return;
}
emitResult(
{
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
modelsCalled: stat.modelCount ?? 0,
successfulCalls: stat.callSuccessCount ?? 0,
usages: (stat.usages ?? []).map((u) => ({
key: u.key,
value: u.value,
unit: u.unit,
label: USAGE_KEY_LABELS[u.key]?.en ?? u.key,
})),
},
format,
);
return;
}
const stat = extractOverviewData(result);
if (!stat) {
process.stdout.write("No usage data found.\n");
return;
+21 -21
View File
@@ -46,8 +46,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
const headersCn = ["空间名称", "Workspace ID", "默认空间"];
const headersEn = ["Name", "", "Default"];
const headersEn = ["Name", "Workspace ID", "Default"];
const rows = workspaces.map((ws) => [
ws.agentName,
@@ -55,20 +54,14 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
ws.defaultAgent ? "Yes" : "-",
]);
const widths = headersCn.map((label, col) =>
Math.max(
displayWidth(label),
displayWidth(headersEn[col]),
...rows.map((row) => displayWidth(row[col])),
),
const widths = headersEn.map((label, col) =>
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
);
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
process.stdout.write(cnLine + "\n");
process.stdout.write(enLine + "\n");
process.stdout.write(headerLine + "\n");
process.stdout.write(separator + "\n");
for (const row of rows) {
@@ -79,9 +72,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
process.stdout.write(cells.join(" ") + "\n");
}
process.stdout.write(
dim(`\n共 ${workspaces.length} 个空间 (Total: ${workspaces.length})`) + "\n",
);
process.stdout.write(dim(`\nTotal: ${workspaces.length} workspaces`) + "\n");
}
export default defineCommand({
@@ -117,21 +108,30 @@ export default defineCommand({
region,
});
if (format === "json") {
emitResult(result, format);
return;
}
const resp = extractResponseData(result as Record<string, unknown>);
const dataArr = resp.data as Record<string, unknown>[] | undefined;
if (!Array.isArray(dataArr) || dataArr.length === 0) {
process.stdout.write("No workspace found.\n");
if (format === "json") {
emitResult([], format);
} else {
process.stdout.write("No workspace found.\n");
}
return;
}
let workspaces = dataArr as unknown as WorkspaceInfo[];
if (limit > 0) workspaces = workspaces.slice(0, limit);
if (format === "json") {
const items = workspaces.map((ws) => ({
workspaceId: ws.workspaceId,
name: ws.agentName,
default: ws.defaultAgent,
}));
emitResult(items, format);
return;
}
printTable(workspaces, config.noColor);
},
});
+19 -18
View File
@@ -96,7 +96,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
expect(data.data?.input?.supports).toBeUndefined();
});
test("quota list 文本输出包含双行表头", async () => {
test("quota list 文本输出包含单行英⽂表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"list",
@@ -105,11 +105,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
"--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("Req/min");
expect(stdout).toContain("Token/min");
expect(stdout).toContain("Max TPM");
});
@@ -125,7 +123,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toMatch(/共 1 个模型/);
expect(stdout).toMatch(/Total: 1 models/);
});
test("quota list --model 不存在的模型报错", async () => {
@@ -141,14 +139,19 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
expect(stderr).toContain("no matching models found");
});
test("quota list JSON 输出包含 qpmInfo", async () => {
test("quota list JSON 输出包含 model/rpm/tpm/maxTPM", 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);
const data =
parseStdoutJson<
Array<{ model?: string; rpm?: number | null; tpm?: number | null; maxTPM?: number | null }>
>(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBeGreaterThan(0);
expect(data[0].model).toBeTypeOf("string");
expect(data[0].qpmInfo).toBeDefined();
expect(data[0].rpm).toBeTypeOf("number");
expect(data[0].tpm).toBeTypeOf("number");
expect(data[0].maxTPM).toBeTypeOf("number");
});
test("quota request --dry-run 输出请求参数", async () => {
@@ -235,7 +238,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
expect(data.apis).toContain("zeldaEasy.bailian-telemetry.monitor.getMonitorData");
});
test("quota check 文本输出包含双行表头", async () => {
test("quota check 文本输出包含单行英⽂表头", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
@@ -244,12 +247,10 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
"--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("状态");
expect(stdout).toContain("TPM Usage/Limit");
expect(stdout).toContain("Status");
});
test("quota check --model 指定单模型", async () => {
@@ -264,7 +265,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toMatch(/共 1 个模型/);
expect(stdout).toMatch(/Total: 1 models/);
});
test("quota check --model 逗号分隔多模型", async () => {
@@ -280,7 +281,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
expect(exitCode, stderr).toBe(0);
expect(stdout).toContain("qwen3.6-plus");
expect(stdout).toContain("qwen-plus");
expect(stdout).toMatch(/共 2 个模型/);
expect(stdout).toMatch(/Total: 2 models/);
});
test("quota check JSON 输出包含用量和限额字段", async () => {
@@ -311,7 +312,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
expect(data[0].tpmLimit).toBeTypeOf("number");
});
test("quota check 状态列显示正常/接近限流/已限流之一", async () => {
test("quota check 状态列显示 Normal/Near Limit/Throttled 之一", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
@@ -323,7 +324,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
]);
expect(exitCode, stderr).toBe(0);
const hasStatus =
stdout.includes("正常") || stdout.includes("接近限流") || stdout.includes("已限流");
stdout.includes("Normal") || stdout.includes("Near Limit") || stdout.includes("Throttled");
expect(hasStatus).toBe(true);
});
+19 -8
View File
@@ -133,12 +133,21 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
code?: string;
successResponse?: boolean;
}>(stdout);
expect(data.code).toBe("200");
expect(data.successResponse).toBe(true);
const data = parseStdoutJson<
Array<{
model?: string;
type?: string | null;
remaining?: number | null;
total?: number | null;
usagePercent?: number | null;
expires?: string | null;
autoStop?: boolean | string | null;
}>
>(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBeGreaterThan(0);
expect(data[0].model).toBe("qwen3-max");
expect(data[0].type).toBeTypeOf("string");
});
test("usage free --model 单模型文本输出包含表头", async () => {
@@ -276,7 +285,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ code?: string }>(stdout);
expect(data.code).toBe("200");
const data = parseStdoutJson<Array<{ model?: string }>>(stdout);
expect(Array.isArray(data)).toBe(true);
expect(data.length).toBeGreaterThan(0);
expect(data[0].model).toBe("qwen3-max");
});
});
+10 -4
View File
@@ -169,11 +169,17 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
code?: string;
successResponse?: boolean;
period?: { start?: string; end?: string; days?: number };
modelsCalled?: number;
successfulCalls?: number;
usages?: Array<{ key?: string; value?: number }>;
}>(stdout);
expect(data.code).toBe("200");
expect(data.successResponse).toBe(true);
expect(data.period).toBeDefined();
expect(data.period?.start).toBeTypeOf("string");
expect(data.period?.end).toBeTypeOf("string");
expect(data.period?.days).toBeTypeOf("number");
expect(data.modelsCalled).toBeTypeOf("number");
expect(data.successfulCalls).toBeTypeOf("number");
});
test("usage stats 概览文本输出包含中英文表头", async () => {