- Introduced a new `PrivilegesModule` to manage user privileges and access control. - Added `RequirePrivilege` decorator to enforce privilege checks on controller handlers. - Implemented `PrivilegesGuard` to handle authorization based on user privileges. - Created database migrations for `privileges`, `privilege_keys`, and `privilege_details` tables. - Updated user model to include `is_superadmin` field for enhanced access control. - Added unit tests for the new privileges functionality and guards to ensure correct behavior.
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { DateTime } from '../../common/value-objects/date-time/date-time';
|
|
import { Status } from '../../common/value-objects/status/status';
|
|
import type { PrivilegeWithDetails } from './privilege';
|
|
import { PrivilegesRepository } from './privileges.repository';
|
|
import { PrivilegesService } from './privileges.service';
|
|
|
|
describe('PrivilegesService', () => {
|
|
let service: PrivilegesService;
|
|
let repository: jest.Mocked<
|
|
Pick<
|
|
PrivilegesRepository,
|
|
| 'list'
|
|
| 'findById'
|
|
| 'create'
|
|
| 'createMany'
|
|
| 'update'
|
|
| 'updateStatus'
|
|
| 'bulkUpdateStatus'
|
|
| 'delete'
|
|
| 'bulkDelete'
|
|
| 'listKeys'
|
|
| 'findKeyById'
|
|
| 'checkPermission'
|
|
| 'getPermissionsMap'
|
|
>
|
|
>;
|
|
|
|
const now = DateTime.fromUnixMs(1_700_000_000_000);
|
|
const sample: PrivilegeWithDetails = {
|
|
id: 'priv-1',
|
|
name: 'Admin',
|
|
code: 'ADMIN',
|
|
status: Status.create('draft'),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
createdBy: 'user-1',
|
|
updatedBy: 'user-1',
|
|
details: [],
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
repository = {
|
|
list: jest.fn(),
|
|
findById: jest.fn(),
|
|
create: jest.fn(),
|
|
createMany: jest.fn(),
|
|
update: jest.fn(),
|
|
updateStatus: jest.fn(),
|
|
bulkUpdateStatus: jest.fn(),
|
|
delete: jest.fn(),
|
|
bulkDelete: jest.fn(),
|
|
listKeys: jest.fn(),
|
|
findKeyById: jest.fn(),
|
|
checkPermission: jest.fn(),
|
|
getPermissionsMap: jest.fn(),
|
|
};
|
|
|
|
const moduleRef: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
PrivilegesService,
|
|
{ provide: PrivilegesRepository, useValue: repository },
|
|
],
|
|
}).compile();
|
|
|
|
service = moduleRef.get(PrivilegesService);
|
|
});
|
|
|
|
it('list maps visible fields and pagination', async () => {
|
|
repository.list.mockResolvedValue({ data: [sample], total: 1 });
|
|
const result = await service.list({ page: 1, limit: 10 });
|
|
expect(result.total).toBe(1);
|
|
expect(result.data[0]).toMatchObject({
|
|
id: 'priv-1',
|
|
status: 'draft',
|
|
createdAt: now.value,
|
|
});
|
|
expect(service.visibleFields).toContain('status');
|
|
});
|
|
|
|
it('findById throws when missing', async () => {
|
|
repository.findById.mockResolvedValue(null);
|
|
await expect(service.findById('missing')).rejects.toBeInstanceOf(
|
|
NotFoundException,
|
|
);
|
|
});
|
|
|
|
it('create defaults status to draft', async () => {
|
|
repository.create.mockResolvedValue(sample);
|
|
await service.create({
|
|
name: 'Admin',
|
|
code: 'ADMIN',
|
|
userId: 'user-1',
|
|
});
|
|
expect(repository.create).toHaveBeenCalled();
|
|
const arg = repository.create.mock.calls[0][0];
|
|
expect(arg.status?.value).toBe('draft');
|
|
expect(arg.details).toBeUndefined();
|
|
});
|
|
|
|
it('update rejects status field', async () => {
|
|
await expect(
|
|
service.update('priv-1', {
|
|
status: 'active',
|
|
userId: 'user-1',
|
|
}),
|
|
).rejects.toBeInstanceOf(BadRequestException);
|
|
});
|
|
|
|
it('updateStatus updates via repository', async () => {
|
|
repository.updateStatus.mockResolvedValue(sample);
|
|
await service.updateStatus('priv-1', 'active', 'user-1');
|
|
expect(repository.updateStatus).toHaveBeenCalledWith(
|
|
'priv-1',
|
|
expect.objectContaining({ value: 'active' }),
|
|
'user-1',
|
|
);
|
|
});
|
|
|
|
it('importCsv creates rows and fails batch on invalid status', async () => {
|
|
await expect(
|
|
service.importCsv('name,code,status\nA,A1,nope', 'user-1'),
|
|
).rejects.toBeInstanceOf(BadRequestException);
|
|
expect(repository.createMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('importCsv imports valid rows', async () => {
|
|
repository.createMany.mockResolvedValue(1);
|
|
const result = await service.importCsv(
|
|
'name,code,status\nAdmin,ADMIN,draft',
|
|
'user-1',
|
|
);
|
|
expect(result.imported).toBe(1);
|
|
expect(repository.createMany).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('checkPermission delegates', async () => {
|
|
repository.checkPermission.mockResolvedValue(true);
|
|
await expect(
|
|
service.checkPermission('user-1', 'PRIVILEGES', 'view'),
|
|
).resolves.toBe(true);
|
|
});
|
|
});
|