|
1 |
| -import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test' |
2 |
| -import { DateTime } from 'luxon' |
3 |
| -import { CronJob, Schedule, sendAt, timeout } from '../src' |
| 1 | +import { beforeEach, describe, expect, it, mock } from 'bun:test' |
| 2 | +import { CronJob, CronTime, Job, Schedule, sendAt, timeout } from '../src' |
| 3 | + |
| 4 | +// Mock log.info |
| 5 | +const mockLogInfo = mock(() => {}) |
| 6 | +mock.module('@stacksjs/cli', () => ({ |
| 7 | + log: { info: mockLogInfo }, |
| 8 | +})) |
4 | 9 |
|
5 | 10 | describe('@stacksjs/scheduler', () => {
|
6 |
| - let originalDateNow: () => number |
| 11 | + let schedule: Schedule |
| 12 | + let mockTask: () => void |
7 | 13 |
|
8 | 14 | beforeEach(() => {
|
9 |
| - originalDateNow = Date.now |
10 |
| - Date.now = () => new Date('2024-01-01T00:00:00Z').getTime() |
11 |
| - }) |
12 |
| - |
13 |
| - afterEach(() => { |
14 |
| - Date.now = originalDateNow |
| 15 | + mockTask = mock(() => {}) |
| 16 | + schedule = new Schedule(mockTask) |
| 17 | + mockLogInfo.mockClear() |
15 | 18 | })
|
16 | 19 |
|
17 |
| - describe('Schedule class', () => { |
18 |
| - it('should create a schedule with everySecond', () => { |
19 |
| - const task = mock(() => {}) |
20 |
| - const schedule = new Schedule(task).everySecond() |
21 |
| - expect((schedule as any).cronPattern).toBe('* * * * * *') |
22 |
| - }) |
23 |
| - |
24 |
| - it('should create a schedule with everyMinute', () => { |
25 |
| - const task = mock(() => {}) |
26 |
| - const schedule = new Schedule(task).everyMinute() |
27 |
| - expect((schedule as any).cronPattern).toBe('0 * * * * *') |
| 20 | + describe('Schedule methods', () => { |
| 21 | + it.each([ |
| 22 | + ['everySecond', '* * * * * *'], |
| 23 | + ['everyMinute', '0 * * * * *'], |
| 24 | + ['everyTwoMinutes', '*/2 * * * * *'], |
| 25 | + ['everyFiveMinutes', '*/5 * * * *'], |
| 26 | + ['everyTenMinutes', '*/10 * * * *'], |
| 27 | + ['everyThirtyMinutes', '*/30 * * * *'], |
| 28 | + ['everyHour', '0 0 * * * *'], |
| 29 | + ['everyDay', '0 0 0 * * *'], |
| 30 | + ['hourly', '0 0 * * * *'], |
| 31 | + ['daily', '0 0 0 * * *'], |
| 32 | + ['weekly', '0 0 0 * * 0'], |
| 33 | + ['monthly', '0 0 0 1 * *'], |
| 34 | + ['yearly', '0 0 0 1 1 *'], |
| 35 | + ])('should set correct cron pattern for %s', (method, expectedPattern) => { |
| 36 | + ;(schedule as any)[method]().start() |
| 37 | + expect(mockLogInfo).toHaveBeenCalledWith( |
| 38 | + `Scheduled task with pattern: ${expectedPattern} in timezone: America/Los_Angeles`, |
| 39 | + ) |
| 40 | + }) |
| 41 | + |
| 42 | + it('should set correct cron pattern for onDays', () => { |
| 43 | + schedule.onDays([1, 3, 5]).start() |
| 44 | + expect(mockLogInfo).toHaveBeenCalledWith( |
| 45 | + 'Scheduled task with pattern: 0 0 0 * * 1,3,5 in timezone: America/Los_Angeles', |
| 46 | + ) |
| 47 | + }) |
| 48 | + |
| 49 | + it('should set correct cron pattern for at', () => { |
| 50 | + schedule.at('14:30').start() |
| 51 | + expect(mockLogInfo).toHaveBeenCalledWith( |
| 52 | + 'Scheduled task with pattern: 30 14 * * * in timezone: America/Los_Angeles', |
| 53 | + ) |
28 | 54 | })
|
29 | 55 |
|
30 |
| - it('should create a schedule with everyTwoMinutes', () => { |
31 |
| - const task = mock(() => {}) |
32 |
| - const schedule = new Schedule(task).everyTwoMinutes() |
33 |
| - expect((schedule as any).cronPattern).toBe('*/2 * * * * *') |
34 |
| - }) |
35 |
| - |
36 |
| - it('should create a schedule with everyFiveMinutes', () => { |
37 |
| - const task = mock(() => {}) |
38 |
| - const schedule = new Schedule(task).everyFiveMinutes() |
39 |
| - expect((schedule as any).cronPattern).toBe('*/5 * * * *') |
40 |
| - }) |
41 |
| - |
42 |
| - it('should create a schedule with everyTenMinutes', () => { |
43 |
| - const task = mock(() => {}) |
44 |
| - const schedule = new Schedule(task).everyTenMinutes() |
45 |
| - expect((schedule as any).cronPattern).toBe('*/10 * * * *') |
| 56 | + it('should set timezone', () => { |
| 57 | + schedule.daily().setTimeZone('Europe/London').start() |
| 58 | + expect(mockLogInfo).toHaveBeenCalledWith('Scheduled task with pattern: 0 0 0 * * * in timezone: Europe/London') |
46 | 59 | })
|
| 60 | + }) |
47 | 61 |
|
48 |
| - it('should create a schedule with everyThirtyMinutes', () => { |
49 |
| - const task = mock(() => {}) |
50 |
| - const schedule = new Schedule(task).everyThirtyMinutes() |
51 |
| - expect((schedule as any).cronPattern).toBe('*/30 * * * *') |
| 62 | + describe('Job and Action methods', () => { |
| 63 | + it('should log job scheduling', () => { |
| 64 | + schedule.job('path/to/job') |
| 65 | + expect(mockLogInfo).toHaveBeenCalledWith('Scheduling job: path/to/job') |
52 | 66 | })
|
53 | 67 |
|
54 |
| - it('should create a schedule with hourly', () => { |
55 |
| - const task = mock(() => {}) |
56 |
| - const schedule = new Schedule(task).hourly() |
57 |
| - expect((schedule as any).cronPattern).toBe('0 0 * * * *') |
| 68 | + it('should log action scheduling', () => { |
| 69 | + schedule.action('path/to/action') |
| 70 | + expect(mockLogInfo).toHaveBeenCalledWith('Scheduling action: path/to/action') |
58 | 71 | })
|
| 72 | + }) |
59 | 73 |
|
60 |
| - it('should create a schedule with daily', () => { |
61 |
| - const task = mock(() => {}) |
62 |
| - const schedule = new Schedule(task).daily() |
63 |
| - expect((schedule as any).cronPattern).toBe('0 0 0 * * *') |
| 74 | + describe('Static command method', () => { |
| 75 | + it('should log command execution', () => { |
| 76 | + Schedule.command('npm run build') |
| 77 | + expect(mockLogInfo).toHaveBeenCalledWith('Executing command: npm run build') |
64 | 78 | })
|
| 79 | + }) |
65 | 80 |
|
66 |
| - it('should create a schedule with weekly', () => { |
67 |
| - const task = mock(() => {}) |
68 |
| - const schedule = new Schedule(task).weekly() |
69 |
| - expect((schedule as any).cronPattern).toBe('0 0 0 * * 0') |
| 81 | + describe('Job class', () => { |
| 82 | + it('should inherit from Schedule', () => { |
| 83 | + const job = new Job(mockTask) |
| 84 | + expect(job).toBeInstanceOf(Schedule) |
70 | 85 | })
|
71 | 86 |
|
72 |
| - it('should create a schedule with monthly', () => { |
73 |
| - const task = mock(() => {}) |
74 |
| - const schedule = new Schedule(task).monthly() |
75 |
| - expect((schedule as any).cronPattern).toBe('0 0 0 1 * *') |
| 87 | + it('should have the same methods as Schedule', () => { |
| 88 | + const job = new Job(mockTask) |
| 89 | + expect(job.everyMinute).toBeDefined() |
| 90 | + expect(job.daily).toBeDefined() |
| 91 | + expect(job.setTimeZone).toBeDefined() |
76 | 92 | })
|
| 93 | + }) |
77 | 94 |
|
78 |
| - it('should create a schedule with yearly', () => { |
79 |
| - const task = mock(() => {}) |
80 |
| - const schedule = new Schedule(task).yearly() |
81 |
| - expect((schedule as any).cronPattern).toBe('0 0 0 1 1 *') |
| 95 | + describe('sendAt function', () => { |
| 96 | + it('should return a DateTime-like object', () => { |
| 97 | + const result = sendAt('0 0 * * *') |
| 98 | + expect(result).toHaveProperty('year') |
| 99 | + expect(result).toHaveProperty('month') |
| 100 | + expect(result).toHaveProperty('day') |
| 101 | + expect(result).toHaveProperty('hour') |
| 102 | + expect(result).toHaveProperty('minute') |
| 103 | + expect(result).toHaveProperty('second') |
82 | 104 | })
|
83 | 105 |
|
84 |
| - it('should create a schedule with onDays', () => { |
85 |
| - const task = mock(() => {}) |
86 |
| - const schedule = new Schedule(task).onDays([1, 3, 5]) |
87 |
| - expect((schedule as any).cronPattern).toBe('0 0 0 * * 1,3,5') |
| 106 | + it('should calculate the next occurrence correctly', () => { |
| 107 | + const now = new Date() |
| 108 | + const result = sendAt('0 0 * * *') |
| 109 | + expect(result.toJSDate() > now).toBe(true) |
| 110 | + expect(result.hour).toBe(0) |
| 111 | + expect(result.minute).toBe(0) |
88 | 112 | })
|
| 113 | + }) |
89 | 114 |
|
90 |
| - it('should create a schedule with at', () => { |
91 |
| - const task = mock(() => {}) |
92 |
| - const schedule = new Schedule(task).at('14:30') |
93 |
| - expect((schedule as any).cronPattern).toBe('30 14 * * *') |
| 115 | + describe('timeout function', () => { |
| 116 | + it('should return a number', () => { |
| 117 | + const result = timeout('0 0 * * *') |
| 118 | + expect(typeof result).toBe('number') |
94 | 119 | })
|
95 | 120 |
|
96 |
| - it('should set timezone', () => { |
97 |
| - const task = mock(() => {}) |
98 |
| - const schedule = new Schedule(task).setTimeZone('UTC') |
99 |
| - expect((schedule as any).timezone).toBe('UTC') |
| 121 | + it('should return a positive number', () => { |
| 122 | + const result = timeout('0 0 * * *') |
| 123 | + expect(result).toBeGreaterThan(0) |
100 | 124 | })
|
| 125 | + }) |
101 | 126 |
|
102 |
| - it('should start the schedule', () => { |
103 |
| - const task = mock(() => {}) |
104 |
| - const schedule = new Schedule(task).everyMinute() |
105 |
| - const cronJobSpy = spyOn(CronJob, 'from').mockImplementation(() => ({}) as any) |
106 |
| - schedule.start() |
107 |
| - expect(cronJobSpy).toHaveBeenCalledWith('0 * * * * *', expect.any(Function), null, true, 'America/Los_Angeles') |
108 |
| - }) |
| 127 | + describe('CronJob integration', () => { |
| 128 | + it('should create a CronJob instance when start is called', () => { |
| 129 | + const mockTask = mock(() => {}) |
| 130 | + const schedule = new Schedule(mockTask) |
109 | 131 |
|
110 |
| - it('should log job scheduling', () => { |
111 |
| - const task = mock(() => {}) |
112 |
| - const logSpy = spyOn(console, 'info') |
113 |
| - new Schedule(task).job('/path/to/job') |
114 |
| - expect(logSpy).toHaveBeenCalledWith('Scheduling job: /path/to/job') |
115 |
| - }) |
| 132 | + schedule.everySecond().start() |
116 | 133 |
|
117 |
| - it('should log action scheduling', () => { |
118 |
| - const task = mock(() => {}) |
119 |
| - const logSpy = spyOn(console, 'info') |
120 |
| - new Schedule(task).action('/path/to/action') |
121 |
| - expect(logSpy).toHaveBeenCalledWith('Scheduling action: /path/to/action') |
| 134 | + // Check if mockLogInfo was called with the correct message |
| 135 | + expect(mockLogInfo).toHaveBeenCalledWith( |
| 136 | + 'Scheduled task with pattern: * * * * * * in timezone: America/Los_Angeles', |
| 137 | + ) |
122 | 138 | })
|
123 | 139 |
|
124 |
| - it('should log command execution', () => { |
125 |
| - const logSpy = spyOn(console, 'info') |
126 |
| - Schedule.command('echo "Hello"') |
127 |
| - expect(logSpy).toHaveBeenCalledWith('Executing command: echo "Hello"') |
128 |
| - }) |
129 |
| - }) |
| 140 | + it('should create a CronJob with correct parameters', () => { |
| 141 | + const mockTask = mock(() => {}) |
| 142 | + const schedule = new Schedule(mockTask) |
130 | 143 |
|
131 |
| - describe('sendAt function', () => { |
132 |
| - it('should return correct DateTime for cron string', () => { |
133 |
| - const result = sendAt('0 0 * * *') |
134 |
| - expect(result).toBeInstanceOf(DateTime) |
135 |
| - expect(result.toISO()).toBe('2024-01-02T00:00:00.000Z') |
136 |
| - }) |
| 144 | + schedule.everyMinute().setTimeZone('Europe/London').start() |
137 | 145 |
|
138 |
| - it('should return correct DateTime for Date object', () => { |
139 |
| - const date = new Date('2024-01-01T12:00:00Z') |
140 |
| - const result = sendAt(date) |
141 |
| - expect(result).toBeInstanceOf(DateTime) |
142 |
| - expect(result.toISO()).toBe('2024-01-01T12:00:00.000Z') |
| 146 | + expect(mockLogInfo).toHaveBeenCalledWith('Scheduled task with pattern: 0 * * * * * in timezone: Europe/London') |
143 | 147 | })
|
144 |
| - }) |
145 | 148 |
|
146 |
| - describe('timeout function', () => { |
147 |
| - it('should return correct timeout for cron string', () => { |
148 |
| - const result = timeout('0 0 * * *') |
149 |
| - expect(result).toBe(86400000) // 24 hours in milliseconds |
150 |
| - }) |
| 149 | + describe('Cron exports', () => { |
| 150 | + it('CronJob is exported and can be instantiated', () => { |
| 151 | + const job = new CronJob('* * * * *', () => {}) |
| 152 | + expect(job).toBeInstanceOf(CronJob) |
| 153 | + }) |
151 | 154 |
|
152 |
| - it('should return correct timeout for Date object', () => { |
153 |
| - const date = new Date('2024-01-01T12:00:00Z') |
154 |
| - const result = timeout(date) |
155 |
| - expect(result).toBe(43200000) // 12 hours in milliseconds |
| 155 | + it('CronTime is exported and can be instantiated', () => { |
| 156 | + const time = new CronTime('* * * * *') |
| 157 | + expect(time).toBeInstanceOf(CronTime) |
| 158 | + }) |
156 | 159 | })
|
157 | 160 | })
|
158 | 161 | })
|
0 commit comments