@vida-global/core 1.4.6 → 2.0.1
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.
- package/README.md +1 -0
- package/config/newrelic-config.js +118 -0
- package/index.js +9 -5
- package/lib/activeRecord/baseRecord.js +49 -3
- package/lib/apm/agent.js +22 -0
- package/lib/apm/index.js +54 -0
- package/lib/apm/utils.js +43 -0
- package/lib/http/client.js +16 -2
- package/lib/jobQueue/README.md +63 -0
- package/lib/jobQueue/abstractJob.js +103 -0
- package/lib/jobQueue/abstractJobComponent.js +37 -0
- package/lib/jobQueue/index.js +10 -0
- package/lib/jobQueue/jobImporter.js +19 -0
- package/lib/jobQueue/queue.js +161 -0
- package/lib/jobQueue/worker.js +273 -0
- package/lib/logger/README.md +1 -1
- package/lib/logger/index.js +33 -15
- package/lib/logger/serverMiddleware.js +14 -12
- package/lib/redis/redisClient.js +32 -24
- package/lib/server/controllerImporter.js +9 -47
- package/lib/server/server.js +30 -13
- package/lib/server/serverController.js +100 -23
- package/lib/utils/abstractAutoImporter.js +64 -0
- package/package.json +4 -2
- package/test/activeRecord/baseRecord.test.js +129 -0
- package/test/activeRecord/db/connection.test.js +4 -1
- package/test/activeRecord/db/connectionConfiguration.test.js +9 -3
- package/test/activeRecord/helpers/baseRecordMocks.js +6 -4
- package/test/activeRecord/helpers/connection.js +0 -3
- package/test/activeRecord/helpers/connectionConfiguration.js +0 -8
- package/test/apm/agent.test.js +56 -0
- package/test/apm/utils.test.js +121 -0
- package/test/helpers/env.js +33 -0
- package/test/http/client.test.js +3 -3
- package/test/jobQueue/abstractJob.test.js +307 -0
- package/test/jobQueue/abstractJobComponent.test.js +110 -0
- package/test/jobQueue/helpers/abstractJob.js +26 -0
- package/test/jobQueue/helpers/apmMock.js +20 -0
- package/test/jobQueue/helpers/bullmqMock.js +67 -0
- package/test/jobQueue/helpers/env.js +29 -0
- package/test/jobQueue/helpers/fixtureJobs/notAJob.js +8 -0
- package/test/jobQueue/helpers/fixtureJobs/testJobA.js +14 -0
- package/test/jobQueue/helpers/fixtureJobs/testJobB.js +14 -0
- package/test/jobQueue/helpers/loggerMock.js +43 -0
- package/test/jobQueue/helpers/worker.js +38 -0
- package/test/jobQueue/queue.test.js +320 -0
- package/test/jobQueue/worker.test.js +526 -0
- package/test/logger/index.test.js +61 -0
- package/test/logger/serverMiddleware.test.js +6 -7
- package/test/server/serverController.test.js +226 -10
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const { AbstractJobComponent } = require('../../lib/jobQueue/abstractJobComponent');
|
|
2
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
3
|
+
const { snapshotEnv } = require('../helpers/env');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
let restoreEnv;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
restoreEnv = snapshotEnv([
|
|
10
|
+
'NODE_ENV',
|
|
11
|
+
'REDIS_BULL_HOST',
|
|
12
|
+
'REDIS_BULL_PORT',
|
|
13
|
+
'REDIS_BULL_PASSWORD',
|
|
14
|
+
]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
restoreEnv();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
describe('AbstractJobComponent', () => {
|
|
24
|
+
describe('.fullQueueName', () => {
|
|
25
|
+
it ('builds a name suffixed with the current NODE_ENV', () => {
|
|
26
|
+
const queueName = TestHelpers.Faker.Text.randomString();
|
|
27
|
+
const env = TestHelpers.Faker.Text.randomString();
|
|
28
|
+
process.env.NODE_ENV = env;
|
|
29
|
+
|
|
30
|
+
expect(AbstractJobComponent.fullQueueName(queueName)).toEqual(`${queueName}-${env}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it ('reflects changes to NODE_ENV between calls', () => {
|
|
34
|
+
const queueName = TestHelpers.Faker.Text.randomString();
|
|
35
|
+
const env1 = TestHelpers.Faker.Text.randomString();
|
|
36
|
+
const env2 = TestHelpers.Faker.Text.randomString();
|
|
37
|
+
|
|
38
|
+
process.env.NODE_ENV = env1;
|
|
39
|
+
const first = AbstractJobComponent.fullQueueName(queueName);
|
|
40
|
+
process.env.NODE_ENV = env2;
|
|
41
|
+
const second = AbstractJobComponent.fullQueueName(queueName);
|
|
42
|
+
|
|
43
|
+
expect(first).toEqual(`${queueName}-${env1}`);
|
|
44
|
+
expect(second).toEqual(`${queueName}-${env2}`);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
describe('.env', () => {
|
|
50
|
+
it ('returns the current NODE_ENV', () => {
|
|
51
|
+
const env = TestHelpers.Faker.Text.randomString();
|
|
52
|
+
process.env.NODE_ENV = env;
|
|
53
|
+
|
|
54
|
+
expect(AbstractJobComponent.env).toEqual(env);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it ('returns undefined when NODE_ENV is not set', () => {
|
|
58
|
+
delete process.env.NODE_ENV;
|
|
59
|
+
|
|
60
|
+
expect(AbstractJobComponent.env).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
describe('#fullQueueName', () => {
|
|
66
|
+
it ('delegates to the static method using `this.queueName`', () => {
|
|
67
|
+
const queueName = TestHelpers.Faker.Text.randomString();
|
|
68
|
+
const env = TestHelpers.Faker.Text.randomString();
|
|
69
|
+
process.env.NODE_ENV = env;
|
|
70
|
+
|
|
71
|
+
class Concrete extends AbstractJobComponent {
|
|
72
|
+
get queueName() { return queueName; }
|
|
73
|
+
}
|
|
74
|
+
const instance = new Concrete();
|
|
75
|
+
|
|
76
|
+
expect(instance.fullQueueName).toEqual(`${queueName}-${env}`);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
describe('#redisConnectionDetails', () => {
|
|
82
|
+
it ('returns the connection details from REDIS_BULL_* environment variables', () => {
|
|
83
|
+
const host = TestHelpers.Faker.Text.randomString();
|
|
84
|
+
const port = String(TestHelpers.Faker.Math.randomNumber(65000));
|
|
85
|
+
const password = TestHelpers.Faker.Text.randomString();
|
|
86
|
+
process.env.REDIS_BULL_HOST = host;
|
|
87
|
+
process.env.REDIS_BULL_PORT = port;
|
|
88
|
+
process.env.REDIS_BULL_PASSWORD = password;
|
|
89
|
+
|
|
90
|
+
const instance = new AbstractJobComponent();
|
|
91
|
+
|
|
92
|
+
expect(instance.redisConnectionDetails).toEqual({ host, password, port });
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
describe('individual redis env getters', () => {
|
|
98
|
+
it.each([
|
|
99
|
+
['host', 'REDIS_BULL_HOST'],
|
|
100
|
+
['port', 'REDIS_BULL_PORT'],
|
|
101
|
+
['password', 'REDIS_BULL_PASSWORD'],
|
|
102
|
+
])('#%s returns process.env.%s', (getter, envVar) => {
|
|
103
|
+
const value = TestHelpers.Faker.Text.randomString();
|
|
104
|
+
process.env[envVar] = value;
|
|
105
|
+
const instance = new AbstractJobComponent();
|
|
106
|
+
|
|
107
|
+
expect(instance[getter]).toEqual(value);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/***************************************************************************************************
|
|
5
|
+
* SUBCLASS FACTORY
|
|
6
|
+
***************************************************************************************************/
|
|
7
|
+
function defineJobSubclass(AbstractJob, { queueName, queueNameFn, run, ...overrides } = {}) {
|
|
8
|
+
const resolvedName = queueName || TestHelpers.Faker.Text.randomString();
|
|
9
|
+
const nameFunction = queueNameFn || (() => resolvedName);
|
|
10
|
+
const Subclass = class extends AbstractJob {
|
|
11
|
+
static queueName(...jobArgs) { return nameFunction(...jobArgs); }
|
|
12
|
+
async run(...args) {
|
|
13
|
+
if (run) return await run(...args);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
17
|
+
Object.defineProperty(Subclass, key, { value });
|
|
18
|
+
}
|
|
19
|
+
Subclass.expectedQueueName = resolvedName;
|
|
20
|
+
return Subclass;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
defineJobSubclass,
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/***************************************************************************************************
|
|
2
|
+
* MOCKS
|
|
3
|
+
***************************************************************************************************/
|
|
4
|
+
function buildApmMock() {
|
|
5
|
+
return {
|
|
6
|
+
addCustomAttribute: jest.fn(),
|
|
7
|
+
recordCustomEvent: jest.fn(),
|
|
8
|
+
recordMetric: jest.fn(),
|
|
9
|
+
reportError: jest.fn(),
|
|
10
|
+
setControllerName: jest.fn(),
|
|
11
|
+
setUserId: jest.fn(),
|
|
12
|
+
startBackgroundTransaction: jest.fn((name, group, handler) => handler()),
|
|
13
|
+
startSegment: jest.fn((name, record, handler) => handler()),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
buildApmMock,
|
|
20
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/***************************************************************************************************
|
|
2
|
+
* MOCKS
|
|
3
|
+
***************************************************************************************************/
|
|
4
|
+
const instances = {
|
|
5
|
+
queues: [],
|
|
6
|
+
workers: [],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MockBullQueue {
|
|
11
|
+
constructor(name, options) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.options = options;
|
|
14
|
+
|
|
15
|
+
this.add = jest.fn();
|
|
16
|
+
this.close = jest.fn();
|
|
17
|
+
this.pause = jest.fn();
|
|
18
|
+
this.resume = jest.fn();
|
|
19
|
+
this.count = jest.fn();
|
|
20
|
+
this.getJobs = jest.fn();
|
|
21
|
+
this.getActiveCount = jest.fn();
|
|
22
|
+
this.getActive = jest.fn();
|
|
23
|
+
this.getFailedCount = jest.fn();
|
|
24
|
+
this.getFailed = jest.fn();
|
|
25
|
+
this.clean = jest.fn();
|
|
26
|
+
this.drain = jest.fn();
|
|
27
|
+
this.getWorkers = jest.fn();
|
|
28
|
+
this.on = jest.fn();
|
|
29
|
+
|
|
30
|
+
instances.queues.push(this);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MockBullWorker {
|
|
36
|
+
constructor(name, processor, options) {
|
|
37
|
+
this.name = name;
|
|
38
|
+
this.processor = processor;
|
|
39
|
+
this.options = options;
|
|
40
|
+
|
|
41
|
+
this.run = jest.fn();
|
|
42
|
+
this.on = jest.fn();
|
|
43
|
+
|
|
44
|
+
instances.workers.push(this);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
function reset() {
|
|
50
|
+
instances.queues.length = 0;
|
|
51
|
+
instances.workers.length = 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function listenerFor(emitter, event) {
|
|
56
|
+
const call = emitter.on.mock.calls.find(([name]) => name === event);
|
|
57
|
+
return call ? call[1] : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
Queue: MockBullQueue,
|
|
63
|
+
Worker: MockBullWorker,
|
|
64
|
+
instances,
|
|
65
|
+
reset,
|
|
66
|
+
listenerFor,
|
|
67
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
2
|
+
const { snapshotEnv } = require('../../helpers/env');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/***************************************************************************************************
|
|
6
|
+
* ENV SETUP
|
|
7
|
+
***************************************************************************************************/
|
|
8
|
+
function setupJobQueueEnv(extraKeysToClear = []) {
|
|
9
|
+
const restore = snapshotEnv([
|
|
10
|
+
'NODE_ENV',
|
|
11
|
+
'REDIS_BULL_HOST',
|
|
12
|
+
'REDIS_BULL_PORT',
|
|
13
|
+
'REDIS_BULL_PASSWORD',
|
|
14
|
+
...extraKeysToClear,
|
|
15
|
+
]);
|
|
16
|
+
process.env.NODE_ENV = 'test';
|
|
17
|
+
process.env.REDIS_BULL_HOST = TestHelpers.Faker.Text.randomString();
|
|
18
|
+
process.env.REDIS_BULL_PORT = String(TestHelpers.Faker.Math.randomNumber(65000));
|
|
19
|
+
process.env.REDIS_BULL_PASSWORD = TestHelpers.Faker.Text.randomString();
|
|
20
|
+
for (const key of extraKeysToClear) {
|
|
21
|
+
delete process.env[key];
|
|
22
|
+
}
|
|
23
|
+
return restore;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
setupJobQueueEnv,
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/***************************************************************************************************
|
|
2
|
+
* MOCKS
|
|
3
|
+
***************************************************************************************************/
|
|
4
|
+
function createLeaf(name) {
|
|
5
|
+
const leaf = {
|
|
6
|
+
name,
|
|
7
|
+
debug: jest.fn(),
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
warn: jest.fn(),
|
|
11
|
+
verbose: jest.fn(),
|
|
12
|
+
silly: jest.fn(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const children = {};
|
|
16
|
+
leaf.child = jest.fn((id) => {
|
|
17
|
+
if (!children[id]) children[id] = createLeaf(`${name}:${id}`);
|
|
18
|
+
return children[id];
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return leaf;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function buildLoggerMock() {
|
|
26
|
+
const scopes = {};
|
|
27
|
+
const root = createLeaf('root');
|
|
28
|
+
root.addScope = jest.fn();
|
|
29
|
+
|
|
30
|
+
return new Proxy(root, {
|
|
31
|
+
get(target, prop) {
|
|
32
|
+
if (typeof prop !== 'string') return target[prop];
|
|
33
|
+
if (Object.hasOwn(target, prop)) return target[prop];
|
|
34
|
+
if (!scopes[prop]) scopes[prop] = createLeaf(prop);
|
|
35
|
+
return scopes[prop];
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
buildLoggerMock,
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const FIXTURE_JOBS_DIR = path.join(__dirname, 'fixtureJobs');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/***************************************************************************************************
|
|
9
|
+
* WORKER FACTORY
|
|
10
|
+
***************************************************************************************************/
|
|
11
|
+
function buildWorker(Worker, queueName) {
|
|
12
|
+
class TestWorker extends Worker {
|
|
13
|
+
get jobDirectories() { return [FIXTURE_JOBS_DIR]; }
|
|
14
|
+
}
|
|
15
|
+
return new TestWorker(queueName || TestHelpers.Faker.Text.randomString());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/***************************************************************************************************
|
|
20
|
+
* BULL JOB BUILDER
|
|
21
|
+
***************************************************************************************************/
|
|
22
|
+
function buildFakeBullJob(overrides = {}) {
|
|
23
|
+
return {
|
|
24
|
+
name: 'TestJobA',
|
|
25
|
+
data: { id: TestHelpers.Faker.Text.randomString(), args: [] },
|
|
26
|
+
updateProgress: jest.fn(),
|
|
27
|
+
attemptsMade: 0,
|
|
28
|
+
opts: { attempts: 3 },
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
FIXTURE_JOBS_DIR,
|
|
36
|
+
buildFakeBullJob,
|
|
37
|
+
buildWorker,
|
|
38
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
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
|
+
});
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
let bullmqMock;
|
|
22
|
+
let getQueue;
|
|
23
|
+
let logger;
|
|
24
|
+
let randomUUID;
|
|
25
|
+
let restoreEnv;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.resetModules();
|
|
29
|
+
restoreEnv = setupJobQueueEnv();
|
|
30
|
+
|
|
31
|
+
bullmqMock = require('./helpers/bullmqMock');
|
|
32
|
+
({ getQueue } = require('../../lib/jobQueue/queue'));
|
|
33
|
+
({ logger } = require('../../lib/logger'));
|
|
34
|
+
({ randomUUID } = require('crypto'));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
restoreEnv();
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
function expectedConnection() {
|
|
45
|
+
return {
|
|
46
|
+
host: process.env.REDIS_BULL_HOST,
|
|
47
|
+
password: process.env.REDIS_BULL_PASSWORD,
|
|
48
|
+
port: process.env.REDIS_BULL_PORT,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
describe('getQueue', () => {
|
|
54
|
+
it ('returns a Queue with the given queueName', () => {
|
|
55
|
+
const name = TestHelpers.Faker.Text.randomString();
|
|
56
|
+
|
|
57
|
+
const queue = getQueue(name);
|
|
58
|
+
|
|
59
|
+
expect(queue.queueName).toEqual(name);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it ('memoizes by full queue name', () => {
|
|
63
|
+
const name = TestHelpers.Faker.Text.randomString();
|
|
64
|
+
|
|
65
|
+
const first = getQueue(name);
|
|
66
|
+
const second = getQueue(name);
|
|
67
|
+
|
|
68
|
+
expect(second).toBe(first);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it ('returns different instances for different names', () => {
|
|
72
|
+
const queueA = getQueue(TestHelpers.Faker.Text.randomString());
|
|
73
|
+
const queueB = getQueue(TestHelpers.Faker.Text.randomString());
|
|
74
|
+
|
|
75
|
+
expect(queueB).not.toBe(queueA);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it ('treats the same raw name under different NODE_ENVs as different queues', () => {
|
|
79
|
+
const name = TestHelpers.Faker.Text.randomString();
|
|
80
|
+
|
|
81
|
+
process.env.NODE_ENV = 'test';
|
|
82
|
+
const inTest = getQueue(name);
|
|
83
|
+
|
|
84
|
+
process.env.NODE_ENV = 'staging';
|
|
85
|
+
const inStaging = getQueue(name);
|
|
86
|
+
|
|
87
|
+
expect(inStaging).not.toBe(inTest);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
describe('Queue', () => {
|
|
93
|
+
let queueName;
|
|
94
|
+
let queue;
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
queueName = TestHelpers.Faker.Text.randomString();
|
|
98
|
+
queue = getQueue(queueName);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
describe('#queueName', () => {
|
|
103
|
+
it ('returns the raw queue name', () => {
|
|
104
|
+
expect(queue.queueName).toEqual(queueName);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
describe('#fullQueueName', () => {
|
|
110
|
+
it ('appends the current NODE_ENV', () => {
|
|
111
|
+
expect(queue.fullQueueName).toEqual(`${queueName}-test`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
describe('#logger', () => {
|
|
117
|
+
it ('uses the queue scope keyed by fullQueueName', () => {
|
|
118
|
+
const child = queue.logger;
|
|
119
|
+
|
|
120
|
+
expect(logger.queue.child).toHaveBeenCalledWith(`${queueName}-test`);
|
|
121
|
+
expect(child).toBe(logger.queue.child.mock.results[0].value);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
describe('lazy BullMQ.Queue construction', () => {
|
|
127
|
+
it ('does not construct the BullMQ queue until a method needs it', () => {
|
|
128
|
+
expect(bullmqMock.instances.queues).toHaveLength(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it ('constructs the BullMQ queue with fullQueueName and connection options on first use', async () => {
|
|
132
|
+
await queue.numQueuedJobs();
|
|
133
|
+
|
|
134
|
+
expect(bullmqMock.instances.queues).toHaveLength(1);
|
|
135
|
+
const instance = bullmqMock.instances.queues[0];
|
|
136
|
+
expect(instance.name).toEqual(`${queueName}-test`);
|
|
137
|
+
expect(instance.options).toEqual({ connection: expectedConnection() });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it ('reuses the same BullMQ queue across multiple method calls', async () => {
|
|
141
|
+
await queue.numQueuedJobs();
|
|
142
|
+
await queue.numActiveJobs();
|
|
143
|
+
await queue.close();
|
|
144
|
+
|
|
145
|
+
expect(bullmqMock.instances.queues).toHaveLength(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it ('registers an error handler on the BullMQ queue', async () => {
|
|
149
|
+
await queue.numQueuedJobs();
|
|
150
|
+
|
|
151
|
+
const instance = bullmqMock.instances.queues[0];
|
|
152
|
+
expect(instance.on).toHaveBeenCalledWith('error', expect.any(Function));
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
describe('#queueJob', () => {
|
|
158
|
+
it ('adds the job to the BullMQ queue with a generated UUID, args, and settings', async () => {
|
|
159
|
+
const id = TestHelpers.Faker.Text.randomString();
|
|
160
|
+
randomUUID.mockReturnValue(id);
|
|
161
|
+
const args = [TestHelpers.Faker.Text.randomString(), TestHelpers.Faker.Math.randomNumber()];
|
|
162
|
+
const settings = { attempts: TestHelpers.Faker.Math.randomNumber(10) };
|
|
163
|
+
const jobClass = { name: TestHelpers.Faker.Text.randomString() };
|
|
164
|
+
|
|
165
|
+
await queue.queueJob(jobClass, args, settings);
|
|
166
|
+
|
|
167
|
+
const instance = bullmqMock.instances.queues[0];
|
|
168
|
+
expect(instance.add).toHaveBeenCalledTimes(1);
|
|
169
|
+
expect(instance.add).toHaveBeenCalledWith(jobClass.name, { id, args }, settings);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
describe.each([
|
|
175
|
+
['close', 'close', []],
|
|
176
|
+
['pause', 'pause', []],
|
|
177
|
+
['resume', 'resume', []],
|
|
178
|
+
])('#%s', (method, bullMethod, expectedArgs) => {
|
|
179
|
+
it (`calls bullmq queue.${bullMethod}`, async () => {
|
|
180
|
+
await queue[method]();
|
|
181
|
+
|
|
182
|
+
const instance = bullmqMock.instances.queues[0];
|
|
183
|
+
expect(instance[bullMethod]).toHaveBeenCalledWith(...expectedArgs);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
describe('count-style queries', () => {
|
|
189
|
+
it.each([
|
|
190
|
+
['numQueuedJobs', 'count'],
|
|
191
|
+
['numActiveJobs', 'getActiveCount'],
|
|
192
|
+
['numFailedJobs', 'getFailedCount'],
|
|
193
|
+
])('#%s delegates to bullmq queue.%s and returns its value', async (method, bullMethod) => {
|
|
194
|
+
const expectedCount = TestHelpers.Faker.Math.randomNumber();
|
|
195
|
+
const queueInstance = (() => { queue[method](); return bullmqMock.instances.queues[0]; })();
|
|
196
|
+
queueInstance[bullMethod].mockResolvedValue(expectedCount);
|
|
197
|
+
|
|
198
|
+
const result = await queue[method]();
|
|
199
|
+
|
|
200
|
+
expect(result).toEqual(expectedCount);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
describe('#numWorkers', () => {
|
|
206
|
+
it ('returns the length of bullmq queue.getWorkers()', async () => {
|
|
207
|
+
await queue.numQueuedJobs();
|
|
208
|
+
const instance = bullmqMock.instances.queues[0];
|
|
209
|
+
const workers = Array.from({ length: TestHelpers.Faker.Math.randomNumber(10) }, () => ({}));
|
|
210
|
+
instance.getWorkers.mockResolvedValue(workers);
|
|
211
|
+
|
|
212
|
+
await expect(queue.numWorkers()).resolves.toEqual(workers.length);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
describe('clear methods', () => {
|
|
218
|
+
it ('#clearFailedJobs calls bullmq clean(0, null, "failed")', async () => {
|
|
219
|
+
await queue.clearFailedJobs();
|
|
220
|
+
|
|
221
|
+
const instance = bullmqMock.instances.queues[0];
|
|
222
|
+
expect(instance.clean).toHaveBeenCalledWith(0, null, 'failed');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it ('#clearCompletedJobs calls bullmq clean(0, null, "completed")', async () => {
|
|
226
|
+
await queue.clearCompletedJobs();
|
|
227
|
+
|
|
228
|
+
const instance = bullmqMock.instances.queues[0];
|
|
229
|
+
expect(instance.clean).toHaveBeenCalledWith(0, null, 'completed');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it ('#clearQueuedJobs calls bullmq drain()', async () => {
|
|
233
|
+
await queue.clearQueuedJobs();
|
|
234
|
+
|
|
235
|
+
const instance = bullmqMock.instances.queues[0];
|
|
236
|
+
expect(instance.drain).toHaveBeenCalled();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
describe('job-list queries', () => {
|
|
242
|
+
function buildBullJob(overrides = {}) {
|
|
243
|
+
return {
|
|
244
|
+
queue: { name: TestHelpers.Faker.Text.randomString() },
|
|
245
|
+
name: TestHelpers.Faker.Text.randomString(),
|
|
246
|
+
data: { args: [TestHelpers.Faker.Text.randomString()] },
|
|
247
|
+
id: TestHelpers.Faker.Text.randomString(),
|
|
248
|
+
attemptsMade: TestHelpers.Faker.Math.randomNumber(10),
|
|
249
|
+
attemptsStarted: TestHelpers.Faker.Math.randomNumber(10),
|
|
250
|
+
progress: TestHelpers.Faker.Math.randomNumber(100),
|
|
251
|
+
failedReason: TestHelpers.Faker.Text.randomString(),
|
|
252
|
+
...overrides,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function expectedShape(bullJob) {
|
|
257
|
+
return {
|
|
258
|
+
queueName: bullJob.queue.name,
|
|
259
|
+
name: bullJob.name,
|
|
260
|
+
args: bullJob.data,
|
|
261
|
+
id: bullJob.id,
|
|
262
|
+
attemptsMade: bullJob.attemptsMade,
|
|
263
|
+
attemptsStarted: bullJob.attemptsStarted,
|
|
264
|
+
progress: bullJob.progress,
|
|
265
|
+
failedReason: bullJob.failedReason,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
it ('#getQueuedJobs maps getJobs(["paused","wait","prioritized","delayed"]) to formatted shape', async () => {
|
|
271
|
+
const bullJob1 = buildBullJob();
|
|
272
|
+
const bullJob2 = buildBullJob();
|
|
273
|
+
await queue.numQueuedJobs();
|
|
274
|
+
const instance = bullmqMock.instances.queues[0];
|
|
275
|
+
instance.getJobs.mockResolvedValue([bullJob1, bullJob2]);
|
|
276
|
+
|
|
277
|
+
const result = await queue.getQueuedJobs();
|
|
278
|
+
|
|
279
|
+
expect(instance.getJobs).toHaveBeenCalledWith(['paused', 'wait', 'prioritized', 'delayed']);
|
|
280
|
+
expect(result).toEqual([expectedShape(bullJob1), expectedShape(bullJob2)]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it ('#getActiveJobs maps getActive() to formatted shape', async () => {
|
|
284
|
+
const bullJob = buildBullJob();
|
|
285
|
+
await queue.numQueuedJobs();
|
|
286
|
+
const instance = bullmqMock.instances.queues[0];
|
|
287
|
+
instance.getActive.mockResolvedValue([bullJob]);
|
|
288
|
+
|
|
289
|
+
const result = await queue.getActiveJobs();
|
|
290
|
+
|
|
291
|
+
expect(result).toEqual([expectedShape(bullJob)]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it ('#getFailedJobs maps getFailed() to formatted shape', async () => {
|
|
295
|
+
const bullJob = buildBullJob();
|
|
296
|
+
await queue.numQueuedJobs();
|
|
297
|
+
const instance = bullmqMock.instances.queues[0];
|
|
298
|
+
instance.getFailed.mockResolvedValue([bullJob]);
|
|
299
|
+
|
|
300
|
+
const result = await queue.getFailedJobs();
|
|
301
|
+
|
|
302
|
+
expect(result).toEqual([expectedShape(bullJob)]);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
describe('error handler', () => {
|
|
308
|
+
it ('forwards arguments from the bullmq error event to logger.error', async () => {
|
|
309
|
+
await queue.numQueuedJobs();
|
|
310
|
+
const instance = bullmqMock.instances.queues[0];
|
|
311
|
+
const errorListener = bullmqMock.listenerFor(instance, 'error');
|
|
312
|
+
const err = new Error(TestHelpers.Faker.Text.randomString());
|
|
313
|
+
|
|
314
|
+
errorListener(err);
|
|
315
|
+
|
|
316
|
+
const child = logger.queue.child.mock.results[0].value;
|
|
317
|
+
expect(child.error).toHaveBeenCalledWith(err);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|