// src/routes/user.routes.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import supertest from 'supertest'; import express from 'express'; // Use * as bcrypt to match the implementation's import style and ensure mocks align. import * as bcrypt from 'bcrypt'; import userRouter from './user.routes'; import { createMockUserProfile, createMockMasterGroceryItem, createMockShoppingList, createMockShoppingListItem, createMockRecipe, createMockNotification, createMockDietaryRestriction, createMockAppliance, createMockUserWithPasswordHash } from '../tests/utils/mockFactories'; import { Appliance, Notification, DietaryRestriction } from '../types'; import { ForeignKeyConstraintError, NotFoundError } from '../services/db/errors.db'; import { createTestApp } from '../tests/utils/createTestApp'; // 1. Mock the Service Layer directly. // The user.routes.ts file imports from '.../db/index.db'. We need to mock that module. vi.mock('../services/db/index.db', () => ({ // Repository instances userRepo: { findUserProfileById: vi.fn(), updateUserProfile: vi.fn(), updateUserPassword: vi.fn(), findUserWithPasswordHashById: vi.fn(), deleteUserById: vi.fn(), updateUserPreferences: vi.fn(), }, personalizationRepo: { getWatchedItems: vi.fn(), removeWatchedItem: vi.fn(), addWatchedItem: vi.fn(), getUserDietaryRestrictions: vi.fn(), setUserDietaryRestrictions: vi.fn(), getUserAppliances: vi.fn(), setUserAppliances: vi.fn(), }, shoppingRepo: { getShoppingLists: vi.fn(), createShoppingList: vi.fn(), deleteShoppingList: vi.fn(), addShoppingListItem: vi.fn(), updateShoppingListItem: vi.fn(), removeShoppingListItem: vi.fn(), }, recipeRepo: { deleteRecipe: vi.fn(), updateRecipe: vi.fn(), }, addressRepo: { getAddressById: vi.fn(), upsertAddress: vi.fn(), }, notificationRepo: { getNotificationsForUser: vi.fn(), markAllNotificationsAsRead: vi.fn(), markNotificationAsRead: vi.fn(), }, })); // 2. Mock bcrypt. // We return an object that satisfies both default and named imports to be safe. vi.mock('bcrypt', () => { const hash = vi.fn(); const compare = vi.fn(); return { default: { hash, compare }, hash, compare, }; }); // Mock the logger vi.mock('../services/logger.server', () => ({ logger: { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn(), }, })); // Mock Passport middleware vi.mock('./passport.routes', () => ({ default: { authenticate: vi.fn(() => (req: express.Request, res: express.Response, next: express.NextFunction) => { // If we are testing the unauthenticated state (no user injected), simulate 401. // If a user WAS injected by our test helper, proceed. if (!req.user) { return res.status(401).json({ message: 'Unauthorized' }); } next(); }), }, // We also need to provide mocks for any other named exports from passport.routes.ts isAdmin: vi.fn((req: express.Request, res: express.Response, next: express.NextFunction) => next()), optionalAuth: vi.fn((req: express.Request, res: express.Response, next: express.NextFunction) => next()), })); // Import the mocked db module to control its functions in tests import * as db from '../services/db/index.db'; describe('User Routes (/api/users)', () => { beforeEach(() => { vi.clearAllMocks(); }); const basePath = '/api/users'; describe('when user is not authenticated', () => { it('GET /profile should return 401', async () => { const app = createTestApp({ router: userRouter, basePath }); // No user injected const response = await supertest(app).get('/api/users/profile'); expect(response.status).toBe(401); }); }); describe('when user is authenticated', () => { const mockUserProfile = createMockUserProfile({ user_id: 'user-123' }); const app = createTestApp({ router: userRouter, basePath, authenticatedUser: mockUserProfile }); beforeEach(() => { // All tests in this block will use the authenticated app }); describe('GET /profile', () => { it('should return the full user profile', async () => { vi.mocked(db.userRepo.findUserProfileById).mockResolvedValue(mockUserProfile); const response = await supertest(app).get('/api/users/profile'); expect(response.status).toBe(200); expect(response.body).toEqual(mockUserProfile); expect(db.userRepo.findUserProfileById).toHaveBeenCalledWith(mockUserProfile.user_id); }); it('should return 404 if profile is not found in DB', async () => { vi.mocked(db.userRepo.findUserProfileById).mockRejectedValue(new NotFoundError('Profile not found for this user.')); const response = await supertest(app).get('/api/users/profile'); expect(response.status).toBe(404); expect(response.body.message).toContain('Profile not found'); }); }); describe('GET /watched-items', () => { it('should return a list of watched items', async () => { const mockItems = [createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Milk' })]; vi.mocked(db.personalizationRepo.getWatchedItems).mockResolvedValue(mockItems); const response = await supertest(app).get('/api/users/watched-items'); expect(response.status).toBe(200); expect(response.body).toEqual(mockItems); }); }); describe('POST /watched-items', () => { it('should add an item to the watchlist and return the new item', async () => { const newItem = { itemName: 'Organic Bananas', category: 'Produce' }; const mockAddedItem = createMockMasterGroceryItem({ master_grocery_item_id: 99, name: 'Organic Bananas', category_name: 'Produce' }); vi.mocked(db.personalizationRepo.addWatchedItem).mockResolvedValue(mockAddedItem); const response = await supertest(app) .post('/api/users/watched-items') .send(newItem); expect(response.status).toBe(201); expect(response.body).toEqual(mockAddedItem); }); }); describe('POST /watched-items (Validation)', () => { it('should return 400 if itemName is missing', async () => { const response = await supertest(app) .post('/api/users/watched-items') .send({ category: 'Produce' }); expect(response.status).toBe(400); expect(response.body.message).toBe("Field 'itemName' is required."); }); it('should return 400 if category is missing', async () => { const response = await supertest(app) .post('/api/users/watched-items') .send({ itemName: 'Apples' }); expect(response.status).toBe(400); expect(response.body.message).toBe("Field 'category' is required."); }); }); it('should return 400 if a foreign key constraint fails', async () => { vi.mocked(db.personalizationRepo.addWatchedItem).mockRejectedValue(new ForeignKeyConstraintError('Category not found')); const response = await supertest(app) .post('/api/users/watched-items') .send({ itemName: 'Test', category: 'Invalid' }); expect(response.status).toBe(400); }); describe('DELETE /watched-items/:masterItemId', () => { it('should remove an item from the watchlist', async () => { vi.mocked(db.personalizationRepo.removeWatchedItem).mockResolvedValue(undefined); const response = await supertest(app).delete(`/api/users/watched-items/99`); expect(response.status).toBe(204); expect(db.personalizationRepo.removeWatchedItem).toHaveBeenCalledWith(mockUserProfile.user_id, 99); }); }); describe('Shopping List Routes', () => { it('GET /shopping-lists should return all shopping lists for the user', async () => { const mockLists = [createMockShoppingList({ shopping_list_id: 1, user_id: mockUserProfile.user_id })]; vi.mocked(db.shoppingRepo.getShoppingLists).mockResolvedValue(mockLists); const response = await supertest(app).get('/api/users/shopping-lists'); expect(response.status).toBe(200); expect(response.body).toEqual(mockLists); }); it('POST /shopping-lists should create a new list', async () => { const mockNewList = createMockShoppingList({ shopping_list_id: 2, user_id: mockUserProfile.user_id, name: 'Party Supplies' }); vi.mocked(db.shoppingRepo.createShoppingList).mockResolvedValue(mockNewList); const response = await supertest(app) .post('/api/users/shopping-lists') .send({ name: 'Party Supplies' }); expect(response.status).toBe(201); expect(response.body).toEqual(mockNewList); }); it('should return 400 if name is missing', async () => { const response = await supertest(app).post('/api/users/shopping-lists').send({}); expect(response.status).toBe(400); expect(response.body.message).toBe("Field 'name' is required."); }); it('should return 400 on foreign key constraint error', async () => { vi.mocked(db.shoppingRepo.createShoppingList).mockRejectedValue(new ForeignKeyConstraintError('User not found')); const response = await supertest(app).post('/api/users/shopping-lists').send({ name: 'Failing List' }); expect(response.status).toBe(400); expect(response.body.message).toBe('User not found'); }); it('should return 400 for an invalid listId on DELETE', async () => { const response = await supertest(app).delete('/api/users/shopping-lists/abc'); expect(response.status).toBe(400); expect(response.body.message).toBe("Invalid ID for parameter 'listId'. Must be a number."); }); describe('DELETE /shopping-lists/:listId', () => { it('should delete a list', async () => { vi.mocked(db.shoppingRepo.deleteShoppingList).mockResolvedValue(undefined); const response = await supertest(app).delete('/api/users/shopping-lists/1'); expect(response.status).toBe(204); }); it('should return 404 if list to delete is not found', async () => { vi.mocked(db.shoppingRepo.deleteShoppingList).mockRejectedValue(new Error('not found')); vi.mocked(db.shoppingRepo.deleteShoppingList).mockRejectedValue(new NotFoundError('not found')); const response = await supertest(app).delete('/api/users/shopping-lists/999'); expect(response.status).toBe(404); }); it('should return 400 for an invalid listId', async () => { const response = await supertest(app).delete('/api/users/shopping-lists/abc'); expect(response.status).toBe(400); expect(response.body.errors[0].message).toBe("Invalid ID for parameter 'listId'. Must be a number."); }); }); }); describe('Shopping List Item Routes', () => { it('POST /shopping-lists/:listId/items should add an item to a list', async () => { const listId = 1; const itemData = { customItemName: 'Paper Towels' }; const mockAddedItem = createMockShoppingListItem({ shopping_list_item_id: 101, shopping_list_id: listId, ...itemData }); vi.mocked(db.shoppingRepo.addShoppingListItem).mockResolvedValue(mockAddedItem); const response = await supertest(app) .post(`/api/users/shopping-lists/${listId}/items`) .send(itemData); expect(response.status).toBe(201); expect(response.body).toEqual(mockAddedItem); }); it('should return 400 on foreign key error when adding an item', async () => { vi.mocked(db.shoppingRepo.addShoppingListItem).mockRejectedValue(new ForeignKeyConstraintError('List not found')); const response = await supertest(app).post('/api/users/shopping-lists/999/items').send({ customItemName: 'Test' }); expect(response.status).toBe(400); }); it('PUT /shopping-lists/items/:itemId should update an item', async () => { const itemId = 101; const updates = { is_purchased: true, quantity: 2 }; const mockUpdatedItem = createMockShoppingListItem({ shopping_list_item_id: itemId, shopping_list_id: 1, ...updates }); vi.mocked(db.shoppingRepo.updateShoppingListItem).mockResolvedValue(mockUpdatedItem); const response = await supertest(app) .put(`/api/users/shopping-lists/items/${itemId}`) .send(updates); expect(response.status).toBe(200); expect(response.body).toEqual(mockUpdatedItem); }); it('should return 404 if item to update is not found', async () => { vi.mocked(db.shoppingRepo.updateShoppingListItem).mockRejectedValue(new NotFoundError('not found')); const response = await supertest(app).put('/api/users/shopping-lists/items/999').send({ is_purchased: true }); expect(response.status).toBe(404); }); describe('DELETE /shopping-lists/items/:itemId', () => { it('should delete an item', async () => { vi.mocked(db.shoppingRepo.removeShoppingListItem).mockResolvedValue(undefined); const response = await supertest(app).delete('/api/users/shopping-lists/items/101'); expect(response.status).toBe(204); }); it('should return 404 if item to delete is not found', async () => { vi.mocked(db.shoppingRepo.removeShoppingListItem).mockRejectedValue(new NotFoundError('not found')); const response = await supertest(app).delete('/api/users/shopping-lists/items/999'); expect(response.status).toBe(404); }); }); }); describe('PUT /profile', () => { it('should update the user profile successfully', async () => { const profileUpdates = { full_name: 'New Name' }; const updatedProfile = { ...mockUserProfile, ...profileUpdates }; vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue(updatedProfile); const response = await supertest(app) .put('/api/users/profile') .send(profileUpdates); expect(response.status).toBe(200); expect(response.body).toEqual(updatedProfile); }); it('should return 400 if the body is empty', async () => { const response = await supertest(app) .put('/api/users/profile') .send({}); expect(response.status).toBe(400); expect(response.body.errors[0].message).toBe('At least one field to update must be provided.'); }); }); describe('PUT /profile/password', () => { it('should update the password successfully with a strong password', async () => { vi.mocked(bcrypt.hash).mockResolvedValue('hashed-password' as never); vi.mocked(db.userRepo.updateUserPassword).mockResolvedValue(undefined); const response = await supertest(app) .put('/api/users/profile/password') .send({ newPassword: 'a-Very-Strong-Password-456!' }); expect(response.status).toBe(200); expect(response.body.message).toBe('Password updated successfully.'); }); it('should return 400 for a weak password', async () => { const response = await supertest(app) .put('/api/users/profile/password') .send({ newPassword: 'weak' }); expect(response.status).toBe(400); expect(response.body.message).toContain('New password is too weak.'); }); }); describe('DELETE /account', () => { it('should delete the account with the correct password', async () => { const userWithHash = createMockUserWithPasswordHash({ ...mockUserProfile.user, password_hash: 'hashed-password' }); vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(userWithHash); vi.mocked(db.userRepo.deleteUserById).mockResolvedValue(undefined); vi.mocked(bcrypt.compare).mockResolvedValue(true as never); const response = await supertest(app) .delete('/api/users/account') .send({ password: 'correct-password' }); expect(response.status).toBe(200); expect(response.body.message).toBe('Account deleted successfully.'); }); it('should return 403 for an incorrect password', async () => { const userWithHash = createMockUserWithPasswordHash({ ...mockUserProfile.user, password_hash: 'hashed-password' }); vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(userWithHash); vi.mocked(bcrypt.compare).mockResolvedValue(false as never); const response = await supertest(app) .delete('/api/users/account') .send({ password: 'wrong-password' }); expect(response.status).toBe(403); expect(response.body.message).toBe('Incorrect password.'); }); it('should return 404 if the user to delete is not found', async () => { vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(undefined); const response = await supertest(app) .delete('/api/users/account') .send({ password: 'any-password' }); expect(response.status).toBe(404); expect(response.body.message).toBe('User not found or password not set.'); }); }); describe('User Preferences and Personalization', () => { describe('PUT /profile/preferences', () => { it('should update user preferences successfully', async () => { const preferencesUpdate = { darkMode: true, unitSystem: 'metric' as const }; const updatedProfile = { ...mockUserProfile, preferences: { ...mockUserProfile.preferences, ...preferencesUpdate } }; vi.mocked(db.userRepo.updateUserPreferences).mockResolvedValue(updatedProfile); const response = await supertest(app) .put('/api/users/profile/preferences') .send(preferencesUpdate); expect(response.status).toBe(200); expect(response.body).toEqual(updatedProfile); }); it('should return 400 if the request body is not a valid object', async () => { const response = await supertest(app) .put('/api/users/profile/preferences') .set('Content-Type', 'application/json') .send('"not-an-object"'); expect(response.status).toBe(400); expect(response.body.message).toBe('Invalid preferences format. Body must be a JSON object.'); }); }); describe('GET and PUT /users/me/dietary-restrictions', () => { it('GET should return a list of restriction IDs', async () => { const mockRestrictions: DietaryRestriction[] = [createMockDietaryRestriction({ name: 'Gluten-Free' })]; vi.mocked(db.personalizationRepo.getUserDietaryRestrictions).mockResolvedValue(mockRestrictions); const response = await supertest(app).get('/api/users/me/dietary-restrictions'); expect(response.status).toBe(200); expect(response.body).toEqual(mockRestrictions); }); it('should return 400 for an invalid masterItemId', async () => { const response = await supertest(app).delete('/api/users/watched-items/abc'); expect(response.status).toBe(400); expect(response.body.message).toBe("Invalid ID for parameter 'masterItemId'. Must be a number."); }); it('PUT should successfully set the restrictions', async () => { vi.mocked(db.personalizationRepo.setUserDietaryRestrictions).mockResolvedValue(undefined); const restrictionIds = [1, 3, 5]; const response = await supertest(app) .put('/api/users/me/dietary-restrictions') .send({ restrictionIds }); expect(response.status).toBe(204); }); it('PUT should return 400 on foreign key constraint error', async () => { vi.mocked(db.personalizationRepo.setUserDietaryRestrictions).mockRejectedValue(new ForeignKeyConstraintError('Invalid restriction ID')); const response = await supertest(app) .put('/api/users/me/dietary-restrictions') .send({ restrictionIds: [999] }); // Invalid ID expect(response.status).toBe(400); }); }); describe('GET and PUT /users/me/appliances', () => { it('GET should return a list of appliance IDs', async () => { const mockAppliances: Appliance[] = [createMockAppliance({ name: 'Air Fryer' })]; vi.mocked(db.personalizationRepo.getUserAppliances).mockResolvedValue(mockAppliances); const response = await supertest(app).get('/api/users/me/appliances'); expect(response.status).toBe(200); expect(response.body).toEqual(mockAppliances); }); it('PUT should successfully set the appliances', async () => { vi.mocked(db.personalizationRepo.setUserAppliances).mockResolvedValue([]); const applianceIds = [2, 4, 6]; const response = await supertest(app).put('/api/users/me/appliances').send({ applianceIds }); expect(response.status).toBe(204); }); it('PUT should return 400 on foreign key constraint error', async () => { vi.mocked(db.personalizationRepo.setUserAppliances).mockRejectedValue(new ForeignKeyConstraintError('Invalid appliance ID')); const response = await supertest(app) .put('/api/users/me/appliances') .send({ applianceIds: [999] }); // Invalid ID expect(response.status).toBe(400); expect(response.body.message).toBe('Invalid appliance ID'); }); }); }); describe('Notification Routes', () => { it('GET /notifications should return notifications for the user', async () => { const mockNotifications: Notification[] = [createMockNotification({ user_id: 'user-123', content: 'Test' })]; vi.mocked(db.notificationRepo.getNotificationsForUser).mockResolvedValue(mockNotifications); const response = await supertest(app).get('/api/users/notifications?limit=10&offset=0'); expect(response.status).toBe(200); expect(response.body).toEqual(mockNotifications); expect(db.notificationRepo.getNotificationsForUser).toHaveBeenCalledWith('user-123', 10, 0); }); it('POST /notifications/mark-all-read should return 204', async () => { vi.mocked(db.notificationRepo.markAllNotificationsAsRead).mockResolvedValue(undefined); const response = await supertest(app).post('/api/users/notifications/mark-all-read'); expect(response.status).toBe(204); expect(db.notificationRepo.markAllNotificationsAsRead).toHaveBeenCalledWith('user-123'); }); it('POST /notifications/:notificationId/mark-read should return 204', async () => { // Fix: Return a mock notification object to match the function's signature. vi.mocked(db.notificationRepo.markNotificationAsRead).mockResolvedValue(createMockNotification({ notification_id: 1, user_id: 'user-123' })); const response = await supertest(app).post('/api/users/notifications/1/mark-read'); expect(response.status).toBe(204); expect(db.notificationRepo.markNotificationAsRead).toHaveBeenCalledWith(1, 'user-123'); }); it('should return 400 for an invalid notificationId', async () => { const response = await supertest(app).post('/api/users/notifications/abc/mark-read').send({}); expect(response.status).toBe(400); expect(response.body.errors[0].message).toBe("Invalid ID for parameter 'notificationId'. Must be a number."); }); }); describe('GET /addresses/:addressId', () => { it('should return 400 for a non-numeric address ID', async () => { const response = await supertest(app).get('/api/users/addresses/abc'); // This was a duplicate, fixed. expect(response.status).toBe(400); }); }); describe('Address Routes', () => { it('GET /addresses/:addressId should return 403 if address does not belong to user', async () => { const appWithDifferentUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: 999 } }); const response = await supertest(appWithDifferentUser).get('/api/users/addresses/1'); expect(response.status).toBe(403); }); it('GET /addresses/:addressId should return 404 if address not found', async () => { const appWithUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: 1 } }); vi.mocked(db.addressRepo.getAddressById).mockRejectedValue(new NotFoundError('Address not found.')); const response = await supertest(appWithUser).get('/api/users/addresses/1'); expect(response.status).toBe(404); expect(response.body.message).toBe('Address not found.'); }); it('PUT /profile/address should call upsertAddress and updateUserProfile if needed', async () => { const appWithUser = createTestApp({ router: userRouter, basePath, authenticatedUser: { ...mockUserProfile, address_id: null } }); // User has no address yet const addressData = { address_line_1: '123 New St' }; vi.mocked(db.addressRepo.upsertAddress).mockResolvedValue(5); // New address ID is 5 vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue({ ...mockUserProfile, address_id: 5 }); const response = await supertest(appWithUser) .put('/api/users/profile/address') .send(addressData); expect(response.status).toBe(200); expect(db.addressRepo.upsertAddress).toHaveBeenCalledWith({ ...addressData, address_id: undefined }); // Verify that the user's profile was updated to link the new address expect(db.userRepo.updateUserProfile).toHaveBeenCalledWith('user-123', { address_id: 5 }); }); }); describe('POST /profile/avatar', () => { it('should upload an avatar and update the user profile', async () => { const mockUpdatedProfile = { ...mockUserProfile, avatar_url: '/uploads/avatars/new-avatar.png' }; vi.mocked(db.userRepo.updateUserProfile).mockResolvedValue(mockUpdatedProfile); // Create a dummy file path for supertest to attach const dummyImagePath = 'test-avatar.png'; const response = await supertest(app) .post('/api/users/profile/avatar') .attach('avatar', Buffer.from('dummy-image-content'), dummyImagePath); expect(response.status).toBe(200); expect(response.body.avatar_url).toContain('/uploads/avatars/'); expect(db.userRepo.updateUserProfile).toHaveBeenCalledWith(mockUserProfile.user_id, { avatar_url: expect.any(String) }); }); it('should return 400 if a non-image file is uploaded', async () => { const dummyTextPath = 'document.txt'; const response = await supertest(app) .post('/api/users/profile/avatar') .attach('avatar', Buffer.from('this is not an image'), dummyTextPath); expect(response.status).toBe(400); expect(response.body.message).toBe('Only image files are allowed!'); }); it('should return 400 if no file is uploaded', async () => { const response = await supertest(app) .post('/api/users/profile/avatar'); // No .attach() call expect(response.status).toBe(400); expect(response.body.message).toBe('No avatar file uploaded.'); }); it('should return 400 for a non-numeric address ID', async () => { const response = await supertest(app).get('/api/users/addresses/abc'); expect(response.status).toBe(400); expect(response.body.errors[0].message).toBe("Invalid ID for parameter 'addressId'. Must be a number."); }); }); describe('Recipe Routes', () => { it('DELETE /recipes/:recipeId should delete a user\'s own recipe', async () => { vi.mocked(db.recipeRepo.deleteRecipe).mockResolvedValue(undefined); const response = await supertest(app).delete('/api/users/recipes/1'); expect(response.status).toBe(204); expect(db.recipeRepo.deleteRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, false); }); it('PUT /recipes/:recipeId should update a user\'s own recipe', async () => { const updates = { description: 'A new delicious description.' }; const mockUpdatedRecipe = { ...createMockRecipe({ recipe_id: 1 }), ...updates }; vi.mocked(db.recipeRepo.updateRecipe).mockResolvedValue(mockUpdatedRecipe); const response = await supertest(app) .put('/api/users/recipes/1') .send(updates); expect(response.status).toBe(200); expect(response.body).toEqual(mockUpdatedRecipe); expect(db.recipeRepo.updateRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, updates); }); it('PUT /recipes/:recipeId should return 404 if recipe not found', async () => { vi.mocked(db.recipeRepo.updateRecipe).mockRejectedValue(new NotFoundError('not found')); const response = await supertest(app).put('/api/users/recipes/999').send({ name: 'New Name' }); expect(response.status).toBe(404); }); it('GET /shopping-lists/:listId should return 404 if list is not found', async () => { vi.mocked(db.shoppingRepo.getShoppingListById).mockRejectedValue(new NotFoundError('Shopping list not found')); const response = await supertest(app).get('/api/users/shopping-lists/999'); expect(response.status).toBe(404); expect(response.body.message).toBe('Shopping list not found'); }); }); // End of Recipe Routes }); });