2026-08-16 19:49:23 +08:00
|
|
|
|
import { describe, expect, test } from "vite-plus/test";
|
|
|
|
|
|
import { isDashScopeE2EReady, parseStdoutJson, runCommandE2e } from "./helpers.ts";
|
|
|
|
|
|
import { PERMISSION_ROUTES } from "./topic-routes.ts";
|
|
|
|
|
|
|
|
|
|
|
|
describe("e2e: permission", () => {
|
|
|
|
|
|
test("permission list --help 正常退出", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
"--help",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
expect(stderr).toContain("--scope");
|
|
|
|
|
|
expect(stderr).toContain("--model");
|
|
|
|
|
|
expect(stderr).toContain("--name");
|
|
|
|
|
|
expect(stderr).toContain("--page-size");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --help 正常退出", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--help",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
expect(stderr).toContain("--model");
|
|
|
|
|
|
expect(stderr).toContain("--action");
|
|
|
|
|
|
expect(stderr).toContain("--all");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission revoke --help 正常退出", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"revoke",
|
|
|
|
|
|
"--help",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
expect(stderr).toContain("--model");
|
|
|
|
|
|
expect(stderr).toContain("--yes");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant 缺少 --model/--all 报用法错误", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--quiet",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("one of --model / --all");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --all 与 --model 互斥", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--all",
|
|
|
|
|
|
"--model",
|
|
|
|
|
|
"qwen-plus",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("cannot be combined");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --action 非法值报错", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--model",
|
|
|
|
|
|
"qwen-plus",
|
|
|
|
|
|
"--action",
|
|
|
|
|
|
"training",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("invalid");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --all 仅支持 inference", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--all",
|
|
|
|
|
|
"--action",
|
|
|
|
|
|
"finetune",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("only supports the inference action");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --model 超过 20 个报错", async () => {
|
|
|
|
|
|
const tooMany = Array.from({ length: 21 }, (_, index) => `model-${index}`).join(",");
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--model",
|
|
|
|
|
|
tooMany,
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("at most 20");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission revoke --all 缺 --yes 拒绝执行", async () => {
|
2026-08-16 23:48:19 +08:00
|
|
|
|
// --yes 护栏在 run() 开头、任何网络调用之前抛出;带 dummy key 让用例不依赖环境凭证(否则 auth stage 先报 AUTH(3))。
|
2026-08-16 19:49:23 +08:00
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"revoke",
|
|
|
|
|
|
"--all",
|
2026-08-16 23:48:19 +08:00
|
|
|
|
"--api-key",
|
|
|
|
|
|
"e2e-dummy-key",
|
2026-08-16 19:49:23 +08:00
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode).toBe(2);
|
|
|
|
|
|
expect(stderr).toContain("Refusing");
|
|
|
|
|
|
expect(stderr).toContain("--yes");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// --dry-run 跳过 auth stage(见 runtime middleware),无需凭证即可断言请求形状。
|
|
|
|
|
|
// 不传 --output:permission 命令组默认 JSON 输出。
|
|
|
|
|
|
test("permission list --dry-run 输出 GET 请求(默认 AUTHORIZABLE + JSON)", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{
|
|
|
|
|
|
endpoint?: string;
|
|
|
|
|
|
method?: string;
|
|
|
|
|
|
query?: { authorization_scope?: string; page_no?: number; page_size?: number };
|
|
|
|
|
|
}>(stdout);
|
|
|
|
|
|
expect(data.endpoint).toContain("/api/v1/models/permissions");
|
|
|
|
|
|
expect(data.method).toBe("GET");
|
|
|
|
|
|
expect(data.query?.authorization_scope).toBe("AUTHORIZABLE");
|
|
|
|
|
|
expect(data.query?.page_no).toBe(1);
|
|
|
|
|
|
expect(data.query?.page_size).toBe(20);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission list --scope authorized --dry-run 透传 scope", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
"--scope",
|
|
|
|
|
|
"authorized",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{ query?: { authorization_scope?: string } }>(stdout);
|
|
|
|
|
|
expect(data.query?.authorization_scope).toBe("AUTHORIZED");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --dry-run 输出逐模型 POST 请求体(默认 JSON)", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--model",
|
|
|
|
|
|
"qwen-plus,qwen3-max",
|
|
|
|
|
|
"--action",
|
|
|
|
|
|
"inference,finetune",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{
|
|
|
|
|
|
endpoint?: string;
|
|
|
|
|
|
method?: string;
|
|
|
|
|
|
request?: { models?: { model?: string; inference?: boolean; finetune?: boolean }[] };
|
|
|
|
|
|
}>(stdout);
|
|
|
|
|
|
expect(data.endpoint).toContain("/api/v1/models/permissions");
|
|
|
|
|
|
expect(data.method).toBe("POST");
|
|
|
|
|
|
expect(data.request?.models?.length).toBe(2);
|
|
|
|
|
|
expect(data.request?.models?.[0]).toEqual({
|
|
|
|
|
|
model: "qwen-plus",
|
|
|
|
|
|
inference: true,
|
|
|
|
|
|
finetune: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission revoke --dry-run 输出取消授权请求体", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"revoke",
|
|
|
|
|
|
"--model",
|
|
|
|
|
|
"qwen-plus",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
"--output",
|
|
|
|
|
|
"json",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{
|
|
|
|
|
|
request?: { models?: { model?: string; inference?: boolean }[] };
|
|
|
|
|
|
}>(stdout);
|
|
|
|
|
|
expect(data.request?.models?.[0]).toEqual({ model: "qwen-plus", inference: false });
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission grant --all --dry-run 输出一键授权 OPEN", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"grant",
|
|
|
|
|
|
"--all",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
"--output",
|
|
|
|
|
|
"json",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{ request?: { access_all_entities?: string } }>(stdout);
|
|
|
|
|
|
expect(data.request?.access_all_entities).toBe("OPEN");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission revoke --all --dry-run 免 --yes 输出 CLOSE", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"revoke",
|
|
|
|
|
|
"--all",
|
|
|
|
|
|
"--dry-run",
|
|
|
|
|
|
"--output",
|
|
|
|
|
|
"json",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{ request?: { access_all_entities?: string } }>(stdout);
|
|
|
|
|
|
expect(data.request?.access_all_entities).toBe("CLOSE");
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 真实调用 GET /api/v1/models/permissions。grant/revoke 只测 --dry-run——live POST
|
|
|
|
|
|
// 会真实改写业务空间的模型授权,不做 e2e。
|
|
|
|
|
|
describe.skipIf(!isDashScopeE2EReady())("e2e: permission(DashScope)", () => {
|
|
|
|
|
|
test("permission list JSON 输出返回授权列表(默认 JSON)", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{ items?: unknown[]; total?: number }>(stdout);
|
|
|
|
|
|
expect(Array.isArray(data.items)).toBe(true);
|
|
|
|
|
|
expect(typeof data.total).toBe("number");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission list --scope authorizable 分页生效", async () => {
|
|
|
|
|
|
const { stdout, stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
"--scope",
|
|
|
|
|
|
"authorizable",
|
|
|
|
|
|
"--page-size",
|
|
|
|
|
|
"5",
|
|
|
|
|
|
"--output",
|
|
|
|
|
|
"json",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
const data = parseStdoutJson<{
|
|
|
|
|
|
items?: { model?: string; permissions?: Record<string, unknown> }[];
|
|
|
|
|
|
total?: number;
|
|
|
|
|
|
}>(stdout);
|
|
|
|
|
|
expect(data.items?.length).toBeLessThanOrEqual(5);
|
|
|
|
|
|
expect(data.total).toBeGreaterThan(0);
|
|
|
|
|
|
expect(data.items?.[0]).toHaveProperty("permissions");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("permission list 文本输出正常退出", async () => {
|
|
|
|
|
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
|
|
|
|
|
"permission",
|
|
|
|
|
|
"list",
|
|
|
|
|
|
"--scope",
|
|
|
|
|
|
"authorizable",
|
|
|
|
|
|
"--output",
|
|
|
|
|
|
"text",
|
|
|
|
|
|
]);
|
|
|
|
|
|
expect(exitCode, stderr).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|