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,349 @@
1
+ // Note: These tests will only run on *nix operating systems
2
+ // You can use SKIP_CLI_TEST_SETUP=true to skip the setup portion of these tests if you are testing this file repeatedly
3
+
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { spawn } from "child_process";
7
+ import axios, { AxiosError } from "axios";
8
+ import { isRunning } from "../../src/modules/utils/isRunning";
9
+ import { sleep } from "../../src/modules/utils/sleep";
10
+
11
+ const testDir = path.join(process.cwd(), "tmp", "actionheroTestProject");
12
+ const binary = "./node_modules/.bin/actionhero";
13
+
14
+ console.log(`testDir: ${testDir}`);
15
+
16
+ const port = 18080 + parseInt(process.env.JEST_WORKER_ID || "0");
17
+ const host = "localhost";
18
+ let pid: number;
19
+ let AHPath: string;
20
+
21
+ class ErrorWithStd extends Error {
22
+ stderr: string;
23
+ stdout: string;
24
+ pid: number;
25
+ exitCode: number;
26
+ }
27
+
28
+ const doCommand = async (
29
+ command: string,
30
+ useCwd = true,
31
+ extraEnv = {},
32
+ ): Promise<{
33
+ stderr: string;
34
+ stdout: string;
35
+ pid: number;
36
+ exitCode: number;
37
+ }> => {
38
+ return new Promise((resolve, reject) => {
39
+ const parts = command.split(" ");
40
+ const bin = parts.shift() as string;
41
+ const args = parts;
42
+ let stdout = "";
43
+ let stderr = "";
44
+
45
+ // we don't want the CLI commands to source typescript files
46
+ // when running jest, it will reset NODE_ENV=test
47
+ // but sometimes we do /shrug/
48
+ const env = Object.assign({ ...process.env }, extraEnv);
49
+ delete env.NODE_ENV;
50
+ delete env.TS_JEST;
51
+
52
+ const cmd = spawn(bin, args, {
53
+ cwd: useCwd ? testDir : __dirname,
54
+ env: env,
55
+ });
56
+
57
+ cmd.stdout.on("data", (data) => {
58
+ stdout += data.toString();
59
+ });
60
+ cmd.stderr.on("data", (data) => {
61
+ stderr += data.toString();
62
+ });
63
+
64
+ pid = cmd.pid ?? -1;
65
+
66
+ cmd.on("close", (exitCode) => {
67
+ if (stderr.length > 0 || exitCode !== 0) {
68
+ const error = new ErrorWithStd(stderr);
69
+ error.stderr = stderr;
70
+ error.stdout = stdout;
71
+ error.pid = pid;
72
+ error.exitCode = exitCode ?? -1;
73
+ return reject(error);
74
+ }
75
+ return resolve({ stderr, stdout, pid, exitCode });
76
+ });
77
+ });
78
+ };
79
+
80
+ describe("Core: CLI", () => {
81
+ if (process.platform === "win32") {
82
+ console.log("*** CANNOT RUN CLI TESTS ON WINDOWS. Sorry. ***");
83
+ } else {
84
+ beforeAll(async () => {
85
+ if (process.env.SKIP_CLI_TEST_SETUP === "true") {
86
+ return;
87
+ }
88
+
89
+ const sourcePackage = path.normalize(
90
+ path.join(__dirname, "/../../templates/package.json.template"),
91
+ );
92
+ AHPath = path.normalize(path.join(__dirname, "/../.."));
93
+
94
+ await doCommand(`rm -rf ${testDir}`, false);
95
+ await doCommand(`mkdir -p ${testDir}`, false);
96
+ await doCommand(`cp ${sourcePackage} ${testDir}/package.json`);
97
+
98
+ const data = fs.readFileSync(testDir + "/package.json").toString();
99
+ const result = data.replace(/%%versionNumber%%/g, `file:${AHPath}`);
100
+ fs.writeFileSync(`${testDir}/package.json`, result);
101
+ });
102
+
103
+ test("should have made the test dir", () => {
104
+ expect(fs.existsSync(testDir)).toEqual(true);
105
+ expect(fs.existsSync(testDir + "/package.json")).toEqual(true);
106
+ });
107
+
108
+ test("can call npm install in the new project", async () => {
109
+ try {
110
+ await doCommand("npm install --ignore-scripts");
111
+ } catch (error) {
112
+ // we might get warnings about package.json locks, etc. we want to ignore them
113
+ if (error.toString().indexOf("npm") < 0) {
114
+ throw error;
115
+ }
116
+ expect(error.exitCode).toEqual(0);
117
+ }
118
+ }, 120000);
119
+
120
+ test("can generate a new project", async () => {
121
+ const { stdout } = await doCommand(`${binary} generate`);
122
+ expect(stdout).toMatch("❤️ the Actionhero Team");
123
+
124
+ [
125
+ "tsconfig.json",
126
+ "src/server.ts",
127
+ "src/actions",
128
+ "src/tasks",
129
+ "src/initializers",
130
+ "src/servers",
131
+ "src/bin",
132
+ "src/actions/swagger.ts",
133
+ "src/actions/status.ts",
134
+ "src/config",
135
+ "src/config/api.ts",
136
+ "src/config/errors.ts",
137
+ "src/config/plugins.ts",
138
+ "src/config/logger.ts",
139
+ "src/config/redis.ts",
140
+ "src/config/routes.ts",
141
+ "src/config/tasks.ts",
142
+ "src/config/web.ts",
143
+ "src/config/websocket.ts",
144
+ "pids",
145
+ "log",
146
+ "public",
147
+ "public/index.html",
148
+ "public/chat.html",
149
+ "public/swagger.html",
150
+ "public/css/cosmo.css",
151
+ "public/javascript",
152
+ "public/logo/actionhero.png",
153
+ "__tests__",
154
+ "__tests__/actions/status.ts",
155
+ ".gitignore",
156
+ ].forEach((f) => {
157
+ expect(fs.existsSync(testDir + "/" + f)).toEqual(true);
158
+ });
159
+ }, 20000);
160
+
161
+ test("the project can be compiled", async () => {
162
+ const { stdout } = await doCommand(`npm run build`);
163
+ expect(stdout).toMatch("tsc");
164
+ }, 20000);
165
+
166
+ test("can call the help command", async () => {
167
+ const { stdout } = await doCommand(`${binary} help`);
168
+ expect(stdout).toMatch(/generate-action/);
169
+ expect(stdout).toMatch(/Usage: actionhero \[options\] \[command\]/);
170
+ expect(stdout).toMatch(/generate-server/);
171
+ expect(stdout).toMatch(/generate-server \[options\]/);
172
+ }, 20000);
173
+
174
+ test("can call the version command (after generate)", async () => {
175
+ const { stdout } = await doCommand(`${binary} --version`);
176
+ expect(stdout).toContain("0.1.0"); // this project's version
177
+ }, 20000);
178
+
179
+ test("will show a warning with bogus input", async () => {
180
+ try {
181
+ await doCommand(`${binary} not-a-thing`);
182
+ throw new Error("should not get here");
183
+ } catch (error) {
184
+ expect(error).toBeTruthy();
185
+ expect(error.exitCode).toEqual(1);
186
+ expect(error.stderr).toMatch(/unknown command 'not-a-thing'/);
187
+ }
188
+ }, 20000);
189
+
190
+ describe("generating files", () => {
191
+ afterAll(() => {
192
+ const files = [
193
+ `${testDir}/src/actions/myAction.ts`,
194
+ `${testDir}/__tests__/actions/myAction.ts`,
195
+ `${testDir}/src/tasks/myTask.ts`,
196
+ `${testDir}/__tests__/tasks/myTask.ts`,
197
+ `${testDir}/src/bin/myCommand.ts`,
198
+ `${testDir}/src/servers/myServer.ts`,
199
+ `${testDir}/src/initializers/myInitializer.ts`,
200
+ ];
201
+
202
+ files.forEach((f) => {
203
+ if (fs.existsSync(f)) {
204
+ fs.unlinkSync(f);
205
+ }
206
+ });
207
+ });
208
+
209
+ test("can generate an action", async () => {
210
+ await doCommand(
211
+ `${binary} generate-action --name=myAction --description=my_description`,
212
+ );
213
+ const actionData = String(
214
+ fs.readFileSync(`${testDir}/src/actions/myAction.ts`),
215
+ );
216
+ expect(actionData).toMatch(/export class MyAction extends Action/);
217
+ expect(actionData).toMatch(/this.name = "myAction"/);
218
+
219
+ const testData = String(
220
+ fs.readFileSync(`${testDir}/__tests__/actions/myAction.ts`),
221
+ );
222
+ expect(testData).toMatch('describe("Action: myAction"');
223
+ }, 20000);
224
+
225
+ test("can generate a task", async () => {
226
+ await doCommand(
227
+ `${binary} generate-task --name=myTask --description=my_description --queue=my_queue --frequency=12345`,
228
+ );
229
+ const taskData = String(
230
+ fs.readFileSync(`${testDir}/src/tasks/myTask.ts`),
231
+ );
232
+ expect(taskData).toMatch(/export class MyTask extends Task/);
233
+ expect(taskData).toMatch(/this.name = "myTask"/);
234
+ expect(taskData).toMatch(/this.queue = "my_queue"/);
235
+ expect(taskData).toMatch(/this.frequency = 12345/);
236
+
237
+ const testData = String(
238
+ fs.readFileSync(`${testDir}/__tests__/tasks/myTask.ts`),
239
+ );
240
+ expect(testData).toMatch('describe("Task: myTask"');
241
+ }, 20000);
242
+
243
+ test("can generate a CLI command", async () => {
244
+ await doCommand(
245
+ `${binary} generate-cli --name=myCommand --description=my_description --example=my_example`,
246
+ );
247
+ const data = String(fs.readFileSync(`${testDir}/src/bin/myCommand.ts`));
248
+ expect(data).toMatch(/this.name = "myCommand"/);
249
+ expect(data).toMatch(/this.example = "my_example"/);
250
+ }, 20000);
251
+
252
+ test("can generate a server", async () => {
253
+ await doCommand(`${binary} generate-server --name=myServer`);
254
+ const data = String(
255
+ fs.readFileSync(`${testDir}/src/servers/myServer.ts`),
256
+ );
257
+ expect(data).toMatch(/this.type = "myServer"/);
258
+ expect(data).toMatch(/canChat: false/);
259
+ expect(data).toMatch(/logConnections: true/);
260
+ expect(data).toMatch(/logExits: true/);
261
+ expect(data).toMatch(/sendWelcomeMessage: false/);
262
+ }, 20000);
263
+
264
+ test("can generate an initializer", async () => {
265
+ await doCommand(
266
+ `${binary} generate-initializer --name=myInitializer --stopPriority=123`,
267
+ );
268
+ const data = String(
269
+ fs.readFileSync(`${testDir}/src/initializers/myInitializer.ts`),
270
+ );
271
+ expect(data).toMatch(/this.loadPriority = 1000/);
272
+ expect(data).toMatch(/this.startPriority = 1000/);
273
+ expect(data).toMatch(/this.stopPriority = 123/);
274
+ expect(data).toMatch(/async initialize\(\) {/);
275
+ expect(data).toMatch(/async start\(\) {/);
276
+ expect(data).toMatch(/async stop\(\) {/);
277
+ }, 20000);
278
+ });
279
+
280
+ test("can call npm test in the new project and not fail", async () => {
281
+ // since prettier no longer works with node < 10, we need to skip this test
282
+ const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)![1]);
283
+ if (nodeVersion < 10) {
284
+ console.log(
285
+ `skipping 'npm test' because this node version ${nodeVersion} < 10.0.0`,
286
+ );
287
+ return;
288
+ }
289
+
290
+ // jest writes to stderr for some reason, so we need to test for the exit code here
291
+ try {
292
+ await doCommand("npm test", true, { NODE_ENV: "test" });
293
+ } catch (error) {
294
+ if (error.exitCode !== 0) {
295
+ throw error;
296
+ }
297
+ }
298
+ }, 120000);
299
+
300
+ describe("can run the server", () => {
301
+ let serverPid: number;
302
+
303
+ beforeAll(async function () {
304
+ doCommand(`node dist/server.js`, true, { PORT: port });
305
+ await sleep(5000);
306
+ serverPid = pid;
307
+ }, 20000);
308
+
309
+ afterAll(async () => {
310
+ if (isRunning(serverPid)) {
311
+ await doCommand(`kill ${serverPid}`);
312
+ }
313
+ });
314
+
315
+ test("can boot the server", async () => {
316
+ const response = await axios(`http://${host}:${port}/api/status`);
317
+ expect(response.data.serverInformation.serverName).toEqual(
318
+ "my_actionhero_project",
319
+ );
320
+ });
321
+
322
+ test("can handle signals to reboot", async () => {
323
+ await doCommand(`kill -s USR2 ${serverPid}`);
324
+ await sleep(3000);
325
+ const response = await axios(`http://${host}:${port}/api/status`);
326
+ expect(response.data.serverInformation.serverName).toEqual(
327
+ "my_actionhero_project",
328
+ );
329
+ }, 5000);
330
+
331
+ test("can handle signals to stop", async () => {
332
+ await doCommand(`kill ${serverPid}`);
333
+ await sleep(1000);
334
+ try {
335
+ await axios.get(`http://${host}:${port}/api/status`);
336
+ throw new Error("should not get here");
337
+ } catch (error) {
338
+ if (error instanceof AxiosError) {
339
+ expect(error.toString()).toMatch(
340
+ /ECONNREFUSED|ECONNRESET|RequestError|AggregateError/,
341
+ );
342
+ } else throw error;
343
+ }
344
+ });
345
+
346
+ // test('will shutdown after the alloted time')
347
+ });
348
+ }
349
+ });
@@ -0,0 +1,132 @@
1
+ import {
2
+ api,
3
+ Process,
4
+ config,
5
+ utils,
6
+ specHelper,
7
+ chatRoom,
8
+ redis,
9
+ } from "./../../src";
10
+
11
+ const actionhero = new Process();
12
+
13
+ describe("Core: Action Cluster", () => {
14
+ beforeAll(async () => {
15
+ await actionhero.start();
16
+ for (var room in config!.general!.startingChatRooms as Record<
17
+ string,
18
+ Record<string, any>
19
+ >) {
20
+ try {
21
+ await chatRoom.destroy(room);
22
+ await chatRoom.add(room);
23
+ } catch (error) {
24
+ console.log(error);
25
+ if (
26
+ config.errors &&
27
+ typeof config.errors.connectionRoomExists === "function"
28
+ ) {
29
+ throw error;
30
+ }
31
+ }
32
+ }
33
+ });
34
+
35
+ afterAll(async () => await actionhero.stop());
36
+
37
+ describe("RPC", () => {
38
+ afterEach(() => {
39
+ delete api.rpcTestMethod;
40
+ });
41
+
42
+ test("can call remote methods on all other servers in the cluster", async () => {
43
+ const data: Record<string, any> = {};
44
+ api.rpcTestMethod = (arg1: any, arg2: any) => {
45
+ data[1] = [arg1, arg2];
46
+ };
47
+ await redis.doCluster("api.rpcTestMethod", ["arg1", "arg2"]);
48
+ await utils.sleep(100);
49
+
50
+ expect(data[1][0]).toEqual("arg1");
51
+ expect(data[1][1]).toEqual("arg2");
52
+ });
53
+
54
+ test("can call remote methods only on one other cluster who holds a specific connectionId", async () => {
55
+ const client = await specHelper.buildConnection();
56
+ const data: Record<string, any> = {};
57
+ api.rpcTestMethod = (arg1: any, arg2: any) => {
58
+ data[1] = [arg1, arg2];
59
+ };
60
+
61
+ await redis.doCluster("api.rpcTestMethod", ["arg1", "arg2"], client.id);
62
+ await utils.sleep(100);
63
+
64
+ expect(data[1][0]).toEqual("arg1");
65
+ expect(data[1][1]).toEqual("arg2");
66
+ client.destroy();
67
+ });
68
+
69
+ test("can get information about connections connected to other servers", async () => {
70
+ const client = await specHelper.buildConnection();
71
+
72
+ const { id, type, canChat } = await api.connections.apply(client.id);
73
+ expect(id).toEqual(client.id);
74
+ expect(type).toEqual("testServer");
75
+ expect(canChat).toEqual(true);
76
+ });
77
+
78
+ test("can call remote methods on/about connections connected to other servers", async () => {
79
+ const client = await specHelper.buildConnection();
80
+ //@ts-ignore
81
+ expect(client["auth"]).toBeUndefined();
82
+
83
+ const connection = await api.connections.apply(client.id, "set", [
84
+ "auth",
85
+ true,
86
+ ]);
87
+ expect(connection.id).toEqual(client.id);
88
+ //@ts-ignore
89
+ expect(client["auth"]).toEqual(true);
90
+ client.destroy();
91
+ });
92
+
93
+ test("can send arbitrary messages to connections connected to other servers", async () => {
94
+ const client = await specHelper.buildConnection();
95
+
96
+ const connection = await api.connections.apply(client.id, "sendMessage", {
97
+ message: "hi",
98
+ });
99
+ if (!connection.messages) throw new Error("no connection.messages");
100
+ const message = connection.messages[connection.messages.length - 1];
101
+ expect(message.message).toEqual("hi");
102
+ });
103
+
104
+ test("can call api.chatRoom.removeMember on other servers", async () => {
105
+ const client = await specHelper.buildConnection();
106
+ const spy = jest
107
+ .spyOn(api.chatRoom, "removeMember")
108
+ .mockImplementationOnce(jest.fn());
109
+ await redis.doCluster("api.chatRoom.removeMember", [
110
+ client.id,
111
+ "defaultRoom",
112
+ ]);
113
+ await utils.sleep(100);
114
+
115
+ expect(spy).toHaveBeenCalledWith(client.id, "defaultRoom");
116
+ });
117
+
118
+ test("failing RPC calls with a callback will have a failure callback", async () => {
119
+ try {
120
+ await redis.doCluster(
121
+ "api.rpcTestMethod",
122
+ [],
123
+ "A missing clientId",
124
+ true,
125
+ );
126
+ throw new Error("should not get here");
127
+ } catch (error) {
128
+ expect(error.toString()).toEqual("Error: RPC Timeout");
129
+ }
130
+ });
131
+ });
132
+ });
@@ -0,0 +1,78 @@
1
+ import { promisify } from "util";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { Process, ActionheroConfigInterface } from "./../../src";
5
+ import { buildConfig } from "./../../src/modules/config";
6
+
7
+ const actionhero = new Process();
8
+ let config: Partial<ActionheroConfigInterface>;
9
+ let configFolders: string | undefined;
10
+
11
+ const newConfigFolderPaths = [
12
+ path.join(__dirname, "first_config"),
13
+ path.join(__dirname, "second_config"),
14
+ ];
15
+
16
+ const routeFilesContent = [
17
+ "export const DEFAULT = { collection: () => { return { a: 1 } } }",
18
+ "export const DEFAULT = { collection: () => { return { b: 2 } } }",
19
+ ];
20
+
21
+ const createRouteFile = async (
22
+ newConfigFolderPath: string,
23
+ routeFileContent: string,
24
+ ) => {
25
+ try {
26
+ await promisify(fs.mkdir)(newConfigFolderPath);
27
+ } catch (ex) {}
28
+
29
+ try {
30
+ const newRoutesFilePath = path.join(newConfigFolderPath, "collection.ts");
31
+
32
+ await promisify(fs.writeFile)(newRoutesFilePath, routeFileContent, {
33
+ encoding: "utf-8",
34
+ });
35
+ } catch (ex) {}
36
+ };
37
+
38
+ const removeRouteFile = async (newConfigFolderPath: string) => {
39
+ try {
40
+ const newRoutesFilePath = path.join(newConfigFolderPath, "collection.ts");
41
+
42
+ await promisify(fs.unlink)(newRoutesFilePath);
43
+ } catch (ex) {}
44
+
45
+ try {
46
+ await promisify(fs.rmdir)(newConfigFolderPath);
47
+ } catch (ex) {}
48
+ };
49
+
50
+ describe("Core: config folders", () => {
51
+ beforeAll(async () => {
52
+ configFolders = process.env.ACTIONHERO_CONFIG;
53
+
54
+ await removeRouteFile(newConfigFolderPaths[0]);
55
+ await removeRouteFile(newConfigFolderPaths[1]);
56
+ await createRouteFile(newConfigFolderPaths[0], routeFilesContent[0]);
57
+ await createRouteFile(newConfigFolderPaths[1], routeFilesContent[1]);
58
+
59
+ process.env.ACTIONHERO_CONFIG = newConfigFolderPaths.join(",");
60
+
61
+ await actionhero.start();
62
+ config = buildConfig();
63
+ });
64
+
65
+ afterAll(async () => {
66
+ await actionhero.stop();
67
+ await removeRouteFile(newConfigFolderPaths[0]);
68
+ await removeRouteFile(newConfigFolderPaths[1]);
69
+ process.env.ACTIONHERO_CONFIG = configFolders;
70
+ });
71
+
72
+ test("routes should be rebuilt and contain both paths", async () => {
73
+ expect(config.collection).toEqual({
74
+ a: 1,
75
+ b: 2,
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,112 @@
1
+ import { api, Process, config, specHelper } from "./../../src";
2
+
3
+ const actionhero = new Process();
4
+ let originalUnknownAction: any;
5
+ let originalGenericError: any;
6
+
7
+ describe("Core", () => {
8
+ describe("errors", () => {
9
+ beforeAll(async () => {
10
+ await actionhero.start();
11
+ originalUnknownAction = config!.errors!.unknownAction;
12
+ });
13
+
14
+ afterAll(async () => {
15
+ await actionhero.stop();
16
+ config!.errors!.unknownAction = originalUnknownAction;
17
+ });
18
+
19
+ test("returns string errors properly", async () => {
20
+ const { error } = await specHelper.runAction("notARealAction");
21
+ expect(error).toEqual("Error: unknown action or invalid apiVersion");
22
+ });
23
+
24
+ test("returns Error object properly", async () => {
25
+ //@ts-ignore
26
+ config.errors.unknownAction = () => {
27
+ return new Error("error test");
28
+ };
29
+ const { error } = await specHelper.runAction("notARealAction");
30
+ expect(error).toEqual("Error: error test");
31
+ });
32
+
33
+ test("returns generic object properly", async () => {
34
+ //@ts-ignore
35
+ config.errors.unknownAction = () => {
36
+ return { code: "error111", reason: "busted" };
37
+ };
38
+
39
+ const { error } = await specHelper.runAction("notARealAction");
40
+ expect(error.code).toEqual("error111");
41
+ expect(error.reason).toEqual("busted");
42
+ });
43
+
44
+ test("can have async error handlers", async () => {
45
+ //@ts-ignore
46
+ config.errors.unknownAction = async () => {
47
+ return new Promise((resolve) => {
48
+ //@ts-ignore
49
+ setTimeout(() => resolve({ sleepy: true }), 100);
50
+ });
51
+ };
52
+
53
+ const { error } = await specHelper.runAction("notARealAction");
54
+ expect(error.sleepy).toEqual(true);
55
+ });
56
+ });
57
+
58
+ describe("Core: Errors: Custom Error Decoration", () => {
59
+ const errorMsg = "worst action ever!";
60
+ beforeAll(async () => {
61
+ await actionhero.start();
62
+ originalGenericError = config!.errors!.genericError;
63
+ api.actions.versions.errorAction = [1];
64
+ api.actions.actions.errorAction = {
65
+ // @ts-ignore
66
+ 1: {
67
+ name: "errorAction",
68
+ description: "this action throws errors",
69
+ version: 1,
70
+ inputs: {},
71
+ run: async (data) => {
72
+ throw new Error(errorMsg);
73
+ },
74
+ },
75
+ };
76
+ });
77
+
78
+ afterAll(async () => {
79
+ await actionhero.stop();
80
+ delete api.actions.actions.errorAction;
81
+ delete api.actions.versions.errorAction;
82
+ config!.errors!.genericError = originalGenericError;
83
+ });
84
+
85
+ test("will return an actions error", async () => {
86
+ const response = await specHelper.runAction<any>("errorAction");
87
+ expect(response.error).toEqual("Error: worst action ever!");
88
+ expect(response.requestId).toBeUndefined();
89
+ });
90
+
91
+ test("can decorate an error", async () => {
92
+ config!.errors!.genericError = async (data, error) => {
93
+ data.response.requestId = "id-12345";
94
+ return error;
95
+ };
96
+ const response = await specHelper.runAction<any>("errorAction");
97
+ expect(response.error).toEqual("Error: worst action ever!");
98
+ expect(response.requestId).toEqual("id-12345");
99
+ });
100
+
101
+ test("exceptionHandlers get action errors", async () => {
102
+ await new Promise(async (resolve) => {
103
+ api.exceptionHandlers.reporters.push((error) => {
104
+ expect(error.message).toMatch(/worst action ever/);
105
+ resolve(null);
106
+ });
107
+
108
+ const response = await specHelper.runAction("errorAction");
109
+ });
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,23 @@
1
+ import { log, loggers, Process } from "./../../src";
2
+
3
+ const actionhero = new Process();
4
+
5
+ describe("Core", () => {
6
+ describe("log", () => {
7
+ beforeAll(async () => {
8
+ await actionhero.start();
9
+ });
10
+
11
+ afterAll(async () => {
12
+ await actionhero.stop();
13
+ });
14
+
15
+ test("the log method should work", () => {
16
+ log("hello");
17
+ });
18
+
19
+ test("the winston loggers are available via the export loggers", () => {
20
+ expect(loggers.length).toBe(2);
21
+ });
22
+ });
23
+ });