feat: add tenant role templates

This commit is contained in:
Codex
2026-06-29 00:29:31 +08:00
parent 9553836ac7
commit b262e87af9
14 changed files with 504 additions and 42 deletions

View File

@@ -2348,8 +2348,63 @@ async function testTenantMemberPermissionsAndAudit() {
userId: TENANT_ADMIN_USER_ID,
});
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'marketing:write'), 'permission matrix should expose marketing permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'roles:write'), 'permission matrix should expose role template permission');
assert.ok(permissionMatrix.menuGroups?.some(item => item.key === 'sales'), 'permission matrix should expose menu groups');
assert.ok(permissionMatrix.roleDefaults?.tenant_operator?.includes('marketing:*'), 'permission matrix should include role defaults');
const roleTemplate = await request('/api/tenant-admin/role-templates', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',
body: {
code: 'ops-marketing',
name: '运营活动模板',
description: '允许运营维护活动内容,同时可配置前端菜单和字段可见性',
baseRole: 'tenant_operator',
permissions: {
'marketing:*': true,
'tenant:payment:write': true,
},
menuPermissions: {
dashboard: true,
marketing: true,
settings: false,
},
modulePermissions: {
banners: true,
coupons: true,
},
fieldPermissions: {
'student.phone': false,
'order.amount': true,
},
dataScope: {
mode: 'tenant',
ownLeadsOnly: false,
},
sortOrder: 10,
},
});
assert.equal(roleTemplate.item?.code, 'ops-marketing', 'tenant admin should upsert custom role template');
assert.equal(roleTemplate.item?.menuPermissions?.marketing, true, 'role template should persist menu permissions');
const roleTemplates = await request('/api/tenant-admin/role-templates', {
userId: TENANT_ADMIN_USER_ID,
});
assert.ok(roleTemplates.items?.some(item => item.id === roleTemplate.item.id), 'role template list should include custom template');
const wildcardTemplateDenied = await request('/api/tenant-admin/role-templates', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',
body: {
code: 'super-admin-template',
name: '危险全权限模板',
baseRole: 'tenant_admin',
permissions: { '*': true },
},
expectStatus: 403,
});
assert.equal(wildcardTemplateDenied.code, 'TENANT_OWNER_REQUIRED', 'tenant admin should not create owner/admin level role template');
const operator = await request('/api/tenant-admin/members', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',
@@ -2359,6 +2414,7 @@ async function testTenantMemberPermissionsAndAudit() {
phone: '13800000003',
name: 'Integration Operator',
role: 'tenant_operator',
roleTemplateId: roleTemplate.item.id,
status: 'active',
permissions: {
'marketing:*': true,
@@ -2367,12 +2423,20 @@ async function testTenantMemberPermissionsAndAudit() {
},
});
assert.equal(operator.item?.role, 'tenant_operator', 'tenant admin should create operator membership');
assert.equal(operator.item?.roleTemplateId, roleTemplate.item.id, 'member should bind custom role template');
const members = await request('/api/tenant-admin/members', {
userId: TENANT_ADMIN_USER_ID,
query: { keyword: 'Integration Operator' },
});
assert.ok(members.items?.some(item => item.userId === TENANT_OPERATOR_USER_ID), 'member list should find operator');
assert.ok(members.items?.some(item => item.userId === TENANT_OPERATOR_USER_ID && item.roleTemplateCode === 'ops-marketing'), 'member list should expose role template binding');
const operatorPermissionMatrix = await request('/api/tenant-admin/permissions', {
userId: TENANT_OPERATOR_USER_ID,
});
assert.equal(operatorPermissionMatrix.current?.roleTemplateCode, 'ops-marketing', 'current permission matrix should include role template');
assert.equal(operatorPermissionMatrix.current?.menuPermissions?.marketing, true, 'current permission matrix should expose menu permissions');
const operatorBanner = await request('/api/tenant-admin/banners', {
userId: TENANT_OPERATOR_USER_ID,
@@ -2468,12 +2532,21 @@ async function testTenantMemberPermissionsAndAudit() {
});
assert.equal(disabledSalesDenied.code, 'TENANT_ADMIN_REQUIRED', 'disabled sales membership should lose tenant admin access');
const disabledTemplate = await request('/api/tenant-admin/role-templates/disable', {
userId: TENANT_ADMIN_USER_ID,
method: 'POST',
body: { roleTemplateId: roleTemplate.item.id },
});
assert.equal(disabledTemplate.item?.status, 'disabled', 'tenant admin should disable custom role template');
const auditLogs = await request('/api/tenant-admin/audit-logs', {
userId: TENANT_ADMIN_USER_ID,
query: { action: 'tenant.member', limit: 20 },
query: { action: 'tenant.', limit: 50 },
});
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.member.upserted'), 'audit logs should include member upsert');
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.member.disabled'), 'audit logs should include member disable');
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.role_template.upserted'), 'audit logs should include role template upsert');
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.role_template.disabled'), 'audit logs should include role template disable');
const partnerAuditDenied = await request('/api/tenant-admin/audit-logs', {
tenantId: PARTNER_TENANT_ID,