complete project using prettier!

This commit is contained in:
2025-12-22 09:45:14 -08:00
parent 621d30b84f
commit a10f84aa48
339 changed files with 18041 additions and 8969 deletions

View File

@@ -36,7 +36,10 @@ describe('Recipe DB Service', () => {
it('should call the correct database function', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await recipeRepo.getRecipesBySalePercentage(50, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_recipes_by_sale_percentage($1)', [50]);
expect(mockQuery).toHaveBeenCalledWith(
'SELECT * FROM public.get_recipes_by_sale_percentage($1)',
[50],
);
});
it('should return an empty array if no recipes are found', async () => {
@@ -48,17 +51,24 @@ describe('Recipe DB Service', () => {
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.getRecipesBySalePercentage(50, mockLogger)).rejects.toThrow('Failed to get recipes by sale percentage.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, minPercentage: 50 }, 'Database error in getRecipesBySalePercentage');
await expect(recipeRepo.getRecipesBySalePercentage(50, mockLogger)).rejects.toThrow(
'Failed to get recipes by sale percentage.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, minPercentage: 50 },
'Database error in getRecipesBySalePercentage',
);
});
});
describe('getRecipesByMinSaleIngredients', () => {
it('should call the correct database function', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await recipeRepo.getRecipesByMinSaleIngredients(3, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_recipes_by_min_sale_ingredients($1)', [3]);
expect(mockQuery).toHaveBeenCalledWith(
'SELECT * FROM public.get_recipes_by_min_sale_ingredients($1)',
[3],
);
});
it('should return an empty array if no recipes are found', async () => {
@@ -70,17 +80,24 @@ describe('Recipe DB Service', () => {
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.getRecipesByMinSaleIngredients(3, mockLogger)).rejects.toThrow('Failed to get recipes by minimum sale ingredients.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, minIngredients: 3 }, 'Database error in getRecipesByMinSaleIngredients');
await expect(recipeRepo.getRecipesByMinSaleIngredients(3, mockLogger)).rejects.toThrow(
'Failed to get recipes by minimum sale ingredients.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, minIngredients: 3 },
'Database error in getRecipesByMinSaleIngredients',
);
});
});
describe('findRecipesByIngredientAndTag', () => {
it('should call the correct database function', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await recipeRepo.findRecipesByIngredientAndTag('chicken', 'quick', mockLogger);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.find_recipes_by_ingredient_and_tag($1, $2)', ['chicken', 'quick']);
expect(mockQuery).toHaveBeenCalledWith(
'SELECT * FROM public.find_recipes_by_ingredient_and_tag($1, $2)',
['chicken', 'quick'],
);
});
it('should return an empty array if no recipes are found', async () => {
@@ -92,17 +109,23 @@ describe('Recipe DB Service', () => {
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.findRecipesByIngredientAndTag('chicken', 'quick', mockLogger)).rejects.toThrow('Failed to find recipes by ingredient and tag.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, ingredient: 'chicken', tag: 'quick' }, 'Database error in findRecipesByIngredientAndTag');
await expect(
recipeRepo.findRecipesByIngredientAndTag('chicken', 'quick', mockLogger),
).rejects.toThrow('Failed to find recipes by ingredient and tag.');
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, ingredient: 'chicken', tag: 'quick' },
'Database error in findRecipesByIngredientAndTag',
);
});
});
describe('getUserFavoriteRecipes', () => {
it('should call the correct database function', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await recipeRepo.getUserFavoriteRecipes('user-123', mockLogger);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_user_favorite_recipes($1)', ['user-123']);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_user_favorite_recipes($1)', [
'user-123',
]);
});
it('should return an empty array if user has no favorites', async () => {
@@ -114,19 +137,31 @@ describe('Recipe DB Service', () => {
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.getUserFavoriteRecipes('user-123', mockLogger)).rejects.toThrow('Failed to get favorite recipes.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123' }, 'Database error in getUserFavoriteRecipes');
await expect(recipeRepo.getUserFavoriteRecipes('user-123', mockLogger)).rejects.toThrow(
'Failed to get favorite recipes.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123' },
'Database error in getUserFavoriteRecipes',
);
});
});
describe('addFavoriteRecipe', () => {
it('should execute an INSERT query and return the new favorite', async () => {
const mockFavorite: FavoriteRecipe = { user_id: 'user-123', recipe_id: 1, created_at: new Date().toISOString() }; // This line is correct.
const mockFavorite: FavoriteRecipe = {
user_id: 'user-123',
recipe_id: 1,
created_at: new Date().toISOString(),
}; // This line is correct.
mockQuery.mockResolvedValue({ rows: [mockFavorite], rowCount: 1 });
const result = await recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger);
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.favorite_recipes'), ['user-123', 1]);
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('INSERT INTO public.favorite_recipes'),
['user-123', 1],
);
expect(result).toEqual(mockFavorite);
});
@@ -134,23 +169,34 @@ describe('Recipe DB Service', () => {
const dbError = new Error('violates foreign key constraint');
(dbError as Error & { code: string }).code = '23503';
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.addFavoriteRecipe('user-123', 999, mockLogger)).rejects.toThrow('The specified user or recipe does not exist.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', recipeId: 999 }, 'Database error in addFavoriteRecipe');
await expect(recipeRepo.addFavoriteRecipe('user-123', 999, mockLogger)).rejects.toThrow(
'The specified user or recipe does not exist.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123', recipeId: 999 },
'Database error in addFavoriteRecipe',
);
});
it('should throw UniqueConstraintError if the favorite already exists (ON CONFLICT)', async () => {
// When ON CONFLICT DO NOTHING happens, the RETURNING clause does not execute, so rows is empty.
// The implementation now throws an error when rowCount is 0.
mockQuery.mockResolvedValue({ rows: [], rowCount: 0 });
await expect(recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger))
.rejects.toThrow(UniqueConstraintError);
await expect(recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger)).rejects.toThrow(
UniqueConstraintError,
);
});
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger)).rejects.toThrow('Failed to add favorite recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', recipeId: 1 }, 'Database error in addFavoriteRecipe');
await expect(recipeRepo.addFavoriteRecipe('user-123', 1, mockLogger)).rejects.toThrow(
'Failed to add favorite recipe.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123', recipeId: 1 },
'Database error in addFavoriteRecipe',
);
});
});
@@ -158,20 +204,30 @@ describe('Recipe DB Service', () => {
it('should execute a DELETE query', async () => {
mockQuery.mockResolvedValue({ rowCount: 1 });
await recipeRepo.removeFavoriteRecipe('user-123', 1, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.favorite_recipes WHERE user_id = $1 AND recipe_id = $2', ['user-123', 1]);
expect(mockQuery).toHaveBeenCalledWith(
'DELETE FROM public.favorite_recipes WHERE user_id = $1 AND recipe_id = $2',
['user-123', 1],
);
});
it('should throw an error if the favorite recipe is not found', async () => {
// Simulate the DB returning 0 rows affected
mockQuery.mockResolvedValue({ rowCount: 0 });
await expect(recipeRepo.removeFavoriteRecipe('user-123', 999, mockLogger)).rejects.toThrow('Favorite recipe not found for this user.');
await expect(recipeRepo.removeFavoriteRecipe('user-123', 999, mockLogger)).rejects.toThrow(
'Favorite recipe not found for this user.',
);
});
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.removeFavoriteRecipe('user-123', 1, mockLogger)).rejects.toThrow('Failed to remove favorite recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', recipeId: 1 }, 'Database error in removeFavoriteRecipe');
await expect(recipeRepo.removeFavoriteRecipe('user-123', 1, mockLogger)).rejects.toThrow(
'Failed to remove favorite recipe.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123', recipeId: 1 },
'Database error in removeFavoriteRecipe',
);
});
});
@@ -179,55 +235,83 @@ describe('Recipe DB Service', () => {
it('should execute a DELETE query with user ownership check', async () => {
mockQuery.mockResolvedValue({ rowCount: 1 });
await recipeRepo.deleteRecipe(1, 'user-123', false, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.recipes WHERE recipe_id = $1 AND user_id = $2', [1, 'user-123']);
expect(mockQuery).toHaveBeenCalledWith(
'DELETE FROM public.recipes WHERE recipe_id = $1 AND user_id = $2',
[1, 'user-123'],
);
});
it('should execute a DELETE query without user ownership check if isAdmin is true', async () => {
mockQuery.mockResolvedValue({ rowCount: 1 });
await recipeRepo.deleteRecipe(1, 'admin-user', true, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.recipes WHERE recipe_id = $1', [1]);
expect(mockQuery).toHaveBeenCalledWith(
'DELETE FROM public.recipes WHERE recipe_id = $1',
[1],
);
});
it('should throw an error if the recipe is not found or not owned by the user', async () => {
mockQuery.mockResolvedValue({ rowCount: 0 });
await expect(recipeRepo.deleteRecipe(999, 'user-123', false, mockLogger)).rejects.toThrow('Recipe not found or user does not have permission to delete.');
await expect(recipeRepo.deleteRecipe(999, 'user-123', false, mockLogger)).rejects.toThrow(
'Recipe not found or user does not have permission to delete.',
);
});
it('should throw a generic error if the database query fails', async () => {
mockQuery.mockRejectedValue(new Error('DB Error'));
await expect(recipeRepo.deleteRecipe(1, 'user-123', false, mockLogger)).rejects.toThrow('Failed to delete recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: expect.any(Error), recipeId: 1, userId: 'user-123', isAdmin: false }, 'Database error in deleteRecipe');
await expect(recipeRepo.deleteRecipe(1, 'user-123', false, mockLogger)).rejects.toThrow(
'Failed to delete recipe.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: expect.any(Error), recipeId: 1, userId: 'user-123', isAdmin: false },
'Database error in deleteRecipe',
);
});
});
describe('updateRecipe', () => {
it('should execute an UPDATE query with the correct fields', async () => {
const mockRecipe = createMockRecipe({ recipe_id: 1, name: 'Updated Recipe', description: 'New desc' });
const mockRecipe = createMockRecipe({
recipe_id: 1,
name: 'Updated Recipe',
description: 'New desc',
});
mockQuery.mockResolvedValue({ rows: [mockRecipe], rowCount: 1 });
const updates = { name: 'Updated Recipe', description: 'New desc' };
const result = await recipeRepo.updateRecipe(1, 'user-123', updates, mockLogger);
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('UPDATE public.recipes'),
['Updated Recipe', 'New desc', 1, 'user-123']
);
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('UPDATE public.recipes'), [
'Updated Recipe',
'New desc',
1,
'user-123',
]);
expect(result).toEqual(mockRecipe);
});
it('should throw an error if no fields are provided to update', async () => {
await expect(recipeRepo.updateRecipe(1, 'user-123', {}, mockLogger)).rejects.toThrow('No fields provided to update.');
await expect(recipeRepo.updateRecipe(1, 'user-123', {}, mockLogger)).rejects.toThrow(
'No fields provided to update.',
);
});
it('should throw an error if the recipe is not found or not owned by the user', async () => {
mockQuery.mockResolvedValue({ rowCount: 0 });
await expect(recipeRepo.updateRecipe(999, 'user-123', { name: 'Fail' }, mockLogger)).rejects.toThrow('Recipe not found or user does not have permission to update.');
await expect(
recipeRepo.updateRecipe(999, 'user-123', { name: 'Fail' }, mockLogger),
).rejects.toThrow('Recipe not found or user does not have permission to update.');
});
it('should throw a generic error if the database query fails', async () => {
mockQuery.mockRejectedValue(new Error('DB Error'));
await expect(recipeRepo.updateRecipe(1, 'user-123', { name: 'Fail' }, mockLogger)).rejects.toThrow('Failed to update recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: expect.any(Error), recipeId: 1, userId: 'user-123', updates: { name: 'Fail' } }, 'Database error in updateRecipe');
await expect(
recipeRepo.updateRecipe(1, 'user-123', { name: 'Fail' }, mockLogger),
).rejects.toThrow('Failed to update recipe.');
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: expect.any(Error), recipeId: 1, userId: 'user-123', updates: { name: 'Fail' } },
'Database error in updateRecipe',
);
});
});
@@ -242,14 +326,21 @@ describe('Recipe DB Service', () => {
it('should throw NotFoundError if recipe is not found', async () => {
mockQuery.mockResolvedValue({ rows: [], rowCount: 0 });
await expect(recipeRepo.getRecipeById(999, mockLogger)).rejects.toThrow('Recipe with ID 999 not found');
await expect(recipeRepo.getRecipeById(999, mockLogger)).rejects.toThrow(
'Recipe with ID 999 not found',
);
});
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.getRecipeById(1, mockLogger)).rejects.toThrow('Failed to retrieve recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, recipeId: 1 }, 'Database error in getRecipeById');
await expect(recipeRepo.getRecipeById(1, mockLogger)).rejects.toThrow(
'Failed to retrieve recipe.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, recipeId: 1 },
'Database error in getRecipeById',
);
});
});
@@ -257,7 +348,10 @@ describe('Recipe DB Service', () => {
it('should execute a SELECT query with a JOIN', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await recipeRepo.getRecipeComments(1, mockLogger);
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.recipe_comments rc'), [1]);
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('FROM public.recipe_comments rc'),
[1],
);
});
it('should return an empty array if recipe has no comments', async () => {
@@ -269,19 +363,34 @@ describe('Recipe DB Service', () => {
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.getRecipeComments(1, mockLogger)).rejects.toThrow('Failed to get recipe comments.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, recipeId: 1 }, 'Database error in getRecipeComments');
await expect(recipeRepo.getRecipeComments(1, mockLogger)).rejects.toThrow(
'Failed to get recipe comments.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, recipeId: 1 },
'Database error in getRecipeComments',
);
});
});
describe('addRecipeComment', () => {
it('should execute an INSERT query and return the new comment', async () => {
const mockComment: RecipeComment = { recipe_comment_id: 1, recipe_id: 1, user_id: 'user-123', content: 'Great!', status: 'visible', created_at: new Date().toISOString() };
const mockComment: RecipeComment = {
recipe_comment_id: 1,
recipe_id: 1,
user_id: 'user-123',
content: 'Great!',
status: 'visible',
created_at: new Date().toISOString(),
};
mockQuery.mockResolvedValue({ rows: [mockComment] });
const result = await recipeRepo.addRecipeComment(1, 'user-123', 'Great!', mockLogger);
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.recipe_comments'), [1, 'user-123', 'Great!', undefined]);
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('INSERT INTO public.recipe_comments'),
[1, 'user-123', 'Great!', undefined],
);
expect(result).toEqual(mockComment);
});
@@ -289,15 +398,25 @@ describe('Recipe DB Service', () => {
const dbError = new Error('violates foreign key constraint');
(dbError as Error & { code: string }).code = '23503';
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.addRecipeComment(999, 'user-123', 'Fail', mockLogger)).rejects.toThrow('The specified recipe, user, or parent comment does not exist.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, recipeId: 999, userId: 'user-123', parentCommentId: undefined }, 'Database error in addRecipeComment');
await expect(
recipeRepo.addRecipeComment(999, 'user-123', 'Fail', mockLogger),
).rejects.toThrow('The specified recipe, user, or parent comment does not exist.');
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, recipeId: 999, userId: 'user-123', parentCommentId: undefined },
'Database error in addRecipeComment',
);
});
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.addRecipeComment(1, 'user-123', 'Fail', mockLogger)).rejects.toThrow('Failed to add recipe comment.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, recipeId: 1, userId: 'user-123', parentCommentId: undefined }, 'Database error in addRecipeComment');
await expect(recipeRepo.addRecipeComment(1, 'user-123', 'Fail', mockLogger)).rejects.toThrow(
'Failed to add recipe comment.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, recipeId: 1, userId: 'user-123', parentCommentId: undefined },
'Database error in addRecipeComment',
);
});
});
@@ -307,7 +426,10 @@ describe('Recipe DB Service', () => {
mockQuery.mockResolvedValue({ rows: [mockRecipe] });
const result = await recipeRepo.forkRecipe('user-123', 1, mockLogger);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.fork_recipe($1, $2)', ['user-123', 1]);
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.fork_recipe($1, $2)', [
'user-123',
1,
]);
expect(result).toEqual(mockRecipe);
});
@@ -316,15 +438,25 @@ describe('Recipe DB Service', () => {
(dbError as Error & { code: string }).code = 'P0001'; // raise_exception
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.forkRecipe('user-123', 1, mockLogger)).rejects.toThrow('Recipe is not public and cannot be forked.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', originalRecipeId: 1 }, 'Database error in forkRecipe');
await expect(recipeRepo.forkRecipe('user-123', 1, mockLogger)).rejects.toThrow(
'Recipe is not public and cannot be forked.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123', originalRecipeId: 1 },
'Database error in forkRecipe',
);
});
it('should throw a generic error if the database query fails', async () => {
const dbError = new Error('DB Connection Error');
mockQuery.mockRejectedValue(dbError);
await expect(recipeRepo.forkRecipe('user-123', 1, mockLogger)).rejects.toThrow('Failed to fork recipe.');
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-123', originalRecipeId: 1 }, 'Database error in forkRecipe');
await expect(recipeRepo.forkRecipe('user-123', 1, mockLogger)).rejects.toThrow(
'Failed to fork recipe.',
);
expect(mockLogger.error).toHaveBeenCalledWith(
{ err: dbError, userId: 'user-123', originalRecipeId: 1 },
'Database error in forkRecipe',
);
});
});
});
});