@vida-global/core 1.4.5 → 2.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 (55) hide show
  1. package/AGENTS.md +13 -0
  2. package/README.md +1 -0
  3. package/config/newrelic-config.js +118 -0
  4. package/index.js +9 -5
  5. package/lib/activeRecord/baseRecord.js +49 -3
  6. package/lib/apm/agent.js +22 -0
  7. package/lib/apm/index.js +54 -0
  8. package/lib/apm/utils.js +43 -0
  9. package/lib/http/client.js +16 -2
  10. package/lib/jobQueue/README.md +63 -0
  11. package/lib/jobQueue/abstractJob.js +103 -0
  12. package/lib/jobQueue/abstractJobComponent.js +37 -0
  13. package/lib/jobQueue/index.js +10 -0
  14. package/lib/jobQueue/jobImporter.js +19 -0
  15. package/lib/jobQueue/queue.js +161 -0
  16. package/lib/jobQueue/worker.js +273 -0
  17. package/lib/logger/README.md +33 -2
  18. package/lib/logger/index.js +117 -20
  19. package/lib/logger/serverMiddleware.js +41 -0
  20. package/lib/redis/redisClient.js +32 -24
  21. package/lib/server/controllerImporter.js +9 -47
  22. package/lib/server/server.js +34 -71
  23. package/lib/server/serverController.js +215 -23
  24. package/lib/utils/abstractAutoImporter.js +64 -0
  25. package/package.json +8 -6
  26. package/test/activeRecord/baseRecord.test.js +129 -0
  27. package/test/activeRecord/db/connection.test.js +4 -1
  28. package/test/activeRecord/db/connectionConfiguration.test.js +9 -3
  29. package/test/activeRecord/db/schema.test.js +217 -0
  30. package/test/activeRecord/helpers/baseRecordMocks.js +6 -4
  31. package/test/activeRecord/helpers/connection.js +0 -3
  32. package/test/activeRecord/helpers/connectionConfiguration.js +0 -8
  33. package/test/activeRecord/utils.test.js +72 -0
  34. package/test/apm/agent.test.js +56 -0
  35. package/test/apm/utils.test.js +121 -0
  36. package/test/helpers/env.js +33 -0
  37. package/test/http/client.test.js +3 -3
  38. package/test/jobQueue/abstractJob.test.js +307 -0
  39. package/test/jobQueue/abstractJobComponent.test.js +110 -0
  40. package/test/jobQueue/helpers/abstractJob.js +26 -0
  41. package/test/jobQueue/helpers/apmMock.js +20 -0
  42. package/test/jobQueue/helpers/bullmqMock.js +67 -0
  43. package/test/jobQueue/helpers/env.js +29 -0
  44. package/test/jobQueue/helpers/fixtureJobs/notAJob.js +8 -0
  45. package/test/jobQueue/helpers/fixtureJobs/testJobA.js +14 -0
  46. package/test/jobQueue/helpers/fixtureJobs/testJobB.js +14 -0
  47. package/test/jobQueue/helpers/loggerMock.js +43 -0
  48. package/test/jobQueue/helpers/worker.js +38 -0
  49. package/test/jobQueue/queue.test.js +320 -0
  50. package/test/jobQueue/worker.test.js +526 -0
  51. package/test/logger/index.test.js +61 -0
  52. package/test/logger/serverMiddleware.test.js +72 -0
  53. package/test/server/apiDocsGenerator.test.js +38 -0
  54. package/test/server/server.test.js +186 -4
  55. package/test/server/serverController.test.js +407 -5
@@ -0,0 +1,38 @@
1
+ const { ApiDocsGenerator } = require('../../lib/server/apiDocsGenerator');
2
+
3
+
4
+ describe('ApiDocsGenerator', () => {
5
+ let generator;
6
+
7
+ beforeEach(() => {
8
+ generator = new ApiDocsGenerator({ controllerDirectories: [] });
9
+ });
10
+
11
+
12
+ describe('#formatEndpoint', () => {
13
+ it ('replaces a single :param with {param}', () => {
14
+ expect(generator.formatEndpoint({ path: '/users/:id' })).toEqual('/users/{id}');
15
+ });
16
+
17
+ it ('replaces multiple :params with {params}', () => {
18
+ expect(generator.formatEndpoint({ path: '/users/:userId/posts/:postId' }))
19
+ .toEqual('/users/{userId}/posts/{postId}');
20
+ });
21
+
22
+ it ('returns the path unchanged when there are no params', () => {
23
+ expect(generator.formatEndpoint({ path: '/users' })).toEqual('/users');
24
+ });
25
+
26
+ it ('handles a trailing :param', () => {
27
+ expect(generator.formatEndpoint({ path: '/foo/bar/:baz' })).toEqual('/foo/bar/{baz}');
28
+ });
29
+
30
+ it ('handles an empty path', () => {
31
+ expect(generator.formatEndpoint({ path: '' })).toEqual('');
32
+ });
33
+
34
+ it ('only replaces up to the next slash', () => {
35
+ expect(generator.formatEndpoint({ path: '/a/:x/b/:y' })).toEqual('/a/{x}/b/{y}');
36
+ });
37
+ });
38
+ });
@@ -5,17 +5,39 @@ const { Request } = require('jest-express/lib/request');
5
5
  const { Response } = require('jest-express/lib/response');
6
6
  const { TestServer } = require('./helpers/server');
7
7
  const { VidaServer } = require('../../lib/server');
8
+ const express = require('express');
9
+ const IoServer = require('socket.io');
10
+ const { Server: HttpServer } = require('http');
11
+
12
+
13
+ const lastApp = () => express.__instances[express.__instances.length - 1];
8
14
 
9
15
 
10
16
  jest.mock('express', () => {
11
- const express = require('jest-express');
12
- const { Response } = require('jest-express/lib/response');
17
+ const jestExpress = require('jest-express');
18
+ const { Response } = require('jest-express/lib/response');
13
19
 
14
20
  // missing functions in mock express
15
21
  Response.prototype.on = jest.fn();
16
- express.raw = jest.fn();
17
22
 
18
- return express;
23
+ // Wrap the factory so tests can inspect the most recently created Express app.
24
+ const instances = [];
25
+ const wrapped = (...args) => {
26
+ const app = jestExpress(...args);
27
+ instances.push(app);
28
+ return app;
29
+ };
30
+ Object.assign(wrapped, jestExpress);
31
+ wrapped.raw = jest.fn();
32
+ wrapped.__instances = instances;
33
+
34
+ return wrapped;
35
+ });
36
+
37
+ jest.mock('socket.io', () => jest.fn());
38
+ jest.mock('http', () => {
39
+ const actual = jest.requireActual('http');
40
+ return { ...actual, Server: jest.fn() };
19
41
  });
20
42
 
21
43
 
@@ -187,4 +209,164 @@ describe('VidaServer', () => {
187
209
  expect(afterSpy5).toHaveBeenCalledTimes(0);
188
210
  });
189
211
  });
212
+
213
+
214
+ describe('VidaServer#listen', () => {
215
+ it ('starts the http server on the configured host/port and invokes the callback', async () => {
216
+ const fakeHttpServer = { listen: jest.fn((port, host, cb) => cb()) };
217
+ HttpServer.mockReturnValueOnce(fakeHttpServer);
218
+
219
+ const server = new TestServer({ port: 4321, host: '0.0.0.0' });
220
+ const callback = jest.fn();
221
+ jest.spyOn(server, 'registerControllers').mockResolvedValue();
222
+
223
+ await server.listen(callback);
224
+ expect(fakeHttpServer.listen).toHaveBeenCalledWith(4321, '0.0.0.0', expect.any(Function));
225
+ expect(callback).toHaveBeenCalledTimes(1);
226
+ });
227
+
228
+ it ('does not require a callback', async () => {
229
+ const fakeHttpServer = { listen: jest.fn((port, host, cb) => cb()) };
230
+ HttpServer.mockReturnValueOnce(fakeHttpServer);
231
+
232
+ const server = new TestServer({ port: 5000, host: 'localhost' });
233
+ jest.spyOn(server, 'registerControllers').mockResolvedValue();
234
+
235
+ await expect(server.listen()).resolves.toBeUndefined();
236
+ expect(fakeHttpServer.listen).toHaveBeenCalledTimes(1);
237
+ });
238
+ });
239
+
240
+
241
+ describe('VidaServer#corsOrigins', () => {
242
+ it ('defaults to an empty array', () => {
243
+ expect(new TestServer().corsOrigins).toEqual([]);
244
+ });
245
+ });
246
+
247
+
248
+ describe('VidaServer#viewsDirectory / staticFilesDirectory', () => {
249
+ it ('returns absolute paths under cwd', () => {
250
+ const server = new TestServer();
251
+ expect(server.viewsDirectory).toEqual(`${process.cwd()}/views`);
252
+ expect(server.staticFilesDirectory).toEqual(`${process.cwd()}/static`);
253
+ });
254
+ });
255
+
256
+
257
+ describe('VidaServer#controllerDirectories (default)', () => {
258
+ it ('returns cwd/controllers when not overridden', () => {
259
+ const server = new VidaServer();
260
+ expect(server.controllerDirectories).toEqual([`${process.cwd()}/controllers`]);
261
+ });
262
+ });
263
+
264
+
265
+ describe('VidaServer#connectionAbortedMiddleware', () => {
266
+ it ('short-circuits with a 400 when err.code === ECONNABORTED', async () => {
267
+ const server = new TestServer();
268
+ const middleware = server.connectionAbortedMiddleware;
269
+ const status = jest.fn().mockReturnValue({ end: jest.fn() });
270
+ const res = { status };
271
+ const next = jest.fn();
272
+
273
+ await middleware({ code: 'ECONNABORTED' }, {}, res, next);
274
+ expect(status).toHaveBeenCalledWith(400);
275
+ expect(next).not.toHaveBeenCalled();
276
+ });
277
+
278
+ it ('calls next() for other errors', async () => {
279
+ const server = new TestServer();
280
+ const middleware = server.connectionAbortedMiddleware;
281
+ const next = jest.fn();
282
+
283
+ await middleware(new Error('something else'), {}, {}, next);
284
+ expect(next).toHaveBeenCalledTimes(1);
285
+ });
286
+
287
+ it ('calls next() when there is no error', async () => {
288
+ const server = new TestServer();
289
+ const middleware = server.connectionAbortedMiddleware;
290
+ const next = jest.fn();
291
+
292
+ await middleware(null, {}, {}, next);
293
+ expect(next).toHaveBeenCalledTimes(1);
294
+ });
295
+ });
296
+
297
+
298
+ describe('VidaServer HTTP verb wrappers', () => {
299
+ const verbs = ['get', 'post', 'put', 'delete', 'patch', 'head'];
300
+
301
+ it.each(verbs)('_%s delegates to express app.%s', (verb) => {
302
+ const server = new TestServer();
303
+ const handler = jest.fn();
304
+ const app = lastApp();
305
+ app[verb].mockClear();
306
+
307
+ server[`_${verb}`]('/path', handler);
308
+ expect(app[verb]).toHaveBeenCalledWith('/path', handler);
309
+ });
310
+ });
311
+
312
+
313
+ describe('VidaServer deprecated routing methods', () => {
314
+ const verbs = ['get', 'post', 'put', 'patch', 'head', 'delete'];
315
+
316
+ it.each(verbs)('%s emits a DeprecationWarning and delegates to express app.%s', (verb) => {
317
+ const emitSpy = jest.spyOn(process, 'emitWarning').mockImplementation(() => {});
318
+ try {
319
+ const server = new TestServer();
320
+ const handler = jest.fn();
321
+ const app = lastApp();
322
+ app[verb].mockClear();
323
+
324
+ server[verb]('/path', handler);
325
+ expect(emitSpy).toHaveBeenCalledWith(expect.stringContaining(`\`${verb}\` is deprecated`), 'DeprecationWarning');
326
+ expect(app[verb]).toHaveBeenCalledWith('/path', handler);
327
+ } finally {
328
+ emitSpy.mockRestore();
329
+ }
330
+ });
331
+
332
+ it ('only emits the warning once per method name', () => {
333
+ const emitSpy = jest.spyOn(process, 'emitWarning').mockImplementation(() => {});
334
+ try {
335
+ const server = new TestServer();
336
+ server.post('/a', jest.fn());
337
+ server.post('/b', jest.fn());
338
+ server.post('/c', jest.fn());
339
+
340
+ const postWarnings = emitSpy.mock.calls.filter(c => c[0].includes('`post`'));
341
+ expect(postWarnings).toHaveLength(1);
342
+ } finally {
343
+ emitSpy.mockRestore();
344
+ }
345
+ });
346
+ });
347
+
348
+
349
+ describe('VidaServer web socket helpers', () => {
350
+ let fakeIo;
351
+ let server;
352
+
353
+ beforeEach(() => {
354
+ fakeIo = { to: jest.fn(() => fakeIo), emit: jest.fn(), on: jest.fn() };
355
+ IoServer.mockReturnValueOnce(fakeIo);
356
+ HttpServer.mockReturnValueOnce({ listen: jest.fn() });
357
+ server = new TestServer();
358
+ });
359
+
360
+ it ('emitWebSocketEvent forwards channel/event/data to the io server', () => {
361
+ server.emitWebSocketEvent('chan', 'evt', { hello: 'world' });
362
+ expect(fakeIo.to).toHaveBeenCalledWith('chan');
363
+ expect(fakeIo.emit).toHaveBeenCalledWith('evt', { hello: 'world' });
364
+ });
365
+
366
+ it ('addWebSocketEventHandler registers the handler on the io server', () => {
367
+ const handler = jest.fn();
368
+ server.addWebSocketEventHandler('connection', handler);
369
+ expect(fakeIo.on).toHaveBeenCalledWith('connection', handler);
370
+ });
371
+ });
190
372
  });