import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; const source = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8"); const highRiskMappings = [ { name: "商品保存", ui: "src/components/admin/product-management.tsx", api: "src/app/api/admin/products/route.ts", permission: "billing.products.write", }, { name: "商品发布", ui: "src/components/admin/product-management.tsx", api: "src/app/api/admin/products/route.ts", permission: "billing.products.publish", }, { name: "订阅调整", ui: "src/components/admin/billing-operations-resources.tsx", api: "src/app/api/admin/subscriptions/route.ts", permission: "billing.adjustments.write", }, { name: "订单调整", ui: "src/components/admin/billing-operations-resources.tsx", api: "src/app/api/admin/orders/route.ts", permission: "billing.adjustments.write", }, { name: "兑换码创建", ui: "src/components/admin/codes-resource.tsx", api: "src/app/api/admin/codes/route.ts", permission: "billing.adjustments.write", }, { name: "兑换码编辑与撤销", ui: "src/components/admin/codes-resource.tsx", api: "src/app/api/admin/codes/[id]/route.ts", permission: "billing.adjustments.write", }, { name: "功能开关发布", ui: "src/components/admin/feature-flags-management.tsx", api: "src/app/api/admin/feature-flags/route.ts", permission: "ops.flags.write", }, { name: "易支付设置保存", ui: "src/components/admin/payment-management.tsx", api: "src/app/api/admin/epay-settings/route.ts", permission: "billing.adjustments.write", }, { name: "管理员角色变更", ui: "src/components/admin/administrators-resource.tsx", api: "src/app/api/admin/administrators/route.ts", permission: "admin.users.manage_roles", }, ] as const; test("high-risk UI reauth permissions match their API guards", async (t) => { for (const mapping of highRiskMappings) { await t.test(mapping.name, () => { const ui = source(mapping.ui); const api = source(mapping.api); assert.match(ui, /ReasonActionModal/); assert.match(ui, new RegExp(`reauthPermission=[\\s\\S]{0,80}["']${mapping.permission.replaceAll(".", "\\.")}["']|reauthPermission="${mapping.permission.replaceAll(".", "\\.")}"`)); assert.match(api, new RegExp(`requireHighRiskAdminMutation\\(\\s*request,\\s*(?:permission|["']${mapping.permission.replaceAll(".", "\\.")}["']),?\\s*\\)`)); assert.match(ui, new RegExp(`permissions\\.includes\\(["']${mapping.permission.replaceAll(".", "\\.")}["']\\)`)); }); } }); test("product management localizes product, billing and entitlement enums", () => { const ui = source("src/components/admin/product-management.tsx"); for (const label of [ "点数包", "体验套餐", "订阅套餐", "一次性", "按天", "按月", "按年", "使用权限", "不限量", "限额", "点数", "不重置", "每个计费周期", ]) { assert.match(ui, new RegExp(label)); } assert.doesNotMatch(ui, /\{value\}<\/Tag>/); assert.doesNotMatch(ui, /\$\{item\.intervalCount\} \$\{item\.billingPeriod\}/); }); test("model management uses ordinary admin mutation guards without reauth", () => { const ui = source("src/components/admin/model-management.tsx"); const route = source("src/app/api/admin/models/route.ts"); const discoverRoute = source("src/app/api/admin/models/discover/route.ts"); for (const permission of ["models.write", "models.test", "models.publish", "models.rollback"]) { assert.ok(ui.includes(`permissions.includes("${permission}")`)); } assert.match(route, /requireAdminMutation\(request, permission\)/); assert.doesNotMatch(route, /requireHighRiskAdminMutation/); assert.match(discoverRoute, /requireAdminMutation\(request,"models\.test"\)/); assert.doesNotMatch(discoverRoute, /requireHighRiskAdminMutation/); assert.doesNotMatch(ui, /reauthPermission=[^\n]*models\./); assert.doesNotMatch(ui, /验证并(?:保存|获取)/); }); test("admin request failures are real Error instances and reason modal keeps failures visible", () => { const providers = source("src/lib/admin/providers.ts"); const modal = source("src/components/admin/reason-action-modal.tsx"); assert.match( providers, /Object\.assign\(new Error\(message\),\s*\{\s*statusCode: response\.status,/, ); assert.doesNotMatch(providers, /throw \{ message, statusCode/); assert.match(modal, /catch \(error\)[\s\S]*setReauthError\(error instanceof Error \? error\.message/); assert.match(modal, /name="reason"[\s\S]*required: true, whitespace: true/); assert.match(modal, /await onSubmit\(reason\.trim\(\)\)/); }); test("customer list is always masked and a single explicit reveal is audited each time", () => { const providers = source("src/lib/admin/providers.ts"); const route = source("src/app/api/admin/customers/route.ts"); const ui = source("src/components/admin/users-resource.tsx"); const migration = source("supabase/migrations/20260806010000_admin_rbac.sql"); assert.match(providers, /customers: \{ read: "admin\.customers\.read" \}/); assert.match(route, /revealUserId[\s\S]*requirePermission\("admin\.customers\.birth_data\.read"\)/); assert.match(route, /\[session\.user\.id, \[parsedUserId\.data\], requestId\(request\)\]/); const listBranch = route.slice(route.indexOf('await requirePermission("admin.customers.read")')); assert.doesNotMatch(listBranch, /admin_read_customer_birth_data/); assert.match(listBranch, /birthDate: null[\s\S]*birthTimeStatus: null[\s\S]*birthPlace: null[\s\S]*birthDataMasked: true/); assert.match(migration, /cardinality\(p_target_user_ids\) <> 1/); assert.match(ui, /revealUserId=\$\{encodeURIComponent\(userId\)\}/); assert.match(ui, /再次读取并审计/); assert.match(ui, /admin\.customers\.birth_data\.read/); }); test("administrator UI supports all six roles and exposes last-owner protection", () => { const ui = source("src/components/admin/administrators-resource.tsx"); const route = source("src/app/api/admin/administrators/route.ts"); const roles = source("src/components/admin/roles-resource.tsx"); for (const role of ["owner", "model_admin", "billing_admin", "operations", "support", "auditor"]) { assert.match(ui, new RegExp(`["']${role}["']`)); } assert.match(ui, /method: pendingAction\.action === "assign" \? "POST" : "DELETE"/); const mutationSection = route.slice(route.indexOf("async function mutate")); assert.match(mutationSection, /last_owner_protected/); assert.match(mutationSection, /不能撤销最后一位 Owner,请先分配另一位 Owner/); assert.doesNotMatch(route.slice(0, route.indexOf("async function mutate")), /last_owner_protected/); assert.match(roles, /系统预置只读矩阵/); assert.match(roles, /不提供角色权限矩阵写 API/); const providers = source("src/lib/admin/providers.ts"); assert.match(providers, /roles: \{ read: "admin\.users\.read" \}/); assert.doesNotMatch(providers, /roles: \{[^}]*write:/); });