comq 0.19.0 → 0.20.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.
@@ -0,0 +1,71 @@
1
+ Feature: Calls under a key
2
+
3
+ A call reaches the one connection holding its key. A call under a key nobody holds is refused
4
+ at once, and a holder that stops withdraws its key before it finishes what it holds.
5
+
6
+ Background:
7
+ Given an active connection to the broker
8
+
9
+ Scenario: Answered by the holder of the key
10
+ Given a holder answering `sums` under the `a` key:
11
+ """
12
+ ({ a, b }) => a + b
13
+ """
14
+ When the consumer calls `sums` under the `a` key with:
15
+ """yaml
16
+ a: 1
17
+ b: 2
18
+ """
19
+ Then the consumer receives the reply:
20
+ """yaml
21
+ 3
22
+ """
23
+
24
+ Scenario: Answered by the holder of that key only
25
+ Given a holder answering `names` under the `a` key:
26
+ """
27
+ () => 'a'
28
+ """
29
+ And a holder answering `names` under the `b` key:
30
+ """
31
+ () => 'b'
32
+ """
33
+ When the consumer calls `names` under the `b` key
34
+ Then the consumer receives the reply:
35
+ """yaml
36
+ b
37
+ """
38
+
39
+ Scenario: Refused when nobody holds the key
40
+ Given a holder answering `names` under the `a` key:
41
+ """
42
+ () => 'a'
43
+ """
44
+ When the consumer calls `names` under the `c` key
45
+ Then the call is refused as unroutable
46
+
47
+ Scenario: A sealed holder answers what it holds and refuses what follows
48
+ Given a holder on another connection answering `slow` under the `a` key in 500ms
49
+ When the consumer calls `slow` under the `a` key
50
+ And after 100ms
51
+ And the holder is sealed
52
+ Then the consumer receives the reply
53
+ When the consumer calls `slow` under the `a` key
54
+ Then the call is refused as unroutable
55
+
56
+ Scenario: A second holder waits for the key
57
+ Given a holder on another connection answering `echo` under the `a` key
58
+ When a second holder on another connection holds `echo` under the `a` key
59
+ Then the second holder finds the key taken
60
+ When the first holder disconnects
61
+ Then the second holder holds the key
62
+ When the consumer calls `echo` under the `a` key
63
+ Then the consumer receives the reply
64
+
65
+ Scenario: A caller stops waiting for a holder that crashed
66
+ Given a holder never answering `void` under the `a` key
67
+ When the consumer calls `void` under the `a` key with a 1000ms timeout
68
+ And after 100ms
69
+ And the silent holder crashes
70
+ Then the consumer stops waiting
71
+ And a call to `void` under the `a` key is refused within 10 seconds
@@ -0,0 +1,75 @@
1
+ @heavy
2
+ Feature: Calls under a key across failures
3
+
4
+ A holder that loses its connection holds its key again once the connection is restored, and a
5
+ caller's timeout ends its wait whatever the broker is doing.
6
+
7
+ Scenario: A holder holds its key again after the broker restarts
8
+ Given an active connection to the broker
9
+ And a holder on another connection answering `echo` under the `a` key
10
+ When the broker has crashed
11
+ And the broker is up
12
+ Then a call to `echo` under the `a` key is answered within 60 seconds
13
+
14
+ Scenario: A holder holds its key again once the broker lets its silent connection go
15
+
16
+ The holder reconnects while the broker still holds the connection that went silent, and with
17
+ it the key. The holder finds the key taken until the broker lets that connection go, and
18
+ claims it then.
19
+
20
+ Given watchdog interval is set to 2000ms with 60s AMQP heartbeat
21
+ And a network that can go silent
22
+ And an active connection to the broker
23
+ And a holder answering `echo` under the `a` key:
24
+ """
25
+ (payload) => payload
26
+ """
27
+ When the network goes silent
28
+ Then the connection is lost within 10 seconds
29
+ And the holder finds its key taken while the broker holds the silent connection
30
+ When the silent connection is let go
31
+ Then a call to `echo` under the `a` key from another connection is answered within 60 seconds
32
+
33
+ Scenario: A call in flight when the broker crashes ends
34
+ Given an active connection to the broker
35
+ And a holder on another connection answering `slow` under the `a` key in 2000ms
36
+ When the consumer calls `slow` under the `a` key
37
+ And after 200ms
38
+ And the broker has crashed
39
+ And the broker is up
40
+ Then the call ends
41
+
42
+ Scenario: A caller stops waiting for a request at its timeout while the broker is down
43
+ Given an active connection to the broker
44
+ And a producer replying `echo` queue
45
+ When the broker has crashed
46
+ And the consumer sends a request to the `echo` queue with a 1000ms timeout
47
+ Then the consumer stops waiting within 5 seconds
48
+
49
+ Scenario: A caller stops waiting for a call at its timeout while the broker is down
50
+ Given an active connection to the broker
51
+ And a holder on another connection answering `echo` under the `a` key
52
+ When the broker has crashed
53
+ And the consumer calls `echo` under the `a` key with a 1000ms timeout
54
+ Then the consumer stops waiting within 5 seconds
55
+
56
+ Scenario: A request re-sent after the broker restarts expires at its caller's deadline
57
+
58
+ The request is lost with the broker and sent again once the connection is restored, carrying
59
+ the time its caller has left.
60
+
61
+ Given an active connection to the broker
62
+ When the consumer sends a request to the `resent` queue with a 30000ms timeout
63
+ And after 200ms
64
+ And the broker has crashed
65
+ And the broker is up
66
+ Then the consumer stops waiting within 40 seconds
67
+ When a producer counting requests to the `resent` queue
68
+ Then the producer receives nothing
69
+
70
+ Scenario: A call reaches its holder while one of the brokers is down
71
+ Given an active sharded connection
72
+ And a holder on another connection answering `echo` under the `a` key
73
+ When one of the brokers has crashed
74
+ And the consumer calls `echo` under the `a` key 20 times
75
+ Then every call is answered
@@ -0,0 +1,24 @@
1
+ Feature: Calls under a key over a sharded connection
2
+
3
+ A call is published to one shard, and a shard where nobody holds the key returns it: it is
4
+ published on the next one, and refused once every shard has returned it.
5
+
6
+ Background:
7
+ Given an active sharded connection
8
+
9
+ Scenario: Answered by a holder on one broker only
10
+ Given a holder connected to broker 0 answering `echo` under the `a` key
11
+ When the consumer calls `echo` under the `a` key 20 times
12
+ Then every call is answered
13
+
14
+ Scenario: Refused when nobody holds the key on any broker
15
+ Given a holder connected to broker 0 answering `echo` under the `a` key
16
+ When the consumer calls `echo` under the `b` key
17
+ Then the call is refused as unroutable
18
+
19
+ Scenario: A key taken on one broker is held on the other, and on both once it is let go
20
+ Given a holder connected to broker 0 answering `whom` under the `a` key as `first`
21
+ When a second holder on another connection holds `whom` under the `a` key as `second`
22
+ Then the second holder finds the key taken
23
+ When the first holder disconnects
24
+ Then every call to `whom` under the `a` key is answered by `second` within 30 seconds
@@ -0,0 +1,20 @@
1
+ Feature: Parked requests
2
+
3
+ Background:
4
+ Given an active connection to the broker
5
+
6
+ Scenario: A parked request can still be answered
7
+
8
+ A caller that sets no timeout is certain of an eventual answer for as long as it is
9
+ alive, and parking is what keeps that true when no consumer can produce one: the
10
+ request is kept, along with who was asking. Somebody else can answer it, and the
11
+ caller — still waiting, still holding an exclusive reply queue — receives it.
12
+
13
+ A caller that sets a timeout trades that for an end to the wait: once it stops
14
+ waiting, a parked request is addressed to a caller that has gone.
15
+
16
+ Given a producer failing every request to the `unanswerable` queue
17
+ When a request is sent to the `unanswerable` queue
18
+ Then the request is parked
19
+ When the parked request from the `unanswerable` queue is answered by hand
20
+ Then the caller receives the reply
@@ -0,0 +1,19 @@
1
+ Feature: Request timeout
2
+
3
+ A caller may say how long it waits for a reply. A request nobody took by then is dropped by the
4
+ broker; one already taken is still processed, and its reply is discarded.
5
+
6
+ Background:
7
+ Given an active connection to the broker
8
+
9
+ Scenario: A caller stops waiting at its timeout
10
+ Given a producer replying `slow` queue in 1000ms
11
+ When the consumer sends a request to the `slow` queue with a 200ms timeout
12
+ Then the consumer stops waiting
13
+
14
+ Scenario: A request nobody took in time is never processed
15
+ When the consumer sends a request to the `later` queue with a 200ms timeout
16
+ Then the consumer stops waiting
17
+ When after 300ms
18
+ And a producer counting requests to the `later` queue
19
+ Then the producer receives nothing
@@ -0,0 +1,462 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const amqplib = require('amqplib')
5
+ const { Given, When, Then, After } = require('@cucumber/cucumber')
6
+
7
+ const { connect, Unroutable } = require('../../')
8
+ const { parse } = require('./yaml')
9
+ const { getAddress, USER, PASSWORD } = require('./brokers')
10
+ const { timeout, until } = require('../../test/helpers')
11
+
12
+ Given('a holder answering {token} under the {token} key:',
13
+ /**
14
+ * @param {string} exchange
15
+ * @param {string} key
16
+ * @param {string} javascript
17
+ * @this {comq.features.Context}
18
+ */
19
+ async function (exchange, key, javascript) {
20
+ // eslint-disable-next-line no-new-func
21
+ const producer = new Function('return ' + javascript)()
22
+
23
+ this.io.diagnose('taken', () => (this.taken = true))
24
+
25
+ await this.io.back(exchange, key, producer)
26
+ })
27
+
28
+ Given('a holder on another connection answering {token} under the {token} key',
29
+ /**
30
+ * @param {string} exchange
31
+ * @param {string} key
32
+ * @this {comq.features.Context}
33
+ */
34
+ async function (exchange, key) {
35
+ this.holder = await another(this)
36
+
37
+ await this.holder.back(exchange, key, echo)
38
+ })
39
+
40
+ Given('a holder on another connection answering {token} under the {token} key in {number}ms',
41
+ /**
42
+ * @param {string} exchange
43
+ * @param {string} key
44
+ * @param {number} delay
45
+ * @this {comq.features.Context}
46
+ */
47
+ async function (exchange, key, delay) {
48
+ this.holder = await another(this)
49
+
50
+ await this.holder.back(exchange, key, async (payload) => {
51
+ await timeout(delay)
52
+
53
+ return echo(payload)
54
+ })
55
+ })
56
+
57
+ Given('a holder connected to broker {number} answering {token} under the {token} key',
58
+ /**
59
+ * @param {number} broker
60
+ * @param {string} exchange
61
+ * @param {string} key
62
+ * @this {comq.features.Context}
63
+ */
64
+ async function (broker, exchange, key) {
65
+ this.holder = await another(this, broker)
66
+
67
+ await this.holder.back(exchange, key, echo)
68
+ })
69
+
70
+ Given('a holder never answering {token} under the {token} key',
71
+ /**
72
+ * Holds the key as comq does, and takes a Request without ever answering it, so that losing
73
+ * its connection loses the Request with it.
74
+ *
75
+ * @param {string} exchange
76
+ * @param {string} key
77
+ * @this {comq.features.Context}
78
+ */
79
+ async function (exchange, key) {
80
+ const connection = await amqplib.connect(url(0))
81
+ const channel = await connection.createChannel()
82
+ const queue = exchange + '.' + key
83
+
84
+ await channel.assertExchange(exchange, 'direct', { durable: true })
85
+ await channel.assertQueue(queue, { exclusive: true })
86
+ await channel.bindQueue(queue, exchange, key)
87
+ await channel.consume(queue, () => undefined)
88
+
89
+ this.silent = connection
90
+ })
91
+
92
+ When('the silent holder crashes',
93
+ /**
94
+ * Its socket is destroyed without a word, as a process that dies leaves it.
95
+ *
96
+ * @this {comq.features.Context}
97
+ */
98
+ function () {
99
+ const connection = this.silent
100
+
101
+ this.silent = undefined
102
+
103
+ connection.on('error', () => undefined)
104
+ connection.connection.stream.destroy()
105
+ })
106
+
107
+ When('the consumer calls {token} under the {token} key',
108
+ /**
109
+ * @param {string} exchange
110
+ * @param {string} key
111
+ * @this {comq.features.Context}
112
+ */
113
+ async function (exchange, key) {
114
+ call(this, this.io.call(exchange, key, { key }))
115
+ })
116
+
117
+ When('the consumer calls {token} under the {token} key with:',
118
+ /**
119
+ * @param {string} exchange
120
+ * @param {string} key
121
+ * @param {string} yaml
122
+ * @this {comq.features.Context}
123
+ */
124
+ async function (exchange, key, yaml) {
125
+ call(this, this.io.call(exchange, key, parse(yaml)))
126
+ })
127
+
128
+ When('the consumer calls {token} under the {token} key with a {number}ms timeout',
129
+ /**
130
+ * @param {string} exchange
131
+ * @param {string} key
132
+ * @param {number} ms
133
+ * @this {comq.features.Context}
134
+ */
135
+ async function (exchange, key, ms) {
136
+ call(this, this.io.call(exchange, key, { key }, { timeout: ms }))
137
+ })
138
+
139
+ When('the consumer calls {token} under the {token} key {number} times',
140
+ /**
141
+ * @param {string} exchange
142
+ * @param {string} key
143
+ * @param {number} times
144
+ * @this {comq.features.Context}
145
+ */
146
+ async function (exchange, key, times) {
147
+ const calls = Array.from({ length: times }, (_, n) => this.io.call(exchange, key, n))
148
+
149
+ this.replies = await Promise.all(calls)
150
+ })
151
+
152
+ When('the consumer sends a request to the {token} queue with a {number}ms timeout',
153
+ /**
154
+ * @param {string} queue
155
+ * @param {number} ms
156
+ * @this {comq.features.Context}
157
+ */
158
+ async function (queue, ms) {
159
+ call(this, this.io.request(queue, { queue }, { timeout: ms }))
160
+ })
161
+
162
+ When('the holder is sealed',
163
+ /**
164
+ * @this {comq.features.Context}
165
+ */
166
+ async function () {
167
+ await this.holder.seal()
168
+ })
169
+
170
+ When('a second holder on another connection holds {token} under the {token} key',
171
+ /**
172
+ * @param {string} exchange
173
+ * @param {string} key
174
+ * @this {comq.features.Context}
175
+ */
176
+ async function (exchange, key) {
177
+ const second = await another(this)
178
+
179
+ second.diagnose('taken', () => (this.taken = true))
180
+
181
+ this.holding = second.back(exchange, key, echo)
182
+
183
+ // asserted on by a later step, and an unhandled rejection would end the run before it
184
+ this.holding.catch(() => undefined)
185
+ })
186
+
187
+ When('the first holder disconnects',
188
+ /**
189
+ * @this {comq.features.Context}
190
+ */
191
+ async function () {
192
+ await this.holder.close()
193
+ })
194
+
195
+ When('a producer counting requests to the {token} queue',
196
+ /**
197
+ * @param {string} queue
198
+ * @this {comq.features.Context}
199
+ */
200
+ async function (queue) {
201
+ this.produced = 0
202
+
203
+ await this.io.reply(queue, () => {
204
+ this.produced++
205
+
206
+ return null
207
+ })
208
+ })
209
+
210
+ Then('the call is refused as unroutable',
211
+ /**
212
+ * @this {comq.features.Context}
213
+ */
214
+ async function () {
215
+ await assert.rejects(this.reply, (error) => error instanceof Unroutable)
216
+ })
217
+
218
+ Then('the consumer stops waiting',
219
+ /**
220
+ * @this {comq.features.Context}
221
+ */
222
+ async function () {
223
+ await assert.rejects(this.reply, (error) => error.name === 'TimeoutError')
224
+ })
225
+
226
+ Given('a holder connected to broker {number} answering {token} under the {token} key as {token}',
227
+ /**
228
+ * @param {number} broker
229
+ * @param {string} exchange
230
+ * @param {string} key
231
+ * @param {string} name
232
+ * @this {comq.features.Context}
233
+ */
234
+ async function (broker, exchange, key, name) {
235
+ this.holder = await another(this, broker)
236
+
237
+ await this.holder.back(exchange, key, () => name)
238
+ })
239
+
240
+ When('a second holder on another connection holds {token} under the {token} key as {token}',
241
+ /**
242
+ * @param {string} exchange
243
+ * @param {string} key
244
+ * @param {string} name
245
+ * @this {comq.features.Context}
246
+ */
247
+ async function (exchange, key, name) {
248
+ const second = await another(this)
249
+
250
+ second.diagnose('taken', () => (this.taken = true))
251
+
252
+ await second.back(exchange, key, () => name)
253
+ })
254
+
255
+ Then('the second holder finds the key taken',
256
+ /**
257
+ * @this {comq.features.Context}
258
+ */
259
+ async function () {
260
+ assert.equal(await until(() => this.taken === true), true, 'The key was not found taken')
261
+ })
262
+
263
+ Then('every call to {token} under the {token} key is answered by {token} within {number} seconds', { timeout: 120_000 },
264
+ /**
265
+ * @param {string} exchange
266
+ * @param {string} key
267
+ * @param {string} name
268
+ * @param {number} seconds
269
+ * @this {comq.features.Context}
270
+ */
271
+ async function (exchange, key, name, seconds) {
272
+ const answered = await eventually(async () => {
273
+ const calls = Array.from({ length: 20 }, () => this.io.call(exchange, key, null, { timeout: 1000 }))
274
+ const replies = await Promise.all(calls)
275
+
276
+ if (!replies.every((reply) => reply === name)) throw new Error('Answered by another')
277
+ }, seconds)
278
+
279
+ assert.equal(answered, true, `Calls were not all answered by ${name}`)
280
+ })
281
+
282
+ Then('the second holder holds the key',
283
+ /**
284
+ * @this {comq.features.Context}
285
+ */
286
+ async function () {
287
+ await this.holding
288
+ })
289
+
290
+ Then('the holder finds its key taken while the broker holds the silent connection', { timeout: 60_000 },
291
+ /**
292
+ * @this {comq.features.Context}
293
+ */
294
+ async function () {
295
+ assert.equal(await until(() => this.taken === true, 50_000), true, 'The key was not found taken')
296
+ })
297
+
298
+ Then('a call to {token} under the {token} key is answered within {number} seconds', { timeout: 120_000 },
299
+ /**
300
+ * @param {string} exchange
301
+ * @param {string} key
302
+ * @param {number} seconds
303
+ * @this {comq.features.Context}
304
+ */
305
+ async function (exchange, key, seconds) {
306
+ const answered = await eventually(() => this.io.call(exchange, key, { key }, { timeout: 1000 }), seconds)
307
+
308
+ assert.equal(answered, true, 'The call was not answered')
309
+ })
310
+
311
+ Then('a call to {token} under the {token} key from another connection is answered within {number} seconds', { timeout: 120_000 },
312
+ /**
313
+ * @param {string} exchange
314
+ * @param {string} key
315
+ * @param {number} seconds
316
+ * @this {comq.features.Context}
317
+ */
318
+ async function (exchange, key, seconds) {
319
+ const caller = await another(this)
320
+ const answered = await eventually(() => caller.call(exchange, key, { key }, { timeout: 1000 }), seconds)
321
+
322
+ assert.equal(answered, true, 'The call was not answered')
323
+ })
324
+
325
+ Then('a call to {token} under the {token} key is refused within {number} seconds', { timeout: 60_000 },
326
+ /**
327
+ * @param {string} exchange
328
+ * @param {string} key
329
+ * @param {number} seconds
330
+ * @this {comq.features.Context}
331
+ */
332
+ async function (exchange, key, seconds) {
333
+ const refused = await eventually(async () => {
334
+ try {
335
+ await this.io.call(exchange, key, { key }, { timeout: 1000 })
336
+ } catch (error) {
337
+ if (error instanceof Unroutable) return
338
+
339
+ throw error
340
+ }
341
+
342
+ throw new Error('Answered')
343
+ }, seconds)
344
+
345
+ assert.equal(refused, true, 'The call was not refused')
346
+ })
347
+
348
+ Then('the call ends', { timeout: 90_000 },
349
+ /**
350
+ * @this {comq.features.Context}
351
+ */
352
+ async function () {
353
+ await this.reply.catch(() => undefined)
354
+ })
355
+
356
+ Then('the consumer stops waiting within {number} second(s)', { timeout: 90_000 },
357
+ /**
358
+ * @param {number} seconds
359
+ * @this {comq.features.Context}
360
+ */
361
+ async function (seconds) {
362
+ const stopped = this.reply.then(() => 'answered', (error) => error.name === 'TimeoutError' ? 'stopped' : error.message)
363
+ const late = timeout(seconds * 1000).then(() => 'still waiting')
364
+
365
+ assert.equal(await Promise.race([stopped, late]), 'stopped')
366
+ })
367
+
368
+ Then('every call is answered',
369
+ /**
370
+ * @this {comq.features.Context}
371
+ */
372
+ async function () {
373
+ assert.deepEqual(this.replies, this.replies.map((_, n) => n))
374
+ })
375
+
376
+ Then('the producer receives nothing',
377
+ /**
378
+ * @this {comq.features.Context}
379
+ */
380
+ async function () {
381
+ await timeout(500)
382
+
383
+ assert.equal(this.produced, 0, 'An expired request was processed')
384
+ })
385
+
386
+ After(
387
+ /**
388
+ * @this {comq.features.Context}
389
+ */
390
+ async function () {
391
+ await Promise.all((this.holders ?? []).map((io) => io.close()))
392
+ await this.silent?.close().catch(() => undefined)
393
+
394
+ this.holders = []
395
+ this.silent = undefined
396
+ })
397
+
398
+ /**
399
+ * Another connection, to the brokers the consumer is connected to, or to one of them.
400
+ *
401
+ * @param {comq.features.Context} context
402
+ * @param {number} [broker]
403
+ * @returns {Promise<comq.IO>}
404
+ */
405
+ async function another (context, broker) {
406
+ const urls = broker !== undefined
407
+ ? [url(broker)]
408
+ : context.sharded ? [url(0), url(1)] : [url(0)]
409
+
410
+ const io = await connect(...urls)
411
+
412
+ context.holders ??= []
413
+ context.holders.push(io)
414
+
415
+ return io
416
+ }
417
+
418
+ /**
419
+ * @param {comq.features.Context} context
420
+ * @param {Promise<any>} reply
421
+ */
422
+ function call (context, reply) {
423
+ // asserted on by a later step, and an unhandled rejection would end the run before it
424
+ reply.catch(() => undefined)
425
+
426
+ context.reply = reply
427
+ }
428
+
429
+ /**
430
+ * @param {number} broker
431
+ * @returns {string}
432
+ */
433
+ function url (broker) {
434
+ return `amqp://${USER}:${PASSWORD}@${getAddress(broker)}`
435
+ }
436
+
437
+ /**
438
+ * Makes an attempt until it succeeds, or until the time is up.
439
+ *
440
+ * @param {() => Promise<unknown>} attempt
441
+ * @param {number} seconds
442
+ * @returns {Promise<boolean>} whether it succeeded
443
+ */
444
+ async function eventually (attempt, seconds) {
445
+ const deadline = Date.now() + seconds * 1000
446
+
447
+ while (Date.now() < deadline) {
448
+ try {
449
+ await attempt()
450
+
451
+ return true
452
+ } catch {
453
+ await timeout(500)
454
+ }
455
+ }
456
+
457
+ return false
458
+ }
459
+
460
+ function echo (payload) {
461
+ return payload
462
+ }
@@ -21,6 +21,8 @@ class Context extends World {
21
21
  attempts = []
22
22
  counts = {}
23
23
  parked = {}
24
+ awaited
25
+ answer
24
26
  processed
25
27
  enqueued
26
28
  tasksProcessedCount = 0
@@ -69,6 +71,8 @@ class Context extends World {
69
71
  this.attempts = []
70
72
  this.counts = {}
71
73
  this.parked = {}
74
+ this.awaited = undefined
75
+ this.answer = undefined
72
76
  }
73
77
 
74
78
  /**
@@ -26,6 +26,14 @@ When('the network goes silent',
26
26
  silence.call(this)
27
27
  })
28
28
 
29
+ When('the silent connection is let go',
30
+ /**
31
+ * @this {comq.features.Context}
32
+ */
33
+ function () {
34
+ for (const network of this.networks) network.release()
35
+ })
36
+
29
37
  // a request that is answered over a connection that is already silent proves
30
38
  // nothing, hence the window between the two is left as short as a step boundary
31
39
  When('the consumer sends a request to the {token} queue as the network goes silent',