/** * Tests for SchedulerService: cron parsing, schedule registration, skip when session active. */ import Database from 'better-sqlite3'; import { runMigrations } from '../../src/db/migrations'; import { ScheduleRepository } from '../../src/db/ScheduleRepository'; import { SchedulerService } from '../../src/server/scheduler/SchedulerService'; import { SessionStore } from '../../src/server/SessionStore'; function makeDb(): Database.Database { const db = new Database(':memory:'); db.pragma('foreign_keys = ON'); runMigrations(db); return db; } describe('SchedulerService', () => { let db: Database.Database; let repo: ScheduleRepository; let store: SessionStore; let scheduler: SchedulerService; beforeEach(() => { db = makeDb(); repo = new ScheduleRepository(db); store = new SessionStore('./reports'); scheduler = new SchedulerService(repo, store); }); afterEach(() => { scheduler.stop(); db.close(); }); it('starts without error when no schedules exist', () => { expect(() => scheduler.start()).not.toThrow(); }); it('registers a valid cron job', () => { repo.create({ id: 'sched_1', name: 'Daily check', url: 'http://example.com', configJson: '{}', cronExpression: '0 2 * * *', enabled: true, }); expect(() => scheduler.register(repo.findById('sched_1')!)).not.toThrow(); }); it('skips invalid cron expressions', () => { repo.create({ id: 'sched_bad', name: 'Bad', url: 'http://x.com', configJson: '{}', cronExpression: 'not-valid-cron', enabled: true, }); expect(() => scheduler.register(repo.findById('sched_bad')!)).not.toThrow(); }); it('unregisters a schedule job', () => { repo.create({ id: 'sched_2', name: 'Test', url: 'http://example.com', configJson: '{}', cronExpression: '0 * * * *', enabled: true, }); scheduler.register(repo.findById('sched_2')!); expect(() => scheduler.unregister('sched_2')).not.toThrow(); }); it('computeNextRunAt returns a future timestamp for valid cron', () => { const nextRun = SchedulerService.computeNextRunAt('0 2 * * *'); expect(nextRun).not.toBeNull(); expect(nextRun!).toBeGreaterThan(Date.now()); }); it('computeNextRunAt returns null for invalid cron', () => { expect(SchedulerService.computeNextRunAt('not-valid')).toBeNull(); }); it('does not register job for disabled schedule', () => { repo.create({ id: 'sched_3', name: 'Disabled', url: 'http://example.com', configJson: '{}', cronExpression: '0 * * * *', enabled: false, }); // Should not throw and job should not be registered expect(() => scheduler.register(repo.findById('sched_3')!)).not.toThrow(); // Unregistering a non-existing job is also safe expect(() => scheduler.unregister('sched_3')).not.toThrow(); }); }); describe('ScheduleRepository', () => { let db: Database.Database; let repo: ScheduleRepository; beforeEach(() => { db = makeDb(); repo = new ScheduleRepository(db); }); afterEach(() => db.close()); it('creates and retrieves a schedule', () => { repo.create({ id: 's1', name: 'Daily', url: 'http://test.com', configJson: '{"maxStates":10}', cronExpression: '0 2 * * *', }); const record = repo.findById('s1'); expect(record).toBeDefined(); expect(record!.name).toBe('Daily'); expect(record!.enabled).toBe(true); }); it('findAll returns all schedules', () => { repo.create({ id: 's1', name: 'A', url: 'http://a.com', configJson: '{}', cronExpression: '0 * * * *' }); repo.create({ id: 's2', name: 'B', url: 'http://b.com', configJson: '{}', cronExpression: '0 2 * * *' }); expect(repo.findAll()).toHaveLength(2); }); it('findAll(true) returns only enabled schedules', () => { repo.create({ id: 's1', name: 'Enabled', url: 'http://a.com', configJson: '{}', cronExpression: '0 * * * *', enabled: true }); repo.create({ id: 's2', name: 'Disabled', url: 'http://b.com', configJson: '{}', cronExpression: '0 * * * *', enabled: false }); expect(repo.findAll(true)).toHaveLength(1); }); it('updates enabled field', () => { repo.create({ id: 's1', name: 'A', url: 'http://a.com', configJson: '{}', cronExpression: '0 * * * *' }); repo.update('s1', { enabled: false }); expect(repo.findById('s1')!.enabled).toBe(false); }); it('deletes a schedule', () => { repo.create({ id: 's1', name: 'A', url: 'http://a.com', configJson: '{}', cronExpression: '0 * * * *' }); repo.delete('s1'); expect(repo.findById('s1')).toBeUndefined(); }); });