Add comprehensive test coverage for getNextScheduledRun()
All checks were successful
CI/CD Pipeline - Apartment API / Scan Dependencies (pull_request) Successful in 12s
CI/CD Pipeline - Apartment API / Lint & Test (pull_request) Successful in 39s
CI/CD Pipeline - Apartment API / Send Webhook Notification (pull_request) Successful in 2s
CI/CD Pipeline - Apartment API / Build & Push Image (pull_request) Has been skipped
CI/CD Pipeline - Apartment API / Deploy to Production (pull_request) Has been skipped

Add 4 new test cases for the getNextScheduledRun() function:
- Verify scheduled time respects configured timezone
- Validate returned time is always in the future
- Handle invalid cron expression gracefully (return null)
- Confirm cron-parser library is used for next run calculation

This brings the total scraperJob test count to 60.
This commit is contained in:
2026-02-06 18:19:22 -07:00
parent 0c07baa977
commit f436d0ed44

View File

@ -1404,5 +1404,159 @@ describe('scraperJob - scheduler', () => {
expect(nextRun).not.toBeNull();
expect(new Date(nextRun).toISOString()).toBe(nextRun);
});
test('should return a time in the future', () => {
jest.resetModules();
jest.doMock('../../config/scraper', () => ({
SCRAPER_ENABLED: true,
SCRAPER_SCHEDULE: '0 6 * * *',
SCRAPER_TIMEZONE: 'UTC'
}));
jest.doMock('node-cron', () => ({
validate: jest.fn(() => true),
schedule: jest.fn(() => ({ stop: jest.fn() }))
}));
jest.doMock('../../services/scraperLogger', () => ({
createLogger: jest.fn(() => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn() }))
}));
jest.doMock('../../services/scraperService', () => ({ runScrape: jest.fn() }));
jest.doMock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
const { initializeScheduler: freshInit, getNextScheduledRun: freshGetNext } = require('../../jobs/scraperJob');
freshInit(mockDb);
const nextRun = freshGetNext();
expect(nextRun).not.toBeNull();
const nextRunDate = new Date(nextRun);
const now = new Date();
expect(nextRunDate.getTime()).toBeGreaterThan(now.getTime());
});
test('should respect configured timezone', () => {
jest.resetModules();
jest.doMock('../../config/scraper', () => ({
SCRAPER_ENABLED: true,
SCRAPER_SCHEDULE: '0 6 * * *',
SCRAPER_TIMEZONE: 'America/Denver'
}));
jest.doMock('node-cron', () => ({
validate: jest.fn(() => true),
schedule: jest.fn(() => ({ stop: jest.fn() }))
}));
jest.doMock('../../services/scraperLogger', () => ({
createLogger: jest.fn(() => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn() }))
}));
jest.doMock('../../services/scraperService', () => ({ runScrape: jest.fn() }));
jest.doMock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
const { initializeScheduler: freshInit, getNextScheduledRun: freshGetNext } = require('../../jobs/scraperJob');
freshInit(mockDb);
const denverNext = freshGetNext();
expect(denverNext).not.toBeNull();
// Get the UTC equivalent for the same schedule
jest.resetModules();
jest.doMock('../../config/scraper', () => ({
SCRAPER_ENABLED: true,
SCRAPER_SCHEDULE: '0 6 * * *',
SCRAPER_TIMEZONE: 'UTC'
}));
jest.doMock('node-cron', () => ({
validate: jest.fn(() => true),
schedule: jest.fn(() => ({ stop: jest.fn() }))
}));
jest.doMock('../../services/scraperLogger', () => ({
createLogger: jest.fn(() => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn() }))
}));
jest.doMock('../../services/scraperService', () => ({ runScrape: jest.fn() }));
jest.doMock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
const { initializeScheduler: utcInit, getNextScheduledRun: utcGetNext } = require('../../jobs/scraperJob');
utcInit(mockDb);
const utcNext = utcGetNext();
expect(utcNext).not.toBeNull();
// Denver is UTC-7 (or UTC-6 during DST), so "6 AM Denver" != "6 AM UTC"
// The two next-run times should be different ISO strings
expect(denverNext).not.toBe(utcNext);
});
test('should return null for invalid cron expression when scheduler was initialized with a modified config', () => {
jest.resetModules();
const mockConfig = {
SCRAPER_ENABLED: true,
SCRAPER_SCHEDULE: '0 6 * * *',
SCRAPER_TIMEZONE: 'UTC'
};
jest.doMock('../../config/scraper', () => mockConfig);
jest.doMock('node-cron', () => ({
validate: jest.fn(() => true),
schedule: jest.fn(() => ({ stop: jest.fn() }))
}));
jest.doMock('../../services/scraperLogger', () => ({
createLogger: jest.fn(() => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn() }))
}));
jest.doMock('../../services/scraperService', () => ({ runScrape: jest.fn() }));
jest.doMock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
const { initializeScheduler: freshInit, getNextScheduledRun: freshGetNext } = require('../../jobs/scraperJob');
// Initialize with valid config to set scheduledJob
freshInit(mockDb);
// Now corrupt the schedule config to simulate an invalid expression
// being evaluated at getNextScheduledRun() call time
mockConfig.SCRAPER_SCHEDULE = 'not-a-valid-cron';
const result = freshGetNext();
// cron-parser should throw on invalid expression, handler returns null
expect(result).toBeNull();
});
test('should use cron-parser library to calculate next run', () => {
jest.resetModules();
jest.doMock('../../config/scraper', () => ({
SCRAPER_ENABLED: true,
SCRAPER_SCHEDULE: '30 14 * * 1-5',
SCRAPER_TIMEZONE: 'UTC'
}));
jest.doMock('node-cron', () => ({
validate: jest.fn(() => true),
schedule: jest.fn(() => ({ stop: jest.fn() }))
}));
jest.doMock('../../services/scraperLogger', () => ({
createLogger: jest.fn(() => ({ info: jest.fn(), warn: jest.fn(), error: jest.fn() }))
}));
jest.doMock('../../services/scraperService', () => ({ runScrape: jest.fn() }));
jest.doMock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid') }));
const { initializeScheduler: freshInit, getNextScheduledRun: freshGetNext } = require('../../jobs/scraperJob');
freshInit(mockDb);
const nextRun = freshGetNext();
expect(nextRun).not.toBeNull();
// Verify the time component: the next run should be at 14:30 UTC
const nextDate = new Date(nextRun);
expect(nextDate.getUTCHours()).toBe(14);
expect(nextDate.getUTCMinutes()).toBe(30);
// Verify it falls on a weekday (Mon=1 to Fri=5, getUTCDay: 0=Sun, 1=Mon...5=Fri)
const day = nextDate.getUTCDay();
expect(day).toBeGreaterThanOrEqual(1);
expect(day).toBeLessThanOrEqual(5);
});
});
});