150 lines
3.9 KiB
TypeScript
150 lines
3.9 KiB
TypeScript
import { NotFoundException } from '@nestjs/common';
|
|
import { RecipesService } from './recipes.service';
|
|
|
|
describe('RecipesService IDOR security', () => {
|
|
const prismaMock = {
|
|
recipe: {
|
|
findUnique: jest.fn(),
|
|
findFirst: jest.fn(),
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
recipeIngredient: {
|
|
findMany: jest.fn(),
|
|
create: jest.fn(),
|
|
deleteMany: jest.fn(),
|
|
},
|
|
product: {
|
|
findMany: jest.fn(),
|
|
findFirst: jest.fn(),
|
|
},
|
|
user: {
|
|
findUnique: jest.fn(),
|
|
},
|
|
recipeShare: {
|
|
findUnique: jest.fn(),
|
|
create: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
$transaction: jest.fn(),
|
|
};
|
|
|
|
const aiServiceMock = {};
|
|
const recipeMatchingServiceMock = {};
|
|
|
|
const service = new RecipesService(prismaMock as any, aiServiceMock as any, recipeMatchingServiceMock as any);
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
const setRecipeAsOwnedByAnotherUser = () => {
|
|
prismaMock.recipe.findUnique.mockResolvedValue({
|
|
id: 1,
|
|
ownerId: 100,
|
|
});
|
|
};
|
|
|
|
it('findAll scopar resultaten till userId (owner eller shared)', async () => {
|
|
prismaMock.recipe.findMany.mockResolvedValue([]);
|
|
|
|
await service.findAll(42);
|
|
|
|
expect(prismaMock.recipe.findMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
OR: expect.arrayContaining([
|
|
{ ownerId: 42 },
|
|
{ isPublic: true },
|
|
{ shares: { some: { userId: 42 } } },
|
|
]),
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('findOne nekar access om recipe inte är owner/shared/public', async () => {
|
|
prismaMock.recipe.findFirst.mockResolvedValue(null);
|
|
|
|
await expect(service.findOne(1, 42)).rejects.toThrow(NotFoundException);
|
|
});
|
|
|
|
it('findOne tillåter owner att läsa eget recipe', async () => {
|
|
prismaMock.recipe.findFirst.mockResolvedValue({
|
|
id: 1,
|
|
ownerId: 42,
|
|
isPublic: false,
|
|
name: 'Test Recipe',
|
|
description: '',
|
|
instructions: '',
|
|
mealPlanDayOfWeek: null,
|
|
imageUrl: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
});
|
|
|
|
const result = await service.findOne(1, 42);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('findOne tillåter shared user att läsa shared recipe', async () => {
|
|
prismaMock.recipe.findFirst.mockResolvedValue({
|
|
id: 1,
|
|
ownerId: 100,
|
|
isPublic: false,
|
|
shares: [{ userId: 42 }],
|
|
});
|
|
|
|
const result = await service.findOne(1, 42);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('findOne tillåter alla att läsa public recipe', async () => {
|
|
prismaMock.recipe.findFirst.mockResolvedValue({
|
|
id: 1,
|
|
ownerId: 100,
|
|
isPublic: true,
|
|
});
|
|
|
|
const result = await service.findOne(1, 42);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: 'update',
|
|
action: () => service.update(1, {} as any, 42),
|
|
},
|
|
{
|
|
name: 'remove',
|
|
action: () => service.remove(1, 42),
|
|
},
|
|
{
|
|
name: 'shareWithUser',
|
|
action: () => service.shareWithUser(1, 42, 'someuser'),
|
|
},
|
|
{
|
|
name: 'unshareWithUser',
|
|
action: () => service.unshareWithUser(1, 42, 'someuser'),
|
|
},
|
|
{
|
|
name: 'setVisibility',
|
|
action: () => service.setVisibility(1, 42, true),
|
|
},
|
|
{
|
|
name: 'addIngredient',
|
|
action: () => service.addIngredient(1, { productId: 1, quantity: 100 } as any, 42),
|
|
},
|
|
{
|
|
name: 'updateImage',
|
|
action: () => service.updateImage(1, 'http://example.com/image.jpg', 42),
|
|
},
|
|
])('%s nekar icke-owner', async ({ action }) => {
|
|
setRecipeAsOwnedByAnotherUser();
|
|
|
|
await expect(action()).rejects.toThrow(NotFoundException);
|
|
});
|
|
});
|