Add user database service and unit tests
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m28s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m28s
- Implement user database service with functions for user management (create, find, update, delete). - Add comprehensive unit tests for user database service using Vitest. - Mock database interactions to ensure isolated testing. - Create setup files for unit tests to handle database connections and global mocks. - Introduce error handling for unique constraints and foreign key violations. - Enhance logging for better traceability during database operations.
This commit is contained in:
201
src/services/db/shopping.db.test.ts
Normal file
201
src/services/db/shopping.db.test.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
// src/services/db/shopping.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
getShoppingLists,
|
||||
createShoppingList,
|
||||
deleteShoppingList,
|
||||
addShoppingListItem,
|
||||
removeShoppingListItem,
|
||||
generateShoppingListForMenuPlan,
|
||||
addMenuPlanToShoppingList,
|
||||
getPantryLocations,
|
||||
createPantryLocation,
|
||||
updateShoppingListItem,
|
||||
completeShoppingList,
|
||||
getShoppingTripHistory,
|
||||
createReceipt,
|
||||
findDealsForReceipt,
|
||||
findReceiptOwner,
|
||||
} from './shopping.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
import type { ShoppingList, ShoppingListItem, PantryLocation, Receipt } from '../../types';
|
||||
|
||||
// Mock the logger to prevent console output during tests
|
||||
vi.mock('../logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Shopping DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
console.log('[DEBUG] shopping.db.test.ts: Local Pool mock instantiated via "new"');
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: vi.fn().mockResolvedValue({ query: mockQuery, release: vi.fn() }),
|
||||
on: vi.fn(),
|
||||
end: vi.fn(),
|
||||
totalCount: 0,
|
||||
idleCount: 0,
|
||||
waitingCount: 0,
|
||||
} as unknown as Pool;
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getShoppingLists', () => {
|
||||
it('should execute the correct query and return shopping lists', async () => {
|
||||
const mockLists: ShoppingList[] = [{ shopping_list_id: 1, name: 'My List', user_id: 'user-123', created_at: '', items: [] }];
|
||||
mockQuery.mockResolvedValue({ rows: mockLists });
|
||||
|
||||
const result = await getShoppingLists('user-123');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.shopping_lists sl'), ['user-123']);
|
||||
expect(result).toEqual(mockLists);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createShoppingList', () => {
|
||||
it('should execute an INSERT query and return the new list', async () => {
|
||||
const mockList: ShoppingList = { shopping_list_id: 1, name: 'New List', user_id: 'user-123', created_at: '', items: [] };
|
||||
mockQuery.mockResolvedValue({ rows: [mockList] });
|
||||
|
||||
const result = await createShoppingList('user-123', 'New List');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_lists'), ['user-123', 'New List']);
|
||||
expect(result).toEqual({ ...mockList, items: [] }); // Ensure items array is empty
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteShoppingList', () => {
|
||||
it('should execute a DELETE query with user ownership check', async () => {
|
||||
mockQuery.mockResolvedValue({ rowCount: 1 });
|
||||
await deleteShoppingList(1, 'user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.shopping_lists WHERE shopping_list_id = $1 AND user_id = $2', [1, 'user-123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addShoppingListItem', () => {
|
||||
it('should execute an INSERT query for a custom item', async () => {
|
||||
const mockItem: ShoppingListItem = { shopping_list_item_id: 1, shopping_list_id: 1, custom_item_name: 'Custom Item', quantity: 1, is_purchased: false, added_at: '' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockItem] });
|
||||
|
||||
const result = await addShoppingListItem(1, { customItemName: 'Custom Item' });
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.shopping_list_items'), [1, undefined, 'Custom Item']);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeShoppingListItem', () => {
|
||||
it('should execute a DELETE query', async () => {
|
||||
mockQuery.mockResolvedValue({ rowCount: 1 });
|
||||
await removeShoppingListItem(101);
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.shopping_list_items WHERE shopping_list_item_id = $1', [101]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateShoppingListForMenuPlan', () => {
|
||||
it('should call the correct database function', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
await generateShoppingListForMenuPlan(1, 'user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.generate_shopping_list_for_menu_plan($1, $2)', [1, 'user-123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addMenuPlanToShoppingList', () => {
|
||||
it('should call the correct database function', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
await addMenuPlanToShoppingList(1, 2, 'user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.add_menu_plan_to_shopping_list($1, $2, $3)', [1, 2, 'user-123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPantryLocations', () => {
|
||||
it('should execute a SELECT query for pantry locations', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
await getPantryLocations('user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.pantry_locations WHERE user_id = $1 ORDER BY name', ['user-123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPantryLocation', () => {
|
||||
it('should execute an INSERT query and return the new location', async () => {
|
||||
const mockLocation: PantryLocation = { pantry_location_id: 1, user_id: 'user-123', name: 'Fridge' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockLocation] });
|
||||
|
||||
const result = await createPantryLocation('user-123', 'Fridge');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.pantry_locations'), ['user-123', 'Fridge']);
|
||||
expect(result).toEqual(mockLocation);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateShoppingListItem', () => {
|
||||
it('should build and execute an UPDATE query', async () => {
|
||||
const mockItem: ShoppingListItem = { shopping_list_item_id: 1, shopping_list_id: 1, custom_item_name: 'Item', quantity: 2, is_purchased: true, added_at: '' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockItem] });
|
||||
|
||||
const result = await updateShoppingListItem(1, { quantity: 2, is_purchased: true });
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(
|
||||
'UPDATE public.shopping_list_items SET quantity = $1, is_purchased = $2 WHERE shopping_list_item_id = $3 RETURNING *',
|
||||
[2, true, 1]
|
||||
);
|
||||
expect(result).toEqual(mockItem);
|
||||
});
|
||||
});
|
||||
|
||||
describe('completeShoppingList', () => {
|
||||
it('should call the correct database function', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ complete_shopping_list: 10 }] });
|
||||
const result = await completeShoppingList(1, 'user-123', 5000);
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT public.complete_shopping_list($1, $2, $3)', [1, 'user-123', 5000]);
|
||||
expect(result).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getShoppingTripHistory', () => {
|
||||
it('should execute the correct query to get shopping trip history', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
await getShoppingTripHistory('user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.shopping_trips st'), ['user-123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createReceipt', () => {
|
||||
it('should execute an INSERT query and return the new receipt', async () => {
|
||||
const mockReceipt: Receipt = { receipt_id: 1, user_id: 'user-123', receipt_image_url: '/img.jpg', status: 'pending', created_at: '' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockReceipt] });
|
||||
|
||||
const result = await createReceipt('user-123', '/img.jpg');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.receipts'), ['user-123', '/img.jpg']);
|
||||
expect(result).toEqual(mockReceipt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findDealsForReceipt', () => {
|
||||
it('should call the correct database function', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
await findDealsForReceipt(1);
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.find_deals_for_receipt_items($1)', [1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findReceiptOwner', () => {
|
||||
it('should execute a SELECT query to find the owner of a receipt', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: 'user-123' }] });
|
||||
const result = await findReceiptOwner(1);
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT user_id FROM public.receipts WHERE receipt_id = $1', [1]);
|
||||
expect(result?.user_id).toBe('user-123');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user