onjs 2.4.1 → 2.5.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/.github/workflows/github-actions.yml +1 -28
- package/.github/workflows/publish.yml +59 -0
- package/README.md +16 -0
- package/bin/prepare-test-environment +1 -2
- package/bin/test +21 -1
- package/lib/on.js +129 -28
- package/package.json +2 -2
- package/tests/tests/queues.js +182 -0
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
--health-timeout 5s
|
|
38
38
|
--health-retries 5
|
|
39
39
|
rabbitmq:
|
|
40
|
-
image: rabbitmq:alpine
|
|
40
|
+
image: rabbitmq:3.13-alpine
|
|
41
41
|
ports:
|
|
42
42
|
- 5672:5672
|
|
43
43
|
env:
|
|
@@ -67,30 +67,3 @@ jobs:
|
|
|
67
67
|
run: npm test
|
|
68
68
|
|
|
69
69
|
- run: echo "Job's status is ${{ job.status }}."
|
|
70
|
-
|
|
71
|
-
publish:
|
|
72
|
-
needs: test
|
|
73
|
-
# This job runs on push to master branch
|
|
74
|
-
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
75
|
-
runs-on: ubuntu-latest
|
|
76
|
-
|
|
77
|
-
steps:
|
|
78
|
-
- run: echo "The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
|
|
79
|
-
- name: Check out repository code
|
|
80
|
-
uses: actions/checkout@v4
|
|
81
|
-
|
|
82
|
-
- run: echo "The ${{ github.repository }} repository has been cloned to the runner."
|
|
83
|
-
- run: echo "The workflow is now ready to build your code on the runner."
|
|
84
|
-
|
|
85
|
-
# Setup .npmrc file to publish to npm
|
|
86
|
-
- uses: actions/setup-node@v4
|
|
87
|
-
with:
|
|
88
|
-
node-version: '20.x'
|
|
89
|
-
registry-url: 'https://registry.npmjs.org'
|
|
90
|
-
|
|
91
|
-
- run: echo "Job's status is ${{ job.status }}."
|
|
92
|
-
|
|
93
|
-
- name: Publishing to NPM
|
|
94
|
-
run: npm publish --provenance --access public
|
|
95
|
-
env:
|
|
96
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
run-name: Publishing onjs ${{ github.ref_name }} to npm
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- master
|
|
9
|
+
|
|
10
|
+
# npm Trusted Publishing: the package is configured on npmjs.com to trust
|
|
11
|
+
# this repository + workflow filename, so publishing authenticates via OIDC
|
|
12
|
+
# (no NPM_TOKEN required). Requires npm >= 11.5.1 (Node 24 ships it).
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
# The job runs in a container so the services are reachable by their
|
|
20
|
+
# hostnames ("rabbitmq", "redis") - on VM runners services are only
|
|
21
|
+
# reachable via localhost:mapped-port. Node 24 ships npm >= 11.5.1,
|
|
22
|
+
# required for OIDC trusted publishing.
|
|
23
|
+
container: node:24-bookworm
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
services:
|
|
26
|
+
rabbitmq:
|
|
27
|
+
image: rabbitmq:3.13-alpine
|
|
28
|
+
ports:
|
|
29
|
+
- 5672:5672
|
|
30
|
+
env:
|
|
31
|
+
RABBITMQ_DEFAULT_USER: rabbitmq
|
|
32
|
+
RABBITMQ_DEFAULT_PASS: rabbitmq
|
|
33
|
+
RABBITMQ_DEFAULT_VHOST: /
|
|
34
|
+
options: >-
|
|
35
|
+
--health-cmd "rabbitmq-diagnostics -q ping"
|
|
36
|
+
--health-interval 10s
|
|
37
|
+
--health-timeout 5s
|
|
38
|
+
--health-retries 10
|
|
39
|
+
redis:
|
|
40
|
+
image: redis:alpine
|
|
41
|
+
ports:
|
|
42
|
+
- 19379:6379
|
|
43
|
+
options: >-
|
|
44
|
+
--health-cmd "redis-cli ping"
|
|
45
|
+
--health-interval 10s
|
|
46
|
+
--health-timeout 5s
|
|
47
|
+
--health-retries 10
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: actions/setup-node@v4
|
|
51
|
+
with:
|
|
52
|
+
node-version: '24.x'
|
|
53
|
+
registry-url: 'https://registry.npmjs.org'
|
|
54
|
+
- name: Installing dependencies
|
|
55
|
+
run: npm ci
|
|
56
|
+
- name: Testing
|
|
57
|
+
run: npm test
|
|
58
|
+
- name: Publishing to NPM
|
|
59
|
+
run: npm publish
|
package/README.md
CHANGED
|
@@ -80,6 +80,22 @@ on
|
|
|
80
80
|
.withProperties(properties: array)
|
|
81
81
|
.do(eventName: string)
|
|
82
82
|
```
|
|
83
|
+
#### Manual Acknowledgement
|
|
84
|
+
|
|
85
|
+
By default, tasks are acknowledged as soon as they are delivered, so a task that fails (or a container that crashes mid-task) is not redelivered.
|
|
86
|
+
|
|
87
|
+
Tasks registered with `.ackAfterProcessing()` are consumed with **manual acknowledgement**: the registered actions are awaited and the message is acknowledged only after they succeed. The broker holds the next delivery until then (prefetch 1 across the channel), so the service processes one task at a time and nothing is lost if the container crashes mid-task (unacknowledged messages are redelivered).
|
|
88
|
+
|
|
89
|
+
If an action fails, the message is dropped without requeueing — the publisher is expected to send the task again when needed (e.g. when the work is requested once more). All tasks sharing the channel share one in-flight message, so registering more than one queue with `.ackAfterProcessing()` keeps a single task in flight across all of them.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
on
|
|
93
|
+
.taskReceived(eventName: string)
|
|
94
|
+
.withProperties(properties: array)
|
|
95
|
+
.ackAfterProcessing()
|
|
96
|
+
.do(eventName: string)
|
|
97
|
+
```
|
|
98
|
+
|
|
83
99
|
### Delayed Queues
|
|
84
100
|
|
|
85
101
|
The pattern of listening to work queues is described at https://www.rabbitmq.com/tutorials/tutorial-two-javascript.html
|
|
@@ -9,13 +9,12 @@ REDIS_CONTAINER_PORT=19379
|
|
|
9
9
|
|
|
10
10
|
function createRabbitContainer() {
|
|
11
11
|
id=$(docker run -d --name ${RABBIT_CONTAINER_NAME} \
|
|
12
|
-
-p 5672:5272 \
|
|
13
12
|
-e RABBITMQ_ERLANG_COOKIE=SWQOKODSQALRPCLNMEQG \
|
|
14
13
|
-e RABBITMQ_DEFAULT_USER=rabbitmq \
|
|
15
14
|
-e RABBITMQ_DEFAULT_PASS=rabbitmq \
|
|
16
15
|
-e RABBITMQ_DEFAULT_VHOST=/ \
|
|
17
16
|
--hostname rabbitmq \
|
|
18
|
-
rabbitmq:alpine)
|
|
17
|
+
rabbitmq:3.13-alpine)
|
|
19
18
|
echo $id
|
|
20
19
|
}
|
|
21
20
|
|
package/bin/test
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
+
# Runs the onjs test suite in a container attached to a shared docker network,
|
|
3
|
+
# so the suite can resolve the broker as "rabbitmq" and redis as "redis"
|
|
4
|
+
# (the hostnames the tests are configured with).
|
|
2
5
|
|
|
3
6
|
$(pwd)/bin/prepare-test-environment
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
docker network create onjs_test > /dev/null 2>&1 || true
|
|
9
|
+
# disconnect first so a re-connect always applies the hostname aliases
|
|
10
|
+
docker network disconnect onjs_test onjs_rabbit_test > /dev/null 2>&1 || true
|
|
11
|
+
docker network disconnect onjs_test onjs_redis_test > /dev/null 2>&1 || true
|
|
12
|
+
docker network connect --alias rabbitmq onjs_test onjs_rabbit_test
|
|
13
|
+
docker network connect --alias redis onjs_test onjs_redis_test
|
|
14
|
+
|
|
15
|
+
if [ ! -d "node_modules" ]; then
|
|
16
|
+
echo "Installing dependencies"
|
|
17
|
+
docker run --rm --network onjs_test -v ${PWD}:/app -w /app rever/node:22 npm install
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
echo "Running the test suite in a container (broker: rabbitmq, redis: redis)"
|
|
21
|
+
docker run --rm --name onjs_test_runner \
|
|
22
|
+
--network onjs_test \
|
|
23
|
+
-v ${PWD}:/app -w /app \
|
|
24
|
+
-e "NODE_ENV=test" \
|
|
25
|
+
rever/node:22 npm test
|
package/lib/on.js
CHANGED
|
@@ -222,6 +222,70 @@ class On extends EventEmitter {
|
|
|
222
222
|
// Where it will be processed to avoid duplicate
|
|
223
223
|
// events sent from multiple processes
|
|
224
224
|
async _initQueues (queues) {
|
|
225
|
+
try {
|
|
226
|
+
var queueChannel = await this.mqInstance.createChannel()
|
|
227
|
+
this.queueInstance.setChannel(queueChannel)
|
|
228
|
+
|
|
229
|
+
const autoTasks = queues.filter((task) => task.ackMode !== 'manual')
|
|
230
|
+
const manualTasks = queues.filter((task) => task.ackMode === 'manual')
|
|
231
|
+
const delayedTasks = this._getEvents('delayedQueue')
|
|
232
|
+
|
|
233
|
+
// The legacy dispatcher executes auto-ack and delayed tasks: register it
|
|
234
|
+
// whenever either exists — _initDelayedQueues consumes through the same
|
|
235
|
+
// queueEventReceived event, so it must not depend on auto-ack queues.
|
|
236
|
+
if (autoTasks.length > 0 || delayedTasks.length > 0) {
|
|
237
|
+
this._registerQueueEventDispatcher()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (autoTasks.length > 0) {
|
|
241
|
+
this._log('info', 'Initializing queues (auto ack)', autoTasks.map((t) => t.eventName))
|
|
242
|
+
await this._initQueuesWithAutoAck(autoTasks, queueChannel)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Manual-ack consumers get a DEDICATED channel: their prefetch window
|
|
246
|
+
// must never throttle legacy auto-ack or delayed consumers sharing the
|
|
247
|
+
// default channel (per Arturo's review).
|
|
248
|
+
if (manualTasks.length > 0) {
|
|
249
|
+
var manualChannel = await this.mqInstance.createChannel()
|
|
250
|
+
this._log('info', 'Initializing queues (manual ack)', manualTasks.map((t) => t.eventName))
|
|
251
|
+
await this._initQueuesWithManualAck(manualTasks, manualChannel)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_registerQueueEventDispatcher () {
|
|
262
|
+
this.queueInstance.on('queueEventReceived', async (data) => {
|
|
263
|
+
try {
|
|
264
|
+
let taskName = data.name
|
|
265
|
+
this._eventLog('info', taskName, `Received task on queue ${taskName}`, data.data)
|
|
266
|
+
|
|
267
|
+
let task = this._getEventByName(taskName)
|
|
268
|
+
|
|
269
|
+
if (hasProperties(task) && !this._validationIsSuccess(task.eventName, data.data)) {
|
|
270
|
+
this._log('error', `Validation failed for task ${task.eventName} due to missing properties, received data`, data.data)
|
|
271
|
+
var error = new Error(`Validation failed for task ${task.eventName}`)
|
|
272
|
+
error.event = task
|
|
273
|
+
|
|
274
|
+
this.emit('error', error)
|
|
275
|
+
throw error
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return this._onTaskReceived(task, data.data)
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw err
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Consumes tasks with automatic acknowledgement, the message is acked
|
|
287
|
+
// as soon as it is received. This is the original (legacy) behavior
|
|
288
|
+
async _initQueuesWithAutoAck (queues, queueChannel) {
|
|
225
289
|
try {
|
|
226
290
|
var queueNames = queues.map((queue) => {
|
|
227
291
|
return queue.eventName
|
|
@@ -229,50 +293,75 @@ class On extends EventEmitter {
|
|
|
229
293
|
|
|
230
294
|
this._log('info', 'Listening for queueNames', queueNames)
|
|
231
295
|
|
|
232
|
-
this.queueInstance.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
296
|
+
var listener = await this.queueInstance.listen(queueNames)
|
|
297
|
+
return listener
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
throw err
|
|
301
|
+
}
|
|
302
|
+
}
|
|
236
303
|
|
|
237
|
-
|
|
304
|
+
// Consumes tasks with manual acknowledgement. The broker holds the delivery
|
|
305
|
+
// until the handler finishes (prefetch 1 across the whole channel), so the
|
|
306
|
+
// service processes one task at a time and nothing is lost on crash (redelivery)
|
|
307
|
+
async _initQueuesWithManualAck (tasks, queueChannel) {
|
|
308
|
+
try {
|
|
309
|
+
const queueNames = tasks.map((task) => task.eventName)
|
|
310
|
+
this._log('info', 'Initializing queues (manual ack)', queueNames)
|
|
238
311
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
312
|
+
const tasksByQueueName = {}
|
|
313
|
+
for (const task of tasks) {
|
|
314
|
+
tasksByQueueName[task.eventName] = task
|
|
315
|
+
}
|
|
243
316
|
|
|
244
|
-
|
|
245
|
-
|
|
317
|
+
const handler = async (data, message) => {
|
|
318
|
+
const taskDef = tasksByQueueName[message.fields.routingKey]
|
|
319
|
+
try {
|
|
320
|
+
if (taskDef && hasProperties(taskDef) && !this._validationIsSuccess(taskDef.eventName, data)) {
|
|
321
|
+
throw new Error(`Validation failed for task ${taskDef.eventName}`)
|
|
246
322
|
}
|
|
247
323
|
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
324
|
+
await this._onTaskReceived(taskDef, data, { awaitActions: true })
|
|
325
|
+
} catch (err) {
|
|
326
|
+
const loggedName = taskDef ? taskDef.eventName : message.fields.routingKey
|
|
327
|
+
this._log('error', loggedName, 'Task failed, dropping (service-api republishes by demand)', err)
|
|
251
328
|
throw err
|
|
252
329
|
}
|
|
253
|
-
}
|
|
330
|
+
}
|
|
254
331
|
|
|
255
|
-
|
|
256
|
-
|
|
332
|
+
await this.queueInstance.listenManual(queueNames, handler, { globalPrefetch: true })
|
|
333
|
+
|
|
334
|
+
return
|
|
257
335
|
}
|
|
258
336
|
catch (err) {
|
|
259
337
|
throw err
|
|
260
338
|
}
|
|
261
339
|
}
|
|
262
340
|
|
|
263
|
-
async
|
|
264
|
-
const queueNames = delayedQueues.map((queue) => queue.eventName)
|
|
265
|
-
this._log('info', 'Listening for delayedQueueNames', queueNames)
|
|
266
|
-
const listener = await this.queueInstance.listen(queueNames, options)
|
|
267
|
-
return listener
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
_onTaskReceived (task, data) {
|
|
341
|
+
async _onTaskReceived (task, data, options = {}) {
|
|
271
342
|
this._eventLog('info', task.eventName, 'Executing task callback for task')
|
|
272
343
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
344
|
+
const executions = task.actions.map((action) => {
|
|
345
|
+
try {
|
|
346
|
+
return action.call(task, data)
|
|
347
|
+
} catch (err) {
|
|
348
|
+
// a synchronous throw must not bypass the settle-wait below
|
|
349
|
+
return Promise.reject(err)
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
if (options.awaitActions) {
|
|
354
|
+
// wait for EVERY action before propagating a failure: one rejection
|
|
355
|
+
// must not release the next delivery while a sibling action still runs
|
|
356
|
+
const settled = await Promise.allSettled(executions)
|
|
357
|
+
const firstRejection = settled.find((result) => result.status === 'rejected')
|
|
358
|
+
if (firstRejection) {
|
|
359
|
+
throw firstRejection.reason
|
|
360
|
+
}
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return Promise.allSettled(executions)
|
|
276
365
|
.then((results) => {
|
|
277
366
|
this._eventLog('info', task.eventName, 'Results of all executions for task ' + task.eventName, results)
|
|
278
367
|
})
|
|
@@ -416,6 +505,18 @@ class On extends EventEmitter {
|
|
|
416
505
|
return this
|
|
417
506
|
}
|
|
418
507
|
|
|
508
|
+
ackAfterProcessing () {
|
|
509
|
+
if (this.currentEventTypeToRegister !== 'queue') {
|
|
510
|
+
throw new Error('ackAfterProcessing is only supported for queue events (not delayed queues, topics or RPC)')
|
|
511
|
+
}
|
|
512
|
+
if (!eventIsDeclaredBeforeCallingRegisterCallback(this._getEventIndexByNameAndType(this.currentEventNameToRegister, this.currentEventTypeToRegister))) {
|
|
513
|
+
throw new Error('Call taskReceived before setting the ack mode')
|
|
514
|
+
}
|
|
515
|
+
const eventIndex = this._getEventIndexByNameAndType(this.currentEventNameToRegister, this.currentEventTypeToRegister)
|
|
516
|
+
this.events[eventIndex].ackMode = 'manual'
|
|
517
|
+
return this
|
|
518
|
+
}
|
|
519
|
+
|
|
419
520
|
// Alias method
|
|
420
521
|
do (callback) {
|
|
421
522
|
return this._registerCallback(callback)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "On.js is an event library based on RabbitMQ that helps you control events on a microservices arquitecture",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"mocha": "^5.2.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"rabbitmq-lib": "^2.
|
|
31
|
+
"rabbitmq-lib": "^2.4.0",
|
|
32
32
|
"amqplib": "^0.10.5",
|
|
33
33
|
"bull": "^3.29.3"
|
|
34
34
|
}
|
package/tests/tests/queues.js
CHANGED
|
@@ -98,3 +98,185 @@ describe('Queue', function () {
|
|
|
98
98
|
})
|
|
99
99
|
})
|
|
100
100
|
})
|
|
101
|
+
|
|
102
|
+
describe('Queue with manual ack', function () {
|
|
103
|
+
this.timeout(10000)
|
|
104
|
+
|
|
105
|
+
var config = {
|
|
106
|
+
mq: {
|
|
107
|
+
exchange_name: 'onjs_test_manual_ack',
|
|
108
|
+
url: 'amqp://rabbitmq:rabbitmq@rabbitmq:5672/',
|
|
109
|
+
connectMaxAttempts: 1,
|
|
110
|
+
delayMS: 10,
|
|
111
|
+
},
|
|
112
|
+
redis: {
|
|
113
|
+
port: REDIS_PORT,
|
|
114
|
+
host: 'redis'
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// These tests use a stubbed queueInstance and a fake channel,
|
|
119
|
+
// so they don't need a live broker connection
|
|
120
|
+
function createFakeChannel () {
|
|
121
|
+
var fake = {
|
|
122
|
+
prefetchCalls: [],
|
|
123
|
+
assertQueueCalls: [],
|
|
124
|
+
consumeCalls: [],
|
|
125
|
+
ackCalls: [],
|
|
126
|
+
nackCalls: [],
|
|
127
|
+
prefetch: async function (count, globalScope) {
|
|
128
|
+
fake.prefetchCalls.push([count, globalScope])
|
|
129
|
+
},
|
|
130
|
+
assertQueue: async function (queueName, options) {
|
|
131
|
+
fake.assertQueueCalls.push([queueName, options])
|
|
132
|
+
return { queue: queueName }
|
|
133
|
+
},
|
|
134
|
+
consume: async function (queueName, handler, options) {
|
|
135
|
+
fake.consumeCalls.push({ queueName: queueName, handler: handler, options: options })
|
|
136
|
+
},
|
|
137
|
+
ack: function (message) {
|
|
138
|
+
fake.ackCalls.push(message)
|
|
139
|
+
},
|
|
140
|
+
nack: function (message, allUpTo, requeue) {
|
|
141
|
+
fake.nackCalls.push([message, allUpTo, requeue])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return fake
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createStubbedOn () {
|
|
148
|
+
var onInstance = new On(config)
|
|
149
|
+
var channel = createFakeChannel()
|
|
150
|
+
var listenCalls = []
|
|
151
|
+
var listenManualCalls = []
|
|
152
|
+
var listenManualHandlers = []
|
|
153
|
+
|
|
154
|
+
onInstance.mqInstance.createChannel = async function () {
|
|
155
|
+
return channel
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onInstance.queueInstance = {
|
|
159
|
+
setChannel: function () {},
|
|
160
|
+
on: function () {},
|
|
161
|
+
listen: async function (queueNames) {
|
|
162
|
+
listenCalls.push(queueNames)
|
|
163
|
+
},
|
|
164
|
+
listenManual: async function (queueNames, handler) {
|
|
165
|
+
listenManualCalls.push(queueNames)
|
|
166
|
+
listenManualHandlers.push(handler)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { onInstance: onInstance, channel: channel, listenCalls: listenCalls, listenManualCalls: listenManualCalls, listenManualHandlers: listenManualHandlers }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
it('Should route auto ack tasks to listen and manual ack tasks to the manual consumer', async function () {
|
|
174
|
+
var stubs = createStubbedOn()
|
|
175
|
+
|
|
176
|
+
function execution (incomingData) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
stubs.onInstance
|
|
181
|
+
.taskReceived('autoAckQueue')
|
|
182
|
+
.andProcess(execution)
|
|
183
|
+
|
|
184
|
+
stubs.onInstance
|
|
185
|
+
.taskReceived('manualAckQueue')
|
|
186
|
+
.ackAfterProcessing()
|
|
187
|
+
.andProcess(execution)
|
|
188
|
+
|
|
189
|
+
await stubs.onInstance._startEventListeners()
|
|
190
|
+
|
|
191
|
+
stubs.listenCalls.should.deepEqual([['autoAckQueue']])
|
|
192
|
+
stubs.listenManualCalls.should.deepEqual([['manualAckQueue']])
|
|
193
|
+
stubs.listenManualCalls[0].should.not.deepEqual(stubs.listenCalls[0])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('Should await the actions and ack the message after processing', async function () {
|
|
197
|
+
var stubs = createStubbedOn()
|
|
198
|
+
var executed = []
|
|
199
|
+
var actionFinished = false
|
|
200
|
+
|
|
201
|
+
stubs.onInstance
|
|
202
|
+
.taskReceived('manualAckQueue')
|
|
203
|
+
.ackAfterProcessing()
|
|
204
|
+
.andProcess(async function (incomingData) {
|
|
205
|
+
await new Promise((resolve) => { setTimeout(resolve, 25) })
|
|
206
|
+
executed.push(incomingData)
|
|
207
|
+
actionFinished = true
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
|
|
211
|
+
|
|
212
|
+
var handler = stubs.listenManualHandlers[0]
|
|
213
|
+
|
|
214
|
+
await handler({ name: 'Superman' }, { fields: { routingKey: 'manualAckQueue' } })
|
|
215
|
+
|
|
216
|
+
actionFinished.should.equal(true)
|
|
217
|
+
executed.should.deepEqual([{ name: 'Superman' }])
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('Should nack and drop the message when the task action fails', async function () {
|
|
221
|
+
var stubs = createStubbedOn()
|
|
222
|
+
var executed = false
|
|
223
|
+
|
|
224
|
+
stubs.onInstance
|
|
225
|
+
.taskReceived('failingManualQueue')
|
|
226
|
+
.ackAfterProcessing()
|
|
227
|
+
.andProcess(async function () {
|
|
228
|
+
executed = true
|
|
229
|
+
throw new Error('action failed')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
|
|
233
|
+
|
|
234
|
+
var handler = stubs.listenManualHandlers[0]
|
|
235
|
+
|
|
236
|
+
await handler({ name: 'Superman' }, { fields: { routingKey: 'failingManualQueue' } }).should.be.rejectedWith('action failed')
|
|
237
|
+
|
|
238
|
+
executed.should.equal(true)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('Should nack the message when payload validation fails', async function () {
|
|
242
|
+
var stubs = createStubbedOn()
|
|
243
|
+
var executed = false
|
|
244
|
+
|
|
245
|
+
stubs.onInstance
|
|
246
|
+
.taskReceived('validatedManualQueue')
|
|
247
|
+
.withProperties(['name'])
|
|
248
|
+
.ackAfterProcessing()
|
|
249
|
+
.andProcess(async function () {
|
|
250
|
+
executed = true
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
|
|
254
|
+
|
|
255
|
+
var handler = stubs.listenManualHandlers[0]
|
|
256
|
+
|
|
257
|
+
await handler({ other: 'value' }, { fields: { routingKey: 'validatedManualQueue' } }).should.be.rejectedWith('Validation failed for task validatedManualQueue')
|
|
258
|
+
|
|
259
|
+
executed.should.equal(false)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('Should ack the message when payload validation passes', async function () {
|
|
263
|
+
var stubs = createStubbedOn()
|
|
264
|
+
var executed = []
|
|
265
|
+
|
|
266
|
+
stubs.onInstance
|
|
267
|
+
.taskReceived('validatedManualQueue')
|
|
268
|
+
.withProperties(['name'])
|
|
269
|
+
.ackAfterProcessing()
|
|
270
|
+
.andProcess(async function (incomingData) {
|
|
271
|
+
executed.push(incomingData)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
|
|
275
|
+
|
|
276
|
+
var handler = stubs.listenManualHandlers[0]
|
|
277
|
+
|
|
278
|
+
await handler({ name: 'Superman' }, { fields: { routingKey: 'validatedManualQueue' } })
|
|
279
|
+
|
|
280
|
+
executed.should.deepEqual([{ name: 'Superman' }])
|
|
281
|
+
})
|
|
282
|
+
})
|