testing routes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 6m53s

This commit is contained in:
2025-11-28 23:41:31 -08:00
parent 5e14da0af2
commit 7a2214d56e
2 changed files with 23 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
// src/routes/admin.test.ts
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import { describe, it, expect, vi, beforeEach, type Mocked, type Mock } from 'vitest';
import supertest from 'supertest';
import express, { Request, Response, NextFunction } from 'express';
import path from 'node:path';
@@ -31,14 +31,14 @@ vi.mock('./passport', () => ({
// Mock the default export (the passport instance)
default: {
// The 'authenticate' method returns a middleware function. We mock that.
authenticate: vi.fn((strategy, options) => (req: Request, res: Response, next: NextFunction) => {
authenticate: vi.fn((_strategy: string, _options: object) => (_req: Request, _res: Response, next: NextFunction) => {
// This mock middleware will be controlled by the `isAdmin` mock below.
// In a real scenario, you might attach a user to `req.user` here if needed.
return next();
}),
},
// Mock the named export 'isAdmin'
isAdmin: vi.fn((req: Request, res: Response, next: NextFunction) => {
isAdmin: vi.fn((_req: Request, res: Response, _next: NextFunction) => {
// The default behavior of this mock is to deny access.
// We will override this implementation in specific tests.
res.status(401).json({ message: 'Unauthorized' });
@@ -47,7 +47,7 @@ vi.mock('./passport', () => ({
// We need to import the mocked 'isAdmin' so we can change its behavior in tests.
import { isAdmin } from './passport';
const mockedIsAdmin = isAdmin as Mocked<any>;
const mockedIsAdmin = isAdmin as Mock;
// Create a minimal Express app to host our router
const app = express();
@@ -59,7 +59,7 @@ describe('Admin Routes (/api/admin)', () => {
vi.clearAllMocks();
// Reset the isAdmin mock to its default implementation before each test.
// This prevents mock configurations from one test leaking into another.
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => {
mockedIsAdmin.mockImplementation((_req: Request, res: Response, _next: NextFunction) => {
// The default behavior is to deny access, which is correct for unauthenticated tests.
res.status(401).json({ message: 'Unauthorized' });
});
@@ -69,7 +69,7 @@ describe('Admin Routes (/api/admin)', () => {
// Arrange: Configure the isAdmin mock to simulate a non-admin user.
// It will call next(), but since req.user.role is not 'admin', the real logic
// inside the original isAdmin would fail. Here, we just simulate the end result of a 403 Forbidden.
mockedIsAdmin.mockImplementation((req: Request, res: Response, next: NextFunction) => {
mockedIsAdmin.mockImplementation((_req: Request, res: Response, _next: NextFunction) => {
res.status(403).json({ message: 'Forbidden: Administrator access required.' });
});
@@ -110,7 +110,7 @@ describe('Admin Routes (/api/admin)', () => {
it('GET /corrections should return corrections data', async () => {
// Arrange
const mockCorrections = [{ correction_id: 1, suggested_value: 'New Price' }];
mockedDb.getSuggestedCorrections.mockResolvedValue(mockCorrections as any);
(mockedDb.getSuggestedCorrections as Mock).mockResolvedValue(mockCorrections);
// Act
const response = await supertest(app).get('/api/admin/corrections');
@@ -128,7 +128,7 @@ describe('Admin Routes (/api/admin)', () => {
{ brand_id: 1, name: 'Brand A', logo_url: '/path/a.png' },
{ brand_id: 2, name: 'Brand B', logo_url: '/path/b.png' },
];
mockedDb.getAllBrands.mockResolvedValue(mockBrands as any);
(mockedDb.getAllBrands as Mock).mockResolvedValue(mockBrands);
// Act
const response = await supertest(app).get('/api/admin/brands');
@@ -181,7 +181,7 @@ describe('Admin Routes (/api/admin)', () => {
{ date: '2024-01-01', new_users: 5, new_flyers: 10 },
{ date: '2024-01-02', new_users: 3, new_flyers: 8 },
];
mockedDb.getDailyStatsForLast30Days.mockResolvedValue(mockDailyStats as any);
(mockedDb.getDailyStatsForLast30Days as Mock).mockResolvedValue(mockDailyStats);
// Act
const response = await supertest(app).get('/api/admin/stats/daily');
@@ -206,7 +206,7 @@ describe('Admin Routes (/api/admin)', () => {
{ flyer_item_id: 1, raw_item_description: 'Ketchup Chips', price_display: '$3.00' },
{ flyer_item_id: 2, raw_item_description: 'Mystery Soda', price_display: '2 for $4.00' },
];
mockedDb.getUnmatchedFlyerItems.mockResolvedValue(mockUnmatchedItems as any);
(mockedDb.getUnmatchedFlyerItems as Mock).mockResolvedValue(mockUnmatchedItems);
// Act
const response = await supertest(app).get('/api/admin/unmatched-items');
@@ -298,7 +298,7 @@ describe('Admin Routes (/api/admin)', () => {
const correctionId = 101;
const requestBody = { suggested_value: 'A new corrected value' };
const mockUpdatedCorrection = { correction_id: correctionId, ...requestBody };
mockedDb.updateSuggestedCorrection.mockResolvedValue(mockUpdatedCorrection as any);
(mockedDb.updateSuggestedCorrection as Mock).mockResolvedValue(mockUpdatedCorrection);
// Act: Use .send() to include a request body
const response = await supertest(app)
@@ -395,7 +395,7 @@ describe('Admin Routes (/api/admin)', () => {
const recipeId = 201;
const requestBody = { status: 'public' };
const mockUpdatedRecipe = { recipe_id: recipeId, status: 'public', name: 'Test Recipe' };
mockedDb.updateRecipeStatus.mockResolvedValue(mockUpdatedRecipe as any);
(mockedDb.updateRecipeStatus as Mock).mockResolvedValue(mockUpdatedRecipe);
// Act
const response = await supertest(app)
@@ -432,7 +432,7 @@ describe('Admin Routes (/api/admin)', () => {
const commentId = 301;
const requestBody = { status: 'hidden' };
const mockUpdatedComment = { comment_id: commentId, status: 'hidden', content: 'Test Comment' };
mockedDb.updateRecipeCommentStatus.mockResolvedValue(mockUpdatedComment as any);
(mockedDb.updateRecipeCommentStatus as Mock).mockResolvedValue(mockUpdatedComment);
// Act
const response = await supertest(app)
@@ -461,7 +461,7 @@ describe('Admin Routes (/api/admin)', () => {
{ user_id: '1', email: 'user1@test.com', role: 'user' },
{ user_id: '2', email: 'user2@test.com', role: 'admin' },
];
(mockedDb.getAllUsers as Mocked<any>).mockResolvedValue(mockUsers);
(mockedDb.getAllUsers as Mock).mockResolvedValue(mockUsers);
// Act
const response = await supertest(app).get('/api/admin/users');
@@ -473,7 +473,7 @@ describe('Admin Routes (/api/admin)', () => {
});
it('should return a 500 error if the database call fails', async () => {
(mockedDb.getAllUsers as Mocked<any>).mockRejectedValue(new Error('Failed to fetch users'));
(mockedDb.getAllUsers as Mock).mockRejectedValue(new Error('Failed to fetch users'));
const response = await supertest(app).get('/api/admin/users');
expect(response.status).toBe(500);
});
@@ -483,7 +483,7 @@ describe('Admin Routes (/api/admin)', () => {
it('should return a list of activity logs with default pagination', async () => {
// Arrange
const mockLogs = [{ log_id: 1, action: 'user_login', user_id: '1' }];
mockedDb.getActivityLog.mockResolvedValue(mockLogs as any);
(mockedDb.getActivityLog as Mock).mockResolvedValue(mockLogs);
// Act
const response = await supertest(app).get('/api/admin/activity-log');
@@ -531,7 +531,7 @@ describe('Admin Routes (/api/admin)', () => {
it('should fetch a single user successfully', async () => {
// Arrange
const mockUser = { user_id: 'user-123', email: 'single@test.com', role: 'user' };
(mockedDb.findUserProfileById as Mocked<any>).mockResolvedValue(mockUser);
(mockedDb.findUserProfileById as Mock).mockResolvedValue(mockUser);
// Act
const response = await supertest(app).get('/api/admin/users/user-123');
@@ -544,7 +544,7 @@ describe('Admin Routes (/api/admin)', () => {
it('should return 404 for a non-existent user', async () => {
// Arrange
(mockedDb.findUserProfileById as Mocked<any>).mockResolvedValue(undefined);
(mockedDb.findUserProfileById as Mock).mockResolvedValue(undefined);
// Act
const response = await supertest(app).get('/api/admin/users/non-existent-id');
@@ -559,7 +559,7 @@ describe('Admin Routes (/api/admin)', () => {
it('should update a user role successfully', async () => {
// Arrange
const updatedUser = { user_id: 'user-to-update', role: 'admin' };
(mockedDb.updateUserRole as Mocked<any>).mockResolvedValue(updatedUser);
(mockedDb.updateUserRole as Mock).mockResolvedValue(updatedUser);
// Act
const response = await supertest(app)
@@ -573,7 +573,7 @@ describe('Admin Routes (/api/admin)', () => {
});
it('should return 404 for a non-existent user', async () => {
(mockedDb.updateUserRole as Mocked<any>).mockRejectedValue(new Error('User with ID non-existent not found.'));
(mockedDb.updateUserRole as Mock).mockRejectedValue(new Error('User with ID non-existent not found.'));
const response = await supertest(app).put('/api/admin/users/non-existent').send({ role: 'user' });
expect(response.status).toBe(404);
});
@@ -587,7 +587,7 @@ describe('Admin Routes (/api/admin)', () => {
describe('DELETE /users/:id', () => {
it('should successfully delete a user', async () => {
(mockedDb.deleteUserById as Mocked<any>).mockResolvedValue(undefined);
(mockedDb.deleteUserById as Mock).mockResolvedValue(undefined);
const response = await supertest(app).delete('/api/admin/users/user-to-delete');
expect(response.status).toBe(204);
expect(mockedDb.deleteUserById).toHaveBeenCalledWith('user-to-delete');

View File

@@ -64,14 +64,14 @@ describe('Public Routes (/api)', () => {
describe('GET /health/storage', () => {
it('should return 200 OK if storage is writable', async () => {
(mockedFs.access as vi.Mock).mockResolvedValue(undefined);
(mockedFs.access as Mock).mockResolvedValue(undefined);
const response = await supertest(app).get('/api/health/storage');
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
});
it('should return 500 if storage is not accessible', async () => {
(mockedFs.access as vi.Mock).mockRejectedValue(new Error('Permission denied'));
(mockedFs.access as Mock).mockRejectedValue(new Error('Permission denied'));
const response = await supertest(app).get('/api/health/storage');
expect(response.status).toBe(500);
expect(response.body.success).toBe(false);
@@ -94,7 +94,6 @@ describe('Public Routes (/api)', () => {
{ flyer_id: 1, store_name: 'Store A' },
{ flyer_id: 2, store_name: 'Store B' },
];
mockedDb.getFlyers.mockResolvedValue(mockFlyers as any);
(mockedDb.getFlyers as Mock).mockResolvedValue(mockFlyers);
// Act: Make the request
@@ -121,7 +120,6 @@ describe('Public Routes (/api)', () => {
describe('GET /master-items', () => {
it('should return a list of master items', async () => {
const mockItems = [{ master_grocery_item_id: 1, name: 'Milk' }];
mockedDb.getAllMasterItems.mockResolvedValue(mockItems as any);
(mockedDb.getAllMasterItems as Mock).mockResolvedValue(mockItems);
const response = await supertest(app).get('/api/master-items');
expect(response.status).toBe(200);
@@ -132,7 +130,6 @@ describe('Public Routes (/api)', () => {
describe('GET /flyers/:id/items', () => {
it('should return items for a specific flyer', async () => {
const mockFlyerItems = [{ flyer_item_id: 1, item: 'Cheese' }];
mockedDb.getFlyerItems.mockResolvedValue(mockFlyerItems as any);
(mockedDb.getFlyerItems as Mock).mockResolvedValue(mockFlyerItems);
const response = await supertest(app).get('/api/flyers/123/items');
expect(response.status).toBe(200);
@@ -144,7 +141,6 @@ describe('Public Routes (/api)', () => {
describe('POST /flyer-items/batch-fetch', () => {
it('should return items for multiple flyers', async () => {
const mockFlyerItems = [{ flyer_item_id: 1, item: 'Bread' }];
mockedDb.getFlyerItemsForFlyers.mockResolvedValue(mockFlyerItems as any);
(mockedDb.getFlyerItemsForFlyers as Mock).mockResolvedValue(mockFlyerItems);
const response = await supertest(app)
.post('/api/flyer-items/batch-fetch')
@@ -165,7 +161,6 @@ describe('Public Routes (/api)', () => {
describe('GET /recipes/by-sale-percentage', () => {
it('should return recipes based on sale percentage', async () => {
const mockRecipes = [{ recipe_id: 1, name: 'Pasta' }];
mockedDb.getRecipesBySalePercentage.mockResolvedValue(mockRecipes as any);
(mockedDb.getRecipesBySalePercentage as Mock).mockResolvedValue(mockRecipes);
const response = await supertest(app).get('/api/recipes/by-sale-percentage?minPercentage=75');
expect(response.status).toBe(200);
@@ -221,7 +216,6 @@ describe('Public Routes (/api)', () => {
describe('GET /recipes/by-ingredient-and-tag', () => {
it('should return recipes for a given ingredient and tag', async () => {
const mockRecipes = [{ recipe_id: 2, name: 'Chicken Tacos' }];
mockedDb.findRecipesByIngredientAndTag.mockResolvedValue(mockRecipes as any);
(mockedDb.findRecipesByIngredientAndTag as Mock).mockResolvedValue(mockRecipes);
const response = await supertest(app).get('/api/recipes/by-ingredient-and-tag?ingredient=chicken&tag=quick');
expect(response.status).toBe(200);