stacks 0.58.50 → 0.58.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/core/actions/dist/src/deploy/index.js +2 -2
  2. package/core/actions/dist/src/upgrade.js +1 -1
  3. package/core/actions/package.json +1 -1
  4. package/core/actions/src/build/cli.ts +1 -1
  5. package/core/actions/src/build/server.ts +9 -0
  6. package/core/actions/src/deploy/index.ts +2 -2
  7. package/core/actions/tsconfig.json +3 -0
  8. package/core/ai/package.json +1 -1
  9. package/core/ai/tsconfig.json +3 -0
  10. package/core/alias/package.json +1 -1
  11. package/core/alias/tsconfig.json +3 -0
  12. package/core/analytics/package.json +1 -1
  13. package/core/analytics/tsconfig.json +3 -0
  14. package/core/api/package.json +1 -1
  15. package/core/api/tsconfig.json +3 -0
  16. package/core/arrays/package.json +1 -1
  17. package/core/arrays/tsconfig.json +3 -0
  18. package/core/auth/package.json +1 -1
  19. package/core/auth/tsconfig.json +3 -0
  20. package/core/buddy/dist/{chunk-91084836e95161e8.js → chunk-d52e40a990b45728.js} +152 -84
  21. package/core/buddy/dist/cli.js +4 -1
  22. package/core/buddy/dist/index.js +3 -1
  23. package/core/buddy/package.json +1 -1
  24. package/core/buddy/src/cli.ts +2 -0
  25. package/core/buddy/src/commands/build.ts +17 -1
  26. package/core/buddy/src/commands/deploy.ts +2 -2
  27. package/core/buddy/src/commands/index.ts +1 -0
  28. package/core/buddy/src/commands/list.ts +39 -0
  29. package/core/buddy/src/commands/setup.ts +57 -1
  30. package/core/buddy/tsconfig.json +3 -0
  31. package/core/build/package.json +1 -1
  32. package/core/build/tsconfig.json +3 -0
  33. package/core/bun-create/bud/package.json +1 -1
  34. package/core/bun-create/buddy/package.json +1 -1
  35. package/core/bun-create/stack/package.json +1 -1
  36. package/core/bun-create/stacks/package.json +1 -1
  37. package/core/bun-create/stx/package.json +2 -1
  38. package/core/bun-plugin-yaml/package.json +1 -1
  39. package/core/bun-plugin-yaml/tsconfig.json +3 -0
  40. package/core/cache/package.json +1 -1
  41. package/core/cache/tsconfig.json +3 -0
  42. package/core/chat/package.json +8 -8
  43. package/core/chat/tsconfig.json +3 -0
  44. package/core/cli/dist/index.js +1 -1
  45. package/core/cli/package.json +1 -1
  46. package/core/cli/tsconfig.json +3 -0
  47. package/core/cloud/build.ts +1 -1
  48. package/core/cloud/deploy.ts +10 -1
  49. package/core/cloud/dist/index.js +167 -807
  50. package/core/cloud/package.json +2 -3
  51. package/core/cloud/src/cloud/aws-sdk-layer/nodejs/package.json +1 -1
  52. package/core/cloud/src/cloud/cdn.ts +59 -6
  53. package/core/cloud/src/cloud/compute.ts +29 -19
  54. package/core/cloud/src/cloud/dns.ts +8 -0
  55. package/core/cloud/src/cloud/index.ts +8 -2
  56. package/core/cloud/src/cloud/queue.ts +46 -0
  57. package/core/cloud/src/cloud/router-layer/nodejs/package.json +2 -2
  58. package/core/cloud/src/cloud/security.ts +1 -1
  59. package/core/cloud/src/helpers.ts +13 -1
  60. package/core/cloud/tsconfig.json +3 -0
  61. package/core/collections/package.json +1 -1
  62. package/core/collections/tsconfig.json +3 -0
  63. package/core/config/dist/index.js +154 -83
  64. package/core/config/package.json +2 -2
  65. package/core/config/tsconfig.json +3 -0
  66. package/core/database/package.json +1 -1
  67. package/core/database/src/kysely-bun-worker/mitt.ts +1 -1
  68. package/core/database/tsconfig.json +3 -0
  69. package/core/datetime/package.json +1 -1
  70. package/core/datetime/tsconfig.json +3 -0
  71. package/core/desktop/package.json +1 -1
  72. package/core/desktop/tsconfig.json +3 -0
  73. package/core/development/package.json +1 -1
  74. package/core/development/tsconfig.json +3 -0
  75. package/core/dns/package.json +1 -1
  76. package/core/dns/tsconfig.json +3 -0
  77. package/core/docs/package.json +1 -1
  78. package/core/docs/tsconfig.json +3 -0
  79. package/core/email/package.json +12 -12
  80. package/core/email/tsconfig.json +3 -0
  81. package/core/enums/dist/index.js +1 -0
  82. package/core/enums/package.json +1 -1
  83. package/core/enums/src/index.ts +1 -0
  84. package/core/enums/tsconfig.json +3 -0
  85. package/core/env/package.json +1 -1
  86. package/core/env/tsconfig.json +3 -0
  87. package/core/error-handling/package.json +1 -1
  88. package/core/error-handling/tsconfig.json +3 -0
  89. package/core/eslint-config/package.json +3 -3
  90. package/core/eslint-config/src/configs/typescript.ts +1 -0
  91. package/core/eslint-config/src/factory.ts +1 -0
  92. package/core/events/package.json +1 -1
  93. package/core/events/src/index.ts +3 -6
  94. package/core/events/tsconfig.json +3 -0
  95. package/core/faker/package.json +1 -1
  96. package/core/faker/tsconfig.json +3 -0
  97. package/core/git/package.json +1 -1
  98. package/core/git/tsconfig.json +3 -0
  99. package/core/health/package.json +1 -1
  100. package/core/health/tsconfig.json +3 -0
  101. package/core/http/package.json +1 -1
  102. package/core/http/tsconfig.json +3 -0
  103. package/core/lint/package.json +1 -1
  104. package/core/lint/tsconfig.json +3 -0
  105. package/core/logging/package.json +1 -1
  106. package/core/logging/tsconfig.json +3 -0
  107. package/core/notifications/package.json +22 -22
  108. package/core/notifications/tsconfig.json +3 -0
  109. package/core/objects/package.json +1 -1
  110. package/core/objects/tsconfig.json +3 -0
  111. package/core/orm/package.json +1 -1
  112. package/core/orm/tsconfig.json +3 -0
  113. package/core/path/package.json +1 -1
  114. package/core/path/tsconfig.json +3 -0
  115. package/core/payments/dist/index.js +21 -3
  116. package/core/payments/package.json +1 -1
  117. package/core/payments/tsconfig.json +3 -0
  118. package/core/push/package.json +5 -5
  119. package/core/push/tsconfig.json +3 -0
  120. package/core/query-builder/package.json +1 -1
  121. package/core/query-builder/tsconfig.json +3 -0
  122. package/core/queue/package.json +1 -1
  123. package/core/queue/tsconfig.json +3 -0
  124. package/core/raycast/package.json +1 -1
  125. package/core/realtime/package.json +1 -1
  126. package/core/realtime/tsconfig.json +3 -0
  127. package/core/repl/package.json +1 -1
  128. package/core/repl/tsconfig.json +3 -0
  129. package/core/router/dist/index.js +3 -4
  130. package/core/router/package.json +1 -1
  131. package/core/router/src/router.ts +11 -11
  132. package/core/router/src/server.ts +4 -4
  133. package/core/router/tsconfig.json +3 -0
  134. package/core/scheduler/build.ts +1 -1
  135. package/core/scheduler/dist/index.js +682 -152
  136. package/core/scheduler/package.json +7 -4
  137. package/core/scheduler/src/constants.ts +81 -0
  138. package/core/scheduler/src/errors.ts +7 -0
  139. package/core/scheduler/src/index.ts +15 -317
  140. package/core/scheduler/src/job.ts +315 -0
  141. package/core/scheduler/src/schedule.ts +111 -0
  142. package/core/scheduler/src/time.ts +823 -0
  143. package/core/scheduler/src/types/cron.ts +98 -0
  144. package/core/scheduler/src/types/utils.ts +14 -0
  145. package/core/scheduler/src/utils.ts +19 -0
  146. package/core/scheduler/tests/cron.fuzz.ts +120 -0
  147. package/core/scheduler/tests/cron.test.ts +1241 -0
  148. package/core/scheduler/tests/crontime.test.ts +778 -0
  149. package/core/scheduler/tsconfig.json +3 -0
  150. package/core/search-engine/package.json +1 -1
  151. package/core/search-engine/tsconfig.json +3 -0
  152. package/core/security/package.json +1 -1
  153. package/core/security/tsconfig.json +3 -0
  154. package/core/server/package.json +1 -1
  155. package/core/server/tsconfig.json +3 -0
  156. package/core/signals/package.json +1 -1
  157. package/core/signals/tsconfig.json +3 -0
  158. package/core/slug/package.json +1 -1
  159. package/core/slug/tsconfig.json +3 -0
  160. package/core/sms/package.json +11 -11
  161. package/core/sms/tsconfig.json +3 -0
  162. package/core/storage/package.json +1 -1
  163. package/core/storage/tsconfig.json +3 -0
  164. package/core/strings/package.json +1 -1
  165. package/core/strings/tsconfig.json +3 -0
  166. package/core/tables/package.json +1 -1
  167. package/core/tables/tsconfig.json +3 -0
  168. package/core/testing/package.json +1 -1
  169. package/core/testing/tsconfig.json +3 -0
  170. package/core/tinker/package.json +1 -1
  171. package/core/tinker/tsconfig.json +3 -0
  172. package/core/tunnel/package.json +1 -1
  173. package/core/tunnel/tsconfig.json +3 -0
  174. package/core/types/package.json +3 -3
  175. package/core/types/src/app.ts +2 -2
  176. package/core/types/src/cli.ts +1 -1
  177. package/core/types/src/cloud.ts +5 -1
  178. package/core/types/src/cron-jobs.ts +1 -0
  179. package/core/types/src/queue.ts +3 -1
  180. package/core/types/tsconfig.json +3 -0
  181. package/core/ui/package.json +4 -4
  182. package/core/ui/tsconfig.json +3 -0
  183. package/core/utils/dist/index.js +1 -1
  184. package/core/utils/package.json +1 -1
  185. package/core/utils/src/promise.ts +2 -2
  186. package/core/utils/tsconfig.json +3 -0
  187. package/core/validation/package.json +1 -1
  188. package/core/validation/src/rules.ts +3 -3
  189. package/core/validation/tsconfig.json +3 -0
  190. package/core/vite/dist/index.js +1 -1
  191. package/core/vite/package.json +1 -1
  192. package/core/vite/tsconfig.json +3 -0
  193. package/core/whois/package.json +1 -1
  194. package/core/whois/tsconfig.json +3 -0
  195. package/core/x-ray/package.json +1 -1
  196. package/core/x-ray/tsconfig.json +3 -0
  197. package/core/zsh-buddy/README.md +6 -2
  198. package/core/zsh-buddy/buddy.plugin.zsh +109 -0
  199. package/core/zsh-buddy/package.json +1 -1
  200. package/ide/dictionary.txt +2 -0
  201. package/ide/vscode/package.json +1 -1
  202. package/package.json +1 -1
  203. package/buddy +0 -2
  204. package/core/actions/src/zip/api.ts +0 -22
  205. package/core/bud +0 -2
  206. package/core/cloud/buddy +0 -2
  207. package/core/cloud/src/cloud/package/README.md +0 -59
  208. package/core/cloud/src/cloud/package/package.json +0 -63
  209. package/core/scheduler/tests/example.test.ts +0 -5
@@ -0,0 +1,1241 @@
1
+ /* eslint-disable no-new */
2
+ import { DateTime } from 'luxon'
3
+ import sinon from 'sinon'
4
+ import { CronJob, CronTime } from '../src'
5
+
6
+ describe('cron', () => {
7
+ afterEach(() => expect.hasAssertions())
8
+
9
+ describe('with seconds', () => {
10
+ it('should run every second (* * * * * *)', () => {
11
+ const clock = sinon.useFakeTimers()
12
+ const callback = jest.fn()
13
+ const job = new CronJob('* * * * * *', callback, null, true)
14
+
15
+ expect(callback).not.toHaveBeenCalled()
16
+ clock.tick(1000)
17
+ job.stop()
18
+ clock.restore()
19
+ expect(callback).toHaveBeenCalledTimes(1)
20
+ })
21
+
22
+ it('should run second with onComplete (* * * * * *)', (done) => {
23
+ const clock = sinon.useFakeTimers()
24
+ const callback = jest.fn()
25
+
26
+ const job = new CronJob(
27
+ '* * * * * *',
28
+ callback,
29
+ () => {
30
+ expect(callback).toHaveBeenCalledTimes(1)
31
+ done()
32
+ },
33
+ true,
34
+ )
35
+
36
+ clock.tick(1000)
37
+ job.stop()
38
+ clock.restore()
39
+ })
40
+
41
+ it('should use standard cron no-seconds syntax (* * * * *)', () => {
42
+ const clock = sinon.useFakeTimers()
43
+ const callback = jest.fn()
44
+ const job = new CronJob('* * * * *', callback, null, true)
45
+
46
+ clock.tick(1000) // tick second
47
+
48
+ clock.tick(59 * 1000) // tick minute
49
+
50
+ job.stop()
51
+ clock.restore()
52
+
53
+ expect(callback).toHaveBeenCalledTimes(1)
54
+ })
55
+
56
+ it('should run every second for 5 seconds (* * * * * *)', () => {
57
+ const clock = sinon.useFakeTimers()
58
+ const callback = jest.fn()
59
+ const job = new CronJob('* * * * * *', callback, null, true)
60
+ for (let i = 0; i < 5; i++) clock.tick(1000)
61
+ job.stop()
62
+ clock.restore()
63
+ expect(callback).toHaveBeenCalledTimes(5)
64
+ })
65
+
66
+ it('should run every second for 5 seconds with onComplete (* * * * * *)', (done) => {
67
+ const callback = jest.fn()
68
+ const clock = sinon.useFakeTimers()
69
+ const job = new CronJob(
70
+ '* * * * * *',
71
+ callback,
72
+ () => {
73
+ expect(callback).toHaveBeenCalledTimes(5)
74
+ done()
75
+ },
76
+ true,
77
+ )
78
+ for (let i = 0; i < 5; i++) clock.tick(1000)
79
+ job.stop()
80
+ clock.restore()
81
+ })
82
+
83
+ it('should run every second for 5 seconds (*/1 * * * * *)', () => {
84
+ const clock = sinon.useFakeTimers()
85
+ const callback = jest.fn()
86
+ const job = new CronJob('*/1 * * * * *', callback, null, true)
87
+ for (let i = 0; i < 5; i++) clock.tick(1000)
88
+ job.stop()
89
+ clock.restore()
90
+ expect(callback).toHaveBeenCalledTimes(5)
91
+ })
92
+
93
+ it('should run every 2 seconds for 1 seconds (*/2 * * * * *)', () => {
94
+ const clock = sinon.useFakeTimers()
95
+ const callback = jest.fn()
96
+ const job = new CronJob('*/2 * * * * *', callback, null, true)
97
+ clock.tick(1000)
98
+ job.stop()
99
+ clock.restore()
100
+ expect(callback).toHaveBeenCalledTimes(0)
101
+ })
102
+
103
+ it('should run every 2 seconds for 5 seconds (*/2 * * * * *)', () => {
104
+ const clock = sinon.useFakeTimers()
105
+ const callback = jest.fn()
106
+ const job = new CronJob('*/2 * * * * *', callback, null, true)
107
+ for (let i = 0; i < 5; i++) clock.tick(1000)
108
+ job.stop()
109
+ clock.restore()
110
+ expect(callback).toHaveBeenCalledTimes(2)
111
+ })
112
+
113
+ it('should run every second for 5 seconds with onComplete (*/1 * * * * *)', (done) => {
114
+ const clock = sinon.useFakeTimers()
115
+ const callback = jest.fn()
116
+ const job = new CronJob(
117
+ '*/1 * * * * *',
118
+ callback,
119
+ () => {
120
+ expect(callback).toHaveBeenCalledTimes(5)
121
+ done()
122
+ },
123
+ true,
124
+ )
125
+ for (let i = 0; i < 5; i++) clock.tick(1000)
126
+ job.stop()
127
+ clock.restore()
128
+ })
129
+
130
+ it('should run every second for a range ([start]-[end] * * * * *)', () => {
131
+ const clock = sinon.useFakeTimers()
132
+ const callback = jest.fn()
133
+ const job = new CronJob('0-8 * * * * *', callback, null, true)
134
+ clock.tick(10000)
135
+ job.stop()
136
+ clock.restore()
137
+ expect(callback).toHaveBeenCalledTimes(8)
138
+ })
139
+
140
+ it('should run every second for a range ([start]-[end] * * * * *) with onComplete', (done) => {
141
+ const clock = sinon.useFakeTimers()
142
+ const callback = jest.fn()
143
+ const job = new CronJob(
144
+ '0-8 * * * * *',
145
+ callback,
146
+ () => {
147
+ expect(callback).toHaveBeenCalledTimes(8)
148
+ done()
149
+ },
150
+ true,
151
+ )
152
+ clock.tick(10000)
153
+ job.stop()
154
+ clock.restore()
155
+ })
156
+
157
+ it('should default to full range when upper range not provided (1/2 * * * * *)', (done) => {
158
+ const clock = sinon.useFakeTimers()
159
+ const callback = jest.fn()
160
+ const job = new CronJob(
161
+ '1/2 * * * * *',
162
+ callback,
163
+ () => {
164
+ expect(callback).toHaveBeenCalledTimes(30)
165
+ done()
166
+ },
167
+ true,
168
+ )
169
+ clock.tick(1000 * 60)
170
+ job.stop()
171
+ clock.restore()
172
+ })
173
+
174
+ it('should run every second (* * * * * *) using the object constructor', () => {
175
+ const clock = sinon.useFakeTimers()
176
+ const callback = jest.fn()
177
+ const job = CronJob.from({
178
+ cronTime: '* * * * * *',
179
+ onTick: callback,
180
+ start: true,
181
+ })
182
+ clock.tick(1000)
183
+ job.stop()
184
+ clock.restore()
185
+ expect(callback).toHaveBeenCalledTimes(1)
186
+ })
187
+
188
+ it('should run every second with onComplete (* * * * * *) using the object constructor', (done) => {
189
+ const clock = sinon.useFakeTimers()
190
+ const callback = jest.fn()
191
+ const job = CronJob.from({
192
+ cronTime: '* * * * * *',
193
+ onTick: callback,
194
+ onComplete: () => {
195
+ expect(callback).toHaveBeenCalledTimes(1)
196
+ done()
197
+ },
198
+ start: true,
199
+ })
200
+ clock.tick(1000)
201
+ job.stop()
202
+ clock.restore()
203
+ })
204
+ })
205
+
206
+ describe('with minutes', () => {
207
+ it('should fire every 60 min', () => {
208
+ const clock = sinon.useFakeTimers()
209
+ const m60 = 60 * 60 * 1000
210
+ const l: number[] = []
211
+ const job = new CronJob(
212
+ '00 30 * * * *',
213
+ () => {
214
+ l.push(Math.floor(Date.now() / 60000))
215
+ },
216
+ null,
217
+ true,
218
+ )
219
+
220
+ clock.tick(m60 * 10)
221
+
222
+ expect(l).toHaveLength(10)
223
+ expect(l.every(i => i % 30 === 0)).toBe(true)
224
+
225
+ job.stop()
226
+ clock.restore()
227
+ })
228
+
229
+ it('should run every 45 minutes for 2 hours (0 */45 * * * *)', () => {
230
+ const clock = sinon.useFakeTimers()
231
+ const callback = jest.fn()
232
+ const job = new CronJob('0 */45 * * * *', callback, null, true)
233
+ for (let i = 0; i < 2; i++) clock.tick(60 * 60 * 1000)
234
+ job.stop()
235
+ clock.restore()
236
+ expect(callback).toHaveBeenCalledTimes(4)
237
+ })
238
+
239
+ it('should run every 45 minutes for 2 hours (0 */45 * * * *) with onComplete', (done) => {
240
+ const clock = sinon.useFakeTimers()
241
+ const callback = jest.fn()
242
+ const job = new CronJob(
243
+ '0 */45 * * * *',
244
+ callback,
245
+ () => {
246
+ expect(callback).toHaveBeenCalledTimes(4)
247
+ done()
248
+ },
249
+ true,
250
+ )
251
+ for (let i = 0; i < 2; i++) clock.tick(60 * 60 * 1000)
252
+ job.stop()
253
+ clock.restore()
254
+ })
255
+ })
256
+
257
+ it('should start and stop job from outside', (done) => {
258
+ const clock = sinon.useFakeTimers()
259
+ const callback = jest.fn()
260
+ const job = new CronJob(
261
+ '* * * * * *',
262
+ () => {
263
+ callback()
264
+ },
265
+ () => {
266
+ expect(callback).toHaveBeenCalledTimes(1)
267
+ clock.restore()
268
+ done()
269
+ },
270
+ true,
271
+ )
272
+ clock.tick(1000)
273
+ job.stop()
274
+ })
275
+
276
+ it('should start and stop job from inside (default context)', (done) => {
277
+ const clock = sinon.useFakeTimers()
278
+ const callback = jest.fn()
279
+ new CronJob(
280
+ '* * * * * *',
281
+ function () {
282
+ callback()
283
+ this.stop()
284
+ },
285
+ () => {
286
+ expect(callback).toHaveBeenCalledTimes(1)
287
+ clock.restore()
288
+ done()
289
+ },
290
+ true,
291
+ )
292
+ clock.tick(1000)
293
+ })
294
+
295
+ describe('with date', () => {
296
+ it('should run on a specific date', () => {
297
+ const d = new Date()
298
+ const clock = sinon.useFakeTimers(d.getTime())
299
+ const s = d.getSeconds() + 1
300
+ d.setSeconds(s)
301
+ const callback = jest.fn()
302
+ const job = new CronJob(
303
+ d,
304
+ () => {
305
+ const t = new Date()
306
+ expect(t.getSeconds()).toBe(d.getSeconds())
307
+ callback()
308
+ },
309
+ null,
310
+ true,
311
+ )
312
+ clock.tick(1000)
313
+ clock.restore()
314
+ job.stop()
315
+ expect(callback).toHaveBeenCalledTimes(1)
316
+ })
317
+
318
+ it('should run on a specific date and call onComplete from onTick', async () => {
319
+ const d = new Date()
320
+ const clock = sinon.useFakeTimers(d.getTime())
321
+ d.setSeconds(d.getSeconds() + 1)
322
+ const callback = jest.fn()
323
+
324
+ await new Promise<void>((resolve) => {
325
+ const job = new CronJob(
326
+ d,
327
+ (onComplete) => {
328
+ const t = new Date()
329
+ expect(t.getSeconds()).toBe(d.getSeconds())
330
+ void onComplete()
331
+ },
332
+ () => {
333
+ callback()
334
+ resolve()
335
+ },
336
+ true,
337
+ )
338
+ clock.tick(1000)
339
+ clock.restore()
340
+ job.stop()
341
+ })
342
+
343
+ // onComplete is called 2 times: once in onTick() & once when calling job.stop()
344
+ expect(callback).toHaveBeenCalledTimes(2)
345
+ })
346
+
347
+ it('should run on a specific date and call onComplete from onTick using the object constructor', async () => {
348
+ const d = new Date()
349
+ const clock = sinon.useFakeTimers(d.getTime())
350
+ d.setSeconds(d.getSeconds() + 1)
351
+ const callback = jest.fn()
352
+
353
+ await new Promise<void>((resolve) => {
354
+ const job = CronJob.from({
355
+ cronTime: d,
356
+ onTick: (onComplete) => {
357
+ const t = new Date()
358
+ expect(t.getSeconds()).toBe(d.getSeconds())
359
+ void onComplete()
360
+ },
361
+ onComplete: function () {
362
+ callback()
363
+ resolve()
364
+ } as () => void,
365
+ start: true,
366
+ })
367
+ clock.tick(1000)
368
+ clock.restore()
369
+ job.stop()
370
+ })
371
+
372
+ // onComplete is called 2 times: once in onTick() & once when calling job.stop()
373
+ expect(callback).toHaveBeenCalledTimes(2)
374
+ })
375
+
376
+ it('should not be able to call onComplete from onTick if if wasn\'t provided', () => {
377
+ expect.assertions(4)
378
+ const d = new Date()
379
+ const clock = sinon.useFakeTimers(d.getTime())
380
+ d.setSeconds(d.getSeconds() + 1)
381
+
382
+ const job = new CronJob(
383
+ d,
384
+ (onComplete) => {
385
+ const t = new Date()
386
+ expect(onComplete).toBeUndefined()
387
+ try {
388
+ // @ts-expect-error should be a TS warning and throw
389
+ onComplete()
390
+ }
391
+ catch (e) {
392
+ // we make sure this isn't skipped with `expect.assertions()`
393
+ // at the beginning of the test
394
+ expect(e).toBeInstanceOf(TypeError)
395
+ }
396
+ expect(onComplete).toBeUndefined()
397
+ expect(t.getSeconds()).toBe(d.getSeconds())
398
+ },
399
+ null,
400
+ true,
401
+ )
402
+ clock.tick(1000)
403
+ job.stop()
404
+ clock.restore()
405
+ })
406
+
407
+ it('should wait and not fire immediately', () => {
408
+ const clock = sinon.useFakeTimers()
409
+ const callback = jest.fn()
410
+
411
+ const d = new Date().getTime() + 31 * 86400 * 1000
412
+
413
+ const job = new CronJob(new Date(d), callback)
414
+ job.start()
415
+
416
+ clock.tick(1000)
417
+
418
+ clock.restore()
419
+ job.stop()
420
+ expect(callback).toHaveBeenCalledTimes(0)
421
+ })
422
+
423
+ it('should wait but fire on init', () => {
424
+ const clock = sinon.useFakeTimers()
425
+ const callback = jest.fn()
426
+
427
+ const d = new Date().getTime() + 31 * 86400 * 1000
428
+
429
+ const job = CronJob.from({
430
+ cronTime: new Date(d),
431
+ onTick: callback,
432
+ runOnInit: true,
433
+ })
434
+
435
+ expect(callback).toHaveBeenCalledTimes(1)
436
+
437
+ job.start()
438
+
439
+ clock.tick(1000)
440
+
441
+ clock.restore()
442
+ job.stop()
443
+ expect(callback).toHaveBeenCalledTimes(1)
444
+ })
445
+
446
+ it('should fire on init but not run until started', () => {
447
+ const clock = sinon.useFakeTimers()
448
+ const callback = jest.fn()
449
+
450
+ const job = CronJob.from({
451
+ cronTime: '* * * * * *',
452
+ onTick: callback,
453
+ runOnInit: true,
454
+ })
455
+
456
+ expect(callback).toHaveBeenCalledTimes(1)
457
+
458
+ job.start()
459
+
460
+ clock.tick(3500)
461
+
462
+ clock.restore()
463
+ job.stop()
464
+ expect(callback).toHaveBeenCalledTimes(4)
465
+ })
466
+ })
467
+
468
+ describe('with timezone', () => {
469
+ it('should run a job using cron syntax with a timezone', () => {
470
+ const clock = sinon.useFakeTimers()
471
+ const callback = jest.fn()
472
+ let zone = 'America/Chicago'
473
+ // New Orleans time
474
+ let t = DateTime.local().setZone(zone)
475
+ // Current time
476
+ const d = DateTime.local()
477
+
478
+ // If current time is New Orleans time, switch to Los Angeles..
479
+ if (t.hour === d.hour) {
480
+ zone = 'America/Los_Angeles'
481
+ t = t.setZone(zone)
482
+ }
483
+ expect(d.hour).not.toBe(t.hour)
484
+
485
+ // If t = 59s12m then t.setSeconds(60)
486
+ // becomes 00s13m so we're fine just doing
487
+ // this and no testRun callback.
488
+ t = t.plus({ seconds: 1 })
489
+ // Run a job designed to be executed at a given
490
+ // time in `zone`, making sure that it is a different
491
+ // hour than local time.
492
+ const job = new CronJob(
493
+ `${t.second} ${t.minute} ${t.hour} * * *`,
494
+ callback,
495
+ null,
496
+ true,
497
+ zone,
498
+ )
499
+
500
+ clock.tick(1000)
501
+ clock.restore()
502
+ job.stop()
503
+ expect(callback).toHaveBeenCalledTimes(1)
504
+ })
505
+
506
+ it('should run a job using cron syntax with a "UTC+HH:mm" offset as timezone', () => {
507
+ const clock = sinon.useFakeTimers()
508
+ const callback = jest.fn()
509
+
510
+ // Current time
511
+ const d = DateTime.local()
512
+
513
+ // Current time with zone offset
514
+ let zone = 'UTC+5:30'
515
+ let t = DateTime.local().setZone(zone)
516
+
517
+ // If current offset is UTC+5:30, switch to UTC+6:30..
518
+ if (t.hour === d.hour && t.minute === d.minute) {
519
+ zone = 'UTC+6:30'
520
+ t = t.setZone(zone)
521
+ }
522
+ expect(`${d.hour}:${d.minute}`).not.toBe(`${t.hour}:${t.minute}`)
523
+
524
+ // If t = 59s12m then t.setSeconds(60)
525
+ // becomes 00s13m so we're fine just doing
526
+ // this and no testRun callback.
527
+ t = t.plus({ seconds: 1 })
528
+ // Run a job designed to be executed at a given
529
+ // time in `zone`, making sure that it is a different
530
+ // hour than local time.
531
+ const job = new CronJob(
532
+ `${t.second} ${t.minute} ${t.hour} * * *`,
533
+ callback,
534
+ null,
535
+ true,
536
+ zone,
537
+ )
538
+
539
+ clock.tick(1000)
540
+ clock.restore()
541
+ job.stop()
542
+ expect(callback).toHaveBeenCalledTimes(1)
543
+ })
544
+
545
+ it('should run a job using a date', () => {
546
+ let zone = 'America/Chicago'
547
+ // New Orleans time
548
+ let t = DateTime.local().setZone(zone)
549
+ // Current time
550
+ let d = DateTime.local()
551
+
552
+ // If current time is New Orleans time, switch to Los Angeles..
553
+ if (t.hour === d.hour) {
554
+ zone = 'America/Los_Angeles'
555
+ t = t.setZone(zone)
556
+ }
557
+
558
+ expect(d.hour).not.toBe(t.hour)
559
+ d = d.plus({ seconds: 1 })
560
+ const clock = sinon.useFakeTimers(d.valueOf())
561
+ const callback = jest.fn()
562
+ const job = new CronJob(d.toJSDate(), callback, null, true, zone)
563
+ clock.tick(1000)
564
+ clock.restore()
565
+ job.stop()
566
+ expect(callback).toHaveBeenCalledTimes(1)
567
+ })
568
+
569
+ it('should test if timezone is valid.', () => {
570
+ expect(() => {
571
+ CronJob.from({
572
+ cronTime: '* * * * * *',
573
+ onTick: () => {},
574
+ timeZone: 'fake/timezone',
575
+ })
576
+ }).toThrow()
577
+ })
578
+ })
579
+
580
+ describe('onTick scoping', () => {
581
+ it('should scope onTick to running job', () => {
582
+ const clock = sinon.useFakeTimers()
583
+
584
+ const job = new CronJob(
585
+ '* * * * * *',
586
+ function () {
587
+ expect(job).toBeInstanceOf(CronJob)
588
+ expect(job).toEqual(this)
589
+ },
590
+ null,
591
+ true,
592
+ )
593
+
594
+ clock.tick(1000)
595
+
596
+ clock.restore()
597
+ job.stop()
598
+ })
599
+
600
+ it('should scope onTick to running job using the object constructor', () => {
601
+ const clock = sinon.useFakeTimers()
602
+
603
+ const job = CronJob.from({
604
+ cronTime: '* * * * * *',
605
+ onTick() {
606
+ expect(job).toBeInstanceOf(CronJob)
607
+ expect(job).toEqual(this)
608
+ },
609
+ start: true,
610
+ })
611
+
612
+ clock.tick(1000)
613
+
614
+ clock.restore()
615
+ job.stop()
616
+ })
617
+
618
+ it('should scope onTick to object', () => {
619
+ const clock = sinon.useFakeTimers()
620
+
621
+ const job = new CronJob(
622
+ '* * * * * *',
623
+ function () {
624
+ expect(this.hello).toBe('world')
625
+ expect(job).not.toEqual(this)
626
+ },
627
+ null,
628
+ true,
629
+ null,
630
+ { hello: 'world' },
631
+ )
632
+
633
+ clock.tick(1000)
634
+
635
+ clock.restore()
636
+ job.stop()
637
+ })
638
+
639
+ it('should scope onTick to object using the object constructor', () => {
640
+ const clock = sinon.useFakeTimers()
641
+
642
+ const job = CronJob.from({
643
+ cronTime: '* * * * * *',
644
+ onTick() {
645
+ expect(this.hello).toBe('world')
646
+ expect(job).not.toEqual(this)
647
+ },
648
+ start: true,
649
+ context: { hello: 'world' },
650
+ })
651
+
652
+ clock.tick(1000)
653
+
654
+ clock.restore()
655
+ job.stop()
656
+ })
657
+ })
658
+
659
+ it('should not get into an infinite loop on invalid times', () => {
660
+ expect(() => {
661
+ new CronJob(
662
+ '* 60 * * * *',
663
+ () => {
664
+ expect(true).toBe(true)
665
+ },
666
+ null,
667
+ true,
668
+ )
669
+ }).toThrow()
670
+
671
+ expect(() => {
672
+ new CronJob(
673
+ '* * 24 * * *',
674
+ () => {
675
+ expect(true).toBe(true)
676
+ },
677
+ null,
678
+ true,
679
+ )
680
+ }).toThrow()
681
+ })
682
+
683
+ it('should test start of month', () => {
684
+ const callback = jest.fn()
685
+ const d = new Date('12/31/2014')
686
+ d.setSeconds(59)
687
+ d.setMinutes(59)
688
+ d.setHours(23)
689
+ const clock = sinon.useFakeTimers(d.getTime())
690
+
691
+ const job = new CronJob('0 0 0 1 * *', callback, null, true)
692
+
693
+ clock.tick(1001)
694
+ expect(callback).toHaveBeenCalledTimes(1)
695
+
696
+ clock.tick(2678399001)
697
+ expect(callback).toHaveBeenCalledTimes(1)
698
+
699
+ clock.tick(2678400001) // jump over 2 firsts
700
+ clock.restore()
701
+ job.stop()
702
+
703
+ expect(callback).toHaveBeenCalledTimes(3)
704
+ })
705
+
706
+ it('should not fire if time was adjusted back', () => {
707
+ const callback = jest.fn()
708
+ const clock = sinon.useFakeTimers({
709
+ toFake: ['setTimeout'],
710
+ })
711
+
712
+ const job = new CronJob('0 * * * * *', callback, null, true)
713
+
714
+ clock.tick(60000)
715
+ expect(callback).toHaveBeenCalledTimes(0)
716
+
717
+ clock.restore()
718
+ job.stop()
719
+ })
720
+
721
+ it('should run every day', () => {
722
+ const callback = jest.fn()
723
+ const d = new Date('12/31/2014')
724
+ d.setSeconds(59)
725
+ d.setMinutes(59)
726
+ d.setHours(23)
727
+ const clock = sinon.useFakeTimers(d.getTime())
728
+
729
+ const job = CronJob.from({
730
+ cronTime: '59 59 3 * * *',
731
+ onTick: callback,
732
+ start: true,
733
+ timeZone: 'America/Los_Angeles',
734
+ })
735
+
736
+ const twoWeeks = 14 * 24 * 60 * 60 * 1000
737
+ clock.tick(twoWeeks)
738
+
739
+ clock.restore()
740
+ job.stop()
741
+ expect(callback).toHaveBeenCalledTimes(14)
742
+ })
743
+
744
+ it('should run every 2 hours between hours', () => {
745
+ const callback = jest.fn()
746
+ const d = new Date('12/31/2014')
747
+ d.setSeconds(0)
748
+ d.setMinutes(0)
749
+ d.setHours(0)
750
+ const clock = sinon.useFakeTimers(d.getTime())
751
+
752
+ const job = CronJob.from({
753
+ cronTime: '0 2-6/2 * * * *',
754
+ onTick: callback,
755
+ start: true,
756
+ })
757
+
758
+ clock.tick(2 * 60 * 1000)
759
+ expect(callback).toHaveBeenCalledTimes(1)
760
+ clock.tick(2 * 60 * 1000)
761
+ expect(callback).toHaveBeenCalledTimes(2)
762
+ clock.tick(2 * 60 * 1000)
763
+ expect(callback).toHaveBeenCalledTimes(3)
764
+ clock.tick(2 * 60 * 1000)
765
+
766
+ clock.restore()
767
+ job.stop()
768
+ expect(callback).toHaveBeenCalledTimes(3)
769
+ })
770
+
771
+ it('should run every minute', () => {
772
+ const callback = jest.fn()
773
+ const d = new Date('12/31/2014')
774
+ d.setSeconds(0)
775
+ d.setMinutes(0)
776
+ d.setHours(0)
777
+ const clock = sinon.useFakeTimers(d.getTime())
778
+
779
+ const job = CronJob.from({
780
+ cronTime: '00 * * * * *',
781
+ onTick: callback,
782
+ start: true,
783
+ })
784
+
785
+ clock.tick(60 * 1000)
786
+ expect(callback).toHaveBeenCalledTimes(1)
787
+ clock.tick(60 * 1000)
788
+ expect(callback).toHaveBeenCalledTimes(2)
789
+
790
+ clock.restore()
791
+ job.stop()
792
+ expect(callback).toHaveBeenCalledTimes(2)
793
+ })
794
+
795
+ it('should run every day at 12:30', () => {
796
+ const callback = jest.fn()
797
+ const d = new Date('12/31/2014')
798
+ d.setSeconds(0)
799
+ d.setMinutes(0)
800
+ d.setHours(0)
801
+ const clock = sinon.useFakeTimers(d.getTime())
802
+
803
+ const job = CronJob.from({
804
+ cronTime: '00 30 00 * * *',
805
+ onTick: callback,
806
+ start: true,
807
+ })
808
+
809
+ const day = 24 * 60 * 60 * 1000
810
+ clock.tick(day)
811
+ expect(callback).toHaveBeenCalledTimes(1)
812
+ clock.tick(day)
813
+ expect(callback).toHaveBeenCalledTimes(2)
814
+ clock.tick(day)
815
+ expect(callback).toHaveBeenCalledTimes(3)
816
+ clock.tick(5 * day)
817
+
818
+ clock.restore()
819
+ job.stop()
820
+ expect(callback).toHaveBeenCalledTimes(8)
821
+ })
822
+
823
+ it('should trigger onTick at midnight', () => {
824
+ const callback = jest.fn()
825
+ const d = new Date('12/31/2014')
826
+ d.setSeconds(59)
827
+ d.setMinutes(59)
828
+ d.setHours(23)
829
+ const clock = sinon.useFakeTimers(d.getTime())
830
+
831
+ const job = CronJob.from({
832
+ cronTime: '00 * * * * *',
833
+ onTick: callback,
834
+ start: true,
835
+ timeZone: 'UTC',
836
+ })
837
+
838
+ clock.tick(1000) // move clock 1 second
839
+ expect(callback).toHaveBeenCalledTimes(1)
840
+
841
+ clock.restore()
842
+ job.stop()
843
+ expect(callback).toHaveBeenCalledTimes(1)
844
+ })
845
+
846
+ it('should run every day UTC', () => {
847
+ const callback = jest.fn()
848
+ const d = new Date('12/31/2014')
849
+ d.setSeconds(0)
850
+ d.setMinutes(0)
851
+ d.setHours(0)
852
+ const clock = sinon.useFakeTimers(d.getTime())
853
+
854
+ const job = CronJob.from({
855
+ cronTime: '00 30 00 * * *',
856
+ onTick: callback,
857
+ start: true,
858
+ timeZone: 'UTC',
859
+ })
860
+
861
+ const day = 24 * 60 * 60 * 1000
862
+ clock.tick(day)
863
+ expect(callback).toHaveBeenCalledTimes(1)
864
+ clock.tick(day)
865
+ expect(callback).toHaveBeenCalledTimes(2)
866
+ clock.tick(day)
867
+ expect(callback).toHaveBeenCalledTimes(3)
868
+ clock.tick(5 * day)
869
+
870
+ clock.restore()
871
+ job.stop()
872
+ expect(callback).toHaveBeenCalledTimes(8)
873
+ })
874
+
875
+ // from https://github.com/kelektiv/node-cron/issues/180#issuecomment-154108131
876
+ it('should run once not double', () => {
877
+ const callback = jest.fn()
878
+ const d = new Date(2015, 1, 1, 1, 1, 41, 0)
879
+ const clock = sinon.useFakeTimers(d.getTime())
880
+
881
+ const job = CronJob.from({
882
+ cronTime: '* * * * *',
883
+ onTick: callback,
884
+ start: true,
885
+ })
886
+
887
+ const minute = 60 * 1000
888
+ clock.tick(minute)
889
+ expect(callback).toHaveBeenCalledTimes(1)
890
+ clock.restore()
891
+ job.stop()
892
+ expect(callback).toHaveBeenCalledTimes(1)
893
+ })
894
+
895
+ /**
896
+ * maximum match interval is 8 years:
897
+ * crontab has '* * 29 2 *' and we are on 1 March 2096:
898
+ * next matching time will be 29 February 2104
899
+ * source: https://github.com/cronie-crond/cronie/blob/0d669551680f733a4bdd6bab082a0b3d6d7f089c/src/cronnext.c#L401-L403
900
+ */
901
+ it('should work correctly for max match interval', () => {
902
+ const callback = jest.fn()
903
+ const d = new Date(2096, 2, 1)
904
+ const clock = sinon.useFakeTimers(d.getTime())
905
+
906
+ const job = CronJob.from({
907
+ cronTime: ' * * 29 2 *',
908
+ onTick: callback,
909
+ start: true,
910
+ })
911
+
912
+ // 7 years, 11 months and 27 days
913
+ const almostEightYears = 2919 * 24 * 60 * 60 * 1000
914
+ clock.tick(almostEightYears)
915
+ expect(callback).toHaveBeenCalledTimes(0)
916
+
917
+ // tick by 1 day
918
+ clock.tick(24 * 60 * 60 * 1000)
919
+ clock.restore()
920
+ job.stop()
921
+ expect(callback).toHaveBeenCalledTimes(1)
922
+ })
923
+
924
+ describe('with utcOffset', () => {
925
+ it('should run a job using cron syntax with number format utcOffset', () => {
926
+ const clock = sinon.useFakeTimers()
927
+ const callback = jest.fn()
928
+ // Current time
929
+ const t = DateTime.local()
930
+ // UTC Offset decreased by an hour
931
+ const utcOffset = t.offset - 60
932
+
933
+ const job = new CronJob(
934
+ `${t.second} ${t.minute} ${t.hour} * * *`,
935
+ callback,
936
+ null,
937
+ true,
938
+ null,
939
+ null,
940
+ null,
941
+ utcOffset,
942
+ )
943
+
944
+ // tick 1 sec before an hour
945
+ clock.tick(1000 * 60 * 60 - 1)
946
+ expect(callback).toHaveBeenCalledTimes(0)
947
+
948
+ clock.tick(1)
949
+ clock.restore()
950
+ job.stop()
951
+ expect(callback).toHaveBeenCalledTimes(1)
952
+ })
953
+
954
+ it('should run a job using cron syntax with numeric format utcOffset with minute support', () => {
955
+ const clock = sinon.useFakeTimers()
956
+ const callback = jest.fn()
957
+ // Current time
958
+ const t = DateTime.local()
959
+
960
+ // UTC Offset decreased by 45 minutes
961
+ const utcOffset = t.offset - 45
962
+ const job = new CronJob(
963
+ `${t.second} ${t.minute} ${t.hour} * * *`,
964
+ callback,
965
+ null,
966
+ true,
967
+ null,
968
+ null,
969
+ null,
970
+ utcOffset,
971
+ )
972
+
973
+ // tick 1 sec before 45 minutes
974
+ clock.tick(1000 * 45 * 60 - 1)
975
+ expect(callback).toHaveBeenCalledTimes(0)
976
+
977
+ clock.tick(1)
978
+ clock.restore()
979
+ job.stop()
980
+ expect(callback).toHaveBeenCalledTimes(1)
981
+ })
982
+
983
+ it('should run a job using cron syntax with number format utcOffset that is 0', () => {
984
+ const clock = sinon.useFakeTimers()
985
+ const callback = jest.fn()
986
+
987
+ const job = new CronJob(
988
+ '* * * * * *',
989
+ callback,
990
+ null,
991
+ true,
992
+ null,
993
+ null,
994
+ null,
995
+ 0,
996
+ )
997
+
998
+ clock.tick(999)
999
+ expect(callback).toHaveBeenCalledTimes(0)
1000
+
1001
+ clock.tick(1)
1002
+ clock.restore()
1003
+ job.stop()
1004
+ expect(callback).toHaveBeenCalledTimes(1)
1005
+ })
1006
+
1007
+ it('should be able to detect out of range days of month', () => {
1008
+ expect(() => {
1009
+ new CronTime('* * 32 FEB *')
1010
+ }).toThrow()
1011
+ })
1012
+ })
1013
+
1014
+ describe('setTime', () => {
1015
+ it('should start, change time, start again', () => {
1016
+ const callback = jest.fn()
1017
+ const clock = sinon.useFakeTimers()
1018
+
1019
+ const job = new CronJob('* * * * * *', callback)
1020
+
1021
+ job.start()
1022
+ clock.tick(1000)
1023
+
1024
+ const time = new CronTime('*/2 * * * * *')
1025
+ job.setTime(time)
1026
+
1027
+ clock.tick(4000)
1028
+
1029
+ clock.restore()
1030
+ job.stop()
1031
+ expect(callback).toHaveBeenCalledTimes(3)
1032
+ })
1033
+
1034
+ it('should start, stop, change time, not start again', () => {
1035
+ const callback = jest.fn()
1036
+ const clock = sinon.useFakeTimers()
1037
+
1038
+ const job = new CronJob('* * * * * *', callback)
1039
+
1040
+ job.start()
1041
+ clock.tick(1000)
1042
+
1043
+ job.stop()
1044
+ const time = new CronTime('*/2 * * * * *')
1045
+ job.setTime(time)
1046
+
1047
+ clock.tick(4000)
1048
+
1049
+ clock.restore()
1050
+ job.stop()
1051
+ expect(callback).toHaveBeenCalledTimes(1)
1052
+ })
1053
+
1054
+ it('should setTime with invalid object', () => {
1055
+ const callback = jest.fn()
1056
+ const job = new CronJob('* * * * * *', callback)
1057
+ expect(() => {
1058
+ // @ts-expect-error time parameter cannot be undefined
1059
+ job.setTime(undefined)
1060
+ }).toThrow()
1061
+ })
1062
+
1063
+ it('should start, change time, exception', () => {
1064
+ const callback = jest.fn()
1065
+ const clock = sinon.useFakeTimers()
1066
+
1067
+ const job = new CronJob('* * * * * *', callback)
1068
+
1069
+ const time = new Date()
1070
+ job.start()
1071
+
1072
+ clock.tick(1000)
1073
+
1074
+ expect(() => {
1075
+ // @ts-expect-error time parameter but be an instance of CronTime
1076
+ job.setTime(time)
1077
+ }).toThrow()
1078
+
1079
+ clock.restore()
1080
+ job.stop()
1081
+ expect(callback).toHaveBeenCalledTimes(1)
1082
+ })
1083
+
1084
+ it('should create recurring job, setTime with actual date, start and run once (#739)', () => {
1085
+ const callback = jest.fn()
1086
+ const clock = sinon.useFakeTimers()
1087
+
1088
+ const job = new CronJob('0 0 20 * * *', callback)
1089
+
1090
+ const startDate = new Date(Date.now() + 5000)
1091
+ job.setTime(new CronTime(startDate))
1092
+
1093
+ job.start()
1094
+
1095
+ clock.tick(5000)
1096
+
1097
+ expect(callback).toHaveBeenCalledTimes(1)
1098
+
1099
+ clock.tick(60000)
1100
+
1101
+ clock.restore()
1102
+
1103
+ expect(callback).toHaveBeenCalledTimes(1)
1104
+ expect(job.running).toBe(false)
1105
+ })
1106
+ })
1107
+
1108
+ describe('nextDate(s)', () => {
1109
+ it('should give the next date to run at', () => {
1110
+ const callback = jest.fn()
1111
+ const clock = sinon.useFakeTimers()
1112
+ const job = new CronJob('* * * * * *', callback)
1113
+ const d = Date.now()
1114
+
1115
+ expect(job.nextDate().toMillis()).toEqual(d + 1000)
1116
+
1117
+ clock.restore()
1118
+ })
1119
+
1120
+ it('should give the next 5 dates to run at', () => {
1121
+ const callback = jest.fn()
1122
+ const clock = sinon.useFakeTimers()
1123
+ const job = new CronJob('* * * * * *', callback)
1124
+ const d = Date.now()
1125
+
1126
+ expect(job.nextDates(5).map(d => d.toMillis())).toEqual([
1127
+ d + 1000,
1128
+ d + 2000,
1129
+ d + 3000,
1130
+ d + 4000,
1131
+ d + 5000,
1132
+ ])
1133
+
1134
+ clock.restore()
1135
+ })
1136
+
1137
+ it('should give an empty array when called without argument', () => {
1138
+ const callback = jest.fn()
1139
+ const clock = sinon.useFakeTimers()
1140
+ const job = new CronJob('* * * * * *', callback)
1141
+
1142
+ expect(job.nextDates()).toHaveLength(0)
1143
+
1144
+ clock.restore()
1145
+ })
1146
+ })
1147
+
1148
+ it('should automatically setup a new timeout if we roll past the max timeout delay', () => {
1149
+ const callback = jest.fn()
1150
+ const clock = sinon.useFakeTimers()
1151
+ const d = new Date()
1152
+ d.setMilliseconds(2147485647 * 2) // MAXDELAY in `job.js` + 2000.
1153
+ const job = new CronJob(d, callback)
1154
+ job.start()
1155
+ clock.tick(2147483648)
1156
+ expect(callback).toHaveBeenCalledTimes(0)
1157
+ clock.tick(2147489648)
1158
+ expect(callback).toHaveBeenCalledTimes(1)
1159
+ job.stop()
1160
+ clock.restore()
1161
+ })
1162
+
1163
+ it('should give the correct last execution date', () => {
1164
+ const callback = jest.fn()
1165
+ const clock = sinon.useFakeTimers()
1166
+ const job = new CronJob('* * * * * *', callback)
1167
+ job.start()
1168
+ clock.tick(1000)
1169
+ expect(callback).toHaveBeenCalledTimes(1)
1170
+ expect(job.lastDate()?.getTime()).toBe(1000)
1171
+ job.stop()
1172
+ clock.restore()
1173
+ })
1174
+
1175
+ it('should give the correct last execution date for intervals greater than 25 days (#710)', () => {
1176
+ const callback = jest.fn()
1177
+ const clock = sinon.useFakeTimers()
1178
+
1179
+ const job = new CronJob('0 0 0 1 * *', callback) // At 00:00 on day-of-month 1.
1180
+ job.start()
1181
+
1182
+ // tick one tick before nextDate()
1183
+ clock.tick(job.nextDate().toMillis() - 1)
1184
+
1185
+ expect(callback).toHaveBeenCalledTimes(0)
1186
+ expect(job.lastDate()?.getTime()).toBeUndefined()
1187
+
1188
+ job.stop()
1189
+ clock.restore()
1190
+ })
1191
+
1192
+ it('should throw when providing both exclusive parameters timeZone and utcOffset', () => {
1193
+ expect(() => {
1194
+ // @ts-expect-error testing runtime exception
1195
+ new CronJob(
1196
+ `* * * * *`,
1197
+ () => {},
1198
+ null,
1199
+ true,
1200
+ 'America/Chicago',
1201
+ null,
1202
+ null,
1203
+ 120,
1204
+ )
1205
+ }).toThrow()
1206
+ })
1207
+
1208
+ it('should throw when providing both exclusive parameters timeZone and utcOffset using the object constructor', () => {
1209
+ expect(() => {
1210
+ // @ts-expect-error testing runtime exception
1211
+ CronJob.from({
1212
+ cronTime: '* * * * * *',
1213
+ onTick() {},
1214
+ timeZone: 'America/Chicago',
1215
+ utcOffset: 120,
1216
+ })
1217
+ }).toThrow()
1218
+ })
1219
+
1220
+ it('should support async callback', () => {
1221
+ const clock = sinon.useFakeTimers()
1222
+ const callback = jest.fn()
1223
+ const job = new CronJob(
1224
+ '* * * * * *',
1225
+ async () => {
1226
+ await new Promise<void>((resolve) => {
1227
+ setTimeout(() => {
1228
+ callback()
1229
+ resolve()
1230
+ }, 500)
1231
+ })
1232
+ },
1233
+ null,
1234
+ true,
1235
+ )
1236
+ clock.tick(1500)
1237
+ job.stop()
1238
+ clock.restore()
1239
+ expect(callback).toHaveBeenCalledTimes(1)
1240
+ })
1241
+ })