@onify/fake-amqplib 2.0.0 → 3.1.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.
Files changed (5) hide show
  1. package/README.md +10 -10
  2. package/index.d.ts +8 -12
  3. package/index.js +309 -117
  4. package/main.cjs +309 -117
  5. package/package.json +9 -7
package/index.js CHANGED
@@ -4,8 +4,26 @@ import { format as urlFormat } from 'url';
4
4
 
5
5
  const kSmqp = Symbol.for('smqp');
6
6
  const kClosed = Symbol.for('closed');
7
- const kEmitter = Symbol.for('event emitter');
7
+ const kDeliveryTag = Symbol.for('channel delivery tag');
8
8
  const kPrefetch = Symbol.for('prefetch');
9
+ const kChannelPrefetch = Symbol.for('channel prefetch');
10
+
11
+ class AmqplibBroker extends Broker {
12
+ constructor(...args) {
13
+ super(...args);
14
+ this[kDeliveryTag] = 0;
15
+ }
16
+ _getNextDeliveryTag() {
17
+ return ++this[kDeliveryTag];
18
+ }
19
+ _getMessageByDeliveryTag(queue, deliveryTag) {
20
+ const q = this.getQueue(queue);
21
+ return q.messages.find((m) => m.fields.deliveryTag === deliveryTag);
22
+ }
23
+ _getChannelConsumers(channelName) {
24
+ return this.getConsumers().filter((f) => f.options.channelName === channelName);
25
+ }
26
+ }
9
27
 
10
28
  class FakeAmqpError extends Error {
11
29
  constructor(message, code, killChannel, killConnection) {
@@ -18,27 +36,55 @@ class FakeAmqpError extends Error {
18
36
 
19
37
  class FakeAmqpNotFoundError extends FakeAmqpError {
20
38
  constructor(type, name, vhost, killConnection = false) {
21
- super(`Channel closed by server: 404 (NOT-FOUND) with message "NOT_FOUND - no ${type} '${name}' in vhost '${vhost || '/'}'`, 404, true, killConnection);
39
+ super(
40
+ `Channel closed by server: 404 (NOT-FOUND) with message "NOT_FOUND - no ${type} '${name}' in vhost '${vhost || '/'}'`,
41
+ 404,
42
+ true,
43
+ killConnection,
44
+ );
22
45
  }
23
46
  }
24
47
 
25
- export class FakeAmqplibChannel {
48
+ class FakeAmqpUnknownDeliveryTag extends FakeAmqpError {
49
+ constructor(deliveryTag) {
50
+ super(
51
+ `Channel closed by server: 406 (PRECONDITION-FAILED) with message "PRECONDITION_FAILED - unknown delivery tag ${deliveryTag}`,
52
+ 406,
53
+ true,
54
+ false,
55
+ );
56
+ }
57
+ get _emit() {
58
+ return true;
59
+ }
60
+ }
61
+
62
+ function Message(smqpMessage, deliveryTag) {
63
+ this[kSmqp] = smqpMessage;
64
+ this.fields = { ...smqpMessage.fields, deliveryTag };
65
+ this.content = Buffer.from(smqpMessage.content);
66
+ this.properties = { ...smqpMessage.properties };
67
+ }
68
+
69
+ export class FakeAmqplibChannel extends EventEmitter {
26
70
  constructor(broker, connection) {
71
+ super();
27
72
  this.connection = connection;
28
73
 
29
74
  this[kPrefetch] = 10000;
75
+ this[kChannelPrefetch] = Infinity;
30
76
  this[kClosed] = false;
31
- this[kEmitter] = new EventEmitter();
32
- this._channelName = `channel-${generateId()}`;
77
+ const channelName = (this._channelName = `channel-${generateId()}`);
33
78
  this._version = connection._version;
34
79
  this._broker = broker;
35
80
 
81
+ this._channelQueue = broker.assertQueue(`#${channelName}`);
36
82
  this._emitReturn = this._emitReturn.bind(this);
37
83
 
38
84
  broker.on('return', this._emitReturn);
39
- }
40
- get _emitter() {
41
- return this[kEmitter];
85
+
86
+ this._createChannelMessage = this._createChannelMessage.bind(this);
87
+ this._calculateChannelCapacity = this._calculateChannelCapacity.bind(this);
42
88
  }
43
89
  get _closed() {
44
90
  return this[kClosed];
@@ -68,12 +114,12 @@ export class FakeAmqplibChannel {
68
114
  }
69
115
  bindExchange(destination, source, ...args) {
70
116
  const broker = this._broker;
71
- return Promise.all([ this.checkExchange(source), this.checkExchange(destination) ]).then(() => {
117
+ return Promise.all([this.checkExchange(source), this.checkExchange(destination)]).then(() => {
72
118
  return this._callBroker(broker.bindExchange, source, destination, ...args);
73
119
  });
74
120
  }
75
121
  bindQueue(queue, source, ...args) {
76
- return Promise.all([ this.checkQueue(queue), this.checkExchange(source) ]).then(() => {
122
+ return Promise.all([this.checkQueue(queue), this.checkExchange(source)]).then(() => {
77
123
  return this._callBroker(this._broker.bindQueue, queue, source, ...args);
78
124
  });
79
125
  }
@@ -104,14 +150,16 @@ export class FakeAmqplibChannel {
104
150
  }
105
151
  get(queue, ...args) {
106
152
  const connPath = this.connection._url.pathname;
153
+ const createMessage = this._createChannelMessage;
107
154
  return this._callBroker(getMessage, ...args);
108
155
 
109
156
  function getMessage(...getargs) {
110
157
  const q = this.getQueue(queue);
111
- if (!q) throw new FakeAmqpNotFoundError('queue', queue, connPath._url.pathname);
158
+ if (!q) throw new FakeAmqpNotFoundError('queue', queue, connPath);
112
159
  const msg = q.get(...getargs) || false;
113
160
  if (!msg) return msg;
114
- return new Message(msg);
161
+
162
+ return createMessage(msg, args[0]?.noAck);
115
163
  }
116
164
  }
117
165
  deleteExchange(exchange, ...args) {
@@ -138,15 +186,17 @@ export class FakeAmqplibChannel {
138
186
  if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
139
187
  if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
140
188
 
141
- const args = [ this._broker.publish, exchange, routingKey, content ];
189
+ const args = [this._broker.publish, exchange, routingKey, content];
142
190
 
143
191
  args.push(options, callback);
144
192
 
145
- this.checkExchange(exchange).then(() => {
146
- return this._callBroker(...args);
147
- }).catch((err) => {
148
- this[kEmitter].emit('error', err);
149
- });
193
+ this.checkExchange(exchange)
194
+ .then(() => {
195
+ return this._callBroker(...args);
196
+ })
197
+ .catch((err) => {
198
+ this.emit('error', err);
199
+ });
150
200
 
151
201
  return true;
152
202
  }
@@ -163,15 +213,17 @@ export class FakeAmqplibChannel {
163
213
  sendToQueue(queue, content, options, callback) {
164
214
  if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
165
215
 
166
- const args = [ this._broker.sendToQueue, queue, content ];
216
+ const args = [this._broker.sendToQueue, queue, content];
167
217
 
168
218
  args.push(options, callback);
169
219
 
170
- this.checkQueue(queue).then(() => {
171
- return this._callBroker(...args);
172
- }).catch((err) => {
173
- this[kEmitter].emit('error', err);
174
- });
220
+ this.checkQueue(queue)
221
+ .then(() => {
222
+ return this._callBroker(...args);
223
+ })
224
+ .catch((err) => {
225
+ this.emit('error', err);
226
+ });
175
227
 
176
228
  return true;
177
229
  }
@@ -214,31 +266,47 @@ export class FakeAmqplibChannel {
214
266
  }
215
267
  consume(queue, onMessage, options = {}, callback) {
216
268
  const { _id: connId, _url: connUrl } = this.connection;
269
+ const createMessage = this._createChannelMessage;
270
+ const calculateCapacity = this._calculateChannelCapacity;
217
271
  const channelName = this._channelName;
218
272
  const prefetch = this[kPrefetch];
219
273
 
220
- return this._callBroker(check, callback);
274
+ return this._callBroker(consume, callback);
221
275
 
222
- function check() {
276
+ function consume() {
223
277
  const q = queue && this.getQueue(queue);
224
278
  if (!q) {
225
279
  throw new FakeAmqpNotFoundError('queue', queue, connUrl.pathname);
226
280
  }
227
281
 
228
282
  if (q.exclusive || (q.options.exclusive && q.options._connectionId !== connId)) {
229
- throw new FakeAmqpError(`Channel closed by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - queue '${queue}' in vhost '${connUrl.pathname}' in exclusive use"`, 403, true, true);
283
+ throw new FakeAmqpError(
284
+ `Channel closed by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - queue '${queue}' in vhost '${connUrl.pathname}' in exclusive use"`,
285
+ 403,
286
+ true,
287
+ true,
288
+ );
230
289
  }
231
290
 
232
- const { consumerTag } = this.consume(queue, onMessage && handler, {
291
+ const consumer = this.consume(queue, onMessage && handler, {
233
292
  ...options,
234
293
  channelName,
235
- prefetch,
294
+ prefetch: calculateCapacity(prefetch),
295
+ _consumerPrefetch: prefetch,
296
+ });
297
+
298
+ const capacityProp = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(consumer), 'capacity');
299
+ Object.defineProperty(consumer, 'capacity', {
300
+ get() {
301
+ return calculateCapacity(capacityProp.get.call(this));
302
+ },
236
303
  });
237
- return { consumerTag };
304
+
305
+ return { consumerTag: consumer.consumerTag };
238
306
  }
239
307
 
240
308
  function handler(_, msg) {
241
- onMessage(new Message(msg));
309
+ onMessage(createMessage(msg, options.noAck));
242
310
  }
243
311
  }
244
312
  cancel(consumerTag, ...args) {
@@ -246,51 +314,137 @@ export class FakeAmqplibChannel {
246
314
  }
247
315
  close(callback) {
248
316
  if (this[kClosed]) return;
249
- const channelName = this._channelName;
250
- const broker = this._broker;
251
-
252
- broker.off('return', this._emitReturn);
253
- const channelConsumers = broker.getConsumers().filter((f) => f.options.channelName === channelName);
254
- channelConsumers.forEach((c) => broker.cancel(c.consumerTag));
255
- this[kClosed] = true;
256
- this[kEmitter].emit('close');
317
+ this._teardown();
318
+ this.emit('close');
257
319
  return resolveOrCallback(callback);
258
320
  }
259
- ack(message, ...args) {
260
- this._broker.ack(message[kSmqp], ...args);
321
+ ack(message, allUpTo) {
322
+ const deliveryTag = message.fields.deliveryTag;
323
+ const channelMessage = this._broker._getMessageByDeliveryTag(this._channelQueue.name, deliveryTag);
324
+ const channelQ = this._channelQueue;
325
+
326
+ if (!allUpTo) this._callBroker(ackMessage);
327
+ else this._callBroker(ackAllUpToMessage);
328
+
329
+ function ackMessage() {
330
+ const msg = message[kSmqp];
331
+ if (!channelMessage || !msg.pending) {
332
+ throw new FakeAmqpUnknownDeliveryTag(message.fields.deliveryTag);
333
+ }
334
+
335
+ channelQ.ack(channelMessage, false);
336
+ this.ack(msg, false);
337
+ }
338
+
339
+ function ackAllUpToMessage() {
340
+ const msg = message[kSmqp];
341
+ if (!channelMessage || !msg.pending) {
342
+ throw new FakeAmqpUnknownDeliveryTag(message.fields.deliveryTag);
343
+ }
344
+
345
+ const brokerMessages = allUpToDeliveryTag(channelQ, deliveryTag, 'ack', false);
346
+ for (const brokerMessage of brokerMessages) {
347
+ brokerMessage.ack(false);
348
+ }
349
+
350
+ channelQ.ack(channelMessage, false);
351
+ this.ack(msg, false);
352
+ }
261
353
  }
262
354
  ackAll() {
263
- const broker = this._broker;
264
- const channelName = this._channelName;
355
+ const channelQ = this._channelQueue;
356
+ let msg;
357
+ const brokerMessages = [];
358
+ while ((msg = channelQ.get())) {
359
+ brokerMessages.push(msg.content[kSmqp]);
360
+ msg.ack();
361
+ }
265
362
 
266
- const consumers = broker.getConsumers().filter(({ options }) => options.channelName === channelName);
267
- consumers.forEach((c) => broker.getConsumer(c.consumerTag).ackAll());
268
- }
269
- nack(message, ...args) {
270
- if (this.connection._version >= 2.3) throw new Error(`Nack is not implemented in versions before 2.3 (${this.connection._version})`);
271
- return this._broker.nack(message[kSmqp], ...args);
272
- }
273
- reject(message, ...args) {
274
- this._broker.reject(message[kSmqp], ...args);
363
+ for (const brokerMessage of brokerMessages) {
364
+ brokerMessage.ack();
365
+ }
275
366
  }
276
- nackAll(requeue = false) {
277
- const broker = this._broker;
278
- const channelName = this._channelName;
367
+ reject(message, requeue = false) {
368
+ const deliveryTag = message.fields.deliveryTag;
369
+ const channelMessage = this._broker._getMessageByDeliveryTag(this._channelQueue.name, deliveryTag);
370
+ const channelQ = this._channelQueue;
371
+
372
+ this._callBroker(rejectMessage);
279
373
 
280
- const consumers = broker.getConsumers().filter(({ options }) => options.channelName === channelName);
281
- consumers.forEach((c) => broker.getConsumer(c.consumerTag).nackAll(requeue));
374
+ function rejectMessage() {
375
+ const msg = message[kSmqp];
376
+ if (!channelMessage || !msg.pending) {
377
+ throw new FakeAmqpUnknownDeliveryTag(deliveryTag);
378
+ }
379
+
380
+ channelQ.reject(channelMessage, false);
381
+ this.reject(msg, requeue);
382
+ }
282
383
  }
283
- prefetch(val) {
284
- this[kPrefetch] = val;
384
+ nack(message, allUpTo = false, requeue = false) {
385
+ if (this.connection._version < 2.3) throw new Error(`Nack is not implemented in versions before 2.3 (${this.connection._version})`);
386
+
387
+ const deliveryTag = message.fields.deliveryTag;
388
+ const channelMessage = this._broker._getMessageByDeliveryTag(this._channelQueue.name, deliveryTag);
389
+ const channelQ = this._channelQueue;
390
+
391
+ if (!allUpTo) this._callBroker(nackMessage);
392
+ else this._callBroker(nackAllUpToMessage);
393
+
394
+ function nackMessage() {
395
+ const msg = message[kSmqp];
396
+ if (!channelMessage || !msg.pending) {
397
+ throw new FakeAmqpUnknownDeliveryTag(deliveryTag);
398
+ }
399
+
400
+ channelQ.nack(channelMessage, false, false);
401
+ this.nack(msg, false, requeue);
402
+ }
403
+
404
+ function nackAllUpToMessage() {
405
+ const msg = message[kSmqp];
406
+ if (!channelMessage || !msg.pending) {
407
+ throw new FakeAmqpUnknownDeliveryTag(deliveryTag);
408
+ }
409
+
410
+ const brokerMessages = allUpToDeliveryTag(channelQ, deliveryTag, 'nack', false, false);
411
+ for (const brokerMessage of brokerMessages) {
412
+ brokerMessage.nack(false, requeue);
413
+ }
414
+
415
+ channelMessage.nack(false, false);
416
+ this.nack(msg, false, requeue);
417
+ }
285
418
  }
286
- on(...args) {
287
- return this[kEmitter].on(...args);
419
+ nackAll(requeue = true) {
420
+ const channelQ = this._channelQueue;
421
+ let msg;
422
+ const brokerMessages = [];
423
+ while ((msg = channelQ.get())) {
424
+ brokerMessages.push(msg.content[kSmqp]);
425
+ msg.reject(false);
426
+ }
427
+
428
+ for (const brokerMessage of brokerMessages) {
429
+ brokerMessage.reject(requeue);
430
+ }
288
431
  }
289
- once(...args) {
290
- return this[kEmitter].once(...args);
432
+ prefetch(val, isChannelPrefetch) {
433
+ if (this.connection._version < 3.3) {
434
+ if (isChannelPrefetch !== undefined) {
435
+ return this.connection.close();
436
+ }
437
+ this[kChannelPrefetch] = val;
438
+ } else {
439
+ if (isChannelPrefetch) {
440
+ this[kChannelPrefetch] = val;
441
+ } else {
442
+ this[kPrefetch] = val;
443
+ }
444
+ }
291
445
  }
292
446
  _callBroker(fn, ...args) {
293
- let [ poppedCb ] = args.slice(-1);
447
+ let [poppedCb] = args.slice(-1);
294
448
  if (typeof poppedCb === 'function') args.splice(-1);
295
449
  else poppedCb = null;
296
450
 
@@ -304,7 +458,8 @@ export class FakeAmqplibChannel {
304
458
  return resolve(result);
305
459
  } catch (err) {
306
460
  if (err._killConnection) this.connection.close();
307
- else if (err._killChannel) this[kClosed] = true;
461
+ else if (err._killChannel) this._teardown();
462
+ if (err._emit) this.emit('error', err);
308
463
  if (!poppedCb) return reject(err);
309
464
  poppedCb(err);
310
465
  return resolve();
@@ -313,9 +468,44 @@ export class FakeAmqplibChannel {
313
468
  }
314
469
  _emitReturn({ fields, content, properties }) {
315
470
  process.nextTick(() => {
316
- this[kEmitter].emit('return', { fields, content, properties });
471
+ this.emit('return', { fields, content, properties });
317
472
  });
318
473
  }
474
+ _createChannelMessage(smqpMessage, noAck) {
475
+ const deliveryTag = this._broker._getNextDeliveryTag();
476
+ const consumeMessage = new Message(smqpMessage, deliveryTag);
477
+ if (!noAck) {
478
+ const channelQ = this._channelQueue;
479
+ channelQ.queueMessage(consumeMessage.fields, consumeMessage);
480
+ }
481
+ return consumeMessage;
482
+ }
483
+ _teardown() {
484
+ this[kClosed] = true;
485
+ const channelName = this._channelName;
486
+ const broker = this._broker;
487
+ const channelConsumers = broker._getChannelConsumers(channelName);
488
+ channelConsumers.forEach((c) => broker.cancel(c.consumerTag));
489
+
490
+ let msg;
491
+ while ((msg = this._channelQueue.get())) {
492
+ msg.content[kSmqp].reject(true);
493
+ msg.reject(false);
494
+ }
495
+
496
+ broker.off('return', this._emitReturn);
497
+ }
498
+ _calculateChannelCapacity(consumerCapacity) {
499
+ const channelPrefetch = this[kChannelPrefetch];
500
+ if (channelPrefetch === Infinity) return consumerCapacity;
501
+
502
+ const channelCapacity = channelPrefetch - this._channelQueue.messageCount;
503
+
504
+ let capacity = consumerCapacity;
505
+ if (channelCapacity <= 0) capacity = 0;
506
+ else if (channelCapacity < capacity) capacity = channelCapacity;
507
+ return capacity;
508
+ }
319
509
  }
320
510
 
321
511
  export class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
@@ -323,38 +513,42 @@ export class FakeAmqplibConfirmChannel extends FakeAmqplibChannel {
323
513
  if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
324
514
  if (exchange === '') return this.sendToQueue(routingKey, content, options, callback);
325
515
 
326
- const args = [ this._broker.publish, exchange, routingKey, content ];
516
+ const args = [this._broker.publish, exchange, routingKey, content];
327
517
 
328
518
  args.push(...addConfirmCallback(this._broker, options, callback));
329
519
 
330
- this.checkExchange(exchange).then(() => {
331
- return this._callBroker(...args);
332
- }).catch((err) => {
333
- this[kEmitter].emit('error', err);
334
- });
520
+ this.checkExchange(exchange)
521
+ .then(() => {
522
+ return this._callBroker(...args);
523
+ })
524
+ .catch((err) => {
525
+ this.emit('error', err);
526
+ });
335
527
 
336
528
  return true;
337
529
  }
338
530
  sendToQueue(queue, content, options, callback) {
339
531
  if (!Buffer.isBuffer(content)) throw new TypeError('content is not a buffer');
340
532
 
341
- const args = [ this._broker.sendToQueue, queue, content ];
533
+ const args = [this._broker.sendToQueue, queue, content];
342
534
 
343
535
  args.push(...addConfirmCallback(this._broker, options, callback));
344
536
 
345
- this.checkQueue(queue).then(() => {
346
- return this._callBroker(...args);
347
- }).catch((err) => {
348
- this[kEmitter].emit('error', err);
349
- });
537
+ this.checkQueue(queue)
538
+ .then(() => {
539
+ return this._callBroker(...args);
540
+ })
541
+ .catch((err) => {
542
+ this.emit('error', err);
543
+ });
350
544
 
351
545
  return true;
352
546
  }
353
547
  }
354
548
 
355
- export class FakeAmqplibConnection {
549
+ export class FakeAmqplibConnection extends EventEmitter {
356
550
  constructor(broker, version, amqpUrl) {
357
- this[kEmitter] = new EventEmitter();
551
+ super();
358
552
  this[kClosed] = false;
359
553
  this._channels = [];
360
554
  this._url = normalizeAmqpUrl(amqpUrl);
@@ -365,9 +559,6 @@ export class FakeAmqplibConnection {
365
559
  get _closed() {
366
560
  return this[kClosed];
367
561
  }
368
- get _emitter() {
369
- return this[kEmitter];
370
- }
371
562
  get connection() {
372
563
  return {
373
564
  serverProperties: {
@@ -401,16 +592,10 @@ export class FakeAmqplibConnection {
401
592
 
402
593
  this._channels.splice(0).forEach((channel) => channel.close());
403
594
 
404
- this[kEmitter].emit('close');
595
+ this.emit('close');
405
596
 
406
597
  return resolveOrCallback(args.slice(-1)[0]);
407
598
  }
408
- on(...args) {
409
- return this[kEmitter].on(...args);
410
- }
411
- once(...args) {
412
- return this[kEmitter].once(...args);
413
- }
414
599
  }
415
600
 
416
601
  export function FakeAmqplib(minorVersion = '3.5') {
@@ -434,14 +619,14 @@ FakeAmqplib.prototype.connect = function fakeConnect(amqpUrl, ...args) {
434
619
 
435
620
  FakeAmqplib.prototype.connectSync = function fakeConnectSync(amqpUrl, ...args) {
436
621
  const { _broker } = this.connections.find((conn) => compareConnectionString(conn._url, amqpUrl)) || {};
437
- const broker = _broker || new Broker(this);
622
+ const broker = _broker || new AmqplibBroker(this);
438
623
  const connection = new FakeAmqplibConnection(broker, this.version, amqpUrl, ...args);
439
624
 
440
625
  const connections = this.connections;
441
626
 
442
627
  connections.push(connection);
443
628
 
444
- connection._emitter.once('close', () => {
629
+ connection.once('close', () => {
445
630
  const idx = connections.indexOf(connection);
446
631
  if (idx > -1) connections.splice(idx, 1);
447
632
  });
@@ -477,39 +662,26 @@ function compareConnectionString(url1, url2) {
477
662
  return parsedUrl1.host === parsedUrl2.host && parsedUrl1.pathname === parsedUrl2.pathname;
478
663
  }
479
664
 
480
- function Message(smqpMessage) {
481
- this[kSmqp] = smqpMessage;
482
- this.content = smqpMessage.content;
483
- this.fields = smqpMessage.fields;
484
- this.properties = smqpMessage.properties;
485
- }
486
-
487
665
  function normalizeAmqpUrl(url) {
488
666
  if (!url) return new URL('amqp://localhost:5672/');
489
667
  if (typeof url === 'string') url = new URL(url);
490
668
 
491
669
  if (!(url instanceof URL)) {
492
- const {
493
- protocol = 'amqp',
494
- hostname = 'localhost',
495
- port = 5672,
496
- vhost = '/',
497
- username,
498
- password,
499
- ...rest
500
- } = url;
670
+ const { protocol = 'amqp', hostname = 'localhost', port = 5672, vhost = '/', username, password, ...rest } = url;
501
671
  let auth = username;
502
672
  if (auth && password) {
503
673
  auth += `:${password}`;
504
674
  }
505
- url = new URL(urlFormat({
506
- protocol,
507
- hostname,
508
- port,
509
- pathname: vhost,
510
- slashes: true,
511
- auth,
512
- }));
675
+ url = new URL(
676
+ urlFormat({
677
+ protocol,
678
+ hostname,
679
+ port,
680
+ pathname: vhost,
681
+ slashes: true,
682
+ auth,
683
+ }),
684
+ );
513
685
 
514
686
  for (const k in rest) {
515
687
  switch (k) {
@@ -556,7 +728,27 @@ function addConfirmCallback(broker, options, callback) {
556
728
  }
557
729
  }
558
730
 
559
- return [ options, confirmCallback ];
731
+ return [options, confirmCallback];
732
+ }
733
+
734
+ function allUpToDeliveryTag(q, deliveryTag, op, ...args) {
735
+ const brokerMessages = [];
736
+
737
+ const consumer = q.consume(
738
+ (_, cmsg) => {
739
+ const msgDeliveryTag = cmsg.fields.deliveryTag;
740
+ if (msgDeliveryTag >= deliveryTag) {
741
+ return q.cancel(cmsg.fields.consumerTag);
742
+ }
743
+ brokerMessages.push(cmsg.content[kSmqp]);
744
+ cmsg[op](...args);
745
+ },
746
+ { prefetch: Infinity },
747
+ );
748
+
749
+ consumer.cancel();
750
+
751
+ return brokerMessages;
560
752
  }
561
753
 
562
754
  const defaultFake = new FakeAmqplib('3.5');