complete project using prettier!
This commit is contained in:
@@ -3,7 +3,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
||||
import type { Pool, PoolClient } from 'pg';
|
||||
import { withTransaction } from './connection.db';
|
||||
import { createMockShoppingList, createMockShoppingListItem } from '../../tests/utils/mockFactories';
|
||||
import {
|
||||
createMockShoppingList,
|
||||
createMockShoppingListItem,
|
||||
} from '../../tests/utils/mockFactories';
|
||||
|
||||
// Un-mock the module we are testing to ensure we use the real implementation.
|
||||
vi.unmock('./shopping.db');
|
||||
@@ -41,25 +44,36 @@ describe('Shopping DB Service', () => {
|
||||
it('should execute the correct query and return shopping lists', async () => {
|
||||
const mockLists = [createMockShoppingList({ user_id: 'user-1' })];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockLists });
|
||||
|
||||
|
||||
const result = await shoppingRepo.getShoppingLists('user-1', mockLogger);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.shopping_lists sl'), ['user-1']);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('FROM public.shopping_lists sl'),
|
||||
['user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockLists);
|
||||
});
|
||||
|
||||
it('should return an empty array if a user has no shopping lists', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
const result = await shoppingRepo.getShoppingLists('user-with-no-lists', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.shopping_lists sl'), ['user-with-no-lists']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('FROM public.shopping_lists sl'),
|
||||
['user-with-no-lists'],
|
||||
);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.getShoppingLists('user-1', mockLogger)).rejects.toThrow('Failed to retrieve shopping lists.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1' }, 'Database error in getShoppingLists');
|
||||
await expect(shoppingRepo.getShoppingLists('user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to retrieve shopping lists.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1' },
|
||||
'Database error in getShoppingLists',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +86,7 @@ describe('Shopping DB Service', () => {
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('WHERE sl.shopping_list_id = $1 AND sl.user_id = $2'),
|
||||
[1, 'user-1']
|
||||
[1, 'user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockList);
|
||||
});
|
||||
@@ -80,15 +94,22 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw NotFoundError if the shopping list is not found or not owned by the user', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [], rowCount: 0 });
|
||||
|
||||
await expect(shoppingRepo.getShoppingListById(999, 'user-1', mockLogger)).rejects.toThrow('Shopping list not found or you do not have permission to view it.');
|
||||
await expect(shoppingRepo.getShoppingListById(999, 'user-1', mockLogger)).rejects.toThrow(
|
||||
'Shopping list not found or you do not have permission to view it.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
|
||||
await expect(shoppingRepo.getShoppingListById(1, 'user-1', mockLogger)).rejects.toThrow('Failed to retrieve shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, listId: 1, userId: 'user-1' }, 'Database error in getShoppingListById');
|
||||
await expect(shoppingRepo.getShoppingListById(1, 'user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to retrieve shopping list.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, listId: 1, userId: 'user-1' },
|
||||
'Database error in getShoppingListById',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,43 +119,67 @@ describe('Shopping DB Service', () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockList] });
|
||||
|
||||
const result = await shoppingRepo.createShoppingList('user-1', 'New List', mockLogger);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_lists'), ['user-1', 'New List']);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.shopping_lists'),
|
||||
['user-1', 'New List'],
|
||||
);
|
||||
expect(result).toEqual(mockList);
|
||||
});
|
||||
|
||||
it('should throw ForeignKeyConstraintError if user does not exist', async () => {
|
||||
const dbError = new Error('insert or update on table "shopping_lists" violates foreign key constraint "shopping_lists_user_id_fkey"');
|
||||
const dbError = new Error(
|
||||
'insert or update on table "shopping_lists" violates foreign key constraint "shopping_lists_user_id_fkey"',
|
||||
);
|
||||
(dbError as Error & { code: string }).code = '23503';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createShoppingList('non-existent-user', 'Wont work', mockLogger)).rejects.toThrow(ForeignKeyConstraintError);
|
||||
await expect(
|
||||
shoppingRepo.createShoppingList('non-existent-user', 'Wont work', mockLogger),
|
||||
).rejects.toThrow(ForeignKeyConstraintError);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails for other reasons', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createShoppingList('user-1', 'New List', mockLogger)).rejects.toThrow('Failed to create shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1', name: 'New List' }, 'Database error in createShoppingList');
|
||||
await expect(
|
||||
shoppingRepo.createShoppingList('user-1', 'New List', mockLogger),
|
||||
).rejects.toThrow('Failed to create shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1', name: 'New List' },
|
||||
'Database error in createShoppingList',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteShoppingList', () => {
|
||||
it('should delete a shopping list if rowCount is 1', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 1, rows: [], command: 'DELETE' });
|
||||
await expect(shoppingRepo.deleteShoppingList(1, 'user-1', mockLogger)).resolves.toBeUndefined();
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.shopping_lists WHERE shopping_list_id = $1 AND user_id = $2', [1, 'user-1']);
|
||||
await expect(
|
||||
shoppingRepo.deleteShoppingList(1, 'user-1', mockLogger),
|
||||
).resolves.toBeUndefined();
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'DELETE FROM public.shopping_lists WHERE shopping_list_id = $1 AND user_id = $2',
|
||||
[1, 'user-1'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if no rows are deleted (list not found or wrong user)', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 0, rows: [], command: 'DELETE' });
|
||||
await expect(shoppingRepo.deleteShoppingList(999, 'user-1', mockLogger)).rejects.toThrow('Failed to delete shopping list.');
|
||||
await expect(shoppingRepo.deleteShoppingList(999, 'user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to delete shopping list.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.deleteShoppingList(1, 'user-1', mockLogger)).rejects.toThrow('Failed to delete shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, listId: 1, userId: 'user-1' }, 'Database error in deleteShoppingList');
|
||||
await expect(shoppingRepo.deleteShoppingList(1, 'user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to delete shopping list.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, listId: 1, userId: 'user-1' },
|
||||
'Database error in deleteShoppingList',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,9 +188,16 @@ describe('Shopping DB Service', () => {
|
||||
const mockItem = createMockShoppingListItem({ custom_item_name: 'Custom Item' });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockItem] });
|
||||
|
||||
const result = await shoppingRepo.addShoppingListItem(1, { customItemName: 'Custom Item' }, mockLogger);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_list_items'), [1, null, 'Custom Item']);
|
||||
const result = await shoppingRepo.addShoppingListItem(
|
||||
1,
|
||||
{ customItemName: 'Custom Item' },
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.shopping_list_items'),
|
||||
[1, null, 'Custom Item'],
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
|
||||
@@ -154,37 +206,59 @@ describe('Shopping DB Service', () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockItem] });
|
||||
|
||||
const result = await shoppingRepo.addShoppingListItem(1, { masterItemId: 123 }, mockLogger);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_list_items'), [1, 123, null]);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.shopping_list_items'),
|
||||
[1, 123, null],
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
|
||||
it('should add an item with both masterItemId and customItemName', async () => {
|
||||
const mockItem = createMockShoppingListItem({ master_item_id: 123, custom_item_name: 'Organic Apples' });
|
||||
const mockItem = createMockShoppingListItem({
|
||||
master_item_id: 123,
|
||||
custom_item_name: 'Organic Apples',
|
||||
});
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockItem] });
|
||||
|
||||
const result = await shoppingRepo.addShoppingListItem(1, { masterItemId: 123, customItemName: 'Organic Apples' }, mockLogger);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_list_items'), [1, 123, 'Organic Apples']);
|
||||
const result = await shoppingRepo.addShoppingListItem(
|
||||
1,
|
||||
{ masterItemId: 123, customItemName: 'Organic Apples' },
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.shopping_list_items'),
|
||||
[1, 123, 'Organic Apples'],
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
|
||||
it('should throw an error if both masterItemId and customItemName are missing', async () => {
|
||||
await expect(shoppingRepo.addShoppingListItem(1, {}, mockLogger)).rejects.toThrow('Either masterItemId or customItemName must be provided.');
|
||||
await expect(shoppingRepo.addShoppingListItem(1, {}, mockLogger)).rejects.toThrow(
|
||||
'Either masterItemId or customItemName must be provided.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw ForeignKeyConstraintError if list or master item does not exist', async () => {
|
||||
const dbError = new Error('violates foreign key constraint');
|
||||
(dbError as Error & { code: string }).code = '23503';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.addShoppingListItem(999, { masterItemId: 999 }, mockLogger)).rejects.toThrow('Referenced list or item does not exist.');
|
||||
await expect(
|
||||
shoppingRepo.addShoppingListItem(999, { masterItemId: 999 }, mockLogger),
|
||||
).rejects.toThrow('Referenced list or item does not exist.');
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.addShoppingListItem(1, { customItemName: 'Test' }, mockLogger)).rejects.toThrow('Failed to add item to shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, listId: 1, item: { customItemName: 'Test' } }, 'Database error in addShoppingListItem');
|
||||
await expect(
|
||||
shoppingRepo.addShoppingListItem(1, { customItemName: 'Test' }, mockLogger),
|
||||
).rejects.toThrow('Failed to add item to shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, listId: 1, item: { customItemName: 'Test' } },
|
||||
'Database error in addShoppingListItem',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -193,11 +267,15 @@ describe('Shopping DB Service', () => {
|
||||
const mockItem = createMockShoppingListItem({ shopping_list_item_id: 1, is_purchased: true });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockItem], rowCount: 1 });
|
||||
|
||||
const result = await shoppingRepo.updateShoppingListItem(1, { is_purchased: true }, mockLogger);
|
||||
|
||||
const result = await shoppingRepo.updateShoppingListItem(
|
||||
1,
|
||||
{ is_purchased: true },
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'UPDATE public.shopping_list_items SET is_purchased = $1 WHERE shopping_list_item_id = $2 RETURNING *',
|
||||
[true, 1]
|
||||
[true, 1],
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
@@ -208,29 +286,38 @@ describe('Shopping DB Service', () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockItem], rowCount: 1 });
|
||||
|
||||
const result = await shoppingRepo.updateShoppingListItem(1, updates, mockLogger);
|
||||
|
||||
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'UPDATE public.shopping_list_items SET quantity = $1, is_purchased = $2, notes = $3 WHERE shopping_list_item_id = $4 RETURNING *',
|
||||
[updates.quantity, updates.is_purchased, updates.notes, 1]
|
||||
[updates.quantity, updates.is_purchased, updates.notes, 1],
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
|
||||
it('should throw an error if the item to update is not found', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 0, rows: [], command: 'UPDATE' });
|
||||
await expect(shoppingRepo.updateShoppingListItem(999, { quantity: 5 }, mockLogger)).rejects.toThrow('Shopping list item not found.');
|
||||
await expect(
|
||||
shoppingRepo.updateShoppingListItem(999, { quantity: 5 }, mockLogger),
|
||||
).rejects.toThrow('Shopping list item not found.');
|
||||
});
|
||||
|
||||
it('should throw an error if no valid fields are provided to update', async () => {
|
||||
// The function should throw before even querying the database.
|
||||
await expect(shoppingRepo.updateShoppingListItem(1, {}, mockLogger)).rejects.toThrow('No valid fields to update.');
|
||||
await expect(shoppingRepo.updateShoppingListItem(1, {}, mockLogger)).rejects.toThrow(
|
||||
'No valid fields to update.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.updateShoppingListItem(1, { is_purchased: true }, mockLogger)).rejects.toThrow('Failed to update shopping list item.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, itemId: 1, updates: { is_purchased: true } }, 'Database error in updateShoppingListItem');
|
||||
await expect(
|
||||
shoppingRepo.updateShoppingListItem(1, { is_purchased: true }, mockLogger),
|
||||
).rejects.toThrow('Failed to update shopping list item.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, itemId: 1, updates: { is_purchased: true } },
|
||||
'Database error in updateShoppingListItem',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,19 +325,29 @@ describe('Shopping DB Service', () => {
|
||||
it('should delete an item if rowCount is 1', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 1, rows: [], command: 'DELETE' });
|
||||
await expect(shoppingRepo.removeShoppingListItem(1, mockLogger)).resolves.toBeUndefined();
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.shopping_list_items WHERE shopping_list_item_id = $1', [1]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'DELETE FROM public.shopping_list_items WHERE shopping_list_item_id = $1',
|
||||
[1],
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if no rows are deleted (item not found)', async () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 0, rows: [], command: 'DELETE' });
|
||||
await expect(shoppingRepo.removeShoppingListItem(999, mockLogger)).rejects.toThrow('Shopping list item not found.');
|
||||
await expect(shoppingRepo.removeShoppingListItem(999, mockLogger)).rejects.toThrow(
|
||||
'Shopping list item not found.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Connection Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.removeShoppingListItem(1, mockLogger)).rejects.toThrow('Failed to remove item from shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, itemId: 1 }, 'Database error in removeShoppingListItem');
|
||||
await expect(shoppingRepo.removeShoppingListItem(1, mockLogger)).rejects.toThrow(
|
||||
'Failed to remove item from shopping list.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, itemId: 1 },
|
||||
'Database error in removeShoppingListItem',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -259,21 +356,31 @@ describe('Shopping DB Service', () => {
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ complete_shopping_list: 1 }] });
|
||||
const result = await shoppingRepo.completeShoppingList(1, 'user-123', mockLogger, 5000);
|
||||
expect(result).toBe(1);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT public.complete_shopping_list($1, $2, $3)', [1, 'user-123', 5000]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT public.complete_shopping_list($1, $2, $3)',
|
||||
[1, 'user-123', 5000],
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw ForeignKeyConstraintError if the shopping list does not exist', async () => {
|
||||
const dbError = new Error('violates foreign key constraint');
|
||||
(dbError as Error & { code: string }).code = '23503';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.completeShoppingList(999, 'user-123', mockLogger)).rejects.toThrow('The specified shopping list does not exist.');
|
||||
await expect(shoppingRepo.completeShoppingList(999, 'user-123', mockLogger)).rejects.toThrow(
|
||||
'The specified shopping list does not exist.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Function Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.completeShoppingList(1, 'user-123', mockLogger)).rejects.toThrow('Failed to complete shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, shoppingListId: 1, userId: 'user-123' }, 'Database error in completeShoppingList');
|
||||
await expect(shoppingRepo.completeShoppingList(1, 'user-123', mockLogger)).rejects.toThrow(
|
||||
'Failed to complete shopping list.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, shoppingListId: 1, userId: 'user-123' },
|
||||
'Database error in completeShoppingList',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -282,7 +389,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockItems = [{ master_item_id: 1, item_name: 'Apples', quantity_needed: 2 }];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockItems });
|
||||
const result = await shoppingRepo.generateShoppingListForMenuPlan(1, 'user-1', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.generate_shopping_list_for_menu_plan($1, $2)', [1, 'user-1']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT * FROM public.generate_shopping_list_for_menu_plan($1, $2)',
|
||||
[1, 'user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockItems);
|
||||
});
|
||||
|
||||
@@ -295,8 +405,13 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.generateShoppingListForMenuPlan(1, 'user-1', mockLogger)).rejects.toThrow('Failed to generate shopping list for menu plan.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, menuPlanId: 1, userId: 'user-1' }, 'Database error in generateShoppingListForMenuPlan');
|
||||
await expect(
|
||||
shoppingRepo.generateShoppingListForMenuPlan(1, 'user-1', mockLogger),
|
||||
).rejects.toThrow('Failed to generate shopping list for menu plan.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, menuPlanId: 1, userId: 'user-1' },
|
||||
'Database error in generateShoppingListForMenuPlan',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -305,7 +420,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockItems = [{ master_item_id: 1, item_name: 'Apples', quantity_needed: 2 }];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockItems });
|
||||
const result = await shoppingRepo.addMenuPlanToShoppingList(1, 10, 'user-1', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.add_menu_plan_to_shopping_list($1, $2, $3)', [1, 10, 'user-1']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT * FROM public.add_menu_plan_to_shopping_list($1, $2, $3)',
|
||||
[1, 10, 'user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockItems);
|
||||
});
|
||||
|
||||
@@ -318,8 +436,13 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.addMenuPlanToShoppingList(1, 10, 'user-1', mockLogger)).rejects.toThrow('Failed to add menu plan to shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, menuPlanId: 1, shoppingListId: 10, userId: 'user-1' }, 'Database error in addMenuPlanToShoppingList');
|
||||
await expect(
|
||||
shoppingRepo.addMenuPlanToShoppingList(1, 10, 'user-1', mockLogger),
|
||||
).rejects.toThrow('Failed to add menu plan to shopping list.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, menuPlanId: 1, shoppingListId: 10, userId: 'user-1' },
|
||||
'Database error in addMenuPlanToShoppingList',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -328,7 +451,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockLocations = [{ pantry_location_id: 1, name: 'Fridge', user_id: 'user-1' }];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockLocations });
|
||||
const result = await shoppingRepo.getPantryLocations('user-1', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.pantry_locations WHERE user_id = $1 ORDER BY name', ['user-1']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT * FROM public.pantry_locations WHERE user_id = $1 ORDER BY name',
|
||||
['user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockLocations);
|
||||
});
|
||||
|
||||
@@ -341,8 +467,13 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.getPantryLocations('user-1', mockLogger)).rejects.toThrow('Failed to get pantry locations.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1' }, 'Database error in getPantryLocations');
|
||||
await expect(shoppingRepo.getPantryLocations('user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to get pantry locations.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1' },
|
||||
'Database error in getPantryLocations',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -351,7 +482,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockLocation = { pantry_location_id: 1, name: 'Freezer', user_id: 'user-1' };
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockLocation] });
|
||||
const result = await shoppingRepo.createPantryLocation('user-1', 'Freezer', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('INSERT INTO public.pantry_locations (user_id, name) VALUES ($1, $2) RETURNING *', ['user-1', 'Freezer']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'INSERT INTO public.pantry_locations (user_id, name) VALUES ($1, $2) RETURNING *',
|
||||
['user-1', 'Freezer'],
|
||||
);
|
||||
expect(result).toEqual(mockLocation);
|
||||
});
|
||||
|
||||
@@ -359,21 +493,30 @@ describe('Shopping DB Service', () => {
|
||||
const dbError = new Error('duplicate key value violates unique constraint');
|
||||
(dbError as Error & { code: string }).code = '23505';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createPantryLocation('user-1', 'Fridge', mockLogger)).rejects.toThrow(UniqueConstraintError);
|
||||
await expect(
|
||||
shoppingRepo.createPantryLocation('user-1', 'Fridge', mockLogger),
|
||||
).rejects.toThrow(UniqueConstraintError);
|
||||
});
|
||||
|
||||
it('should throw ForeignKeyConstraintError if user does not exist', async () => {
|
||||
const dbError = new Error('violates foreign key constraint');
|
||||
(dbError as Error & { code: string }).code = '23503';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createPantryLocation('non-existent-user', 'Pantry', mockLogger)).rejects.toThrow(ForeignKeyConstraintError);
|
||||
await expect(
|
||||
shoppingRepo.createPantryLocation('non-existent-user', 'Pantry', mockLogger),
|
||||
).rejects.toThrow(ForeignKeyConstraintError);
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createPantryLocation('user-1', 'Pantry', mockLogger)).rejects.toThrow('Failed to create pantry location.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1', name: 'Pantry' }, 'Database error in createPantryLocation');
|
||||
await expect(
|
||||
shoppingRepo.createPantryLocation('user-1', 'Pantry', mockLogger),
|
||||
).rejects.toThrow('Failed to create pantry location.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1', name: 'Pantry' },
|
||||
'Database error in createPantryLocation',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -382,7 +525,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockTrips = [{ shopping_trip_id: 1, user_id: 'user-1', items: [] }];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockTrips });
|
||||
const result = await shoppingRepo.getShoppingTripHistory('user-1', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.shopping_trips st'), ['user-1']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('FROM public.shopping_trips st'),
|
||||
['user-1'],
|
||||
);
|
||||
expect(result).toEqual(mockTrips);
|
||||
});
|
||||
|
||||
@@ -395,17 +541,30 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.getShoppingTripHistory('user-1', mockLogger)).rejects.toThrow('Failed to retrieve shopping trip history.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1' }, 'Database error in getShoppingTripHistory');
|
||||
await expect(shoppingRepo.getShoppingTripHistory('user-1', mockLogger)).rejects.toThrow(
|
||||
'Failed to retrieve shopping trip history.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1' },
|
||||
'Database error in getShoppingTripHistory',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createReceipt', () => {
|
||||
it('should insert a new receipt and return it', async () => {
|
||||
const mockReceipt = { receipt_id: 1, user_id: 'user-1', receipt_image_url: 'url', status: 'pending' };
|
||||
const mockReceipt = {
|
||||
receipt_id: 1,
|
||||
user_id: 'user-1',
|
||||
receipt_image_url: 'url',
|
||||
status: 'pending',
|
||||
};
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockReceipt] });
|
||||
const result = await shoppingRepo.createReceipt('user-1', 'url', mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.receipts'), ['user-1', 'url']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.receipts'),
|
||||
['user-1', 'url'],
|
||||
);
|
||||
expect(result).toEqual(mockReceipt);
|
||||
});
|
||||
|
||||
@@ -413,14 +572,21 @@ describe('Shopping DB Service', () => {
|
||||
const dbError = new Error('violates foreign key constraint');
|
||||
(dbError as Error & { code: string }).code = '23503';
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createReceipt('non-existent-user', 'url', mockLogger)).rejects.toThrow('User not found');
|
||||
await expect(
|
||||
shoppingRepo.createReceipt('non-existent-user', 'url', mockLogger),
|
||||
).rejects.toThrow('User not found');
|
||||
});
|
||||
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.createReceipt('user-1', 'url', mockLogger)).rejects.toThrow('Failed to create receipt record.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, userId: 'user-1', receiptImageUrl: 'url' }, 'Database error in createReceipt');
|
||||
await expect(shoppingRepo.createReceipt('user-1', 'url', mockLogger)).rejects.toThrow(
|
||||
'Failed to create receipt record.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, userId: 'user-1', receiptImageUrl: 'url' },
|
||||
'Database error in createReceipt',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -429,7 +595,10 @@ describe('Shopping DB Service', () => {
|
||||
const mockOwner = { user_id: 'owner-123' };
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockOwner] });
|
||||
const result = await shoppingRepo.findReceiptOwner(1, mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT user_id FROM public.receipts WHERE receipt_id = $1', [1]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT user_id FROM public.receipts WHERE receipt_id = $1',
|
||||
[1],
|
||||
);
|
||||
expect(result).toEqual(mockOwner);
|
||||
});
|
||||
|
||||
@@ -442,8 +611,13 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw a generic error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.findReceiptOwner(1, mockLogger)).rejects.toThrow('Failed to retrieve receipt owner from database.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, receiptId: 1 }, 'Database error in findReceiptOwner');
|
||||
await expect(shoppingRepo.findReceiptOwner(1, mockLogger)).rejects.toThrow(
|
||||
'Failed to retrieve receipt owner from database.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, receiptId: 1 },
|
||||
'Database error in findReceiptOwner',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -456,13 +630,16 @@ describe('Shopping DB Service', () => {
|
||||
});
|
||||
|
||||
const items = [{ raw_item_description: 'Milk', price_paid_cents: 399 }];
|
||||
|
||||
|
||||
await shoppingRepo.processReceiptItems(1, items, mockLogger);
|
||||
|
||||
const expectedItemsWithQuantity = [{ raw_item_description: 'Milk', price_paid_cents: 399, quantity: 1 }];
|
||||
|
||||
const expectedItemsWithQuantity = [
|
||||
{ raw_item_description: 'Milk', price_paid_cents: 399, quantity: 1 },
|
||||
];
|
||||
expect(withTransaction).toHaveBeenCalledTimes(1);
|
||||
expect(mockClientQuery).toHaveBeenCalledWith(
|
||||
'SELECT public.process_receipt_items($1, $2, $3)', [1, JSON.stringify(expectedItemsWithQuantity), JSON.stringify(expectedItemsWithQuantity)]
|
||||
'SELECT public.process_receipt_items($1, $2, $3)',
|
||||
[1, JSON.stringify(expectedItemsWithQuantity), JSON.stringify(expectedItemsWithQuantity)],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -476,21 +653,43 @@ describe('Shopping DB Service', () => {
|
||||
});
|
||||
|
||||
const items = [{ raw_item_description: 'Milk', price_paid_cents: 399 }];
|
||||
await expect(shoppingRepo.processReceiptItems(1, items, mockLogger)).rejects.toThrow('Failed to process and save receipt items.');
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, receiptId: 1 }, 'Database transaction error in processReceiptItems');
|
||||
await expect(shoppingRepo.processReceiptItems(1, items, mockLogger)).rejects.toThrow(
|
||||
'Failed to process and save receipt items.',
|
||||
);
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, receiptId: 1 },
|
||||
'Database transaction error in processReceiptItems',
|
||||
);
|
||||
// Verify that the status was updated to 'failed' in the catch block
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith("UPDATE public.receipts SET status = 'failed' WHERE receipt_id = $1", [1]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
"UPDATE public.receipts SET status = 'failed' WHERE receipt_id = $1",
|
||||
[1],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDealsForReceipt', () => {
|
||||
it('should call the find_deals_for_receipt_items database function', async () => {
|
||||
const mockDeals = [{ receipt_item_id: 1, master_item_id: 10, item_name: 'Milk', price_paid_cents: 399, current_best_price_in_cents: 350, potential_savings_cents: 49, deal_store_name: 'Grocer', flyer_id: 101 }];
|
||||
const mockDeals = [
|
||||
{
|
||||
receipt_item_id: 1,
|
||||
master_item_id: 10,
|
||||
item_name: 'Milk',
|
||||
price_paid_cents: 399,
|
||||
current_best_price_in_cents: 350,
|
||||
potential_savings_cents: 49,
|
||||
deal_store_name: 'Grocer',
|
||||
flyer_id: 101,
|
||||
},
|
||||
];
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockDeals });
|
||||
|
||||
const result = await shoppingRepo.findDealsForReceipt(1, mockLogger);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.find_deals_for_receipt_items($1)', [1]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
'SELECT * FROM public.find_deals_for_receipt_items($1)',
|
||||
[1],
|
||||
);
|
||||
expect(result).toEqual(mockDeals);
|
||||
});
|
||||
|
||||
@@ -503,8 +702,13 @@ describe('Shopping DB Service', () => {
|
||||
it('should throw an error if the database query fails', async () => {
|
||||
const dbError = new Error('DB Error');
|
||||
mockPoolInstance.query.mockRejectedValue(dbError);
|
||||
await expect(shoppingRepo.findDealsForReceipt(1, mockLogger)).rejects.toThrow('Failed to find deals for receipt.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, receiptId: 1 }, 'Database error in findDealsForReceipt');
|
||||
await expect(shoppingRepo.findDealsForReceipt(1, mockLogger)).rejects.toThrow(
|
||||
'Failed to find deals for receipt.',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: dbError, receiptId: 1 },
|
||||
'Database error in findDealsForReceipt',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -522,10 +726,18 @@ describe('Shopping DB Service', () => {
|
||||
mockPoolInstance.query.mockRejectedValueOnce(updateStatusError);
|
||||
|
||||
const items = [{ raw_item_description: 'Milk', price_paid_cents: 399 }];
|
||||
await expect(shoppingRepo.processReceiptItems(1, items, mockLogger)).rejects.toThrow('Failed to process and save receipt items.');
|
||||
await expect(shoppingRepo.processReceiptItems(1, items, mockLogger)).rejects.toThrow(
|
||||
'Failed to process and save receipt items.',
|
||||
);
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ err: transactionError, receiptId: 1 }, 'Database transaction error in processReceiptItems');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith({ updateError: updateStatusError, receiptId: 1 }, 'Failed to update receipt status to "failed" after transaction rollback.');
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ err: transactionError, receiptId: 1 },
|
||||
'Database transaction error in processReceiptItems',
|
||||
);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
{ updateError: updateStatusError, receiptId: 1 },
|
||||
'Failed to update receipt status to "failed" after transaction rollback.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user