@vida-global/core 2.3.6 → 2.4.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/index.js +10 -8
- package/lib/appDependencies/index.js +48 -0
- 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/README.md +32 -1
- package/lib/server/controllerMixins/renderer.js +11 -0
- package/lib/server/doc/renderer.md +23 -0
- package/package.json +1 -1
- package/test/appDependencies/appDependencies.test.js +93 -0
- 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/controllerMixins/renderer.test.js +59 -0
package/lib/server/README.md
CHANGED
|
@@ -130,7 +130,38 @@ See [Callbacks](./doc/callbacks.md) — before/after action callbacks and how su
|
|
|
130
130
|
|
|
131
131
|
## Rendering responses
|
|
132
132
|
|
|
133
|
-
See [Rendering responses](./doc/renderer.md) — the render envelope, status-code helpers, error handling, and Server-Sent Events / streaming.
|
|
133
|
+
See [Rendering responses](./doc/renderer.md) — the render envelope, status-code helpers, binary and file responses, error handling, and Server-Sent Events / streaming.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
## App dependencies
|
|
137
|
+
|
|
138
|
+
`AppDependencies` is a registry for the long-lived collaborators an application builds at boot — helper libraries, service objects, queues. Code that runs far from that wiring reaches them by name instead of having them threaded through every call.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
// at boot
|
|
142
|
+
const { AppDependencies } = require('@vida-global/core');
|
|
143
|
+
|
|
144
|
+
AppDependencies.setDependency('billingHelper', billingHelper);
|
|
145
|
+
AppDependencies.installOn(MyBaseController);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`installOn` exposes every registered name as a getter on that controller class, so controllers read the dependency directly:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
class InvoicesController extends MyBaseController {
|
|
152
|
+
async getIndex() {
|
|
153
|
+
return await this.billingHelper.invoicesFor(this.currentUser);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Anything else reads them off the registry:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
const helper = AppDependencies.get('billingHelper'); // null when nothing is registered
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Registration order does not matter — a dependency registered after `installOn` still gets its accessor. A controller that already defines the name keeps its own property. `reset()` clears the registry, which tests need so their doubles do not leak into the next one.
|
|
134
165
|
|
|
135
166
|
|
|
136
167
|
## Documentation
|
|
@@ -244,6 +244,17 @@ const InstanceMethods = {
|
|
|
244
244
|
/***********************************************************************************************
|
|
245
245
|
* FILE RENDERING
|
|
246
246
|
***********************************************************************************************/
|
|
247
|
+
renderBinary(body, { contentType, filename } = {}) {
|
|
248
|
+
if (this.rendered) return;
|
|
249
|
+
if (this.isStreaming) return;
|
|
250
|
+
|
|
251
|
+
this._response.setHeader('Content-Type', contentType);
|
|
252
|
+
if (filename) this._response.attachment(filename);
|
|
253
|
+
this._send(body);
|
|
254
|
+
this.markRendered();
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
|
|
247
258
|
renderFile(filePath, { contentType, cacheControl, maxAge } = {}) {
|
|
248
259
|
if (!filePath) throw new Error('renderFile requires a filePath');
|
|
249
260
|
const resolvedType = contentType || guessContentType(filePath);
|
|
@@ -70,6 +70,29 @@ Don't set `this.statusCode` directly — use the helper that pairs the status wi
|
|
|
70
70
|
After a render helper runs, the action does not need to return — the response has already been sent.
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
## Binary responses
|
|
74
|
+
|
|
75
|
+
`render` JSON encodes its body, which corrupts anything that is not text. Use `renderBinary` for a
|
|
76
|
+
body you already hold in memory — a buffer proxied from another service, a generated document.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
async getRecordRecording() {
|
|
80
|
+
const { body, contentType } = await fetchRecording(sid);
|
|
81
|
+
this.renderBinary(body, { contentType });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Naming a `filename` sets `Content-Disposition`, so the browser downloads the body instead of
|
|
86
|
+
displaying it:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
this.renderBinary(csv, { contentType: 'text/csv', filename: 'report.csv' });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
To send a file from disk rather than from memory, use `renderFile(path, { contentType,
|
|
93
|
+
cacheControl, maxAge })` instead — it streams and guesses the content type from the extension.
|
|
94
|
+
|
|
95
|
+
|
|
73
96
|
## Error handling
|
|
74
97
|
|
|
75
98
|
```js
|
package/package.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { AppDependencies } = require('../../lib/appDependencies');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FooController {}
|
|
5
|
+
class BarController {}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
afterEach(() => AppDependencies.reset());
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
describe('AppDependencies', () => {
|
|
12
|
+
describe('#get', () => {
|
|
13
|
+
it ('returns what was registered', () => {
|
|
14
|
+
const helper = { name: 'helper' };
|
|
15
|
+
|
|
16
|
+
AppDependencies.setDependency('myHelper', helper);
|
|
17
|
+
|
|
18
|
+
expect(AppDependencies.get('myHelper')).toBe(helper);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// Null rather than undefined, so a missing dependency reads the same as one registered as
|
|
23
|
+
// nothing and callers can test it with a single check.
|
|
24
|
+
it ('returns null for a name nothing registered', () => {
|
|
25
|
+
expect(AppDependencies.get('nothingHere')).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
it ('replaces a name that is registered twice', () => {
|
|
30
|
+
AppDependencies.setDependency('myHelper', 'first');
|
|
31
|
+
AppDependencies.setDependency('myHelper', 'second');
|
|
32
|
+
|
|
33
|
+
expect(AppDependencies.get('myHelper')).toBe('second');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
describe('#reset', () => {
|
|
39
|
+
it ('forgets everything registered', () => {
|
|
40
|
+
AppDependencies.setDependency('myHelper', 'something');
|
|
41
|
+
|
|
42
|
+
AppDependencies.reset();
|
|
43
|
+
|
|
44
|
+
expect(AppDependencies.get('myHelper')).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
describe('#installOn', () => {
|
|
50
|
+
it ('exposes what was registered before the install as a getter', () => {
|
|
51
|
+
AppDependencies.setDependency('myHelper', 'early');
|
|
52
|
+
|
|
53
|
+
AppDependencies.installOn(FooController);
|
|
54
|
+
|
|
55
|
+
expect(new FooController().myHelper).toBe('early');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
// Wiring order must not matter: a dependency registered after the install still appears.
|
|
60
|
+
it ('exposes what is registered after the install', () => {
|
|
61
|
+
AppDependencies.installOn(FooController);
|
|
62
|
+
|
|
63
|
+
AppDependencies.setDependency('lateHelper', 'late');
|
|
64
|
+
|
|
65
|
+
expect(new FooController().lateHelper).toBe('late');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
it ('reads through, so a later replacement is picked up', () => {
|
|
70
|
+
AppDependencies.installOn(FooController);
|
|
71
|
+
AppDependencies.setDependency('myHelper', 'first');
|
|
72
|
+
const controller = new FooController();
|
|
73
|
+
|
|
74
|
+
AppDependencies.setDependency('myHelper', 'second');
|
|
75
|
+
|
|
76
|
+
expect(controller.myHelper).toBe('second');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// A controller that defines the name itself keeps its own.
|
|
81
|
+
it ('does not overwrite a property the controller already has', () => {
|
|
82
|
+
Object.defineProperty(BarController.prototype, 'ownProperty', {
|
|
83
|
+
configurable: true,
|
|
84
|
+
get() { return 'mine' },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
AppDependencies.setDependency('ownProperty', 'theirs');
|
|
88
|
+
AppDependencies.installOn(BarController);
|
|
89
|
+
|
|
90
|
+
expect(new BarController().ownProperty).toBe('mine');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
package/test/http/client.test.js
CHANGED
|
@@ -109,6 +109,27 @@ describe('HttpClient', () => {
|
|
|
109
109
|
expect.objectContaining({ body: JSON.stringify(body) }),
|
|
110
110
|
);
|
|
111
111
|
});
|
|
112
|
+
|
|
113
|
+
it('sends string request bodies without JSON serialization', async () => {
|
|
114
|
+
const body = 'firstname=Jane&lastname=Doe';
|
|
115
|
+
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
116
|
+
|
|
117
|
+
await client[methodName](Helpers.apiUrl, { body, headers });
|
|
118
|
+
|
|
119
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
120
|
+
Helpers.apiUrl,
|
|
121
|
+
expect.objectContaining({ body, headers: expect.objectContaining(headers) }),
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('preserves empty string request bodies', async () => {
|
|
126
|
+
await client[methodName](Helpers.apiUrl, { body: '' });
|
|
127
|
+
|
|
128
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
129
|
+
Helpers.apiUrl,
|
|
130
|
+
expect.objectContaining({ body: '' }),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
if (behavior.supportsParams) {
|
|
@@ -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 },
|