ac-bootstrap-bull 3.0.12 → 3.0.13

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.
@@ -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,27 @@
1
+ ## [3.0.13](https://github.com/admiralcloud/ac-bootstrap-bull/compare/v3.0.12..v3.0.13) (2026-04-04 10:49:38)
2
+
3
+
4
+ ### Bug Fix
5
+
6
+
7
+ * **App:** Package updates | MP | [f3c59d460e36ec89cddf9ecd0723f31d57892e4e](https://github.com/admiralcloud/ac-bootstrap-bull/commit/f3c59d460e36ec89cddf9ecd0723f31d57892e4e)
8
+ Package udpates
9
+ Related issues:
10
+ * **App:** Package updates | MP | [44274e75c86c4dc5dae9b831dd63a492d83a0117](https://github.com/admiralcloud/ac-bootstrap-bull/commit/44274e75c86c4dc5dae9b831dd63a492d83a0117)
11
+ Package updates
12
+ Related issues:
13
+ ### Tests
14
+
15
+
16
+ * **App:** Added tests | MP | [4046d6909ae3525ec71f0bd60ba9b834b935dc08](https://github.com/admiralcloud/ac-bootstrap-bull/commit/4046d6909ae3525ec71f0bd60ba9b834b935dc08)
17
+ Added tests
18
+ Related issues:
19
+ ### Documentation
20
+
21
+
22
+ * **App:** Added badge | MP | [83793ee1d6d33029a05d4ca86aa6cf049ec1c29f](https://github.com/admiralcloud/ac-bootstrap-bull/commit/83793ee1d6d33029a05d4ca86aa6cf049ec1c29f)
23
+ Added badge for CI
24
+ Related issues:
1
25
 
2
26
  ## [3.0.12](https://github.com/admiralcloud/ac-bootstrap-bull/compare/v3.0.11..v3.0.12) (2026-03-21 11:23:53)
3
27
 
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 index.js
5
+ ./node_modules/.bin/eslint --fix
6
6
 
7
7
  lint-check:
8
- ./node_modules/.bin/eslint index.js
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
+ [![Node.js CI](https://github.com/AdmiralCloud/ac-bootstrap-bull/actions/workflows/node.js.yml/badge.svg)](https://github.com/AdmiralCloud/ac-bootstrap-bull/actions/workflows/node.js.yml) [![CodeQL](https://github.com/AdmiralCloud/ac-bootstrap-bull/actions/workflows/github-code-scanning/codeql/badge.svg)](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.12",
3
+ "version": "3.0.13",
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.5",
12
+ "ac-redislock": "^6.0.6",
13
13
  "bull": "^4.16.5",
14
14
  "ioredis": "^5.10.1",
15
- "lodash": "^4.17.23"
15
+ "lodash": "^4.18.1"
16
+ },
17
+ "scripts": {
18
+ "test": "mocha test/**/*.test.js"
16
19
  },
17
20
  "devDependencies": {
18
- "ac-semantic-release": "^0.4.10",
19
- "eslint": "^10.1.0",
20
- "globals": "^17.4.0"
21
+ "ac-semantic-release": "^1.0.1",
22
+ "chai": "^5.2.0",
23
+ "eslint": "^10.2.0",
24
+ "globals": "^17.4.0",
25
+ "mocha": "^11.1.0"
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
+ })