Refactor geocoding services and improve logging
- Updated the Nominatim geocoding service to use a class-based structure and accept a logger instance for better logging control. - Modified tests for the Nominatim service to align with the new structure and improved logging assertions. - Removed the disabled notification service test file. - Added a new GeocodingFailedError class to handle geocoding failures more explicitly. - Enhanced error logging in the queue service to include structured error objects. - Updated user service to accept a logger instance for better logging in address upsert operations. - Added request-scoped logger to Express Request interface for type-safe logging in route handlers. - Improved logging in utility functions for better debugging and error tracking. - Created a new GoogleGeocodingService class for Google Maps geocoding with structured logging. - Added tests for the useAiAnalysis hook to ensure proper functionality and error handling.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// src/services/backgroundJobService.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Logger } from 'pino';
|
||||
|
||||
// Use vi.hoisted to ensure the mock variable is available when vi.mock is executed.
|
||||
const { mockCronSchedule } = vi.hoisted(() => {
|
||||
@@ -14,6 +15,9 @@ vi.mock('../services/logger.server', () => ({
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
silent: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -56,16 +60,18 @@ describe('Background Job Service', () => {
|
||||
{ user_id: 'user-2', email: 'user2@test.com', full_name: 'User Two', master_item_id: 3, item_name: 'Bread', best_price_in_cents: 250, store_name: 'Bakery', flyer_id: 103, valid_to: '2024-10-22' },
|
||||
];
|
||||
|
||||
// This mockLogger is for the service instance, not the global one used by cron.schedule
|
||||
const mockServiceLogger = {
|
||||
// Helper to create a type-safe mock logger
|
||||
const createMockLogger = (): Logger => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
};
|
||||
child: vi.fn(() => createMockLogger()),
|
||||
} as unknown as Logger);
|
||||
|
||||
// Instantiate the service with mock dependencies for each test run
|
||||
const service = new BackgroundJobService(mockPersonalizationRepo as any, mockNotificationRepo as any, mockEmailQueue as unknown as Queue<any>, mockServiceLogger);
|
||||
const mockServiceLogger = createMockLogger();
|
||||
const service = new BackgroundJobService(mockPersonalizationRepo as any, mockNotificationRepo as any, mockEmailQueue as unknown as Queue<any>, mockServiceLogger as any);
|
||||
|
||||
it('should do nothing if no deals are found for any user', async () => {
|
||||
mockPersonalizationRepo.getBestSalePricesForAllUsers.mockResolvedValue([]);
|
||||
@@ -125,8 +131,8 @@ describe('Background Job Service', () => {
|
||||
|
||||
// Check that it logged the error for user 1
|
||||
expect(mockServiceLogger.error).toHaveBeenCalledWith(
|
||||
{ err: expect.any(Error) },
|
||||
expect.stringContaining('Failed to process deals for user user-1'),
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
// Check that it still processed user 2 successfully
|
||||
@@ -141,8 +147,8 @@ describe('Background Job Service', () => {
|
||||
mockPersonalizationRepo.getBestSalePricesForAllUsers.mockRejectedValue(new Error('Critical DB Failure'));
|
||||
await expect(service.runDailyDealCheck()).rejects.toThrow('Critical DB Failure');
|
||||
expect(mockServiceLogger.error).toHaveBeenCalledWith(
|
||||
'[BackgroundJob] A critical error occurred during the daily deal check:',
|
||||
expect.any(Object)
|
||||
{ error: expect.any(Error) },
|
||||
'[BackgroundJob] A critical error occurred during the daily deal check:'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -155,7 +161,7 @@ describe('Background Job Service', () => {
|
||||
// Act & Assert
|
||||
await expect(service.runDailyDealCheck()).rejects.toThrow(dbError);
|
||||
expect(mockServiceLogger.error).toHaveBeenCalledWith(
|
||||
'[BackgroundJob] A critical error occurred during the daily deal check:', { error: dbError }
|
||||
{ err: dbError }, '[BackgroundJob] A critical error occurred during the daily deal check'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -181,7 +187,7 @@ describe('Background Job Service', () => {
|
||||
});
|
||||
|
||||
it('should schedule three cron jobs with the correct schedules', () => {
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
expect(mockCronSchedule).toHaveBeenCalledTimes(3);
|
||||
expect(mockCronSchedule).toHaveBeenCalledWith('0 2 * * *', expect.any(Function));
|
||||
@@ -190,7 +196,7 @@ describe('Background Job Service', () => {
|
||||
});
|
||||
|
||||
it('should call runDailyDealCheck when the first cron job function is executed', async () => {
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
// Get the callback function for the first cron job
|
||||
const dailyDealCheckCallback = mockCronSchedule.mock.calls[0][1];
|
||||
@@ -202,15 +208,15 @@ describe('Background Job Service', () => {
|
||||
it('should log an error and release the lock if runDailyDealCheck fails', async () => {
|
||||
const jobError = new Error('Cron job failed');
|
||||
vi.mocked(mockBackgroundJobService.runDailyDealCheck).mockRejectedValue(jobError);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
const dailyDealCheckCallback = mockCronSchedule.mock.calls[0][1];
|
||||
await dailyDealCheckCallback();
|
||||
|
||||
expect(mockBackgroundJobService.runDailyDealCheck).toHaveBeenCalledTimes(1);
|
||||
expect(globalMockLogger.error).toHaveBeenCalledWith(
|
||||
'[BackgroundJob] Cron job for daily deal check failed unexpectedly.',
|
||||
{ error: jobError }
|
||||
{ err: jobError },
|
||||
'[BackgroundJob] Cron job for daily deal check failed unexpectedly.'
|
||||
);
|
||||
// It should run again, proving the lock was released in the finally block
|
||||
await dailyDealCheckCallback();
|
||||
@@ -224,7 +230,7 @@ describe('Background Job Service', () => {
|
||||
// Make the first call hang indefinitely
|
||||
vi.mocked(mockBackgroundJobService.runDailyDealCheck).mockReturnValue(new Promise(() => {}));
|
||||
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
const dailyDealCheckCallback = mockCronSchedule.mock.calls[0][1];
|
||||
|
||||
// Trigger the job once, it will hang
|
||||
@@ -239,7 +245,7 @@ describe('Background Job Service', () => {
|
||||
});
|
||||
|
||||
it('should enqueue an analytics job when the second cron job function is executed', async () => {
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
const analyticsJobCallback = mockCronSchedule.mock.calls[1][1];
|
||||
await analyticsJobCallback();
|
||||
@@ -250,17 +256,17 @@ describe('Background Job Service', () => {
|
||||
it('should log an error if enqueuing the analytics job fails', async () => {
|
||||
const queueError = new Error('Redis is down');
|
||||
vi.mocked(mockAnalyticsQueue.add).mockRejectedValue(queueError);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
const analyticsJobCallback = mockCronSchedule.mock.calls[1][1];
|
||||
await analyticsJobCallback();
|
||||
|
||||
expect(mockAnalyticsQueue.add).toHaveBeenCalledTimes(1);
|
||||
expect(globalMockLogger.error).toHaveBeenCalledWith('[BackgroundJob] Failed to enqueue daily analytics job.', { error: queueError });
|
||||
expect(globalMockLogger.error).toHaveBeenCalledWith({ err: queueError }, '[BackgroundJob] Failed to enqueue daily analytics job.');
|
||||
});
|
||||
|
||||
it('should enqueue a weekly analytics job when the third cron job function is executed', async () => {
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
// The weekly job is the third one scheduled
|
||||
const weeklyAnalyticsJobCallback = mockCronSchedule.mock.calls[2][1];
|
||||
@@ -276,12 +282,12 @@ describe('Background Job Service', () => {
|
||||
it('should log an error if enqueuing the weekly analytics job fails', async () => {
|
||||
const queueError = new Error('Redis is down for weekly job');
|
||||
vi.mocked(mockWeeklyAnalyticsQueue.add).mockRejectedValue(queueError);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue);
|
||||
startBackgroundJobs(mockBackgroundJobService, mockAnalyticsQueue, mockWeeklyAnalyticsQueue, globalMockLogger as any);
|
||||
|
||||
const weeklyAnalyticsJobCallback = mockCronSchedule.mock.calls[2][1];
|
||||
await weeklyAnalyticsJobCallback();
|
||||
|
||||
expect(globalMockLogger.error).toHaveBeenCalledWith('[BackgroundJob] Failed to enqueue weekly analytics job.', { error: queueError });
|
||||
expect(globalMockLogger.error).toHaveBeenCalledWith({ err: queueError }, '[BackgroundJob] Failed to enqueue weekly analytics job.');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user