Implement admin inventory management features including CRUD operations, merging, filtering, sorting, previewing, and security enhancements. Update documentation and add comprehensive test coverage for security and validation.
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-10 00:20:25 +02:00
parent 65137b41fb
commit 1709bb1dad
33 changed files with 1879 additions and 71 deletions
@@ -0,0 +1,95 @@
import { ExecutionContext, ForbiddenException, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { ROLES_KEY } from '../auth/decorators/roles.decorator';
import { RolesGuard } from '../auth/roles.guard';
import { InventoryController } from './inventory.controller';
type MockHttpContextOptions = {
handler: Function;
clazz: Function;
user?: any;
};
function mockHttpContext(options: MockHttpContextOptions): ExecutionContext {
return {
getClass: () => options.clazz,
getHandler: () => options.handler,
switchToHttp: () => ({
getRequest: () => ({ user: options.user }),
getResponse: () => ({}),
getNext: () => undefined,
}),
} as unknown as ExecutionContext;
}
describe('Inventory admin security', () => {
const adminHandlers: Array<[string, Function]> = [
['findAllAdmin', InventoryController.prototype.findAllAdmin],
['createAdmin', InventoryController.prototype.createAdmin],
['updateAdmin', InventoryController.prototype.updateAdmin],
['removeAdmin', InventoryController.prototype.removeAdmin],
['mergeAdmin', InventoryController.prototype.mergeAdmin],
['previewMergeAdmin', InventoryController.prototype.previewMergeAdmin],
];
it.each(adminHandlers)('admin-endpoint %s har @Roles("admin") metadata', (_name, handler) => {
const roles = Reflect.getMetadata(ROLES_KEY, handler) as string[] | undefined;
expect(roles).toEqual(['admin']);
});
it.each(adminHandlers)('RolesGuard nekar icke-admin (403) på %s', (_name, handler) => {
const reflector = new Reflector();
const guard = new RolesGuard(reflector);
const context = mockHttpContext({
handler,
clazz: InventoryController,
user: { role: 'user' },
});
expect(() => guard.canActivate(context)).toThrow(ForbiddenException);
});
it.each(adminHandlers)('RolesGuard tillåter admin (200/allow) på %s', (_name, handler) => {
const reflector = new Reflector();
const guard = new RolesGuard(reflector);
const context = mockHttpContext({
handler,
clazz: InventoryController,
user: { role: 'admin' },
});
expect(guard.canActivate(context)).toBe(true);
});
it('JwtAuthGuard mappar saknad användare till 401', () => {
const guard = new JwtAuthGuard(new Reflector());
expect(() => guard.handleRequest(null, null, null)).toThrow(UnauthorizedException);
});
it('JwtAuthGuard släpper igenom autentiserad användare (200/allow)', () => {
const guard = new JwtAuthGuard(new Reflector());
const user = { userId: 42, role: 'admin' };
expect(guard.handleRequest(null, user, null)).toBe(user);
});
it('JwtAuthGuard-logg innehåller userId men inte token', () => {
const guard = new JwtAuthGuard(new Reflector());
const logSpy = jest.fn();
(guard as any).logger = { log: logSpy };
const user = {
userId: 77,
role: 'admin',
accessToken: 'secret-token-should-not-appear',
};
guard.handleRequest(null, user, null);
expect(logSpy).toHaveBeenCalledTimes(1);
const loggedMessage = String(logSpy.mock.calls[0][0] ?? '');
expect(loggedMessage).toContain('77');
expect(loggedMessage).not.toContain('secret-token-should-not-appear');
});
});