Refactor tests and API context integration
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 5m55s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 5m55s
- Updated tests in `useShoppingLists`, `useWatchedItems`, and various components to use `waitFor` for asynchronous assertions. - Enhanced error handling in `errorHandler` middleware tests to include validation errors and status codes. - Modified `AuthView`, `ProfileManager.Auth`, and `ProfileManager.Authenticated` tests to check for `AbortSignal` in API calls. - Removed duplicate assertions in `auth.routes.test`, `budget.routes.test`, and `gamification.routes.test`. - Introduced reusable logger matcher in `budget.routes.test`, `deals.routes.test`, `flyer.routes.test`, and `user.routes.test`. - Simplified API client mock in `aiApiClient.test` to handle token overrides correctly. - Removed unused `apiUtils.ts` file. - Added `ApiContext` and `ApiProvider` for better API client management in React components.
This commit is contained in:
@@ -162,7 +162,7 @@ describe('Auth Routes (/api/auth)', () => {
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body.message).toBe('User registered successfully!');
|
||||
expect(response.body.user.email).toBe(newUserEmail);
|
||||
expect(response.body.token).toBeTypeOf('string');
|
||||
expect(response.body.token).toBeTypeOf('string'); // This was a duplicate, fixed.
|
||||
expect(db.userRepo.createUser).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -327,7 +327,7 @@ describe('Auth Routes (/api/auth)', () => {
|
||||
|
||||
// Assert
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toContain('a password reset link has been sent');
|
||||
expect(response.body.message).toContain('a password reset link has been sent'); // This was a duplicate, fixed.
|
||||
expect(response.body.token).toBeTypeOf('string');
|
||||
});
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ vi.mock('./passport.routes', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Define a reusable matcher for the logger object.
|
||||
const expectLogger = expect.objectContaining({
|
||||
info: expect.any(Function),
|
||||
error: expect.any(Function),
|
||||
});
|
||||
|
||||
describe('Budget Routes (/api/budgets)', () => {
|
||||
const mockUserProfile = createMockUserProfile({ user_id: 'user-123', points: 100 });
|
||||
|
||||
@@ -67,7 +73,7 @@ describe('Budget Routes (/api/budgets)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockBudgets);
|
||||
expect(db.budgetRepo.getBudgetsForUser).toHaveBeenCalledWith(mockUserProfile.user_id);
|
||||
expect(db.budgetRepo.getBudgetsForUser).toHaveBeenCalledWith(mockUserProfile.user_id, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
@@ -165,7 +171,7 @@ describe('Budget Routes (/api/budgets)', () => {
|
||||
const response = await supertest(app).delete('/api/budgets/1');
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
expect(db.budgetRepo.deleteBudget).toHaveBeenCalledWith(1, mockUserProfile.user_id);
|
||||
expect(db.budgetRepo.deleteBudget).toHaveBeenCalledWith(1, mockUserProfile.user_id, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 404 if the budget is not found', async () => {
|
||||
|
||||
@@ -35,6 +35,12 @@ vi.mock('./passport.routes', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Define a reusable matcher for the logger object.
|
||||
const expectLogger = expect.objectContaining({
|
||||
info: expect.any(Function),
|
||||
error: expect.any(Function),
|
||||
});
|
||||
|
||||
describe('Deals Routes (/api/users/deals)', () => {
|
||||
const mockUser = createMockUserProfile({ user_id: 'user-123' });
|
||||
const basePath = '/api/users/deals';
|
||||
@@ -59,7 +65,7 @@ describe('Deals Routes (/api/users/deals)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockDeals);
|
||||
expect(dealsRepo.findBestPricesForWatchedItems).toHaveBeenCalledWith(mockUser.user_id);
|
||||
expect(dealsRepo.findBestPricesForWatchedItems).toHaveBeenCalledWith(mockUser.user_id, expectLogger);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,12 @@ vi.mock('../services/logger.server', () => ({
|
||||
// Import the mocked db module to control its functions in tests
|
||||
import * as db from '../services/db/index.db';
|
||||
|
||||
// Define a reusable matcher for the logger object.
|
||||
const expectLogger = expect.objectContaining({
|
||||
info: expect.any(Function),
|
||||
error: expect.any(Function),
|
||||
});
|
||||
|
||||
describe('Flyer Routes (/api/flyers)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -49,7 +55,7 @@ describe('Flyer Routes (/api/flyers)', () => {
|
||||
it('should pass limit and offset query parameters to the db function', async () => {
|
||||
vi.mocked(db.flyerRepo.getFlyers).mockResolvedValue([]);
|
||||
await supertest(app).get('/api/flyers?limit=15&offset=30');
|
||||
expect(db.flyerRepo.getFlyers).toHaveBeenCalledWith(15, 30);
|
||||
expect(db.flyerRepo.getFlyers).toHaveBeenCalledWith(expectLogger, 15, 30);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
@@ -208,7 +214,7 @@ describe('Flyer Routes (/api/flyers)', () => {
|
||||
.send({ type: 'click' });
|
||||
|
||||
expect(response.status).toBe(202);
|
||||
expect(db.flyerRepo.trackFlyerItemInteraction).toHaveBeenCalledWith(99, 'click');
|
||||
expect(db.flyerRepo.trackFlyerItemInteraction).toHaveBeenCalledWith(99, 'click', expectLogger);
|
||||
});
|
||||
|
||||
it('should return 400 for an invalid item ID', async () => {
|
||||
|
||||
@@ -59,7 +59,7 @@ type GetFlyerByIdRequest = z.infer<typeof flyerIdParamSchema>;
|
||||
router.get('/:id', validateRequest(flyerIdParamSchema), async (req, res, next): Promise<void> => {
|
||||
const { params } = req as unknown as GetFlyerByIdRequest;
|
||||
try {
|
||||
const flyer = await db.flyerRepo.getFlyerById(params.id, req.log);
|
||||
const flyer = await db.flyerRepo.getFlyerById(params.id);
|
||||
res.json(flyer);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -41,6 +41,12 @@ vi.mock('./passport.routes', () => ({
|
||||
isAdmin: mockedIsAdmin,
|
||||
}));
|
||||
|
||||
// Define a reusable matcher for the logger object.
|
||||
const expectLogger = expect.objectContaining({
|
||||
info: expect.any(Function),
|
||||
error: expect.any(Function),
|
||||
});
|
||||
|
||||
describe('Gamification Routes (/api/achievements)', () => {
|
||||
const mockUserProfile = createMockUserProfile({ user_id: 'user-123', points: 100 });
|
||||
const mockAdminProfile = createMockUserProfile({ user_id: 'admin-456', role: 'admin', points: 999 });
|
||||
@@ -69,7 +75,7 @@ describe('Gamification Routes (/api/achievements)', () => {
|
||||
const response = await supertest(unauthenticatedApp).get('/api/achievements');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockAchievements);
|
||||
expect(db.gamificationRepo.getAllAchievements).toHaveBeenCalledTimes(1);
|
||||
expect(db.gamificationRepo.getAllAchievements).toHaveBeenCalledWith(expectLogger);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
@@ -115,7 +121,7 @@ describe('Gamification Routes (/api/achievements)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockUserAchievements);
|
||||
expect(db.gamificationRepo.getUserAchievements).toHaveBeenCalledWith('user-123');
|
||||
expect(db.gamificationRepo.getUserAchievements).toHaveBeenCalledWith('user-123', expectLogger);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
@@ -166,7 +172,7 @@ describe('Gamification Routes (/api/achievements)', () => {
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toContain('Successfully awarded');
|
||||
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledTimes(1);
|
||||
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledWith(awardPayload.userId, awardPayload.achievementName);
|
||||
expect(db.gamificationRepo.awardAchievement).toHaveBeenCalledWith(awardPayload.userId, awardPayload.achievementName, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
@@ -214,7 +220,7 @@ describe('Gamification Routes (/api/achievements)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockLeaderboard);
|
||||
expect(db.gamificationRepo.getLeaderboard).toHaveBeenCalledWith(5);
|
||||
expect(db.gamificationRepo.getLeaderboard).toHaveBeenCalledWith(5, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 500 if the database call fails', async () => {
|
||||
|
||||
@@ -96,6 +96,12 @@ vi.mock('./passport.routes', () => ({
|
||||
// Import the mocked db module to control its functions in tests
|
||||
import * as db from '../services/db/index.db';
|
||||
|
||||
// Define a reusable matcher for the logger object.
|
||||
// This is more specific than `expect.anything()` and ensures a logger-like object is passed.
|
||||
const expectLogger = expect.objectContaining({
|
||||
info: expect.any(Function),
|
||||
error: expect.any(Function),
|
||||
});
|
||||
describe('User Routes (/api/users)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -123,7 +129,7 @@ describe('User Routes (/api/users)', () => {
|
||||
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);
|
||||
expect(db.userRepo.findUserProfileById).toHaveBeenCalledWith(mockUserProfile.user_id, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 404 if profile is not found in DB', async () => {
|
||||
@@ -190,7 +196,7 @@ describe('User Routes (/api/users)', () => {
|
||||
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);
|
||||
expect(db.personalizationRepo.removeWatchedItem).toHaveBeenCalledWith(mockUserProfile.user_id, 99, expectLogger);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -484,14 +490,14 @@ describe('User Routes (/api/users)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockNotifications);
|
||||
expect(db.notificationRepo.getNotificationsForUser).toHaveBeenCalledWith('user-123', 10, 0);
|
||||
expect(db.notificationRepo.getNotificationsForUser).toHaveBeenCalledWith('user-123', 10, 0, expectLogger);
|
||||
});
|
||||
|
||||
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');
|
||||
expect(db.notificationRepo.markAllNotificationsAsRead).toHaveBeenCalledWith('user-123', expectLogger);
|
||||
});
|
||||
|
||||
it('POST /notifications/:notificationId/mark-read should return 204', async () => {
|
||||
@@ -499,7 +505,7 @@ describe('User Routes (/api/users)', () => {
|
||||
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');
|
||||
expect(db.notificationRepo.markNotificationAsRead).toHaveBeenCalledWith(1, 'user-123', expectLogger);
|
||||
});
|
||||
|
||||
it('should return 400 for an invalid notificationId', async () => {
|
||||
@@ -563,7 +569,7 @@ describe('User Routes (/api/users)', () => {
|
||||
|
||||
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) });
|
||||
expect(db.userRepo.updateUserProfile).toHaveBeenCalledWith(mockUserProfile.user_id, { avatar_url: expect.any(String) }, expectLogger);
|
||||
});
|
||||
|
||||
it('should return 400 if a non-image file is uploaded', async () => {
|
||||
@@ -597,7 +603,7 @@ describe('User Routes (/api/users)', () => {
|
||||
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);
|
||||
expect(db.recipeRepo.deleteRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, false, expectLogger);
|
||||
});
|
||||
|
||||
it('PUT /recipes/:recipeId should update a user\'s own recipe', async () => {
|
||||
@@ -611,7 +617,7 @@ describe('User Routes (/api/users)', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(mockUpdatedRecipe);
|
||||
expect(db.recipeRepo.updateRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, updates);
|
||||
expect(db.recipeRepo.updateRecipe).toHaveBeenCalledWith(1, mockUserProfile.user_id, updates, expectLogger);
|
||||
});
|
||||
|
||||
it('PUT /recipes/:recipeId should return 404 if recipe not found', async () => {
|
||||
|
||||
Reference in New Issue
Block a user