Files
ai-drama-platform/scripts/smoke-commercial-governance.mjs

101 lines
7.1 KiB
JavaScript

import assert from "node:assert/strict";
import { dbRun } from "../server/db.mjs";
const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
async function request(path, headers, options = {}) {
const response = await fetch(`${api}${path}`, {
...options,
headers: { "content-type": "application/json", ...(headers || {}), ...(options.headers || {}) }
});
const payload = await response.json().catch(() => ({}));
return { response, payload };
}
async function login(email) {
const result = await request("/api/auth/login", null, { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) });
assert.equal(result.response.ok, true, `${email} 登录失败`);
return { authorization: `Bearer ${result.payload.session.token}` };
}
const owner = await login("producer@local.test");
const writer = await login("writer@local.test");
const createdEmail = `commercial-governance-${Date.now()}@local.test`;
let createdUserId = "";
let originalBilling = null;
let originalCostCenter = null;
try {
const deniedCreate = await request("/api/system/users", writer, { method: "POST", body: JSON.stringify({ displayName: "越权用户", email: `denied-${Date.now()}@local.test` }) });
assert.equal(deniedCreate.response.status, 403, "普通用户不应手动创建全局账号");
const created = await request("/api/system/users", owner, {
method: "POST",
body: JSON.stringify({ displayName: "治理回归用户", email: createdEmail, organizationId: "org-studio-lab", workspaceId: "ws-local-aidrama", projectId: "thunder-mouth" })
});
assert.equal(created.response.status, 201, `系统管理员创建用户失败:${JSON.stringify(created.payload)}`);
assert.ok(created.payload.temporaryPassword, "留空密码时必须返回一次性初始密码");
createdUserId = created.payload.user.id;
assert.ok(created.payload.user.organizations.some((item) => item.id === "org-studio-lab"), "创建用户时组织归属未落库");
assert.ok(created.payload.user.workspaces.some((item) => item.id === "ws-local-aidrama"), "创建用户时工作区归属未落库");
const resetPassword = await request(`/api/system/users/${encodeURIComponent(createdUserId)}/reset-password`, owner, { method: "POST", body: "{}" });
assert.equal(resetPassword.response.ok, true, "管理员重置密码失败");
assert.ok(resetPassword.payload.temporaryPassword, "自动重置密码必须返回一次性密码");
const resetMfa = await request(`/api/system/users/${encodeURIComponent(createdUserId)}/reset-mfa`, owner, { method: "POST", body: "{}" });
assert.equal(resetMfa.response.ok, true, "管理员重置 MFA 失败");
const memberships = await request(`/api/system/users/${encodeURIComponent(createdUserId)}/memberships`, owner, { method: "POST", body: JSON.stringify({ organizationId: "org-studio-lab", organizationRoleKey: "org_member", workspaceId: "ws-pilot", workspaceRoleKey: "producer", projectId: "template-original-manhua", projectRoleKey: "project_viewer" }) });
assert.equal(memberships.response.ok, true, "管理员调整用户归属失败");
assert.ok(memberships.payload.user.workspaces.some((item) => item.id === "ws-pilot"), "工作区归属调整未生效");
const commercial = await request("/api/organizations/org-studio-lab/commercial", owner);
assert.equal(commercial.response.ok, true, "读取商业运营数据失败");
assert.ok(commercial.payload.billing.billing_cycle && Array.isArray(commercial.payload.costCenters), "商业数据缺少账期或成本中心");
assert.ok(Array.isArray(commercial.payload.quotaWarnings) && Array.isArray(commercial.payload.billingHistory), "商业数据缺少预警或账单变更记录");
assert.ok(Array.isArray(commercial.payload.usageTrend), "商业数据缺少按日用量趋势");
assert.ok(commercial.payload.usageTrend.every((row) => row.day && Number.isFinite(row.units) && Number.isFinite(row.estimatedCost) && Number.isFinite(row.events)), "按日用量趋势字段不完整");
originalBilling = {
planName: commercial.payload.billing.plan_name,
billingCycle: commercial.payload.billing.billing_cycle,
currency: commercial.payload.billing.currency,
seatLimit: Number(commercial.payload.billing.seat_limit),
storageGb: Number(commercial.payload.billing.storage_gb),
monthlyClipQuota: Number(commercial.payload.billing.monthly_clip_quota),
quotaWarningPercent: Number(commercial.payload.billing.quota_warning_percent),
localRunnerOnly: Boolean(commercial.payload.billing.local_runner_only),
cloudConnectorsRequireApproval: Boolean(commercial.payload.billing.cloud_connectors_require_approval)
};
originalCostCenter = commercial.payload.costCenters[0];
const updatedBilling = await request("/api/organizations/org-studio-lab/billing", owner, { method: "PATCH", body: JSON.stringify({ ...originalBilling, billingCycle: "quarterly", quotaWarningPercent: 85 }) });
assert.equal(updatedBilling.response.ok, true, "账单周期更新失败");
assert.equal(updatedBilling.payload.billing.billing_cycle, "quarterly", "账单周期未保存");
assert.ok(Array.isArray(updatedBilling.payload.usageTrend), "保存套餐后没有返回用量趋势");
const updatedCenter = await request(`/api/organizations/org-studio-lab/cost-centers/${encodeURIComponent(originalCostCenter.id)}`, owner, { method: "PATCH", body: JSON.stringify({ monthlyBudget: Number(originalCostCenter.monthly_budget) + 1 }) });
assert.equal(updatedCenter.response.ok, true, "成本中心预算更新失败");
const exported = await request("/api/organizations/org-studio-lab/commercial/export", owner);
assert.equal(exported.response.ok, true, "商业运营数据导出失败");
assert.ok(exported.payload.costCenterDetail && exported.payload.billingHistory && Array.isArray(exported.payload.usageTrend), "导出数据不完整");
const workerStatus = await request("/api/system/worker", owner);
assert.equal(workerStatus.response.ok, true, "Worker 状态接口失败");
assert.ok(workerStatus.payload.worker.healthStatus, "Worker 状态缺少心跳健康字段");
const batch = await request("/api/admin/queue/batch", owner, { method: "POST", body: JSON.stringify({ action: "retry", jobIds: ["missing-governance-job"] }) });
assert.equal(batch.response.ok, true, "批量队列运维接口失败");
assert.equal(batch.payload.failureCount, 1, "批量队列应返回逐任务失败结果");
const deniedBatch = await request("/api/admin/queue/batch", writer, { method: "POST", body: JSON.stringify({ action: "retry", jobIds: ["missing-governance-job"] }) });
assert.equal(deniedBatch.response.status, 403, "普通用户不应执行批量队列运维");
console.log(`commercial governance smoke passed: ${api}`);
} finally {
if (originalBilling) {
await request("/api/organizations/org-studio-lab/billing", owner, { method: "PATCH", body: JSON.stringify(originalBilling) });
}
if (originalCostCenter) {
await request(`/api/organizations/org-studio-lab/cost-centers/${encodeURIComponent(originalCostCenter.id)}`, owner, { method: "PATCH", body: JSON.stringify({ monthlyBudget: Number(originalCostCenter.monthly_budget) }) });
}
if (createdUserId) {
dbRun("DELETE FROM users WHERE id = ?", [createdUserId]);
}
}