Files
thebet365/apps/api/src/applications/admin/admin-rbac.spec.ts
Mars 567ec9ec8a feat: 管理端 RBAC 权限体系与员工管理
新增多角色权限控制(赛事/财务/客服管理员),支持员工 CRUD、路由菜单按权限显隐、审计日志范围过滤;登录返回角色与权限列表。玩家端赛事列表增加静默刷新避免图片闪烁。Seed 补充演示员工账号与充值相关权限。附带 RBAC/审计范围单元测试及 UAT 文档更新。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 17:52:39 +08:00

101 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PermissionsGuard } from '../../domains/identity/guards';
import { isAuditListUnrestricted } from '../../domains/operations/audit/audit-list-scope';
import { PERMISSIONS_KEY } from '../../shared/common/decorators';
/** Mirrors run-seed.ts role permission assignments (SEC010SEC012). */
const ROLE_PERMISSIONS: Record<string, string[]> = {
SUPER_ADMIN: ['*'],
MATCH_ADMIN: [
'matches.manage',
'settlement.confirm',
'content.manage',
'bets.view',
'reports.view',
'audit.view',
],
FINANCE_ADMIN: [
'wallet.deposit',
'wallet.withdraw',
'cashback.confirm',
'agents.view',
'agents.credit',
'users.view',
'users.create',
'deposit.manage',
'deposit.review',
'reports.view',
'bets.view',
'audit.view',
],
SUPPORT: ['users.view', 'users.reset_password', 'bets.view', 'reports.view', 'audit.view'],
};
function mockContext(user: Record<string, unknown>): ExecutionContext {
return {
switchToHttp: () => ({
getRequest: () => ({ user }),
}),
getHandler: () => ({}),
getClass: () => ({}),
} as ExecutionContext;
}
function guardAllows(user: Record<string, unknown>, ...required: string[]): boolean {
const reflector = {
getAllAndOverride: (key: string) => (key === PERMISSIONS_KEY ? required : undefined),
} as unknown as Reflector;
const guard = new PermissionsGuard(reflector);
try {
return guard.canActivate(mockContext(user));
} catch {
return false;
}
}
function userWithRole(role: string) {
return {
userType: 'ADMIN',
role,
permissions: ROLE_PERMISSIONS[role] ?? [],
};
}
describe('Admin RBAC (SEC010SEC012)', () => {
it('SEC010: SUPPORT cannot perform wallet deposit', () => {
const user = userWithRole('SUPPORT');
expect(guardAllows(user, 'wallet.deposit')).toBe(false);
expect(guardAllows(user, 'users.view')).toBe(true);
expect(guardAllows(user, 'users.reset_password')).toBe(true);
});
it('SEC011: FINANCE_ADMIN cannot manage matches', () => {
const user = userWithRole('FINANCE_ADMIN');
expect(guardAllows(user, 'matches.manage')).toBe(false);
expect(guardAllows(user, 'wallet.deposit')).toBe(true);
expect(guardAllows(user, 'agents.credit')).toBe(true);
});
it('SEC012: MATCH_ADMIN cannot perform wallet deposit', () => {
const user = userWithRole('MATCH_ADMIN');
expect(guardAllows(user, 'wallet.deposit')).toBe(false);
expect(guardAllows(user, 'settlement.confirm')).toBe(true);
expect(guardAllows(user, 'content.manage')).toBe(true);
expect(guardAllows(user, 'settlement.resettle')).toBe(false);
});
it('SUPER_ADMIN bypasses permission checks', () => {
const user = userWithRole('SUPER_ADMIN');
expect(guardAllows(user, 'wallet.deposit')).toBe(true);
expect(guardAllows(user, 'matches.manage')).toBe(true);
expect(guardAllows(user, 'settings.manage')).toBe(true);
});
it('audit list scope: only SUPER_ADMIN is unrestricted', () => {
expect(isAuditListUnrestricted('SUPER_ADMIN')).toBe(true);
expect(isAuditListUnrestricted('FINANCE_ADMIN')).toBe(false);
expect(isAuditListUnrestricted('MATCH_ADMIN')).toBe(false);
});
});