- 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.
155 lines
4.1 KiB
TypeScript
155 lines
4.1 KiB
TypeScript
import { INestApplication } from '@nestjs/common';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { SwaggerModule } from '@nestjs/swagger';
|
|
import { AppController } from '../../app.controller';
|
|
import { AppService } from '../../app.service';
|
|
import { AuthController } from '../../modules/auth/auth.controller';
|
|
import { AuthService } from '../../modules/auth/auth.service';
|
|
import { PrivilegesService } from '../../modules/privileges/privileges.service';
|
|
import { UsersService } from '../../modules/users/users.service';
|
|
import {
|
|
createOpenApiDocument,
|
|
isSwaggerEnabled,
|
|
setupSwagger,
|
|
} from './setup-swagger';
|
|
|
|
describe('isSwaggerEnabled', () => {
|
|
it('is disabled in production by default', () => {
|
|
expect(isSwaggerEnabled({ NODE_ENV: 'production' })).toBe(false);
|
|
});
|
|
|
|
it('is enabled in production when SWAGGER_ENABLED=true', () => {
|
|
expect(
|
|
isSwaggerEnabled({
|
|
NODE_ENV: 'production',
|
|
SWAGGER_ENABLED: 'true',
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it('is enabled when not production', () => {
|
|
expect(isSwaggerEnabled({ NODE_ENV: 'development' })).toBe(true);
|
|
});
|
|
|
|
it('is disabled when SWAGGER_ENABLED=false', () => {
|
|
expect(
|
|
isSwaggerEnabled({
|
|
NODE_ENV: 'development',
|
|
SWAGGER_ENABLED: 'false',
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('createOpenApiDocument', () => {
|
|
let app: INestApplication;
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef: TestingModule = await Test.createTestingModule({
|
|
controllers: [AppController, AuthController],
|
|
providers: [
|
|
AppService,
|
|
{
|
|
provide: AuthService,
|
|
useValue: {
|
|
register: jest.fn(),
|
|
login: jest.fn(),
|
|
refresh: jest.fn(),
|
|
revoke: jest.fn(),
|
|
},
|
|
},
|
|
{
|
|
provide: UsersService,
|
|
useValue: { findById: jest.fn() },
|
|
},
|
|
{
|
|
provide: PrivilegesService,
|
|
useValue: {
|
|
findPrivilegeSummary: jest.fn(),
|
|
getPermissionsMap: jest.fn(),
|
|
},
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
app = moduleRef.createNestApplication();
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it('includes auth and app paths with bearer scheme', () => {
|
|
const document = createOpenApiDocument(app);
|
|
|
|
expect(document.info.title).toBe('Tracking API');
|
|
expect(document.paths['/auth/login']).toBeDefined();
|
|
expect(document.paths['/auth/me']).toBeDefined();
|
|
expect(document.paths['/']).toBeDefined();
|
|
expect(document.components?.securitySchemes?.['access-token']).toEqual(
|
|
expect.objectContaining({ type: 'http', scheme: 'bearer' }),
|
|
);
|
|
});
|
|
|
|
it('requires bearer on /auth/me but not on public auth posts', () => {
|
|
const document = createOpenApiDocument(app);
|
|
|
|
expect(document.paths['/auth/me']?.get?.security).toEqual([
|
|
{ 'access-token': [] },
|
|
]);
|
|
expect(document.paths['/auth/login']?.post?.security).toBeUndefined();
|
|
expect(document.paths['/auth/register']?.post?.security).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('setupSwagger', () => {
|
|
let app: INestApplication;
|
|
let setupSpy: jest.SpyInstance;
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef: TestingModule = await Test.createTestingModule({
|
|
controllers: [AppController],
|
|
providers: [AppService],
|
|
}).compile();
|
|
|
|
app = moduleRef.createNestApplication();
|
|
await app.init();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
setupSpy = jest
|
|
.spyOn(SwaggerModule, 'setup')
|
|
.mockImplementation(() => undefined);
|
|
});
|
|
|
|
afterEach(() => {
|
|
setupSpy.mockRestore();
|
|
});
|
|
|
|
it('skips mounting when swagger is disabled', () => {
|
|
setupSwagger(app, {
|
|
NODE_ENV: 'production',
|
|
});
|
|
|
|
expect(setupSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('mounts at /docs when swagger is enabled', () => {
|
|
setupSwagger(app, {
|
|
NODE_ENV: 'test',
|
|
});
|
|
|
|
expect(setupSpy).toHaveBeenCalledWith(
|
|
'docs',
|
|
app,
|
|
expect.any(Object),
|
|
expect.objectContaining({ jsonDocumentUrl: 'docs-json' }),
|
|
);
|
|
});
|
|
});
|