UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

197 lines • 7.86 kB
import { SchedulerService } from './scheduler-service.js'; import MaintenanceService from '../maintenance/maintenance-service.js'; import { createTestConfig } from '../../../test/config/test-config.js'; import SettingService from '../../services/setting-service.js'; import FakeSettingStore from '../../../test/fixtures/fake-setting-store.js'; import { SCHEDULER_JOB_TIME } from '../../metric-events.js'; import EventEmitter from 'events'; import { TEST_AUDIT_USER } from '../../types/index.js'; import { vi } from 'vitest'; function ms(timeMs) { return new Promise((resolve) => setTimeout(resolve, timeMs)); } const getLogger = () => { const records = []; const logger = () => ({ error(...args) { records.push(args); }, debug() { }, info() { }, warn() { }, fatal() { }, }); const getRecords = () => records; return { logger, getRecords }; }; const toggleMaintenanceMode = async (maintenanceService, enabled) => { await maintenanceService.toggleMaintenanceMode({ enabled }, TEST_AUDIT_USER); }; const createSchedulerTestService = ({ loggerOverride, eventBusOverride, } = {}) => { const config = { ...createTestConfig(), eventBus: eventBusOverride || new EventEmitter(), }; const logger = loggerOverride || config.getLogger; const settingStore = new FakeSettingStore(); const settingService = new SettingService({ settingStore }, config, { storeEvent() { }, }); const maintenanceService = new MaintenanceService(config, settingService); const schedulerService = new SchedulerService(logger, maintenanceService, config.eventBus); return { schedulerService, maintenanceService }; }; test('Schedules job immediately', async () => { const { schedulerService } = createSchedulerTestService(); const NO_JITTER = 0; const job = vi.fn(); await schedulerService.schedule(job, 10, 'test-id', NO_JITTER); expect(job).toHaveBeenCalledTimes(1); schedulerService.stop(); }); test('Does not schedule job immediately when paused', async () => { const { schedulerService, maintenanceService } = createSchedulerTestService(); const job = vi.fn(); await toggleMaintenanceMode(maintenanceService, true); await schedulerService.schedule(job, 10, 'test-id-2'); expect(job).toHaveBeenCalledTimes(0); schedulerService.stop(); }); test('Can schedule a single regular job', async () => { const { schedulerService } = createSchedulerTestService(); const job = vi.fn(); await schedulerService.schedule(job, 50, 'test-id-3'); await ms(75); expect(job).toHaveBeenCalledTimes(2); schedulerService.stop(); }); test('Scheduled job ignored in a paused mode', async () => { const { schedulerService, maintenanceService } = createSchedulerTestService(); const job = vi.fn(); await toggleMaintenanceMode(maintenanceService, true); await schedulerService.schedule(job, 50, 'test-id-4'); await ms(75); expect(job).toHaveBeenCalledTimes(0); schedulerService.stop(); }); test('Can resume paused job', async () => { const { schedulerService, maintenanceService } = createSchedulerTestService(); const job = vi.fn(); await toggleMaintenanceMode(maintenanceService, true); await schedulerService.schedule(job, 50, 'test-id-5'); await toggleMaintenanceMode(maintenanceService, false); await ms(75); expect(job).toHaveBeenCalledTimes(1); schedulerService.stop(); }); test('Can schedule multiple jobs at the same interval', async () => { const { schedulerService } = createSchedulerTestService(); const job = vi.fn(); const anotherJob = vi.fn(); await schedulerService.schedule(job, 50, 'test-id-6'); await schedulerService.schedule(anotherJob, 50, 'test-id-7'); await ms(75); expect(job).toHaveBeenCalledTimes(2); expect(anotherJob).toHaveBeenCalledTimes(2); schedulerService.stop(); }); test('Can schedule multiple jobs at the different intervals', async () => { const { schedulerService } = createSchedulerTestService(); const job = vi.fn(); const anotherJob = vi.fn(); await schedulerService.schedule(job, 100, 'test-id-8'); await schedulerService.schedule(anotherJob, 200, 'test-id-9'); await ms(250); expect(job).toHaveBeenCalledTimes(3); expect(anotherJob).toHaveBeenCalledTimes(2); schedulerService.stop(); }); test('Can handle crash of a async job', async () => { const { logger, getRecords } = getLogger(); const { schedulerService } = createSchedulerTestService({ loggerOverride: logger, }); const job = async () => { await Promise.reject('async reason'); }; await schedulerService.schedule(job, 50, 'test-id-10', 0); await ms(75); schedulerService.stop(); const records = getRecords(); expect(records[0][0]).toContain('initial scheduled job failed | id: test-id-10'); expect(records[0][1]).toContain('async reason'); expect(records[1][0]).toContain('interval scheduled job failed | id: test-id-10'); expect(records[1][1]).toContain('async reason'); }); test('Can handle crash of a sync job', async () => { const { logger, getRecords } = getLogger(); const { schedulerService } = createSchedulerTestService({ loggerOverride: logger, }); const job = () => { throw new Error('sync reason'); }; await schedulerService.schedule(job, 50, 'test-id-11'); await ms(75); schedulerService.stop(); const records = getRecords(); expect(records[0][0]).toContain('initial scheduled job failed | id: test-id-11'); expect(records[0][1].message).toContain('sync reason'); expect(records[1][0]).toContain('interval scheduled job failed | id: test-id-11'); }); it('should emit scheduler job time event when scheduled function is run', async () => { const eventBus = new EventEmitter(); const { schedulerService } = createSchedulerTestService({ eventBusOverride: eventBus, }); const mockJob = async () => { return Promise.resolve(); }; const eventPromise = new Promise((resolve, reject) => { eventBus.on(SCHEDULER_JOB_TIME, ({ jobId, time }) => { try { expect(jobId).toBe('testJobId'); expect(typeof time).toBe('number'); resolve(null); } catch (e) { reject(e); } }); }); await schedulerService.schedule(mockJob, 50, 'testJobId'); await eventPromise; }); test('Delays initial job execution by jitter duration', async () => { const { schedulerService } = createSchedulerTestService(); const job = vi.fn(); const jitterMs = 10; await schedulerService.schedule(job, 10000, 'test-id', jitterMs); expect(job).toHaveBeenCalledTimes(0); await ms(50); expect(job).toHaveBeenCalledTimes(1); schedulerService.stop(); }); test('Does not apply jitter if schedule interval is smaller than max jitter', async () => { const { schedulerService } = createSchedulerTestService(); const job = vi.fn(); // default jitter 2s-30s await schedulerService.schedule(job, 1000, 'test-id'); expect(job).toHaveBeenCalledTimes(1); schedulerService.stop(); }); test('Does not allow to run scheduled job when it is already pending', async () => { const { schedulerService } = createSchedulerTestService(); const NO_JITTER = 0; const job = vi.fn(); const slowJob = async () => { job(); await ms(25); }; void schedulerService.schedule(slowJob, 10, 'test-id', NO_JITTER); // scheduler had 2 chances to run but the initial slowJob was pending await ms(25); expect(job).toHaveBeenCalledTimes(1); schedulerService.stop(); }); //# sourceMappingURL=scheduler-service.test.js.map