f19c157e8f
Test Suite / test (24.15.0) (push) Has been cancelled
- Implemented a new PATCH endpoint in ProductsController to update the category of a user's product. - Added corresponding service method in ProductsService to handle business logic and validation. - Created UpdateCategoryMineDto for request validation. - Enhanced error handling for forbidden actions and not found resources. - Updated API error mapping in Flutter to handle specific forbidden messages. - Modified ProductPickerField to allow product creation directly from the picker. - Added tests for the new endpoint and service method to ensure proper functionality and error handling.
129 lines
5.1 KiB
TypeScript
129 lines
5.1 KiB
TypeScript
import { IS_PUBLIC_KEY } from '../auth/decorators/public.decorator';
|
|
import { ProductsController } from './products.controller';
|
|
import { getRolesMetadata } from '../test-utils/security-test-helpers';
|
|
|
|
describe('Products controller security', () => {
|
|
const productsServiceMock = {
|
|
findByOwner: jest.fn(),
|
|
findOne: jest.fn(),
|
|
createPrivate: jest.fn(),
|
|
createPending: jest.fn(),
|
|
updateCanonicalNamePrivate: jest.fn(),
|
|
mergePrivate: jest.fn(),
|
|
updateCategoryMine: jest.fn(),
|
|
};
|
|
|
|
const aiServiceMock = {
|
|
suggestCategory: jest.fn(),
|
|
};
|
|
|
|
const categoriesServiceMock = {
|
|
findFlattened: jest.fn(),
|
|
};
|
|
|
|
const controller = new ProductsController(
|
|
productsServiceMock as any,
|
|
aiServiceMock as any,
|
|
categoriesServiceMock as any,
|
|
);
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it.each([
|
|
['findAll', ProductsController.prototype.findAll],
|
|
['findAllTags', ProductsController.prototype.findAllTags],
|
|
])('endpoint %s har @Public metadata', (_name, handler) => {
|
|
const isPublic = Reflect.getMetadata(IS_PUBLIC_KEY, handler) as boolean | undefined;
|
|
expect(isPublic).toBe(true);
|
|
});
|
|
|
|
it('alla admin-endpoints har @Roles("admin") metadata', () => {
|
|
for (const [, handler] of [
|
|
['findDuplicates', ProductsController.prototype.findDuplicates],
|
|
['previewMerge', ProductsController.prototype.previewMerge],
|
|
['backfillCanonical', ProductsController.prototype.backfillCanonical],
|
|
['findPending', ProductsController.prototype.findPending],
|
|
['findPrivate', ProductsController.prototype.findPrivate],
|
|
['aiCategorizeBulk', ProductsController.prototype.aiCategorizeBulk],
|
|
['findDeleted', ProductsController.prototype.findDeleted],
|
|
['create', ProductsController.prototype.create],
|
|
['merge', ProductsController.prototype.merge],
|
|
['updateCanonicalName', ProductsController.prototype.updateCanonicalName],
|
|
['setTags', ProductsController.prototype.setTags],
|
|
['upsertNutrition', ProductsController.prototype.upsertNutrition],
|
|
['update', ProductsController.prototype.update],
|
|
['promotePrivateToGlobal', ProductsController.prototype.promotePrivateToGlobal],
|
|
['permanentDelete', ProductsController.prototype.permanentDelete],
|
|
['remove', ProductsController.prototype.remove],
|
|
['setStatus', ProductsController.prototype.setStatus],
|
|
['restore', ProductsController.prototype.restore],
|
|
['resetAll', ProductsController.prototype.resetAll],
|
|
['bulkUpdate', ProductsController.prototype.bulkUpdate],
|
|
]) {
|
|
expect(getRolesMetadata(handler as Function)).toEqual(['admin']);
|
|
}
|
|
});
|
|
|
|
it('findMine vidarebefordrar req.user.id till owner-scope', () => {
|
|
productsServiceMock.findByOwner.mockResolvedValue([]);
|
|
|
|
controller.findMine({ user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.findByOwner).toHaveBeenCalledWith(42);
|
|
});
|
|
|
|
it('createPrivate vidarebefordrar req.user.id', () => {
|
|
const dto = { name: 'Private Product' };
|
|
productsServiceMock.createPrivate.mockResolvedValue({ id: 1 });
|
|
|
|
controller.createPrivate(dto as any, { user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.createPrivate).toHaveBeenCalledWith(dto, 42);
|
|
});
|
|
|
|
it('createPending vidarebefordrar req.user.id', () => {
|
|
const dto = { name: 'Pending Product' };
|
|
productsServiceMock.createPending.mockResolvedValue({ id: 1 });
|
|
|
|
controller.createPending(dto as any, { user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.createPending).toHaveBeenCalledWith(dto, 42);
|
|
});
|
|
|
|
it('updateCanonicalNamePrivate vidarebefordrar req.user.id', () => {
|
|
productsServiceMock.updateCanonicalNamePrivate.mockResolvedValue({ id: 1 });
|
|
|
|
controller.updateCanonicalNamePrivate(1, { canonicalName: 'milk' } as any, { user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.updateCanonicalNamePrivate).toHaveBeenCalledWith(42, 1, 'milk');
|
|
});
|
|
|
|
it('mergePrivate vidarebefordrar req.user.id', () => {
|
|
productsServiceMock.mergePrivate.mockResolvedValue({ merged: true });
|
|
|
|
controller.mergePrivate({ sourceProductId: 10, targetProductId: 20 } as any, { user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.mergePrivate).toHaveBeenCalledWith(42, 10, 20);
|
|
});
|
|
|
|
it('updateCategoryMine vidarebefordrar req.user.id', () => {
|
|
productsServiceMock.updateCategoryMine.mockResolvedValue({ id: 1, categoryId: 5 });
|
|
|
|
controller.updateCategoryMine(1, { categoryId: 5 } as any, { user: { id: 42 } } as any);
|
|
|
|
expect(productsServiceMock.updateCategoryMine).toHaveBeenCalledWith(42, 1, 5);
|
|
});
|
|
|
|
it('suggestCategory använder canonicalName fallback name', async () => {
|
|
productsServiceMock.findOne.mockResolvedValue({ id: 1, name: 'Mjolk', canonicalName: 'Mjolk 1L' });
|
|
categoriesServiceMock.findFlattened.mockResolvedValue([{ id: 1, name: 'Mejeri' }]);
|
|
aiServiceMock.suggestCategory.mockResolvedValue({ categoryId: 1 });
|
|
|
|
await controller.suggestCategory(1);
|
|
|
|
expect(aiServiceMock.suggestCategory).toHaveBeenCalledWith('Mjolk 1L', [{ id: 1, name: 'Mejeri' }]);
|
|
});
|
|
});
|