0z2i6v3u5t 1.0.0

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 (211) hide show
  1. package/.devcontainer/devcontainer.json +4 -0
  2. package/.devcontainer/setup.sh +11 -0
  3. package/.dockerignore +2 -0
  4. package/.github/CONTRIBUTING.md +52 -0
  5. package/.github/FUNDING.yml +3 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +43 -0
  9. package/.github/dependabot.yml +17 -0
  10. package/.github/workflows/codeql.yml +76 -0
  11. package/.github/workflows/publish_docs.yml +25 -0
  12. package/.github/workflows/test.yml +78 -0
  13. package/.nvmrc +1 -0
  14. package/.prettierignore +1 -0
  15. package/.prettierrc +1 -0
  16. package/.vscode/launch.json +42 -0
  17. package/CODE_OF_CONDUCT.md +76 -0
  18. package/Dockerfile +17 -0
  19. package/LICENSE +21 -0
  20. package/README.md +3 -0
  21. package/SECURITY.md +5 -0
  22. package/__tests__/actions/cacheTest.ts +58 -0
  23. package/__tests__/actions/randomNumber.ts +26 -0
  24. package/__tests__/actions/recursiveAction.ts +16 -0
  25. package/__tests__/actions/sleepTest.ts +24 -0
  26. package/__tests__/actions/status.ts +17 -0
  27. package/__tests__/actions/swagger.ts +76 -0
  28. package/__tests__/actions/validationTest.ts +63 -0
  29. package/__tests__/cli/cli.ts +126 -0
  30. package/__tests__/core/api.ts +632 -0
  31. package/__tests__/core/cache.ts +400 -0
  32. package/__tests__/core/chatRoom.ts +589 -0
  33. package/__tests__/core/cli.ts +349 -0
  34. package/__tests__/core/cluster.ts +132 -0
  35. package/__tests__/core/config.ts +78 -0
  36. package/__tests__/core/errors.ts +112 -0
  37. package/__tests__/core/log.ts +23 -0
  38. package/__tests__/core/middleware.ts +427 -0
  39. package/__tests__/core/plugins/partialPlugin.ts +94 -0
  40. package/__tests__/core/plugins/withPlugin.ts +88 -0
  41. package/__tests__/core/plugins/withoutPlugin.ts +81 -0
  42. package/__tests__/core/process.ts +42 -0
  43. package/__tests__/core/specHelper.ts +330 -0
  44. package/__tests__/core/staticFile/compression.ts +99 -0
  45. package/__tests__/core/staticFile/staticFile.ts +180 -0
  46. package/__tests__/core/tasks/customQueueFunction.ts +67 -0
  47. package/__tests__/core/tasks/fullWorkerFlow.ts +199 -0
  48. package/__tests__/core/tasks/tasks.ts +605 -0
  49. package/__tests__/integration/browser.ts +133 -0
  50. package/__tests__/integration/ioredis-mock.ts +194 -0
  51. package/__tests__/integration/sendBuffer.ts +97 -0
  52. package/__tests__/integration/sendFile.ts +24 -0
  53. package/__tests__/integration/sharedFingerprint.ts +82 -0
  54. package/__tests__/integration/taskFlow.ts +110 -0
  55. package/__tests__/jest.ts +5 -0
  56. package/__tests__/modules/action.ts +103 -0
  57. package/__tests__/modules/config.ts +19 -0
  58. package/__tests__/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +24 -0
  59. package/__tests__/servers/web/allowedRequestHosts.ts +88 -0
  60. package/__tests__/servers/web/enableMultiples.ts +83 -0
  61. package/__tests__/servers/web/fileUpload.ts +79 -0
  62. package/__tests__/servers/web/jsonp.ts +57 -0
  63. package/__tests__/servers/web/nonMultiples.ts +83 -0
  64. package/__tests__/servers/web/rawBody.ts +208 -0
  65. package/__tests__/servers/web/returnErrorCodes.ts +55 -0
  66. package/__tests__/servers/web/routes/deepRoutes.ts +96 -0
  67. package/__tests__/servers/web/routes/routes.ts +579 -0
  68. package/__tests__/servers/web/routes/veryDeepRoutes.ts +92 -0
  69. package/__tests__/servers/web/web.ts +1031 -0
  70. package/__tests__/servers/websocket.ts +795 -0
  71. package/__tests__/tasks/runAction.ts +37 -0
  72. package/__tests__/template.ts.example +20 -0
  73. package/__tests__/testCliCommands/hello.ts +44 -0
  74. package/__tests__/testPlugin/public/plugin.html +1 -0
  75. package/__tests__/testPlugin/src/actions/pluginAction.ts +14 -0
  76. package/__tests__/testPlugin/src/bin/hello.ts +22 -0
  77. package/__tests__/testPlugin/src/initializers/pluginInitializer.ts +17 -0
  78. package/__tests__/testPlugin/src/tasks/pluginTask.ts +15 -0
  79. package/__tests__/testPlugin/tsconfig.json +10 -0
  80. package/__tests__/utils/utils.ts +492 -0
  81. package/app.json +23 -0
  82. package/bin/deploy-docs +39 -0
  83. package/client/ActionheroWebsocketClient.js +277 -0
  84. package/docker-compose.yml +73 -0
  85. package/package.json +24 -0
  86. package/public/chat.html +194 -0
  87. package/public/css/cosmo.css +12 -0
  88. package/public/favicon.ico +0 -0
  89. package/public/index.html +115 -0
  90. package/public/javascript/.gitkeep +0 -0
  91. package/public/linkedSession.html +80 -0
  92. package/public/logo/actionhero-small.png +0 -0
  93. package/public/logo/actionhero.png +0 -0
  94. package/public/pixel.gif +0 -0
  95. package/public/simple.html +2 -0
  96. package/public/swagger.html +32 -0
  97. package/public/websocketLoadTest.html +322 -0
  98. package/src/actions/cacheTest.ts +58 -0
  99. package/src/actions/createChatRoom.ts +20 -0
  100. package/src/actions/randomNumber.ts +17 -0
  101. package/src/actions/recursiveAction.ts +13 -0
  102. package/src/actions/sendFile.ts +12 -0
  103. package/src/actions/sleepTest.ts +40 -0
  104. package/src/actions/status.ts +73 -0
  105. package/src/actions/swagger.ts +155 -0
  106. package/src/actions/validationTest.ts +36 -0
  107. package/src/bin/actionhero.ts +225 -0
  108. package/src/bin/methods/actions/list.ts +30 -0
  109. package/src/bin/methods/console.ts +26 -0
  110. package/src/bin/methods/generate/action.ts +58 -0
  111. package/src/bin/methods/generate/cli.ts +51 -0
  112. package/src/bin/methods/generate/initializer.ts +54 -0
  113. package/src/bin/methods/generate/plugin.ts +57 -0
  114. package/src/bin/methods/generate/server.ts +38 -0
  115. package/src/bin/methods/generate/task.ts +68 -0
  116. package/src/bin/methods/generate.ts +176 -0
  117. package/src/bin/methods/task/enqueue.ts +35 -0
  118. package/src/classes/action.ts +98 -0
  119. package/src/classes/actionProcessor.ts +463 -0
  120. package/src/classes/api.ts +51 -0
  121. package/src/classes/cli.ts +67 -0
  122. package/src/classes/config.ts +15 -0
  123. package/src/classes/connection.ts +321 -0
  124. package/src/classes/exceptionReporter.ts +9 -0
  125. package/src/classes/initializer.ts +59 -0
  126. package/src/classes/initializers.ts +5 -0
  127. package/src/classes/input.ts +9 -0
  128. package/src/classes/inputs.ts +34 -0
  129. package/src/classes/process/actionheroVersion.ts +15 -0
  130. package/src/classes/process/env.ts +16 -0
  131. package/src/classes/process/id.ts +34 -0
  132. package/src/classes/process/pid.ts +32 -0
  133. package/src/classes/process/projectRoot.ts +16 -0
  134. package/src/classes/process/typescript.ts +47 -0
  135. package/src/classes/process.ts +479 -0
  136. package/src/classes/server.ts +251 -0
  137. package/src/classes/task.ts +87 -0
  138. package/src/config/api.ts +107 -0
  139. package/src/config/errors.ts +162 -0
  140. package/src/config/logger.ts +113 -0
  141. package/src/config/plugins.ts +37 -0
  142. package/src/config/redis.ts +78 -0
  143. package/src/config/routes.ts +44 -0
  144. package/src/config/tasks.ts +84 -0
  145. package/src/config/web.ts +136 -0
  146. package/src/config/websocket.ts +62 -0
  147. package/src/index.ts +46 -0
  148. package/src/initializers/actions.ts +125 -0
  149. package/src/initializers/chatRoom.ts +214 -0
  150. package/src/initializers/connections.ts +124 -0
  151. package/src/initializers/exceptions.ts +155 -0
  152. package/src/initializers/params.ts +52 -0
  153. package/src/initializers/redis.ts +191 -0
  154. package/src/initializers/resque.ts +248 -0
  155. package/src/initializers/routes.ts +229 -0
  156. package/src/initializers/servers.ts +134 -0
  157. package/src/initializers/specHelper.ts +195 -0
  158. package/src/initializers/staticFile.ts +253 -0
  159. package/src/initializers/tasks.ts +188 -0
  160. package/src/modules/action.ts +89 -0
  161. package/src/modules/cache.ts +326 -0
  162. package/src/modules/chatRoom.ts +321 -0
  163. package/src/modules/config.ts +246 -0
  164. package/src/modules/log.ts +62 -0
  165. package/src/modules/redis.ts +93 -0
  166. package/src/modules/route.ts +59 -0
  167. package/src/modules/specHelper.ts +182 -0
  168. package/src/modules/task.ts +527 -0
  169. package/src/modules/utils/argv.ts +3 -0
  170. package/src/modules/utils/arrayStartingMatch.ts +21 -0
  171. package/src/modules/utils/arrayUnique.ts +15 -0
  172. package/src/modules/utils/collapseObjectToArray.ts +33 -0
  173. package/src/modules/utils/deepCopy.ts +3 -0
  174. package/src/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +19 -0
  175. package/src/modules/utils/eventLoopDelay.ts +34 -0
  176. package/src/modules/utils/fileUtils.ts +119 -0
  177. package/src/modules/utils/filterObjectForLogging.ts +51 -0
  178. package/src/modules/utils/filterResponseForLogging.ts +53 -0
  179. package/src/modules/utils/getExternalIPAddress.ts +17 -0
  180. package/src/modules/utils/hashMerge.ts +63 -0
  181. package/src/modules/utils/isPlainObject.ts +45 -0
  182. package/src/modules/utils/isRunning.ts +7 -0
  183. package/src/modules/utils/parseCookies.ts +20 -0
  184. package/src/modules/utils/parseHeadersForClientAddress.ts +53 -0
  185. package/src/modules/utils/parseIPv6URI.ts +24 -0
  186. package/src/modules/utils/replaceDistWithSrc.ts +9 -0
  187. package/src/modules/utils/safeGlob.ts +6 -0
  188. package/src/modules/utils/sleep.ts +8 -0
  189. package/src/modules/utils/sortGlobalMiddleware.ts +17 -0
  190. package/src/modules/utils/sourceRelativeLinkPath.ts +29 -0
  191. package/src/modules/utils.ts +66 -0
  192. package/src/server.ts +20 -0
  193. package/src/servers/web.ts +894 -0
  194. package/src/servers/websocket.ts +304 -0
  195. package/src/tasks/runAction.ts +29 -0
  196. package/tea.yaml +9 -0
  197. package/templates/README.md.template +17 -0
  198. package/templates/action.ts.template +15 -0
  199. package/templates/boot.js.template +9 -0
  200. package/templates/cli.ts.template +15 -0
  201. package/templates/gitignore.template +23 -0
  202. package/templates/initializer.ts.template +17 -0
  203. package/templates/package-plugin.json.template +12 -0
  204. package/templates/package.json.template +45 -0
  205. package/templates/projectMap.txt +39 -0
  206. package/templates/projectServer.ts.template +20 -0
  207. package/templates/server.ts.template +37 -0
  208. package/templates/task.ts.template +16 -0
  209. package/templates/test/action.ts.template +13 -0
  210. package/templates/test/task.ts.template +20 -0
  211. package/tsconfig.json +11 -0
@@ -0,0 +1,605 @@
1
+ import {
2
+ api,
3
+ Process,
4
+ Task,
5
+ utils,
6
+ config,
7
+ task,
8
+ specHelper,
9
+ } from "../../../src/index";
10
+ import { sleep } from "../../../src/modules/utils/sleep";
11
+
12
+ const actionhero = new Process();
13
+
14
+ let taskOutput: any[] = [];
15
+ const queue = "testQueue";
16
+
17
+ describe("Core: Tasks", () => {
18
+ beforeAll(async () => {
19
+ await actionhero.start();
20
+
21
+ api.resque.multiWorker.options.minTaskProcessors = 1;
22
+ api.resque.multiWorker.options.maxTaskProcessors = 1;
23
+ api.resque.multiWorker.options.connection!.redis!.setMaxListeners(100);
24
+
25
+ class RegularTask extends Task {
26
+ constructor() {
27
+ super();
28
+ this.name = "regular";
29
+ this.description = "task: regular";
30
+ this.queue = queue;
31
+ this.frequency = 0;
32
+ }
33
+
34
+ run(params: Record<string, any>) {
35
+ taskOutput.push(params.word);
36
+ return params.word;
37
+ }
38
+ }
39
+
40
+ class PeriodicTask extends Task {
41
+ constructor() {
42
+ super();
43
+ this.name = "periodicTask";
44
+ this.description = "task: periodicTask";
45
+ this.queue = queue;
46
+ this.frequency = 100;
47
+ }
48
+
49
+ async run() {
50
+ await sleep(10);
51
+ taskOutput.push("periodicTask");
52
+ return "periodicTask";
53
+ }
54
+ }
55
+
56
+ class SlowTask extends Task {
57
+ constructor() {
58
+ super();
59
+ this.name = "slowTask";
60
+ this.description = "task: slowTask";
61
+ this.queue = queue;
62
+ this.frequency = 0;
63
+ }
64
+
65
+ async run() {
66
+ await utils.sleep(5000);
67
+ taskOutput.push("slowTask");
68
+ return "slowTask";
69
+ }
70
+ }
71
+
72
+ class TaskWithInputs extends Task {
73
+ constructor() {
74
+ super();
75
+ this.name = "taskWithInputs";
76
+ this.description = "task: taskWithInputs";
77
+ this.queue = queue;
78
+ this.frequency = 0;
79
+ this.inputs = {
80
+ a: { required: true, default: 1 },
81
+ b: {
82
+ required: true,
83
+ default: () => {
84
+ return 2;
85
+ },
86
+ },
87
+ c: {
88
+ required: true,
89
+ validator: (p: unknown) => {
90
+ if (p !== 3) {
91
+ throw new Error("nope");
92
+ }
93
+ },
94
+ },
95
+ d: {
96
+ required: true,
97
+ validator: (p: unknown) => {
98
+ if (p !== 4) {
99
+ return false;
100
+ }
101
+ },
102
+ },
103
+ };
104
+ }
105
+
106
+ async run() {
107
+ taskOutput.push("taskWithInputs");
108
+ return "taskWithInputs";
109
+ }
110
+ }
111
+
112
+ api.tasks.tasks.regularTask = new RegularTask();
113
+ api.tasks.tasks.periodicTask = new PeriodicTask();
114
+ api.tasks.tasks.slowTask = new SlowTask();
115
+ api.tasks.tasks.taskWithInputs = new TaskWithInputs();
116
+
117
+ api.tasks.jobs.regularTask = api.tasks.jobWrapper("regularTask");
118
+ api.tasks.jobs.periodicTask = api.tasks.jobWrapper("periodicTask");
119
+ api.tasks.jobs.slowTask = api.tasks.jobWrapper("slowTask");
120
+ api.tasks.jobs.taskWithInputs = api.tasks.jobWrapper("taskWithInputs");
121
+ });
122
+
123
+ afterAll(async () => {
124
+ delete api.tasks.tasks.regularTask;
125
+ delete api.tasks.tasks.periodicTask;
126
+ delete api.tasks.tasks.slowTask;
127
+ delete api.tasks.tasks.taskWithInputs;
128
+ delete api.tasks.jobs.regularTask;
129
+ delete api.tasks.jobs.periodicTask;
130
+ delete api.tasks.jobs.slowTask;
131
+ delete api.tasks.jobs.taskWithInputs;
132
+
133
+ config.tasks!.queues = [];
134
+
135
+ api.resque.multiWorker.options.minTaskProcessors = 0;
136
+ api.resque.multiWorker.options.maxTaskProcessors = 0;
137
+
138
+ await actionhero.stop();
139
+ });
140
+
141
+ beforeEach(async () => {
142
+ taskOutput = [];
143
+ await api.resque.queue.connection.redis.flushdb();
144
+ });
145
+
146
+ afterEach(async () => {
147
+ await api.resque.stopScheduler();
148
+ await api.resque.stopMultiWorker();
149
+ });
150
+
151
+ test("validates tasks", () => {
152
+ if (api.tasks.tasks.regularTask.validate) {
153
+ api.tasks.tasks.regularTask.validate();
154
+ }
155
+ });
156
+
157
+ test("a bad task definition causes an exception", () => {
158
+ class BadTask extends Task {
159
+ constructor() {
160
+ super();
161
+ // this.name = 'noName'
162
+ this.description = "no name";
163
+ this.queue = queue;
164
+ this.frequency = 0;
165
+ }
166
+
167
+ async run() {}
168
+ }
169
+
170
+ const task = new BadTask();
171
+
172
+ try {
173
+ if (task.validate) task.validate();
174
+ throw new Error("should not get here");
175
+ } catch (error) {
176
+ expect(error.toString()).toMatch(/name is required for this task/);
177
+ }
178
+ });
179
+
180
+ test("setup worked", () => {
181
+ expect(
182
+ Object.keys(api.tasks.tasks).filter((k) => k !== "test-task"), // test-task might be in scope from integration test
183
+ ).toHaveLength(4 + 1);
184
+ });
185
+
186
+ test("all queues should start empty", async () => {
187
+ const length = await api.resque.queue.length("default");
188
+ expect(length).toEqual(0);
189
+ });
190
+
191
+ test("can run a task manually", async () => {
192
+ const response = await specHelper.runTask("regularTask", {
193
+ word: "theWord",
194
+ });
195
+ expect(response).toEqual("theWord");
196
+ expect(taskOutput[0]).toEqual("theWord");
197
+ });
198
+
199
+ test("can run a task fully", async () => {
200
+ const response = await specHelper.runFullTask("regularTask", {
201
+ word: "theWord",
202
+ });
203
+ expect(response).toEqual("theWord");
204
+ expect(taskOutput[0]).toEqual("theWord");
205
+ });
206
+
207
+ test("it can detect that a task was enqueued to run now", async () => {
208
+ await task.enqueue("regularTask", { word: "testing" });
209
+ const found = await specHelper.findEnqueuedTasks("regularTask");
210
+ expect(found.length).toEqual(1);
211
+ expect(found[0].args[0].word).toEqual("testing");
212
+ expect(found[0].timestamp).toBeNull();
213
+ });
214
+
215
+ test("it can detect that a task was enqueued to run in the future", async () => {
216
+ await task.enqueueIn(1000, "regularTask", { word: "testing" });
217
+ const found = await specHelper.findEnqueuedTasks("regularTask");
218
+ expect(found.length).toEqual(1);
219
+ expect(found[0].args[0].word).toEqual("testing");
220
+ expect(found[0].timestamp).toBeGreaterThan(1);
221
+ });
222
+
223
+ test("can call task methods inside the run", async () => {
224
+ class TaskWithMethod extends Task {
225
+ constructor() {
226
+ super();
227
+ this.name = "taskWithMethod";
228
+ this.description = "task with additional methods to execute in run";
229
+ this.queue = queue;
230
+ }
231
+
232
+ async stepOne() {
233
+ await utils.sleep(100);
234
+ taskOutput.push("one");
235
+ }
236
+
237
+ stepTwo() {
238
+ taskOutput.push("two");
239
+ }
240
+
241
+ async run() {
242
+ await this.stepOne();
243
+ this.stepTwo();
244
+ taskOutput.push("tree");
245
+ }
246
+ }
247
+ api.tasks.tasks.taskWithMethod = new TaskWithMethod();
248
+ api.tasks.jobs.taskWithMethod = api.tasks.jobWrapper("taskWithMethod");
249
+ await specHelper.runFullTask("taskWithMethod", {});
250
+ expect(taskOutput).toHaveLength(3);
251
+ expect(taskOutput[0]).toEqual("one");
252
+ expect(taskOutput[1]).toEqual("two");
253
+ expect(taskOutput[2]).toEqual("tree");
254
+ });
255
+
256
+ test("no delayed tasks should be scheduled", async () => {
257
+ const timestamps = await api.resque.queue.scheduledAt(
258
+ queue,
259
+ "periodicTask",
260
+ );
261
+ expect(timestamps).toHaveLength(0);
262
+ });
263
+
264
+ test("all periodic tasks can be enqueued at boot", async () => {
265
+ await task.enqueueAllRecurrentTasks();
266
+ const length = await api.resque.queue.length(queue);
267
+ expect(length).toEqual(1);
268
+ });
269
+
270
+ test("re-enquing a recurring task will not throw", async () => {
271
+ let length = await api.resque.queue.length(queue);
272
+ expect(length).toEqual(0);
273
+
274
+ // does not throw
275
+ await Promise.all([
276
+ task.enqueueAllRecurrentTasks(),
277
+ task.enqueueAllRecurrentTasks(),
278
+ task.enqueueAllRecurrentTasks(),
279
+ ]);
280
+
281
+ length = await api.resque.queue.length(queue);
282
+ expect(length).toEqual(1);
283
+ });
284
+
285
+ test("if we get in a situation where 2 instances of a periodic task end at the same time, only one instance will be enqueued", async () => {
286
+ let length = await api.resque.queue.length(queue);
287
+ expect(length).toEqual(0);
288
+ let timestamps = await task.timestamps();
289
+ expect(timestamps).toHaveLength(0);
290
+
291
+ // also tests that jobLock does what we want
292
+ await Promise.all([
293
+ specHelper.runFullTask("periodicTask", {}),
294
+ specHelper.runFullTask("periodicTask", {}),
295
+ specHelper.runFullTask("periodicTask", {}),
296
+ specHelper.runFullTask("periodicTask", {}),
297
+ specHelper.runFullTask("periodicTask", {}),
298
+ ]);
299
+
300
+ expect(taskOutput.length).toBe(1);
301
+
302
+ timestamps = await task.timestamps();
303
+ expect(timestamps).toHaveLength(1);
304
+ const { tasks } = await task.delayedAt(timestamps[0]);
305
+ expect(tasks).toHaveLength(1);
306
+ expect(tasks[0].class).toEqual("periodicTask");
307
+ });
308
+
309
+ test("re-enqueuing a periodic task should not enqueue it again", async () => {
310
+ const tryOne = await task.enqueue("periodicTask", {});
311
+ const tryTwo = await task.enqueue("periodicTask", {});
312
+ const length = await api.resque.queue.length(queue);
313
+ expect(tryOne).toEqual(true);
314
+ expect(tryTwo).toEqual(false);
315
+ expect(length).toEqual(1);
316
+ });
317
+
318
+ test("can add a normal job", async () => {
319
+ await task.enqueue("regularTask", { word: "first" });
320
+ const length = await api.resque.queue.length(queue);
321
+ expect(length).toEqual(1);
322
+ });
323
+
324
+ test("can add a delayed job", async () => {
325
+ const time = new Date().getTime() + 1000;
326
+ await task.enqueueAt(time, "regularTask", { word: "first" });
327
+ const timestamps = await api.resque.queue.scheduledAt(
328
+ queue,
329
+ "regularTask",
330
+ [{ word: "first" }],
331
+ );
332
+ expect(timestamps).toHaveLength(1);
333
+
334
+ const completeTime = Math.floor(time / 1000);
335
+ expect(Number(timestamps[0])).toBeGreaterThanOrEqual(completeTime);
336
+ expect(Number(timestamps[0])).toBeLessThan(completeTime + 2);
337
+ });
338
+
339
+ test("can see enqueued timestamps & see jobs within those timestamps (single + batch)", async () => {
340
+ const time = new Date().getTime() + 1000;
341
+ const roundedTime = Math.round(time / 1000) * 1000;
342
+
343
+ await task.enqueueAt(time, "regularTask", { word: "first" });
344
+ const timestamps = await task.timestamps();
345
+ expect(timestamps).toHaveLength(1);
346
+ expect(timestamps[0]).toEqual(roundedTime);
347
+
348
+ const { tasks } = await task.delayedAt(roundedTime);
349
+ expect(tasks).toHaveLength(1);
350
+ expect(tasks[0].class).toEqual("regularTask");
351
+
352
+ const allTasks = await task.allDelayed();
353
+ expect(Object.keys(allTasks)).toHaveLength(1);
354
+ expect(Object.keys(allTasks)[0]).toEqual(String(roundedTime));
355
+ expect(allTasks[roundedTime][0].class).toEqual("regularTask");
356
+ });
357
+
358
+ test("I can remove an enqueued job", async () => {
359
+ await task.enqueue("regularTask", { word: "first" });
360
+ const length = await api.resque.queue.length(queue);
361
+ expect(length).toEqual(1);
362
+
363
+ const count = await task.del(queue, "regularTask", { word: "first" });
364
+ expect(count).toEqual(1);
365
+
366
+ const lengthAgain = await api.resque.queue.length(queue);
367
+ expect(lengthAgain).toEqual(0);
368
+ });
369
+
370
+ test("I can remove enqueued jobs by name", async () => {
371
+ await task.enqueue("regularTask", { word: "first" });
372
+ const length = await api.resque.queue.length(queue);
373
+ expect(length).toEqual(1);
374
+
375
+ await task.delByFunction(queue, "regularTask");
376
+
377
+ const lengthAgain = await api.resque.queue.length(queue);
378
+ expect(lengthAgain).toEqual(0);
379
+ });
380
+
381
+ test("I can remove a delayed job", async () => {
382
+ await task.enqueueIn(1000, "regularTask", { word: "first" });
383
+ const timestamps = await api.resque.queue.scheduledAt(
384
+ queue,
385
+ "regularTask",
386
+ [{ word: "first" }],
387
+ );
388
+ expect(timestamps).toHaveLength(1);
389
+
390
+ const timestampsDeleted = await task.delDelayed(queue, "regularTask", {
391
+ word: "first",
392
+ });
393
+
394
+ expect(timestampsDeleted).toHaveLength(1);
395
+ expect(timestampsDeleted).toEqual(timestamps);
396
+
397
+ const timestampsDeletedAgain = await task.delDelayed(queue, "regularTask", {
398
+ word: "first",
399
+ });
400
+ expect(timestampsDeletedAgain).toHaveLength(0);
401
+ });
402
+
403
+ test("I can remove and stop a recurring task", async () => {
404
+ // enqueue the delayed job 2x, one in each type of queue
405
+ await task.enqueue("periodicTask");
406
+ await task.enqueueIn(1000, "periodicTask");
407
+
408
+ const count = await task.stopRecurrentTask("periodicTask");
409
+ expect(count).toEqual(2);
410
+ });
411
+
412
+ describe("input validation", () => {
413
+ test("tasks which provide input can be enqueued", async () => {
414
+ await expect(task.enqueue("taskWithInputs", {})).rejects.toThrow(
415
+ /input for task/,
416
+ );
417
+
418
+ await task.enqueue("taskWithInputs", { a: 1, b: 2, c: 3, d: 4 }); // does not throw
419
+ });
420
+
421
+ test("tasks which provide input can be enqueuedAt", async () => {
422
+ await expect(task.enqueueIn(1, "taskWithInputs", {})).rejects.toThrow(
423
+ /input for task/,
424
+ );
425
+
426
+ await task.enqueueIn(1, "taskWithInputs", { a: 1, b: 2, c: 3, d: 4 }); // does not throw
427
+ });
428
+
429
+ test("tasks which provide input can be enqueuedIn", async () => {
430
+ await expect(task.enqueueAt(1, "taskWithInputs", {})).rejects.toThrow(
431
+ /input for task/,
432
+ );
433
+
434
+ await task.enqueueAt(1, "taskWithInputs", { a: 1, b: 2, c: 3, d: 4 }); // does not throw
435
+ });
436
+
437
+ test("defaults can be provided (via literal)", async () => {
438
+ await task.enqueue("taskWithInputs", { b: 2, c: 3, d: 4 });
439
+ const enqueuedTask = await specHelper.findEnqueuedTasks("taskWithInputs");
440
+ expect(enqueuedTask[0].args[0]).toEqual({ a: 1, b: 2, c: 3, d: 4 });
441
+ });
442
+
443
+ test("defaults can be provided (via function)", async () => {
444
+ await task.enqueue("taskWithInputs", { a: 1, c: 3, d: 4 });
445
+ const enqueuedTask = await specHelper.findEnqueuedTasks("taskWithInputs");
446
+ expect(enqueuedTask[0].args[0]).toEqual({ a: 1, b: 2, c: 3, d: 4 });
447
+ });
448
+
449
+ test("validation will fail with input that does not match the validation method (via throw)", async () => {
450
+ await expect(
451
+ task.enqueue("taskWithInputs", { a: 1, b: 2, c: -1, d: 4 }),
452
+ ).rejects.toThrow(/nope/);
453
+ });
454
+
455
+ test("validation will fail with input that does not match the validation method (via false)", async () => {
456
+ await expect(
457
+ task.enqueue("taskWithInputs", { a: 1, b: 2, c: 3, d: -1 }),
458
+ ).rejects.toThrow(/-1 is not a valid value for d in task taskWithInputs/);
459
+ });
460
+ });
461
+
462
+ describe("middleware", () => {
463
+ describe("enqueue modification", () => {
464
+ beforeAll(async () => {
465
+ const middleware = {
466
+ name: "test-middleware",
467
+ priority: 1000,
468
+ global: false,
469
+ preEnqueue: () => {
470
+ throw new Error("You cannot enqueue me!");
471
+ },
472
+ };
473
+
474
+ task.addMiddleware(middleware);
475
+
476
+ api.tasks.tasks.middlewareTask = {
477
+ name: "middlewareTask",
478
+ description: "middlewaretask",
479
+ queue: "default",
480
+ frequency: 0,
481
+ middleware: ["test-middleware"],
482
+ run: (params, worker) => {
483
+ throw new Error("Should never get here");
484
+ },
485
+ };
486
+
487
+ api.tasks.jobs.middlewareTask = api.tasks.jobWrapper("middlewareTask");
488
+ });
489
+
490
+ afterAll(async () => {
491
+ api.tasks.globalMiddleware = [];
492
+ delete api.tasks.jobs.middlewareTask;
493
+ });
494
+
495
+ test("can modify the behavior of enqueue with middleware.preEnqueue", async () => {
496
+ try {
497
+ await task.enqueue("middlewareTask", {});
498
+ } catch (error) {
499
+ expect(error.toString()).toEqual("Error: You cannot enqueue me!");
500
+ }
501
+ });
502
+ });
503
+
504
+ describe("Pre and Post processing", () => {
505
+ beforeAll(() => {
506
+ const middleware = {
507
+ name: "test-middleware",
508
+ priority: 1000,
509
+ global: false,
510
+ preProcessor: function () {
511
+ const params = this.args[0];
512
+
513
+ if (params.stop === true) {
514
+ return false;
515
+ }
516
+ if (params.throw === true) {
517
+ throw new Error("thown!");
518
+ }
519
+
520
+ params.test = true;
521
+ if (!this.worker.result) {
522
+ this.worker.result = {};
523
+ }
524
+ this.worker.result.pre = true;
525
+ return true;
526
+ },
527
+ postProcessor: function () {
528
+ this.worker.result.post = true;
529
+ return true;
530
+ },
531
+ };
532
+
533
+ task.addMiddleware(middleware);
534
+
535
+ api.tasks.tasks.middlewareTask = {
536
+ name: "middlewareTask",
537
+ description: "middlewaretask",
538
+ queue: "default",
539
+ frequency: 0,
540
+ middleware: ["test-middleware"],
541
+ run: function (params, worker) {
542
+ expect(params.test).toEqual(true);
543
+ const result = worker.result;
544
+ result.run = true;
545
+ return result;
546
+ },
547
+ };
548
+
549
+ api.tasks.jobs.middlewareTask = api.tasks.jobWrapper("middlewareTask");
550
+ });
551
+
552
+ afterAll(() => {
553
+ api.tasks.globalMiddleware = [];
554
+ delete api.tasks.jobs.middlewareTask;
555
+ });
556
+
557
+ test("can modify parameters before a task and modify result after task completion", async () => {
558
+ const result = await specHelper.runFullTask("middlewareTask", {
559
+ foo: "bar",
560
+ });
561
+ expect(result.run).toEqual(true);
562
+ expect(result.pre).toEqual(true);
563
+ expect(result.post).toEqual(true);
564
+ });
565
+
566
+ test("can prevent the running of a task with error", async () => {
567
+ try {
568
+ await specHelper.runFullTask("middlewareTask", { throw: true });
569
+ } catch (error) {
570
+ expect(error.toString()).toEqual("Error: thown!");
571
+ }
572
+ });
573
+
574
+ test("can prevent the running of a task with return value", async () => {
575
+ const result = await specHelper.runFullTask("middlewareTask", {
576
+ stop: true,
577
+ });
578
+ expect(result).toBeUndefined();
579
+ });
580
+ });
581
+ });
582
+
583
+ describe("details view in a working system", () => {
584
+ test("can use api.tasks.details to learn about the system", async () => {
585
+ config.tasks!.queues = ["*"];
586
+
587
+ await task.enqueue("slowTask", { a: 1 });
588
+ api.resque.multiWorker.start();
589
+
590
+ await utils.sleep(2000);
591
+
592
+ const details = await task.details();
593
+
594
+ expect(Object.keys(details.queues)).toEqual(["testQueue"]);
595
+ expect(details.queues.testQueue).toHaveLength(0);
596
+ expect(Object.keys(details.workers)).toHaveLength(1);
597
+ const workerName = Object.keys(details.workers)[0];
598
+ expect(details.workers[workerName].queue).toEqual("testQueue");
599
+ expect(details.workers[workerName].payload.args).toEqual([{ a: 1 }]);
600
+ expect(details.workers[workerName].payload.class).toEqual("slowTask");
601
+
602
+ await api.resque.multiWorker.stop();
603
+ }, 10000);
604
+ });
605
+ });