import { ValidationPipe } from '@nestjs/common'; import { RbacService } from './rbac.service'; import { CreateRoleDto, CreateUserDto, UpdateUserDto } from './dto/rbac.dto'; function makeService(overrides?: { permRepo?: Record; roleRepo?: Record; userRepo?: Record; }) { const permRepo = { findByIds: jest.fn().mockResolvedValue([]), ...(overrides?.permRepo ?? {}), }; const roleRepo = { create: jest.fn((value) => ({ ...value })), save: jest.fn(async (value) => value), findByIds: jest.fn().mockResolvedValue([]), findOneOrFail: jest.fn(), ...(overrides?.roleRepo ?? {}), }; const userRepo = { create: jest.fn((value) => ({ ...value })), save: jest.fn(async (value) => value), findOne: jest.fn().mockResolvedValue(null), ...(overrides?.userRepo ?? {}), }; return { service: new RbacService( permRepo as never, roleRepo as never, userRepo as never, {} as never, {} as never, {} as never, {} as never, {} as never, ), permRepo, roleRepo, userRepo, }; } describe('RBAC mutation boundaries', () => { it('rejects a role when any requested permission id does not exist', async () => { const { service, roleRepo } = makeService({ permRepo: { findByIds: jest.fn().mockResolvedValue([{ id: 1, code: 'student:view' }]) }, }); await expect(service.createRole({ name: 'partial', permissionIds: [1, 999] })).rejects.toThrow( '权限不存在: 999', ); expect(roleRepo.save).not.toHaveBeenCalled(); }); it('rejects a user when any requested role id does not exist', async () => { const { service, userRepo } = makeService({ roleRepo: { findByIds: jest.fn().mockResolvedValue([{ id: 2, name: '老师' }]) }, }); await expect( service.createUser({ username: 'alice', password: 'secret', name: 'Alice', roleIds: [2, 404], }), ).rejects.toThrow('角色不存在: 404'); expect(userRepo.save).not.toHaveBeenCalled(); }); it('allows explicitly clearing all roles from an existing user', async () => { const user = { id: 7, username: 'alice', name: 'Alice', roles: [{ id: 2 }] }; const { service, userRepo } = makeService({ userRepo: { findOne: jest.fn().mockResolvedValue(user) }, }); await expect(service.updateUser(7, { roleIds: [] })).resolves.toEqual({ message: '更新成功' }); expect(user.roles).toEqual([]); expect(userRepo.save).toHaveBeenCalledWith(user); }); }); describe('RBAC DTO id arrays', () => { const pipe = new ValidationPipe({ transform: true, whitelist: true }); it.each([ [CreateRoleDto, { name: 'role', permissionIds: [1, '2'] }], [CreateRoleDto, { name: 'role', permissionIds: [1, 1] }], [CreateUserDto, { username: 'alice', password: 'secret', name: 'Alice', roleIds: [0] }], [UpdateUserDto, { roleIds: [1.5] }], ])('rejects invalid, duplicate, or non-positive ids for %p', async (metatype, value) => { await expect(pipe.transform(value, { type: 'body', metatype })).rejects.toBeDefined(); }); });