@vida-global/core 2.3.5 → 2.4.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.
- package/lib/http/README.md +1 -1
- package/lib/http/client.js +2 -2
- package/lib/jobQueue/README.md +56 -17
- package/lib/jobQueue/abstractJob.js +39 -3
- package/lib/jobQueue/queue.js +28 -20
- package/lib/jobQueue/worker.js +69 -22
- package/lib/logger/README.md +12 -0
- package/lib/logger/index.js +24 -8
- package/lib/server/openApi/apiDocGenerator.js +1 -1
- package/package.json +1 -1
- package/test/http/client.test.js +21 -0
- package/test/jobQueue/abstractJob.test.js +64 -5
- package/test/jobQueue/helpers/bullmqMock.js +19 -16
- package/test/jobQueue/helpers/fixtureJobs/defaultJobForBeta.js +18 -0
- package/test/jobQueue/helpers/loggerMock.js +16 -5
- package/test/jobQueue/helpers/queue.js +2 -4
- package/test/jobQueue/helpers/worker.js +10 -3
- package/test/jobQueue/queue.test.js +80 -34
- package/test/jobQueue/worker.test.js +147 -21
- package/test/logger/index.test.js +113 -0
- package/test/server/apiDocGenerator.test.js +39 -0
|
@@ -106,12 +106,14 @@ describe('AbstractJob', () => {
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
describe('.queueSettings', () => {
|
|
109
|
-
it ('returns default retry settings', () => {
|
|
109
|
+
it ('returns default retry and history settings', () => {
|
|
110
110
|
const Subclass = defineJobSubclass(AbstractJob);
|
|
111
111
|
|
|
112
112
|
expect(Subclass.queueSettings()).toEqual({
|
|
113
|
-
attempts:
|
|
114
|
-
backoff:
|
|
113
|
+
attempts: 3,
|
|
114
|
+
backoff: { type: 'exponential', delay: 2000 },
|
|
115
|
+
removeOnComplete: { age: 86400, count: 1000 },
|
|
116
|
+
removeOnFail: { age: 604800 },
|
|
115
117
|
});
|
|
116
118
|
});
|
|
117
119
|
|
|
@@ -125,11 +127,34 @@ describe('AbstractJob', () => {
|
|
|
125
127
|
retryBackoffDelay,
|
|
126
128
|
});
|
|
127
129
|
|
|
128
|
-
expect(Subclass.queueSettings()).
|
|
130
|
+
expect(Subclass.queueSettings()).toMatchObject({
|
|
129
131
|
attempts: numRetries,
|
|
130
132
|
backoff: { type: retryBackoffType, delay: retryBackoffDelay },
|
|
131
133
|
});
|
|
132
134
|
});
|
|
135
|
+
|
|
136
|
+
it ('reflects subclass overrides for how much history to keep', () => {
|
|
137
|
+
const Subclass = defineJobSubclass(AbstractJob, {
|
|
138
|
+
keepCompleted: true,
|
|
139
|
+
keepFailed: 50,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(Subclass.queueSettings()).toMatchObject({
|
|
143
|
+
removeOnComplete: true,
|
|
144
|
+
removeOnFail: 50,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
describe('history defaults', () => {
|
|
151
|
+
it ('.keepCompleted bounds by age and count', () => {
|
|
152
|
+
expect(AbstractJob.keepCompleted).toEqual({ age: 86400, count: 1000 });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it ('.keepFailed keeps a week', () => {
|
|
156
|
+
expect(AbstractJob.keepFailed).toEqual({ age: 604800 });
|
|
157
|
+
});
|
|
133
158
|
});
|
|
134
159
|
|
|
135
160
|
|
|
@@ -196,6 +221,40 @@ describe('AbstractJob', () => {
|
|
|
196
221
|
});
|
|
197
222
|
|
|
198
223
|
|
|
224
|
+
describe('attempt tracking', () => {
|
|
225
|
+
it ('exposes the attempt the worker handed it', async () => {
|
|
226
|
+
const Subclass = defineJobSubclass(AbstractJob);
|
|
227
|
+
const job = new Subclass();
|
|
228
|
+
|
|
229
|
+
await job._run('id', [], jest.fn(), { attempt: 2, maxAttempts: 3 });
|
|
230
|
+
|
|
231
|
+
expect(job.attempt).toEqual(2);
|
|
232
|
+
expect(job.maxAttempts).toEqual(3);
|
|
233
|
+
expect(job.isFinalAttempt).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it ('is on its final attempt once the attempts are spent', async () => {
|
|
237
|
+
const Subclass = defineJobSubclass(AbstractJob);
|
|
238
|
+
const job = new Subclass();
|
|
239
|
+
|
|
240
|
+
await job._run('id', [], jest.fn(), { attempt: 3, maxAttempts: 3 });
|
|
241
|
+
|
|
242
|
+
expect(job.isFinalAttempt).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it ('reads as a single final attempt when the worker said nothing', async () => {
|
|
246
|
+
const Subclass = defineJobSubclass(AbstractJob);
|
|
247
|
+
const job = new Subclass();
|
|
248
|
+
|
|
249
|
+
await job._run('id', [], jest.fn());
|
|
250
|
+
|
|
251
|
+
expect(job.attempt).toEqual(1);
|
|
252
|
+
expect(job.maxAttempts).toEqual(1);
|
|
253
|
+
expect(job.isFinalAttempt).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
|
|
199
258
|
describe('#updateProgress', () => {
|
|
200
259
|
it ('forwards all arguments to the stored callback', async () => {
|
|
201
260
|
const Subclass = defineJobSubclass(AbstractJob);
|
|
@@ -269,7 +328,7 @@ describe('AbstractJob', () => {
|
|
|
269
328
|
const id = TestHelpers.Faker.Text.randomString();
|
|
270
329
|
await job._run(id, [], jest.fn());
|
|
271
330
|
|
|
272
|
-
const scope = job.loggerLibrary
|
|
331
|
+
const scope = job.loggerLibrary.scope(Subclass.name);
|
|
273
332
|
const child = job.logger;
|
|
274
333
|
|
|
275
334
|
expect(scope.child).toHaveBeenCalledWith(id);
|
|
@@ -12,20 +12,22 @@ class MockBullQueue {
|
|
|
12
12
|
this.name = name;
|
|
13
13
|
this.options = options;
|
|
14
14
|
|
|
15
|
-
this.add
|
|
16
|
-
this.close
|
|
17
|
-
this.pause
|
|
18
|
-
this.resume
|
|
19
|
-
this.
|
|
20
|
-
this.getJobs
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
15
|
+
this.add = jest.fn();
|
|
16
|
+
this.close = jest.fn();
|
|
17
|
+
this.pause = jest.fn();
|
|
18
|
+
this.resume = jest.fn();
|
|
19
|
+
this.getJobCounts = jest.fn();
|
|
20
|
+
this.getJobs = jest.fn();
|
|
21
|
+
this.getActive = jest.fn();
|
|
22
|
+
this.getFailed = jest.fn();
|
|
23
|
+
this.getJob = jest.fn();
|
|
24
|
+
this.clean = jest.fn();
|
|
25
|
+
this.drain = jest.fn();
|
|
26
|
+
this.getWorkers = jest.fn();
|
|
27
|
+
this.setGlobalConcurrency = jest.fn();
|
|
28
|
+
this.getGlobalConcurrency = jest.fn();
|
|
29
|
+
this.removeGlobalConcurrency = jest.fn();
|
|
30
|
+
this.on = jest.fn();
|
|
29
31
|
|
|
30
32
|
instances.queues.push(this);
|
|
31
33
|
}
|
|
@@ -38,8 +40,9 @@ class MockBullWorker {
|
|
|
38
40
|
this.processor = processor;
|
|
39
41
|
this.options = options;
|
|
40
42
|
|
|
41
|
-
this.run
|
|
42
|
-
this.
|
|
43
|
+
this.run = jest.fn();
|
|
44
|
+
this.close = jest.fn();
|
|
45
|
+
this.on = jest.fn();
|
|
43
46
|
|
|
44
47
|
instances.workers.push(this);
|
|
45
48
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { AbstractJob } = require('../../../../lib/jobQueue/abstractJob');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// Opts in as the fallback for one specific queue, which is what lets a worker consume a payload
|
|
5
|
+
// whose name matches no registered job — the shape bull writes for an unnamed job.
|
|
6
|
+
class DefaultJobForBeta extends AbstractJob {
|
|
7
|
+
static defaultFor = 'beta';
|
|
8
|
+
|
|
9
|
+
static queueName() { return 'beta'; }
|
|
10
|
+
|
|
11
|
+
async run() {
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
DefaultJobForBeta,
|
|
18
|
+
}
|
|
@@ -22,17 +22,28 @@ function createLeaf(name) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
// Registration answers to both spellings, as the real Logger does. An unregistered name only
|
|
26
|
+
// resolves if it is lowercase — a double that invented a scope for any spelling would hide the bug
|
|
27
|
+
// where production code indexes with a class name and silently gets undefined.
|
|
25
28
|
function buildLoggerMock() {
|
|
26
29
|
const scopes = {};
|
|
27
30
|
const root = createLeaf('root');
|
|
28
|
-
|
|
31
|
+
|
|
32
|
+
const resolveScope = (name) => {
|
|
33
|
+
const key = `${name}`.toLowerCase();
|
|
34
|
+
return scopes[name] = scopes[key] ||= createLeaf(key);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
root.addScope = jest.fn(resolveScope);
|
|
38
|
+
root.scope = jest.fn(resolveScope);
|
|
29
39
|
|
|
30
40
|
return new Proxy(root, {
|
|
31
41
|
get(target, prop) {
|
|
32
|
-
if (typeof prop !== 'string')
|
|
33
|
-
if (Object.hasOwn(target, prop))
|
|
34
|
-
if (
|
|
35
|
-
return
|
|
42
|
+
if (typeof prop !== 'string') return target[prop];
|
|
43
|
+
if (Object.hasOwn(target, prop)) return target[prop];
|
|
44
|
+
if (scopes[prop]) return scopes[prop];
|
|
45
|
+
if (prop !== prop.toLowerCase()) return undefined;
|
|
46
|
+
return resolveScope(prop);
|
|
36
47
|
}
|
|
37
48
|
});
|
|
38
49
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/***************************************************************************************************
|
|
2
2
|
* QUEUE FACTORY
|
|
3
3
|
* bullmqMock/loggerMock are required lazily so the mock instances align with whatever the test
|
|
4
|
-
* re-requires after jest.resetModules(). MockQueue injects the BullMQ
|
|
4
|
+
* re-requires after jest.resetModules(). MockQueue injects the BullMQ and logger seams.
|
|
5
5
|
***************************************************************************************************/
|
|
6
6
|
function buildQueue(queueName) {
|
|
7
7
|
const { Queue } = require('../../../lib/jobQueue/queue');
|
|
8
8
|
const bullmqMock = require('./bullmqMock');
|
|
9
9
|
const { buildLoggerMock } = require('./loggerMock');
|
|
10
10
|
|
|
11
|
-
const loggerMock
|
|
12
|
-
const idGenerator = jest.fn();
|
|
11
|
+
const loggerMock = buildLoggerMock();
|
|
13
12
|
|
|
14
13
|
class MockQueue extends Queue {
|
|
15
14
|
get queueClass() { return bullmqMock.Queue; }
|
|
16
|
-
get idGenerator() { return idGenerator; }
|
|
17
15
|
get loggerLibrary() { return loggerMock; }
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -10,22 +10,28 @@ const FIXTURE_JOBS_DIR = path.join(__dirname, 'fixtureJobs');
|
|
|
10
10
|
* bullmqMock/loggerMock/apmMock are required lazily so the mock instances align with whatever the
|
|
11
11
|
* test re-requires after jest.resetModules() (used to reset the worker's module-level registeredJobs).
|
|
12
12
|
***************************************************************************************************/
|
|
13
|
-
function buildWorker(Worker, queueName) {
|
|
13
|
+
function buildWorker(Worker, queueName, options) {
|
|
14
14
|
const bullmqMock = require('./bullmqMock');
|
|
15
15
|
const { buildLoggerMock } = require('./loggerMock');
|
|
16
16
|
const { buildApmMock } = require('./apmMock');
|
|
17
17
|
|
|
18
18
|
const loggerMock = buildLoggerMock();
|
|
19
19
|
const apmMock = buildApmMock();
|
|
20
|
+
// listen() applies the queue limits, which would otherwise reach a real BullMQ queue.
|
|
21
|
+
const queueMock = {
|
|
22
|
+
setMaximumConcurrency: jest.fn(),
|
|
23
|
+
removeMaximumConcurrency: jest.fn(),
|
|
24
|
+
};
|
|
20
25
|
|
|
21
26
|
class TestWorker extends Worker {
|
|
22
27
|
get jobDirectories() { return [FIXTURE_JOBS_DIR]; }
|
|
23
28
|
get workerClass() { return bullmqMock.Worker; }
|
|
24
29
|
get loggerLibrary() { return loggerMock; }
|
|
25
30
|
get apm() { return apmMock; }
|
|
31
|
+
get queue() { return queueMock; }
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
return new TestWorker(queueName || TestHelpers.Faker.Text.randomString());
|
|
34
|
+
return new TestWorker(queueName || TestHelpers.Faker.Text.randomString(), options);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
|
|
@@ -35,7 +41,8 @@ function buildWorker(Worker, queueName) {
|
|
|
35
41
|
function buildFakeBullJob(overrides = {}) {
|
|
36
42
|
return {
|
|
37
43
|
name: 'TestJobA',
|
|
38
|
-
|
|
44
|
+
id: TestHelpers.Faker.Text.randomString(),
|
|
45
|
+
data: { args: [] },
|
|
39
46
|
updateProgress: jest.fn(),
|
|
40
47
|
attemptsMade: 0,
|
|
41
48
|
opts: { attempts: 3 },
|
|
@@ -110,7 +110,7 @@ describe('Queue', () => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
it ('constructs the BullMQ queue with fullQueueName and connection options on first use', async () => {
|
|
113
|
-
await queue.
|
|
113
|
+
await queue.getJobCounts();
|
|
114
114
|
|
|
115
115
|
expect(bullmqMock.instances.queues).toHaveLength(1);
|
|
116
116
|
const instance = bullmqMock.instances.queues[0];
|
|
@@ -119,15 +119,15 @@ describe('Queue', () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
it ('reuses the same BullMQ queue across multiple method calls', async () => {
|
|
122
|
-
await queue.
|
|
123
|
-
await queue.
|
|
122
|
+
await queue.getJobCounts();
|
|
123
|
+
await queue.pause();
|
|
124
124
|
await queue.close();
|
|
125
125
|
|
|
126
126
|
expect(bullmqMock.instances.queues).toHaveLength(1);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
it ('registers an error handler on the BullMQ queue', async () => {
|
|
130
|
-
await queue.
|
|
130
|
+
await queue.getJobCounts();
|
|
131
131
|
|
|
132
132
|
const instance = bullmqMock.instances.queues[0];
|
|
133
133
|
expect(instance.on).toHaveBeenCalledWith('error', expect.any(Function));
|
|
@@ -136,9 +136,7 @@ describe('Queue', () => {
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
describe('#queueJob', () => {
|
|
139
|
-
it ('adds the job to the BullMQ queue with
|
|
140
|
-
const id = TestHelpers.Faker.Text.randomString();
|
|
141
|
-
queue.idGenerator.mockReturnValue(id);
|
|
139
|
+
it ('adds the job to the BullMQ queue with its args and settings', async () => {
|
|
142
140
|
const args = [TestHelpers.Faker.Text.randomString(), TestHelpers.Faker.Math.randomNumber()];
|
|
143
141
|
const settings = { attempts: TestHelpers.Faker.Math.randomNumber(10) };
|
|
144
142
|
const jobClass = { name: TestHelpers.Faker.Text.randomString() };
|
|
@@ -147,7 +145,7 @@ describe('Queue', () => {
|
|
|
147
145
|
|
|
148
146
|
const instance = bullmqMock.instances.queues[0];
|
|
149
147
|
expect(instance.add).toHaveBeenCalledTimes(1);
|
|
150
|
-
expect(instance.add).toHaveBeenCalledWith(jobClass.name, {
|
|
148
|
+
expect(instance.add).toHaveBeenCalledWith(jobClass.name, { args }, settings);
|
|
151
149
|
});
|
|
152
150
|
});
|
|
153
151
|
|
|
@@ -166,26 +164,20 @@ describe('Queue', () => {
|
|
|
166
164
|
});
|
|
167
165
|
|
|
168
166
|
|
|
169
|
-
describe('
|
|
170
|
-
it
|
|
171
|
-
|
|
172
|
-
[
|
|
173
|
-
|
|
174
|
-
])('#%s delegates to bullmq queue.%s and returns its value', async (method, bullMethod) => {
|
|
175
|
-
const expectedCount = TestHelpers.Faker.Math.randomNumber();
|
|
176
|
-
const queueInstance = (() => { queue[method](); return bullmqMock.instances.queues[0]; })();
|
|
177
|
-
queueInstance[bullMethod].mockResolvedValue(expectedCount);
|
|
167
|
+
describe('#getJobCounts', () => {
|
|
168
|
+
it ('returns what bullmq reports', async () => {
|
|
169
|
+
const counts = { waiting: 1, active: 2, completed: 3, failed: 4, delayed: 5, paused: 6 };
|
|
170
|
+
const queueInstance = (() => { queue.getJobCounts(); return bullmqMock.instances.queues[0]; })();
|
|
171
|
+
queueInstance.getJobCounts.mockResolvedValue(counts);
|
|
178
172
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
expect(result).toEqual(expectedCount);
|
|
173
|
+
await expect(queue.getJobCounts()).resolves.toEqual(counts);
|
|
182
174
|
});
|
|
183
175
|
});
|
|
184
176
|
|
|
185
177
|
|
|
186
178
|
describe('#numWorkers', () => {
|
|
187
179
|
it ('returns the length of bullmq queue.getWorkers()', async () => {
|
|
188
|
-
await queue.
|
|
180
|
+
await queue.getJobCounts();
|
|
189
181
|
const instance = bullmqMock.instances.queues[0];
|
|
190
182
|
const workers = Array.from({ length: TestHelpers.Faker.Math.randomNumber(10) }, () => ({}));
|
|
191
183
|
instance.getWorkers.mockResolvedValue(workers);
|
|
@@ -195,22 +187,29 @@ describe('Queue', () => {
|
|
|
195
187
|
});
|
|
196
188
|
|
|
197
189
|
|
|
198
|
-
describe('
|
|
199
|
-
it ('
|
|
200
|
-
|
|
190
|
+
describe('#clean', () => {
|
|
191
|
+
it ('passes the grace period, limit and type through in bullmq order', async () => {
|
|
192
|
+
const grace = TestHelpers.Faker.Math.randomNumber(100000);
|
|
193
|
+
const limit = TestHelpers.Faker.Math.randomNumber(100);
|
|
194
|
+
|
|
195
|
+
await queue.clean(grace, limit, 'failed');
|
|
201
196
|
|
|
202
197
|
const instance = bullmqMock.instances.queues[0];
|
|
203
|
-
expect(instance.clean).toHaveBeenCalledWith(
|
|
198
|
+
expect(instance.clean).toHaveBeenCalledWith(grace, limit, 'failed');
|
|
204
199
|
});
|
|
205
200
|
|
|
206
|
-
it ('
|
|
207
|
-
|
|
201
|
+
it ('returns the jobs bullmq removed', async () => {
|
|
202
|
+
const removed = [TestHelpers.Faker.Text.randomString()];
|
|
203
|
+
const queueInstance = (() => { queue.clean(0, 1, 'failed'); return bullmqMock.instances.queues[0]; })();
|
|
204
|
+
queueInstance.clean.mockResolvedValue(removed);
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
expect(instance.clean).toHaveBeenCalledWith(0, null, 'completed');
|
|
206
|
+
await expect(queue.clean(0, 1, 'failed')).resolves.toEqual(removed);
|
|
211
207
|
});
|
|
208
|
+
});
|
|
212
209
|
|
|
213
|
-
|
|
210
|
+
|
|
211
|
+
describe('#clearQueuedJobs', () => {
|
|
212
|
+
it ('calls bullmq drain()', async () => {
|
|
214
213
|
await queue.clearQueuedJobs();
|
|
215
214
|
|
|
216
215
|
const instance = bullmqMock.instances.queues[0];
|
|
@@ -219,6 +218,33 @@ describe('Queue', () => {
|
|
|
219
218
|
});
|
|
220
219
|
|
|
221
220
|
|
|
221
|
+
describe('maximum concurrency', () => {
|
|
222
|
+
it ('sets the queue maximum', async () => {
|
|
223
|
+
const maximum = TestHelpers.Faker.Math.randomNumber(20);
|
|
224
|
+
|
|
225
|
+
await queue.setMaximumConcurrency(maximum);
|
|
226
|
+
|
|
227
|
+
const instance = bullmqMock.instances.queues[0];
|
|
228
|
+
expect(instance.setGlobalConcurrency).toHaveBeenCalledWith(maximum);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it ('reads the queue maximum back', async () => {
|
|
232
|
+
const maximum = TestHelpers.Faker.Math.randomNumber(20);
|
|
233
|
+
const queueInstance = (() => { queue.maximumConcurrency(); return bullmqMock.instances.queues[0]; })();
|
|
234
|
+
queueInstance.getGlobalConcurrency.mockResolvedValue(maximum);
|
|
235
|
+
|
|
236
|
+
await expect(queue.maximumConcurrency()).resolves.toEqual(maximum);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it ('removes the queue maximum', async () => {
|
|
240
|
+
await queue.removeMaximumConcurrency();
|
|
241
|
+
|
|
242
|
+
const instance = bullmqMock.instances.queues[0];
|
|
243
|
+
expect(instance.removeGlobalConcurrency).toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
|
|
222
248
|
describe('job-list queries', () => {
|
|
223
249
|
function buildBullJob(overrides = {}) {
|
|
224
250
|
return {
|
|
@@ -251,7 +277,7 @@ describe('Queue', () => {
|
|
|
251
277
|
it ('#getQueuedJobs maps getJobs(["paused","wait","prioritized","delayed"]) to formatted shape', async () => {
|
|
252
278
|
const bullJob1 = buildBullJob();
|
|
253
279
|
const bullJob2 = buildBullJob();
|
|
254
|
-
await queue.
|
|
280
|
+
await queue.getJobCounts();
|
|
255
281
|
const instance = bullmqMock.instances.queues[0];
|
|
256
282
|
instance.getJobs.mockResolvedValue([bullJob1, bullJob2]);
|
|
257
283
|
|
|
@@ -263,7 +289,7 @@ describe('Queue', () => {
|
|
|
263
289
|
|
|
264
290
|
it ('#getActiveJobs maps getActive() to formatted shape', async () => {
|
|
265
291
|
const bullJob = buildBullJob();
|
|
266
|
-
await queue.
|
|
292
|
+
await queue.getJobCounts();
|
|
267
293
|
const instance = bullmqMock.instances.queues[0];
|
|
268
294
|
instance.getActive.mockResolvedValue([bullJob]);
|
|
269
295
|
|
|
@@ -274,7 +300,7 @@ describe('Queue', () => {
|
|
|
274
300
|
|
|
275
301
|
it ('#getFailedJobs maps getFailed() to formatted shape', async () => {
|
|
276
302
|
const bullJob = buildBullJob();
|
|
277
|
-
await queue.
|
|
303
|
+
await queue.getJobCounts();
|
|
278
304
|
const instance = bullmqMock.instances.queues[0];
|
|
279
305
|
instance.getFailed.mockResolvedValue([bullJob]);
|
|
280
306
|
|
|
@@ -282,12 +308,32 @@ describe('Queue', () => {
|
|
|
282
308
|
|
|
283
309
|
expect(result).toEqual([expectedShape(bullJob)]);
|
|
284
310
|
});
|
|
311
|
+
|
|
312
|
+
// The way a single failed job gets fetched for inspection or replay.
|
|
313
|
+
it ('#getJob maps one job by id to the formatted shape', async () => {
|
|
314
|
+
const bullJob = buildBullJob();
|
|
315
|
+
await queue.getJobCounts();
|
|
316
|
+
const instance = bullmqMock.instances.queues[0];
|
|
317
|
+
instance.getJob.mockResolvedValue(bullJob);
|
|
318
|
+
|
|
319
|
+
const result = await queue.getJob(bullJob.id);
|
|
320
|
+
|
|
321
|
+
expect(instance.getJob).toHaveBeenCalledWith(bullJob.id);
|
|
322
|
+
expect(result).toEqual(expectedShape(bullJob));
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it ('#getJob returns null when there is no such job', async () => {
|
|
326
|
+
await queue.getJobCounts();
|
|
327
|
+
bullmqMock.instances.queues[0].getJob.mockResolvedValue(undefined);
|
|
328
|
+
|
|
329
|
+
await expect(queue.getJob('nope')).resolves.toBeNull();
|
|
330
|
+
});
|
|
285
331
|
});
|
|
286
332
|
|
|
287
333
|
|
|
288
334
|
describe('error handler', () => {
|
|
289
335
|
it ('forwards arguments from the bullmq error event to logger.error', async () => {
|
|
290
|
-
await queue.
|
|
336
|
+
await queue.getJobCounts();
|
|
291
337
|
const instance = bullmqMock.instances.queues[0];
|
|
292
338
|
const errorListener = bullmqMock.listenerFor(instance, 'error');
|
|
293
339
|
const err = new Error(TestHelpers.Faker.Text.randomString());
|