onjs 2.4.1 → 2.5.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.
@@ -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
 
@@ -88,3 +87,11 @@ else
88
87
  createRabbitContainer
89
88
  sleep 5
90
89
  fi
90
+
91
+ function installDelayedMessageExchangePlugin() {
92
+ docker exec ${RABBIT_CONTAINER_NAME} sh -c "wget -q https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.13.0/rabbitmq_delayed_message_exchange-3.13.0.ez -O /opt/rabbitmq/plugins/rabbitmq_delayed_message_exchange-3.13.0.ez"
93
+ docker exec ${RABBIT_CONTAINER_NAME} rabbitmq-plugins enable rabbitmq_delayed_message_exchange
94
+ }
95
+
96
+ echo "Ensuring the delayed-message exchange plugin is installed"
97
+ installDelayedMessageExchangePlugin
package/bin/test CHANGED
@@ -1,5 +1,26 @@
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
- npm test
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
+ -e "ONJS_DELAYED_PLUGIN_TESTS=true" \
26
+ 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,31 +293,45 @@ class On extends EventEmitter {
229
293
 
230
294
  this._log('info', 'Listening for queueNames', queueNames)
231
295
 
232
- this.queueInstance.on('queueEventReceived', async (data) => {
233
- try {
234
- let taskName = data.name
235
- this._eventLog('info', taskName, `Received task on queue ${taskName}`, data.data)
296
+ var listener = await this.queueInstance.listen(queueNames)
297
+ return listener
298
+ }
299
+ catch (err) {
300
+ throw err
301
+ }
302
+ }
236
303
 
237
- let task = this._getEventByName(taskName)
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
- if (hasProperties(task) && !this._validationIsSuccess(task.eventName, data.data)) {
240
- this._log('error', `Validation failed for task ${task.eventName} due to missing properties, received data`, data.data)
241
- var error = new Error(`Validation failed for task ${task.eventName}`)
242
- error.event = task
312
+ const tasksByQueueName = {}
313
+ for (const task of tasks) {
314
+ tasksByQueueName[task.eventName] = task
315
+ }
243
316
 
244
- this.emit('error', error)
245
- throw error
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
- return this._onTaskReceived(task, data.data)
249
- }
250
- catch (err) {
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
- var listener = await this.queueInstance.listen(queueNames)
256
- return listener
332
+ await this.queueInstance.listenManual(queueNames, handler, { globalPrefetch: true })
333
+
334
+ return
257
335
  }
258
336
  catch (err) {
259
337
  throw err
@@ -267,12 +345,30 @@ class On extends EventEmitter {
267
345
  return listener
268
346
  }
269
347
 
270
- _onTaskReceived (task, data) {
348
+ async _onTaskReceived (task, data, options = {}) {
271
349
  this._eventLog('info', task.eventName, 'Executing task callback for task')
272
350
 
273
- return Promise.allSettled(task.actions.map((action) => {
274
- return action.call(task, data)
275
- }))
351
+ const executions = task.actions.map((action) => {
352
+ try {
353
+ return action.call(task, data)
354
+ } catch (err) {
355
+ // a synchronous throw must not bypass the settle-wait below
356
+ return Promise.reject(err)
357
+ }
358
+ })
359
+
360
+ if (options.awaitActions) {
361
+ // wait for EVERY action before propagating a failure: one rejection
362
+ // must not release the next delivery while a sibling action still runs
363
+ const settled = await Promise.allSettled(executions)
364
+ const firstRejection = settled.find((result) => result.status === 'rejected')
365
+ if (firstRejection) {
366
+ throw firstRejection.reason
367
+ }
368
+ return
369
+ }
370
+
371
+ return Promise.allSettled(executions)
276
372
  .then((results) => {
277
373
  this._eventLog('info', task.eventName, 'Results of all executions for task ' + task.eventName, results)
278
374
  })
@@ -416,6 +512,18 @@ class On extends EventEmitter {
416
512
  return this
417
513
  }
418
514
 
515
+ ackAfterProcessing () {
516
+ if (this.currentEventTypeToRegister !== 'queue') {
517
+ throw new Error('ackAfterProcessing is only supported for queue events (not delayed queues, topics or RPC)')
518
+ }
519
+ if (!eventIsDeclaredBeforeCallingRegisterCallback(this._getEventIndexByNameAndType(this.currentEventNameToRegister, this.currentEventTypeToRegister))) {
520
+ throw new Error('Call taskReceived before setting the ack mode')
521
+ }
522
+ const eventIndex = this._getEventIndexByNameAndType(this.currentEventNameToRegister, this.currentEventTypeToRegister)
523
+ this.events[eventIndex].ackMode = 'manual'
524
+ return this
525
+ }
526
+
419
527
  // Alias method
420
528
  do (callback) {
421
529
  return this._registerCallback(callback)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onjs",
3
- "version": "2.4.1",
3
+ "version": "2.5.1",
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.3.2",
31
+ "rabbitmq-lib": "^2.4.0",
32
32
  "amqplib": "^0.10.5",
33
33
  "bull": "^3.29.3"
34
34
  }
@@ -14,6 +14,7 @@ describe('Queue', function () {
14
14
  var config = {
15
15
  mq: {
16
16
  exchange_name: 'onjs_test',
17
+ delayed_exchange_name: 'onjs_test_delayed',
17
18
  url: 'amqp://rabbitmq:rabbitmq@rabbitmq:5672/',
18
19
  connectMaxAttempts: 1,
19
20
  delayMS: 10,
@@ -97,4 +98,238 @@ describe('Queue', function () {
97
98
  superpower: 'Fly'
98
99
  })
99
100
  })
101
+
102
+ it("Should initialize and process a delayed queue task", async function () {
103
+ // The delayed round trip needs the delayed-message exchange plugin,
104
+ // installed by bin/prepare-test-environment. CI runs the stock image
105
+ // without the plugin: bin/test sets ONJS_DELAYED_PLUGIN_TESTS=true so
106
+ // this test runs there, and CI skips it (the routing regression test
107
+ // still guards the delayed path on every broker).
108
+ if (process.env.ONJS_DELAYED_PLUGIN_TESTS !== 'true') this.skip()
109
+
110
+ var executed = null
111
+
112
+ function execution(incomingData) {
113
+ executed = incomingData
114
+ return
115
+ }
116
+
117
+ on
118
+ .taskReceived('delayedQueueRegression', { isDelayed: true })
119
+ .andProcess(execution)
120
+
121
+ await on.init()
122
+
123
+ await sender.send('delayedQueueRegression', { name: 'Delayed Superman' }, 300)
124
+
125
+ var waited = 0
126
+ while (!executed && waited < 5000) {
127
+ await new Promise((resolve) => { setTimeout(resolve, 100) })
128
+ waited += 100
129
+ }
130
+
131
+ executed.should.have.property('name', 'Delayed Superman')
132
+ })
133
+ })
134
+
135
+ describe('Queue with manual ack', function () {
136
+ this.timeout(10000)
137
+
138
+ var config = {
139
+ mq: {
140
+ exchange_name: 'onjs_test_manual_ack',
141
+ delayed_exchange_name: 'onjs_test_manual_ack_delayed',
142
+ url: 'amqp://rabbitmq:rabbitmq@rabbitmq:5672/',
143
+ connectMaxAttempts: 1,
144
+ delayMS: 10,
145
+ },
146
+ redis: {
147
+ port: REDIS_PORT,
148
+ host: 'redis'
149
+ },
150
+ }
151
+
152
+ // These tests use a stubbed queueInstance and a fake channel,
153
+ // so they don't need a live broker connection
154
+ function createFakeChannel () {
155
+ var fake = {
156
+ prefetchCalls: [],
157
+ assertQueueCalls: [],
158
+ consumeCalls: [],
159
+ ackCalls: [],
160
+ nackCalls: [],
161
+ prefetch: async function (count, globalScope) {
162
+ fake.prefetchCalls.push([count, globalScope])
163
+ },
164
+ assertQueue: async function (queueName, options) {
165
+ fake.assertQueueCalls.push([queueName, options])
166
+ return { queue: queueName }
167
+ },
168
+ consume: async function (queueName, handler, options) {
169
+ fake.consumeCalls.push({ queueName: queueName, handler: handler, options: options })
170
+ },
171
+ ack: function (message) {
172
+ fake.ackCalls.push(message)
173
+ },
174
+ nack: function (message, allUpTo, requeue) {
175
+ fake.nackCalls.push([message, allUpTo, requeue])
176
+ }
177
+ }
178
+ return fake
179
+ }
180
+
181
+ function createStubbedOn () {
182
+ var onInstance = new On(config)
183
+ var channel = createFakeChannel()
184
+ var listenCalls = []
185
+ var listenManualCalls = []
186
+ var listenManualHandlers = []
187
+ var listenOptions = []
188
+
189
+ onInstance.mqInstance.createChannel = async function () {
190
+ return channel
191
+ }
192
+
193
+ onInstance.queueInstance = {
194
+ setChannel: function () {},
195
+ on: function () {},
196
+ listen: async function (queueNames, options) {
197
+ listenCalls.push(queueNames)
198
+ listenOptions.push(options)
199
+ },
200
+ listenManual: async function (queueNames, handler) {
201
+ listenManualCalls.push(queueNames)
202
+ listenManualHandlers.push(handler)
203
+ }
204
+ }
205
+
206
+ return { onInstance: onInstance, channel: channel, listenCalls: listenCalls, listenOptions: listenOptions, listenManualCalls: listenManualCalls, listenManualHandlers: listenManualHandlers }
207
+ }
208
+
209
+ it('Should route auto ack tasks to listen and manual ack tasks to the manual consumer', async function () {
210
+ var stubs = createStubbedOn()
211
+
212
+ function execution (incomingData) {
213
+ return
214
+ }
215
+
216
+ stubs.onInstance
217
+ .taskReceived('autoAckQueue')
218
+ .andProcess(execution)
219
+
220
+ stubs.onInstance
221
+ .taskReceived('manualAckQueue')
222
+ .ackAfterProcessing()
223
+ .andProcess(execution)
224
+
225
+ await stubs.onInstance._startEventListeners()
226
+
227
+ stubs.listenCalls.should.deepEqual([['autoAckQueue']])
228
+ stubs.listenManualCalls.should.deepEqual([['manualAckQueue']])
229
+ stubs.listenManualCalls[0].should.not.deepEqual(stubs.listenCalls[0])
230
+ })
231
+
232
+ it('Should route delayed tasks through the auto-ack listen with isDelayed', async function () {
233
+ var stubs = createStubbedOn()
234
+
235
+ function execution (incomingData) {
236
+ return
237
+ }
238
+
239
+ stubs.onInstance
240
+ .taskReceived('delayedQueue', { isDelayed: true })
241
+ .andProcess(execution)
242
+
243
+ await stubs.onInstance._startEventListeners()
244
+
245
+ stubs.listenCalls.should.deepEqual([['delayedQueue']])
246
+ stubs.listenOptions[0].should.have.property('isDelayed', true)
247
+ })
248
+
249
+ it('Should await the actions and ack the message after processing', async function () {
250
+ var stubs = createStubbedOn()
251
+ var executed = []
252
+ var actionFinished = false
253
+
254
+ stubs.onInstance
255
+ .taskReceived('manualAckQueue')
256
+ .ackAfterProcessing()
257
+ .andProcess(async function (incomingData) {
258
+ await new Promise((resolve) => { setTimeout(resolve, 25) })
259
+ executed.push(incomingData)
260
+ actionFinished = true
261
+ })
262
+
263
+ await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
264
+
265
+ var handler = stubs.listenManualHandlers[0]
266
+
267
+ await handler({ name: 'Superman' }, { fields: { routingKey: 'manualAckQueue' } })
268
+
269
+ actionFinished.should.equal(true)
270
+ executed.should.deepEqual([{ name: 'Superman' }])
271
+ })
272
+
273
+ it('Should nack and drop the message when the task action fails', async function () {
274
+ var stubs = createStubbedOn()
275
+ var executed = false
276
+
277
+ stubs.onInstance
278
+ .taskReceived('failingManualQueue')
279
+ .ackAfterProcessing()
280
+ .andProcess(async function () {
281
+ executed = true
282
+ throw new Error('action failed')
283
+ })
284
+
285
+ await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
286
+
287
+ var handler = stubs.listenManualHandlers[0]
288
+
289
+ await handler({ name: 'Superman' }, { fields: { routingKey: 'failingManualQueue' } }).should.be.rejectedWith('action failed')
290
+
291
+ executed.should.equal(true)
292
+ })
293
+
294
+ it('Should nack the message when payload validation fails', async function () {
295
+ var stubs = createStubbedOn()
296
+ var executed = false
297
+
298
+ stubs.onInstance
299
+ .taskReceived('validatedManualQueue')
300
+ .withProperties(['name'])
301
+ .ackAfterProcessing()
302
+ .andProcess(async function () {
303
+ executed = true
304
+ })
305
+
306
+ await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
307
+
308
+ var handler = stubs.listenManualHandlers[0]
309
+
310
+ await handler({ other: 'value' }, { fields: { routingKey: 'validatedManualQueue' } }).should.be.rejectedWith('Validation failed for task validatedManualQueue')
311
+
312
+ executed.should.equal(false)
313
+ })
314
+
315
+ it('Should ack the message when payload validation passes', async function () {
316
+ var stubs = createStubbedOn()
317
+ var executed = []
318
+
319
+ stubs.onInstance
320
+ .taskReceived('validatedManualQueue')
321
+ .withProperties(['name'])
322
+ .ackAfterProcessing()
323
+ .andProcess(async function (incomingData) {
324
+ executed.push(incomingData)
325
+ })
326
+
327
+ await stubs.onInstance._initQueuesWithManualAck(stubs.onInstance._getEvents('queue'), stubs.channel)
328
+
329
+ var handler = stubs.listenManualHandlers[0]
330
+
331
+ await handler({ name: 'Superman' }, { fields: { routingKey: 'validatedManualQueue' } })
332
+
333
+ executed.should.deepEqual([{ name: 'Superman' }])
334
+ })
100
335
  })