moar unit test !
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 6m0s

This commit is contained in:
2025-12-07 02:54:19 -08:00
parent a148ff8a45
commit 51e2874bab
6 changed files with 749 additions and 352 deletions

View File

@@ -1,25 +1,31 @@
// src/services/backgroundJobService.test.ts
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import cron from 'node-cron';
import * as db from './db/index.db';
import { logger } from './logger.server';
import { sendDealNotificationEmail } from './emailService.server';
import { runDailyDealCheck, startBackgroundJobs } from './backgroundJobService';
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the 'node-cron' module
const mockCronSchedule = vi.fn();
vi.mock('node-cron', () => ({ default: { schedule: mockCronSchedule } }));
import { BackgroundJobService, startBackgroundJobs } from './backgroundJobService';
import { WatchedItemDeal } from '../types';
import { AdminUserView } from './db/admin.db';
// Mock dependencies
vi.mock('node-cron');
vi.mock('./db/index.db');
vi.mock('./logger.server');
vi.mock('./emailService.server');
const mockedDb = db as Mocked<typeof db>;
const mockedCron = cron as Mocked<typeof cron>;
const mockedLogger = logger as Mocked<typeof logger>;
const mockedEmailService = { sendDealNotificationEmail } as Mocked<{ sendDealNotificationEmail: typeof sendDealNotificationEmail }>;
describe('Background Job Service', () => {
// Create mock dependencies that will be injected into the service
const mockDbService = {
getAllUsers: vi.fn(),
getBestSalePricesForUser: vi.fn(),
createBulkNotifications: vi.fn(),
};
const mockEmailService = {
sendDealNotificationEmail: vi.fn(),
};
const mockLogger = {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
@@ -34,48 +40,51 @@ describe('Background Job Service', () => {
{ master_item_id: 1, item_name: 'Apples', best_price_in_cents: 199, store_name: 'Green Grocer', flyer_id: 101, valid_to: '2024-10-20' },
];
// Instantiate the service with mock dependencies for each test run
const service = new BackgroundJobService(mockDbService, mockEmailService, mockLogger);
it('should do nothing if no users are found', async () => {
mockedDb.getAllUsers.mockResolvedValue([]);
mockDbService.getAllUsers.mockResolvedValue([]);
await runDailyDealCheck();
await service.runDailyDealCheck();
expect(mockedLogger.info).toHaveBeenCalledWith('[BackgroundJob] Starting daily deal check for all users...');
expect(mockedLogger.info).toHaveBeenCalledWith('[BackgroundJob] No users found. Skipping deal check.');
expect(mockedDb.getBestSalePricesForUser).not.toHaveBeenCalled();
expect(mockedEmailService.sendDealNotificationEmail).not.toHaveBeenCalled();
expect(mockedDb.createBulkNotifications).not.toHaveBeenCalled();
expect(mockLogger.info).toHaveBeenCalledWith('[BackgroundJob] Starting daily deal check for all users...');
expect(mockLogger.info).toHaveBeenCalledWith('[BackgroundJob] No users found. Skipping deal check.');
expect(mockDbService.getBestSalePricesForUser).not.toHaveBeenCalled();
expect(mockEmailService.sendDealNotificationEmail).not.toHaveBeenCalled();
expect(mockDbService.createBulkNotifications).not.toHaveBeenCalled();
});
it('should process users but not send notifications if no deals are found', async () => {
mockedDb.getAllUsers.mockResolvedValue([mockUsers[0]]);
mockedDb.getBestSalePricesForUser.mockResolvedValue([]);
mockDbService.getAllUsers.mockResolvedValue([mockUsers[0]]);
mockDbService.getBestSalePricesForUser.mockResolvedValue([]);
await runDailyDealCheck();
await service.runDailyDealCheck();
expect(mockedDb.getBestSalePricesForUser).toHaveBeenCalledWith('user-1');
expect(mockedEmailService.sendDealNotificationEmail).not.toHaveBeenCalled();
expect(mockedDb.createBulkNotifications).not.toHaveBeenCalled();
expect(mockedLogger.info).toHaveBeenCalledWith('[BackgroundJob] Daily deal check completed successfully.');
expect(mockDbService.getBestSalePricesForUser).toHaveBeenCalledWith('user-1');
expect(mockEmailService.sendDealNotificationEmail).not.toHaveBeenCalled();
expect(mockDbService.createBulkNotifications).not.toHaveBeenCalled();
expect(mockLogger.info).toHaveBeenCalledWith('[BackgroundJob] Daily deal check completed successfully.');
});
it('should create notifications and send emails when deals are found', async () => {
mockedDb.getAllUsers.mockResolvedValue(mockUsers);
mockedDb.getBestSalePricesForUser.mockResolvedValue(mockDeals);
mockDbService.getAllUsers.mockResolvedValue(mockUsers);
mockDbService.getBestSalePricesForUser.mockResolvedValue(mockDeals);
await runDailyDealCheck();
await service.runDailyDealCheck();
// Check that it processed both users
expect(mockedDb.getBestSalePricesForUser).toHaveBeenCalledTimes(2);
expect(mockedDb.getBestSalePricesForUser).toHaveBeenCalledWith('user-1');
expect(mockedDb.getBestSalePricesForUser).toHaveBeenCalledWith('user-2');
expect(mockDbService.getBestSalePricesForUser).toHaveBeenCalledTimes(2);
expect(mockDbService.getBestSalePricesForUser).toHaveBeenCalledWith('user-1');
expect(mockDbService.getBestSalePricesForUser).toHaveBeenCalledWith('user-2');
// Check that emails were sent for both users
expect(mockedEmailService.sendDealNotificationEmail).toHaveBeenCalledTimes(2);
expect(mockedEmailService.sendDealNotificationEmail).toHaveBeenCalledWith('user1@test.com', 'User One', mockDeals);
expect(mockEmailService.sendDealNotificationEmail).toHaveBeenCalledTimes(2);
expect(mockEmailService.sendDealNotificationEmail).toHaveBeenCalledWith('user1@test.com', 'User One', mockDeals);
// Check that in-app notifications were created for both users
expect(mockedDb.createBulkNotifications).toHaveBeenCalledTimes(1);
const notificationPayload = mockedDb.createBulkNotifications.mock.calls[0][0];
expect(mockDbService.createBulkNotifications).toHaveBeenCalledTimes(1);
const notificationPayload = mockDbService.createBulkNotifications.mock.calls[0][0];
expect(notificationPayload).toHaveLength(2);
expect(notificationPayload[0]).toEqual({
user_id: 'user-1',
@@ -85,33 +94,33 @@ describe('Background Job Service', () => {
});
it('should handle and log errors for individual users without stopping the process', async () => {
mockedDb.getAllUsers.mockResolvedValue(mockUsers);
mockDbService.getAllUsers.mockResolvedValue(mockUsers);
// First user fails, second succeeds
mockedDb.getBestSalePricesForUser
mockDbService.getBestSalePricesForUser
.mockRejectedValueOnce(new Error('User 1 DB Error'))
.mockResolvedValueOnce(mockDeals);
await runDailyDealCheck();
await service.runDailyDealCheck();
// Check that it logged the error for user 1
expect(mockedLogger.error).toHaveBeenCalledWith(
expect(mockLogger.error).toHaveBeenCalledWith(
expect.stringContaining('Failed to process deals for user user-1'),
expect.any(Object)
);
// Check that it still processed user 2 successfully
expect(mockedEmailService.sendDealNotificationEmail).toHaveBeenCalledTimes(1);
expect(mockedEmailService.sendDealNotificationEmail).toHaveBeenCalledWith('user2@test.com', 'User Two', mockDeals);
expect(mockedDb.createBulkNotifications).toHaveBeenCalledTimes(1);
expect(mockedDb.createBulkNotifications.mock.calls[0][0]).toHaveLength(1); // Only one notification created
expect(mockEmailService.sendDealNotificationEmail).toHaveBeenCalledTimes(1);
expect(mockEmailService.sendDealNotificationEmail).toHaveBeenCalledWith('user2@test.com', 'User Two', mockDeals);
expect(mockDbService.createBulkNotifications).toHaveBeenCalledTimes(1);
expect(mockDbService.createBulkNotifications.mock.calls[0][0]).toHaveLength(1); // Only one notification created
});
it('should log a critical error if getAllUsers fails', async () => {
mockedDb.getAllUsers.mockRejectedValue(new Error('Critical DB Failure'));
mockDbService.getAllUsers.mockRejectedValue(new Error('Critical DB Failure'));
await runDailyDealCheck();
await service.runDailyDealCheck();
expect(mockedLogger.error).toHaveBeenCalledWith(
expect(mockLogger.error).toHaveBeenCalledWith(
'[BackgroundJob] A critical error occurred during the daily deal check:',
expect.any(Object)
);
@@ -123,12 +132,15 @@ describe('Background Job Service', () => {
startBackgroundJobs();
// Expect at least one job to be scheduled
expect(mockedCron.schedule).toHaveBeenCalled();
expect(mockCronSchedule).toHaveBeenCalled();
// Check specifically for the daily deal check job
expect(mockedCron.schedule).toHaveBeenCalledWith('0 2 * * *', runDailyDealCheck);
expect(mockCronSchedule).toHaveBeenCalledWith('0 2 * * *', expect.any(Function));
expect(mockedLogger.info).toHaveBeenCalledWith(expect.stringContaining('Cron job for daily deal checks has been scheduled'));
// We can't directly test the bound function instance easily, but we can check the logger
// which is called from within startBackgroundJobs.
// The mockLogger from the `runDailyDealCheck` scope won't be called here.
// This test now primarily verifies that cron.schedule is called correctly.
});
});
});