@vida-global/core 2.0.6 → 2.0.7

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.
@@ -7,11 +7,24 @@ const FIXTURE_JOBS_DIR = path.join(__dirname, 'fixtureJobs');
7
7
 
8
8
  /***************************************************************************************************
9
9
  * WORKER FACTORY
10
+ * bullmqMock/loggerMock/apmMock are required lazily so the mock instances align with whatever the
11
+ * test re-requires after jest.resetModules() (used to reset the worker's module-level registeredJobs).
10
12
  ***************************************************************************************************/
11
13
  function buildWorker(Worker, queueName) {
14
+ const bullmqMock = require('./bullmqMock');
15
+ const { buildLoggerMock } = require('./loggerMock');
16
+ const { buildApmMock } = require('./apmMock');
17
+
18
+ const loggerMock = buildLoggerMock();
19
+ const apmMock = buildApmMock();
20
+
12
21
  class TestWorker extends Worker {
13
22
  get jobDirectories() { return [FIXTURE_JOBS_DIR]; }
23
+ get workerClass() { return bullmqMock.Worker; }
24
+ get loggerLibrary() { return loggerMock; }
25
+ get apm() { return apmMock; }
14
26
  }
27
+
15
28
  return new TestWorker(queueName || TestHelpers.Faker.Text.randomString());
16
29
  }
17
30
 
@@ -1,27 +1,10 @@
1
- const TestHelpers = require('@vida-global/test-helpers');
1
+ const TestHelpers = require('@vida-global/test-helpers');
2
2
  const { setupJobQueueEnv } = require('./helpers/env');
3
-
4
-
5
- jest.mock('bullmq', () => {
6
- const mock = require('./helpers/bullmqMock');
7
- return { Queue: mock.Queue, Worker: mock.Worker };
8
- });
9
-
10
- jest.mock('../../lib/logger', () => {
11
- const { buildLoggerMock } = require('./helpers/loggerMock');
12
- return { logger: buildLoggerMock() };
13
- });
14
-
15
- jest.mock('crypto', () => {
16
- const actual = jest.requireActual('crypto');
17
- return { ...actual, randomUUID: jest.fn() };
18
- });
3
+ const { buildQueue } = require('./helpers/queue');
19
4
 
20
5
 
21
6
  let bullmqMock;
22
7
  let getQueue;
23
- let logger;
24
- let randomUUID;
25
8
  let restoreEnv;
26
9
 
27
10
  beforeEach(() => {
@@ -30,8 +13,6 @@ beforeEach(() => {
30
13
 
31
14
  bullmqMock = require('./helpers/bullmqMock');
32
15
  ({ getQueue } = require('../../lib/jobQueue/queue'));
33
- ({ logger } = require('../../lib/logger'));
34
- ({ randomUUID } = require('crypto'));
35
16
  });
36
17
 
37
18
 
@@ -95,7 +76,7 @@ describe('Queue', () => {
95
76
 
96
77
  beforeEach(() => {
97
78
  queueName = TestHelpers.Faker.Text.randomString();
98
- queue = getQueue(queueName);
79
+ queue = buildQueue(queueName);
99
80
  });
100
81
 
101
82
 
@@ -117,8 +98,8 @@ describe('Queue', () => {
117
98
  it ('uses the queue scope keyed by fullQueueName', () => {
118
99
  const child = queue.logger;
119
100
 
120
- expect(logger.queue.child).toHaveBeenCalledWith(`${queueName}-test`);
121
- expect(child).toBe(logger.queue.child.mock.results[0].value);
101
+ expect(queue.loggerLibrary.queue.child).toHaveBeenCalledWith(`${queueName}-test`);
102
+ expect(child).toBe(queue.loggerLibrary.queue.child.mock.results[0].value);
122
103
  });
123
104
  });
124
105
 
@@ -157,7 +138,7 @@ describe('Queue', () => {
157
138
  describe('#queueJob', () => {
158
139
  it ('adds the job to the BullMQ queue with a generated UUID, args, and settings', async () => {
159
140
  const id = TestHelpers.Faker.Text.randomString();
160
- randomUUID.mockReturnValue(id);
141
+ queue.idGenerator.mockReturnValue(id);
161
142
  const args = [TestHelpers.Faker.Text.randomString(), TestHelpers.Faker.Math.randomNumber()];
162
143
  const settings = { attempts: TestHelpers.Faker.Math.randomNumber(10) };
163
144
  const jobClass = { name: TestHelpers.Faker.Text.randomString() };
@@ -313,7 +294,7 @@ describe('Queue', () => {
313
294
 
314
295
  errorListener(err);
315
296
 
316
- const child = logger.queue.child.mock.results[0].value;
297
+ const child = queue.loggerLibrary.queue.child.mock.results[0].value;
317
298
  expect(child.error).toHaveBeenCalledWith(err);
318
299
  });
319
300
  });
@@ -3,26 +3,8 @@ const { setupJobQueueEnv } = require('./helpers/env');
3
3
  const { buildFakeBullJob, buildWorker } = require('./helpers/worker');
4
4
 
5
5
 
6
- jest.mock('bullmq', () => {
7
- const mock = require('./helpers/bullmqMock');
8
- return { Queue: mock.Queue, Worker: mock.Worker };
9
- });
10
-
11
- jest.mock('../../lib/logger', () => {
12
- const { buildLoggerMock } = require('./helpers/loggerMock');
13
- return { logger: buildLoggerMock() };
14
- });
15
-
16
- jest.mock('../../lib/apm', () => {
17
- const { buildApmMock } = require('./helpers/apmMock');
18
- return buildApmMock();
19
- });
20
-
21
-
22
6
  let bullmqMock;
23
7
  let Worker;
24
- let logger;
25
- let APM;
26
8
  let TestJobA;
27
9
  let TestJobB;
28
10
  let restoreEnv;
@@ -33,8 +15,6 @@ beforeEach(() => {
33
15
 
34
16
  bullmqMock = require('./helpers/bullmqMock');
35
17
  ({ Worker } = require('../../lib/jobQueue/worker'));
36
- ({ logger } = require('../../lib/logger'));
37
- APM = require('../../lib/apm');
38
18
  ({ TestJobA } = require('./helpers/fixtureJobs/testJobA'));
39
19
  ({ TestJobB } = require('./helpers/fixtureJobs/testJobB'));
40
20
  });
@@ -110,9 +90,9 @@ describe('Worker', () => {
110
90
 
111
91
  describe('#logger', () => {
112
92
  it ('returns the worker scope', () => {
113
- const worker = new Worker('q');
93
+ const worker = buildWorker(Worker);
114
94
 
115
- expect(worker.logger).toBe(logger.worker);
95
+ expect(worker.logger).toBe(worker.loggerLibrary.worker);
116
96
  });
117
97
  });
118
98
 
@@ -203,8 +183,8 @@ describe('Worker', () => {
203
183
 
204
184
  await worker.listen();
205
185
 
206
- expect(logger.worker.verbose).toHaveBeenCalledWith('JOB: TestJobA');
207
- expect(logger.worker.verbose).toHaveBeenCalledWith('JOB: TestJobB');
186
+ expect(worker.loggerLibrary.worker.verbose).toHaveBeenCalledWith('JOB: TestJobA');
187
+ expect(worker.loggerLibrary.worker.verbose).toHaveBeenCalledWith('JOB: TestJobB');
208
188
  });
209
189
 
210
190
  it ('skips per-job verbose logging when NODE_ENV is test', async () => {
@@ -213,7 +193,7 @@ describe('Worker', () => {
213
193
 
214
194
  await worker.listen();
215
195
 
216
- const verboseCalls = logger.worker.verbose.mock.calls.filter(
196
+ const verboseCalls = worker.loggerLibrary.worker.verbose.mock.calls.filter(
217
197
  ([msg]) => typeof msg === 'string' && msg.startsWith('JOB:')
218
198
  );
219
199
  expect(verboseCalls).toHaveLength(0);
@@ -288,8 +268,8 @@ describe('Worker', () => {
288
268
 
289
269
  await processor(fakeBull);
290
270
 
291
- expect(APM.startBackgroundTransaction).toHaveBeenCalledTimes(1);
292
- expect(APM.startBackgroundTransaction).toHaveBeenCalledWith(
271
+ expect(worker.apm.startBackgroundTransaction).toHaveBeenCalledTimes(1);
272
+ expect(worker.apm.startBackgroundTransaction).toHaveBeenCalledWith(
293
273
  'TestJobA',
294
274
  `${queueName}-test`,
295
275
  expect.any(Function),
@@ -304,8 +284,8 @@ describe('Worker', () => {
304
284
 
305
285
  await processor(fakeBull);
306
286
 
307
- expect(APM.startSegment).toHaveBeenCalledTimes(1);
308
- expect(APM.startSegment).toHaveBeenCalledWith('job:TestJobA', true, expect.any(Function));
287
+ expect(worker.apm.startSegment).toHaveBeenCalledTimes(1);
288
+ expect(worker.apm.startSegment).toHaveBeenCalledWith('job:TestJobA', true, expect.any(Function));
309
289
  });
310
290
 
311
291
  it ('adds jobId, jobName, queueName, attempt, and maxAttempts as custom attributes', async () => {
@@ -323,11 +303,11 @@ describe('Worker', () => {
323
303
 
324
304
  await processor(fakeBull);
325
305
 
326
- expect(APM.addCustomAttribute).toHaveBeenCalledWith('jobId', id);
327
- expect(APM.addCustomAttribute).toHaveBeenCalledWith('jobName', 'TestJobA');
328
- expect(APM.addCustomAttribute).toHaveBeenCalledWith('queueName', queueName);
329
- expect(APM.addCustomAttribute).toHaveBeenCalledWith('attempt', 2);
330
- expect(APM.addCustomAttribute).toHaveBeenCalledWith('maxAttempts', 5);
306
+ expect(worker.apm.addCustomAttribute).toHaveBeenCalledWith('jobId', id);
307
+ expect(worker.apm.addCustomAttribute).toHaveBeenCalledWith('jobName', 'TestJobA');
308
+ expect(worker.apm.addCustomAttribute).toHaveBeenCalledWith('queueName', queueName);
309
+ expect(worker.apm.addCustomAttribute).toHaveBeenCalledWith('attempt', 2);
310
+ expect(worker.apm.addCustomAttribute).toHaveBeenCalledWith('maxAttempts', 5);
331
311
  });
332
312
 
333
313
  it ('records a JobCompleted custom event and a duration metric on success', async () => {
@@ -344,13 +324,13 @@ describe('Worker', () => {
344
324
 
345
325
  await processor(fakeBull);
346
326
 
347
- expect(APM.recordCustomEvent).toHaveBeenCalledWith('JobCompleted', {
327
+ expect(worker.apm.recordCustomEvent).toHaveBeenCalledWith('JobCompleted', {
348
328
  jobName: 'TestJobA',
349
329
  queueName,
350
330
  durationMs: finishedAt - startedAt,
351
331
  attempts: 1,
352
332
  });
353
- expect(APM.recordMetric).toHaveBeenCalledWith(
333
+ expect(worker.apm.recordMetric).toHaveBeenCalledWith(
354
334
  `Custom/Worker/${queueName}/TestJobA/duration`,
355
335
  finishedAt - startedAt,
356
336
  );
@@ -372,8 +352,8 @@ describe('Worker', () => {
372
352
 
373
353
  await expect(processor(fakeBull)).rejects.toBe(err);
374
354
 
375
- expect(APM.reportError).toHaveBeenCalledTimes(1);
376
- expect(APM.reportError).toHaveBeenCalledWith(err, {
355
+ expect(worker.apm.reportError).toHaveBeenCalledTimes(1);
356
+ expect(worker.apm.reportError).toHaveBeenCalledWith(err, {
377
357
  jobId: id,
378
358
  jobName: 'TestJobA',
379
359
  queueName,
@@ -381,7 +361,7 @@ describe('Worker', () => {
381
361
  maxAttempts: 3,
382
362
  isFinalRetry: false,
383
363
  });
384
- expect(APM.recordCustomEvent).not.toHaveBeenCalledWith('JobFailed', expect.anything());
364
+ expect(worker.apm.recordCustomEvent).not.toHaveBeenCalledWith('JobFailed', expect.anything());
385
365
  });
386
366
 
387
367
  it ('records a JobFailed custom event on the final retry', async () => {
@@ -398,13 +378,13 @@ describe('Worker', () => {
398
378
 
399
379
  await expect(processor(fakeBull)).rejects.toBe(err);
400
380
 
401
- expect(APM.recordCustomEvent).toHaveBeenCalledWith('JobFailed', {
381
+ expect(worker.apm.recordCustomEvent).toHaveBeenCalledWith('JobFailed', {
402
382
  jobName: 'TestJobA',
403
383
  queueName,
404
384
  attempts: 3,
405
385
  error: err.message,
406
386
  });
407
- expect(APM.reportError).toHaveBeenCalledWith(err, expect.objectContaining({
387
+ expect(worker.apm.reportError).toHaveBeenCalledWith(err, expect.objectContaining({
408
388
  isFinalRetry: true,
409
389
  }));
410
390
  });
@@ -418,8 +398,8 @@ describe('Worker', () => {
418
398
 
419
399
  await expect(processor(fakeBull)).rejects.toBe(err);
420
400
 
421
- expect(APM.recordCustomEvent).not.toHaveBeenCalledWith('JobCompleted', expect.anything());
422
- expect(APM.recordMetric).not.toHaveBeenCalled();
401
+ expect(worker.apm.recordCustomEvent).not.toHaveBeenCalledWith('JobCompleted', expect.anything());
402
+ expect(worker.apm.recordMetric).not.toHaveBeenCalled();
423
403
  });
424
404
 
425
405
  /*
@@ -453,7 +433,7 @@ describe('Worker', () => {
453
433
 
454
434
  handler({ name: 'TestJobA', data: { id } }, duration);
455
435
 
456
- const child = logger.worker.child.mock.results[0].value;
436
+ const child = worker.loggerLibrary.worker.child.mock.results[0].value;
457
437
  expect(child.debug).toHaveBeenCalledWith(`Completed in ${duration}ms`);
458
438
  });
459
439
 
@@ -466,7 +446,7 @@ describe('Worker', () => {
466
446
 
467
447
  handler({ name: 'TestJobA', data: { id } }, payload);
468
448
 
469
- const child = logger.worker.child.mock.results[0].value;
449
+ const child = worker.loggerLibrary.worker.child.mock.results[0].value;
470
450
  expect(child.debug).toHaveBeenCalledWith(payload);
471
451
  });
472
452
 
@@ -483,7 +463,7 @@ describe('Worker', () => {
483
463
  const err = new Error('boom');
484
464
 
485
465
  expect(() => handler(job, err)).not.toThrow();
486
- const child = logger.worker.child.mock.results[0].value;
466
+ const child = worker.loggerLibrary.worker.child.mock.results[0].value;
487
467
  expect(child.debug).toHaveBeenCalledWith('ERROR (1/3) Error: boom');
488
468
  });
489
469
 
@@ -510,7 +490,7 @@ describe('Worker', () => {
510
490
  const err = new Error(TestHelpers.Faker.Text.randomString());
511
491
 
512
492
  expect(() => handler(err)).toThrow(err);
513
- expect(logger.worker.error).toHaveBeenCalledWith(err);
493
+ expect(worker.loggerLibrary.worker.error).toHaveBeenCalledWith(err);
514
494
  });
515
495
 
516
496
  it ('"error" handler logs and does NOT throw outside development', async () => {
@@ -520,7 +500,7 @@ describe('Worker', () => {
520
500
  const err = new Error(TestHelpers.Faker.Text.randomString());
521
501
 
522
502
  expect(() => handler(err)).not.toThrow();
523
- expect(logger.worker.error).toHaveBeenCalledWith(err);
503
+ expect(worker.loggerLibrary.worker.error).toHaveBeenCalledWith(err);
524
504
  });
525
505
  });
526
506
  });
@@ -46,6 +46,32 @@ describe('Logger', () => {
46
46
  });
47
47
 
48
48
 
49
+ describe('#child', () => {
50
+ it ('returns a fresh, uncached logger for an id so per-request loggers are not retained', () => {
51
+ const scope = randomScope();
52
+ const id = TestHelpers.Faker.Text.randomString();
53
+ const root = new Logger(scope);
54
+
55
+ const first = root.child(id);
56
+ const second = root.child(id);
57
+
58
+ expect(first).not.toBe(second);
59
+ expect(first.id).toEqual(id);
60
+ expect(first.scopes).toEqual([scope]);
61
+ });
62
+
63
+ it ('caches and reuses the logger when no id is provided', () => {
64
+ const scope = randomScope();
65
+ const root = new Logger(scope);
66
+
67
+ const first = root.child();
68
+ const second = root.child();
69
+
70
+ expect(first).toBe(second);
71
+ });
72
+ });
73
+
74
+
49
75
  describe('#messageFormatter', () => {
50
76
  it ('renders every nested scope in the prefix in order', () => {
51
77
  const scopeA = randomScope();
@@ -1,22 +1,13 @@
1
- jest.mock('express-winston', () => {
2
- return {
3
- logger: jest.fn(() => jest.fn()) // returns a noop middleware
4
- };
5
- });
6
-
7
- const expressWinston = require('express-winston');
1
+ const { LoggingMiddleware } = require('../../lib/logger/serverMiddleware');
8
2
 
9
- // Invoking middleware() triggers expressWinston.logger(options) — capturing options gives us
10
- // access to the otherwise-unexported msgFormatter
11
- const { middleware } = require('../../lib/logger/serverMiddleware');
12
- middleware({ _winstonLogger: {} });
13
3
 
14
- const options = expressWinston.logger.mock.calls[0][0];
15
- const msgFormatter = options.msg;
4
+ // msgFormatter is an instance method, so we exercise it directly — no need to mock express-winston
5
+ // or build the middleware (the logger collaborator is only read lazily when middleware is built).
6
+ const msgFormatter = (req, res) => new LoggingMiddleware({}).msgFormatter(req, res);
16
7
 
17
8
 
18
- describe('logger/serverMiddleware', () => {
19
- describe('msgFormatter', () => {
9
+ describe('LoggingMiddleware', () => {
10
+ describe('#msgFormatter', () => {
20
11
  const baseRes = (overrides = {}) => ({
21
12
  statusCode: 200,
22
13
  responseTime: 12,
@@ -1,14 +1,48 @@
1
- const { VidaServer } = require('../../../lib/server');
1
+ const { VidaServer } = require('../../../lib/server');
2
+ const jestExpress = require('jest-express');
3
+ const { Response } = require('jest-express/lib/response');
4
+
5
+
6
+ // missing function in mock express
7
+ Response.prototype.on = jest.fn();
8
+
9
+
10
+ function buildServerEngineFactory() {
11
+ const instances = [];
12
+ const factory = (...args) => {
13
+ const app = jestExpress(...args);
14
+ instances.push(app);
15
+ return app;
16
+ };
17
+ Object.assign(factory, jestExpress);
18
+ factory.raw = jest.fn();
19
+ factory.__instances = instances;
20
+ return factory;
21
+ }
22
+
23
+
24
+ const serverEngineDouble = buildServerEngineFactory();
25
+ const socketServerDouble = jest.fn();
26
+ const httpServerDouble = jest.fn();
2
27
 
3
28
 
4
29
  class TestServer extends VidaServer {
5
- get controllerDirectories() {
6
- return [`${__dirname}/controllers`];
7
- }
30
+ get controllerDirectories() { return [`${__dirname}/controllers`]; }
31
+ get serverEngineFactory() { return serverEngineDouble; }
32
+ get socketServerFactory() { return socketServerDouble; }
33
+ get httpServerFactory() { return httpServerDouble; }
8
34
  }
9
35
 
10
36
 
11
- module.exports = {
12
- TestServer
37
+ function lastApp() {
38
+ return serverEngineDouble.__instances[serverEngineDouble.__instances.length - 1];
13
39
  }
14
40
 
41
+
42
+ module.exports = {
43
+ TestServer,
44
+ lastApp,
45
+ serverEngineDouble,
46
+ socketServerDouble,
47
+ httpServerDouble,
48
+ }
@@ -3,42 +3,13 @@ const { BazController } = require('./helpers/controllers/sub/bazController');
3
3
  const { FooController } = require('./helpers/controllers/fooController');
4
4
  const { Request } = require('jest-express/lib/request');
5
5
  const { Response } = require('jest-express/lib/response');
6
- const { TestServer } = require('./helpers/server');
6
+ const {
7
+ TestServer,
8
+ lastApp,
9
+ socketServerDouble: IoServer,
10
+ httpServerDouble: HttpServer,
11
+ } = require('./helpers/server');
7
12
  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];
14
-
15
-
16
- jest.mock('express', () => {
17
- const jestExpress = require('jest-express');
18
- const { Response } = require('jest-express/lib/response');
19
-
20
- // missing functions in mock express
21
- Response.prototype.on = jest.fn();
22
-
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() };
41
- });
42
13
 
43
14
 
44
15
  afterEach(() => {