linting done now fix unit tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 8m19s

This commit is contained in:
2025-12-14 21:48:53 -08:00
parent 43ca7f9df2
commit 69e2287870
12 changed files with 106 additions and 57 deletions

View File

@@ -23,12 +23,20 @@ import { backgroundJobService } from '../services/backgroundJobService';
import { flyerQueue, emailQueue, analyticsQueue, cleanupQueue, weeklyAnalyticsQueue, flyerWorker, emailWorker, analyticsWorker, cleanupWorker, weeklyAnalyticsWorker } from '../services/queueService.server'; // Import your queues
import { getSimpleWeekAndYear } from '../utils/dateUtils';
const uuidParamSchema = (key: string) => z.object({
params: z.object({ [key]: z.string().uuid() }),
/**
* A factory for creating a Zod schema that validates a UUID in the request parameters.
* @param key The name of the parameter key (e.g., 'userId').
* @param message A custom error message for invalid UUIDs.
*/
const uuidParamSchema = (key: string, message = `Invalid UUID for parameter '${key}'.`) => z.object({
params: z.object({ [key]: z.string().uuid({ message }) }),
});
const numericIdParamSchema = (key: string) => z.object({
params: z.object({ [key]: z.coerce.number().int().positive() }),
/**
* A factory for creating a Zod schema that validates a numeric ID in the request parameters.
*/
const numericIdParamSchema = (key: string, message = `Invalid ID for parameter '${key}'. Must be a positive integer.`) => z.object({
params: z.object({ [key]: z.coerce.number().int({ message }).positive({ message }) }),
});
const updateCorrectionSchema = numericIdParamSchema('id').extend({
@@ -49,8 +57,7 @@ const updateCommentStatusSchema = numericIdParamSchema('id').extend({
}),
});
const updateUserRoleSchema = z.object({
params: z.object({ id: z.string().uuid() }),
const updateUserRoleSchema = uuidParamSchema('id', 'A valid user ID is required.').extend({
body: z.object({
role: z.enum(['user', 'admin']),
}),
@@ -64,7 +71,10 @@ const activityLogSchema = z.object({
});
const jobRetrySchema = z.object({
params: z.object({ queueName: z.string(), jobId: z.string() }),
params: z.object({
queueName: z.enum(['flyer-processing', 'email-sending', 'analytics-reporting', 'file-cleanup', 'weekly-analytics-reporting']),
jobId: z.string().min(1, 'A valid Job ID is required.'),
}),
});
const router = Router();
@@ -281,7 +291,7 @@ router.get('/activity-log', validateRequest(activityLogSchema), async (req: Requ
}
});
router.get('/users/:id', validateRequest(uuidParamSchema('id')), async (req: Request, res: Response, next: NextFunction) => {
router.get('/users/:id', validateRequest(uuidParamSchema('id', 'A valid user ID is required.')), async (req: Request, res: Response, next: NextFunction) => {
// Apply ADR-003 pattern for type safety
const { params } = req as unknown as z.infer<ReturnType<typeof uuidParamSchema>>;
try {
@@ -304,7 +314,7 @@ router.put('/users/:id', validateRequest(updateUserRoleSchema), async (req: Requ
}
});
router.delete('/users/:id', validateRequest(uuidParamSchema('id')), async (req: Request, res: Response, next: NextFunction) => {
router.delete('/users/:id', validateRequest(uuidParamSchema('id', 'A valid user ID is required.')), async (req: Request, res: Response, next: NextFunction) => {
const adminUser = req.user as UserProfile;
// Apply ADR-003 pattern for type safety
const { params } = req as unknown as z.infer<ReturnType<typeof uuidParamSchema>>;
@@ -365,10 +375,9 @@ router.post('/trigger/analytics-report', async (req: Request, res: Response, nex
* This is triggered by an admin after they have verified the flyer processing was successful.
*/
router.post('/flyers/:flyerId/cleanup', validateRequest(numericIdParamSchema('flyerId')), async (req: Request, res: Response, next: NextFunction) => {
// Define schema locally to simplify type inference
const schema = numericIdParamSchema('flyerId');
const adminUser = req.user as UserProfile;
const { params } = req as unknown as z.infer<typeof schema>;
// Infer type from the schema generator for type safety, as per ADR-003.
const { params } = req as unknown as z.infer<ReturnType<typeof numericIdParamSchema>>;
logger.info(`[Admin] Manual trigger for flyer file cleanup received from user: ${adminUser.user_id} for flyer ID: ${params.flyerId}`);
// Enqueue the cleanup job. The worker will handle the file deletion.

View File

@@ -163,7 +163,8 @@ describe('User Routes (/api/users)', () => {
.post('/api/users/watched-items')
.send({ category: 'Produce' });
expect(response.status).toBe(400);
expect(response.body.message).toBe("Field 'itemName' is required.");
// Check the 'errors' array for the specific validation message.
expect(response.body.errors[0].message).toBe("Field 'itemName' is required.");
});
it('should return 400 if category is missing', async () => {
@@ -171,7 +172,8 @@ describe('User Routes (/api/users)', () => {
.post('/api/users/watched-items')
.send({ itemName: 'Apples' });
expect(response.status).toBe(400);
expect(response.body.message).toBe("Field 'category' is required.");
// Check the 'errors' array for the specific validation message.
expect(response.body.errors[0].message).toBe("Field 'category' is required.");
});
});
@@ -215,7 +217,8 @@ describe('User Routes (/api/users)', () => {
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.");
// Check the 'errors' array for the specific validation message.
expect(response.body.errors[0].message).toBe("Field 'name' is required.");
});
it('should return 400 on foreign key constraint error', async () => {
@@ -228,7 +231,8 @@ describe('User Routes (/api/users)', () => {
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.");
// Check the 'errors' array for the specific validation message.
expect(response.body.errors[0].message).toBe("Invalid ID for parameter 'listId'. Must be a number.");
});
describe('DELETE /shopping-lists/:listId', () => {
@@ -375,7 +379,7 @@ describe('User Routes (/api/users)', () => {
});
it('should return 404 if the user to delete is not found', async () => {
vi.mocked(db.userRepo.findUserWithPasswordHashById).mockResolvedValue(undefined);
vi.mocked(db.userRepo.findUserWithPasswordHashById).mockRejectedValue(new NotFoundError('User not found'));
const response = await supertest(app)
.delete('/api/users/account')
.send({ password: 'any-password' });
@@ -422,7 +426,8 @@ describe('User Routes (/api/users)', () => {
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.");
// Check the 'errors' array for the specific validation message.
expect(response.body.errors[0].message).toBe("Invalid ID for parameter 'masterItemId'. Must be a number.");
});
it('PUT should successfully set the restrictions', async () => {