ac-bootstrap-bull 3.0.12 → 3.0.14
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/.github/workflows/node.js.yml +34 -0
- package/CHANGELOG.md +32 -0
- package/Makefile +2 -2
- package/README.md +2 -0
- package/package.json +15 -6
- package/test/index.test.js +267 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ master ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ master ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
|
|
14
|
+
build:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
|
|
21
|
+
strategy:
|
|
22
|
+
matrix:
|
|
23
|
+
node-version: [22.x, 24.x]
|
|
24
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v5
|
|
28
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
29
|
+
uses: actions/setup-node@v5
|
|
30
|
+
with:
|
|
31
|
+
node-version: ${{ matrix.node-version }}
|
|
32
|
+
|
|
33
|
+
- run: yarn install
|
|
34
|
+
- run: yarn run test
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## [3.0.14](https://github.com/admiralcloud/ac-bootstrap-bull/compare/v3.0.13..v3.0.14) (2026-04-17 07:53:09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fix
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
* **App:** Package updates | MP | [50bf8c77a3463c672748752256b47cbdd0979d9a](https://github.com/admiralcloud/ac-bootstrap-bull/commit/50bf8c77a3463c672748752256b47cbdd0979d9a)
|
|
8
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
9
|
+
## [3.0.13](https://github.com/admiralcloud/ac-bootstrap-bull/compare/v3.0.12..v3.0.13) (2026-04-04 10:49:38)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fix
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
* **App:** Package updates | MP | [f3c59d460e36ec89cddf9ecd0723f31d57892e4e](https://github.com/admiralcloud/ac-bootstrap-bull/commit/f3c59d460e36ec89cddf9ecd0723f31d57892e4e)
|
|
16
|
+
Package udpates
|
|
17
|
+
Related issues:
|
|
18
|
+
* **App:** Package updates | MP | [44274e75c86c4dc5dae9b831dd63a492d83a0117](https://github.com/admiralcloud/ac-bootstrap-bull/commit/44274e75c86c4dc5dae9b831dd63a492d83a0117)
|
|
19
|
+
Package updates
|
|
20
|
+
Related issues:
|
|
21
|
+
### Tests
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
* **App:** Added tests | MP | [4046d6909ae3525ec71f0bd60ba9b834b935dc08](https://github.com/admiralcloud/ac-bootstrap-bull/commit/4046d6909ae3525ec71f0bd60ba9b834b935dc08)
|
|
25
|
+
Added tests
|
|
26
|
+
Related issues:
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
* **App:** Added badge | MP | [83793ee1d6d33029a05d4ca86aa6cf049ec1c29f](https://github.com/admiralcloud/ac-bootstrap-bull/commit/83793ee1d6d33029a05d4ca86aa6cf049ec1c29f)
|
|
31
|
+
Added badge for CI
|
|
32
|
+
Related issues:
|
|
1
33
|
|
|
2
34
|
## [3.0.12](https://github.com/admiralcloud/ac-bootstrap-bull/compare/v3.0.11..v3.0.12) (2026-03-21 11:23:53)
|
|
3
35
|
|
package/Makefile
CHANGED
|
@@ -2,10 +2,10 @@ MOCHA_OPTS= --slow 0 -A
|
|
|
2
2
|
REPORTER = spec
|
|
3
3
|
|
|
4
4
|
lint-fix:
|
|
5
|
-
./node_modules/.bin/eslint --fix
|
|
5
|
+
./node_modules/.bin/eslint --fix
|
|
6
6
|
|
|
7
7
|
lint-check:
|
|
8
|
-
./node_modules/.bin/eslint
|
|
8
|
+
./node_modules/.bin/eslint
|
|
9
9
|
|
|
10
10
|
commit:
|
|
11
11
|
@node ./node_modules/ac-semantic-release/lib/commit.js
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# AdmiralCloud Bull Queue List
|
|
2
2
|
This helper initialitzes Bull lists and helper functions and makes them available on a globally scoped variable.
|
|
3
3
|
|
|
4
|
+
[](https://github.com/AdmiralCloud/ac-bootstrap-bull/actions/workflows/node.js.yml) [](https://github.com/AdmiralCloud/ac-bootstrap-bull/actions/workflows/github-code-scanning/codeql)
|
|
5
|
+
|
|
4
6
|
# Usage
|
|
5
7
|
```
|
|
6
8
|
const bullOptions = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ac-bootstrap-bull",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.14",
|
|
4
4
|
"description": "Bull helper",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,15 +9,24 @@
|
|
|
9
9
|
"author": "Mark Poepping <mark.poepping@admiralcloud.com>",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"ac-redislock": "^6.0.
|
|
12
|
+
"ac-redislock": "^6.0.6",
|
|
13
13
|
"bull": "^4.16.5",
|
|
14
14
|
"ioredis": "^5.10.1",
|
|
15
|
-
"lodash": "^4.
|
|
15
|
+
"lodash": "^4.18.1"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "mocha test/**/*.test.js"
|
|
16
19
|
},
|
|
17
20
|
"devDependencies": {
|
|
18
|
-
"ac-semantic-release": "^0.
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
+
"ac-semantic-release": "^1.0.2",
|
|
22
|
+
"chai": "^6.2.2",
|
|
23
|
+
"eslint": "^10.2.0",
|
|
24
|
+
"globals": "^17.5.0",
|
|
25
|
+
"mocha": "^11.7.5"
|
|
26
|
+
},
|
|
27
|
+
"resolutions": {
|
|
28
|
+
"mocha/serialize-javascript": "^7.0.5",
|
|
29
|
+
"mocha/diff": "^8.0.3"
|
|
21
30
|
},
|
|
22
31
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
23
32
|
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai')
|
|
4
|
+
const bullModule = require('../index')
|
|
5
|
+
|
|
6
|
+
// ACError is a global in the consuming application
|
|
7
|
+
global.ACError = class ACError extends Error {
|
|
8
|
+
constructor(code, status, meta) {
|
|
9
|
+
super(code)
|
|
10
|
+
this.code = code
|
|
11
|
+
this.status = status
|
|
12
|
+
this.meta = meta
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const createAcapi = (overrides = {}) => ({
|
|
17
|
+
config: {
|
|
18
|
+
environment: 'test',
|
|
19
|
+
bull: {
|
|
20
|
+
log: { functionNameLength: 20, functionIdentifierLength: 20 },
|
|
21
|
+
jobLists: [
|
|
22
|
+
{ jobList: 'myQueue' },
|
|
23
|
+
{ jobList: 'otherQueue', retentionTime: 5000 }
|
|
24
|
+
],
|
|
25
|
+
...(overrides.bull || {})
|
|
26
|
+
},
|
|
27
|
+
redis: {
|
|
28
|
+
servers: [{ server: 'jobProcessing', host: 'localhost', port: 6379 }],
|
|
29
|
+
databases: [{ name: 'jobProcessing', db: 3 }]
|
|
30
|
+
},
|
|
31
|
+
...(overrides.config || {})
|
|
32
|
+
},
|
|
33
|
+
log: {
|
|
34
|
+
error: () => {},
|
|
35
|
+
warn: () => {},
|
|
36
|
+
info: () => {},
|
|
37
|
+
debug: () => {},
|
|
38
|
+
silly: () => {}
|
|
39
|
+
},
|
|
40
|
+
aclog: { serverInfo: () => [] },
|
|
41
|
+
bull: {},
|
|
42
|
+
redis: {},
|
|
43
|
+
...overrides
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// ------------------------------------------------------------------
|
|
47
|
+
// helpers
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
const makeMockQueue = (addResult = { id: 'job-123' }) => ({
|
|
51
|
+
add: async (...args) => addResult,
|
|
52
|
+
close: async () => {}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ------------------------------------------------------------------
|
|
56
|
+
// tests
|
|
57
|
+
// ------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
describe('ac-bootstrap-bull', () => {
|
|
60
|
+
|
|
61
|
+
describe('prepareQueue', () => {
|
|
62
|
+
let acapi, bull
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
acapi = createAcapi()
|
|
66
|
+
bull = bullModule(acapi)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('returns false when jobList is not in config', () => {
|
|
70
|
+
expect(bull.prepareQueue({ jobList: 'nonexistent' })).to.be.false
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('builds queueName as <environment>.<jobList>', () => {
|
|
74
|
+
const { queueName } = bull.prepareQueue({ jobList: 'myQueue' })
|
|
75
|
+
expect(queueName).to.equal('test.myQueue')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('appends localDevelopment suffix to queueName', () => {
|
|
79
|
+
acapi.config.localDevelopment = '-dev'
|
|
80
|
+
const { queueName } = bull.prepareQueue({ jobList: 'myQueue' })
|
|
81
|
+
expect(queueName).to.equal('test-dev.myQueue')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('skips localDevelopment suffix when ignore.localDevelopment is true', () => {
|
|
85
|
+
acapi.config.localDevelopment = '-dev'
|
|
86
|
+
const { queueName } = bull.prepareQueue({ jobList: 'myQueue', ignore: { localDevelopment: true } })
|
|
87
|
+
expect(queueName).to.equal('test.myQueue')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('uses customJobList.environment instead of acapi.config.environment', () => {
|
|
91
|
+
const { queueName } = bull.prepareQueue({ jobList: 'myQueue', customJobList: { environment: 'staging' } })
|
|
92
|
+
expect(queueName).to.equal('staging.myQueue')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('returns the matching jobListConfig', () => {
|
|
96
|
+
const { jobListConfig } = bull.prepareQueue({ jobList: 'myQueue' })
|
|
97
|
+
expect(jobListConfig).to.deep.equal({ jobList: 'myQueue' })
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('uses a custom configPath to look up jobLists', () => {
|
|
101
|
+
acapi.config.custom = { jobLists: [{ jobList: 'myQueue' }] }
|
|
102
|
+
const { queueName } = bull.prepareQueue({ jobList: 'myQueue', configPath: 'custom' })
|
|
103
|
+
expect(queueName).to.equal('test.myQueue')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
describe('scope', () => {
|
|
110
|
+
let acapi, bull
|
|
111
|
+
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
acapi = createAcapi()
|
|
114
|
+
bull = bullModule(acapi)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('sets bull.redis.database.name from params.redis.config', () => {
|
|
118
|
+
bull.scope({ redis: { config: 'myDatabase' } })
|
|
119
|
+
expect(acapi.config.bull.redis.database.name).to.equal('myDatabase')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('defaults to "jobProcessing" when redis.config is not provided', () => {
|
|
123
|
+
bull.scope({})
|
|
124
|
+
expect(acapi.config.bull.redis.database.name).to.equal('jobProcessing')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// ------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
describe('handleFailedJobs', () => {
|
|
131
|
+
it('logs an error', () => {
|
|
132
|
+
let errorLogged = false
|
|
133
|
+
const acapi = createAcapi()
|
|
134
|
+
acapi.log.error = () => { errorLogged = true }
|
|
135
|
+
const bull = bullModule(acapi)
|
|
136
|
+
|
|
137
|
+
bull.handleFailedJobs('test.myQueue', 'job-1', new Error('timeout'))
|
|
138
|
+
expect(errorLogged).to.be.true
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
describe('addJob', () => {
|
|
145
|
+
const QUEUE_NAME = 'test.myQueue'
|
|
146
|
+
let acapi, bull
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
acapi = createAcapi()
|
|
150
|
+
bull = bullModule(acapi)
|
|
151
|
+
acapi.bull[QUEUE_NAME] = makeMockQueue()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('throws ACError when jobList is not configured', async () => {
|
|
155
|
+
try {
|
|
156
|
+
await bull.addJob('unknownQueue', {})
|
|
157
|
+
expect.fail('expected ACError')
|
|
158
|
+
} catch (e) {
|
|
159
|
+
expect(e).to.be.instanceOf(ACError)
|
|
160
|
+
expect(e.code).to.equal('jobListNotDefined')
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('throws ACError when bull queue is not initialized', async () => {
|
|
165
|
+
delete acapi.bull[QUEUE_NAME]
|
|
166
|
+
try {
|
|
167
|
+
await bull.addJob('myQueue', { jobPayload: {} })
|
|
168
|
+
expect.fail('expected ACError')
|
|
169
|
+
} catch (e) {
|
|
170
|
+
expect(e).to.be.instanceOf(ACError)
|
|
171
|
+
expect(e.code).to.equal('bullNotAvailableForQueueName')
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('prefixes jobId with customerId when customerId is present', async () => {
|
|
176
|
+
let capturedOptions
|
|
177
|
+
acapi.bull[QUEUE_NAME].add = async (payload, options) => {
|
|
178
|
+
capturedOptions = options
|
|
179
|
+
return { id: options.jobId }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await bull.addJob('myQueue', { jobPayload: { customerId: 'cust-1' } })
|
|
183
|
+
expect(capturedOptions.jobId).to.match(/^cust-1:::/)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('does not double-prefix when jobId already starts with customerId', async () => {
|
|
187
|
+
let capturedOptions
|
|
188
|
+
acapi.bull[QUEUE_NAME].add = async (payload, options) => {
|
|
189
|
+
capturedOptions = options
|
|
190
|
+
return { id: options.jobId }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await bull.addJob('myQueue', {
|
|
194
|
+
jobPayload: { customerId: 'cust-1' },
|
|
195
|
+
jobOptions: { jobId: 'cust-1:::existing-id' }
|
|
196
|
+
})
|
|
197
|
+
expect(capturedOptions.jobId).to.equal('cust-1:::existing-id')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('returns { jobId } after adding a named job', async () => {
|
|
201
|
+
acapi.bull[QUEUE_NAME].add = async (name, payload, options) => ({ id: 'named-job-id' })
|
|
202
|
+
const result = await bull.addJob('myQueue', { name: 'myJobName', jobPayload: {} })
|
|
203
|
+
expect(result).to.deep.equal({ jobId: 'named-job-id' })
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
describe('removeJob', () => {
|
|
210
|
+
let acapi, bull
|
|
211
|
+
|
|
212
|
+
beforeEach(() => {
|
|
213
|
+
acapi = createAcapi()
|
|
214
|
+
bull = bullModule(acapi)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('logs an error and returns early when job is null', async () => {
|
|
218
|
+
let errorLogged = false
|
|
219
|
+
acapi.log.error = () => { errorLogged = true }
|
|
220
|
+
bull = bullModule(acapi)
|
|
221
|
+
|
|
222
|
+
await bull.removeJob(null, 'test.myQueue')
|
|
223
|
+
expect(errorLogged).to.be.true
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('calls job.remove() on a valid job', async () => {
|
|
227
|
+
let removed = false
|
|
228
|
+
const mockJob = {
|
|
229
|
+
id: 'job-abc',
|
|
230
|
+
data: {},
|
|
231
|
+
remove: async () => { removed = true }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await bull.removeJob(mockJob, 'test.myQueue')
|
|
235
|
+
expect(removed).to.be.true
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('logs success after removing a valid job', async () => {
|
|
239
|
+
let infoLogged = false
|
|
240
|
+
acapi.log.info = () => { infoLogged = true }
|
|
241
|
+
bull = bullModule(acapi)
|
|
242
|
+
|
|
243
|
+
const mockJob = { id: 'job-abc', data: {}, remove: async () => {} }
|
|
244
|
+
await bull.removeJob(mockJob, 'test.myQueue')
|
|
245
|
+
expect(infoLogged).to.be.true
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// ------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
describe('shutdown', () => {
|
|
252
|
+
it('closes all registered queues', async () => {
|
|
253
|
+
const acapi = createAcapi()
|
|
254
|
+
const bull = bullModule(acapi)
|
|
255
|
+
const closedQueues = []
|
|
256
|
+
|
|
257
|
+
// manually populate jobLists as init would
|
|
258
|
+
bull.jobLists.push('test.myQueue', 'test.otherQueue')
|
|
259
|
+
acapi.bull['test.myQueue'] = { close: async () => closedQueues.push('test.myQueue') }
|
|
260
|
+
acapi.bull['test.otherQueue'] = { close: async () => closedQueues.push('test.otherQueue') }
|
|
261
|
+
|
|
262
|
+
await bull.shutdown()
|
|
263
|
+
expect(closedQueues).to.include.members(['test.myQueue', 'test.otherQueue'])
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
})
|