comq 0.4.1 → 0.5.0-dev.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/labels.yaml +15 -0
- package/.github/workflows/labels.yaml +18 -0
- package/.github/workflows/release.yaml +20 -8
- package/docker-compose.yaml +20 -2
- package/docs/notes.md +14 -8
- package/examples/const.js +1 -3
- package/examples/events/consumer.js +5 -6
- package/examples/events/producer.js +3 -3
- package/examples/package.json +3 -0
- package/examples/rpc/consumer.js +3 -3
- package/examples/rpc/producer.js +3 -3
- package/features/connection.feature +3 -0
- package/features/events.feature +1 -1
- package/features/shards.feature +61 -0
- package/features/steps/.test/backpressure.test.js +2 -2
- package/features/steps/.test/broker.test.js +32 -6
- package/features/steps/.test/connection.test.js +105 -1
- package/features/steps/.test/context.mock.js +2 -2
- package/features/steps/.test/context.test.js +13 -5
- package/features/steps/.test/events.test.js +66 -3
- package/features/steps/.test/rpc.test.js +99 -1
- package/features/steps/.types/context.d.ts +11 -1
- package/features/steps/backpressure.js +1 -1
- package/features/steps/broker.js +26 -9
- package/features/steps/connection.js +69 -2
- package/features/steps/context.js +28 -12
- package/features/steps/events.js +41 -2
- package/features/steps/rpc.js +58 -1
- package/lerna.json +1 -1
- package/package.json +5 -5
- package/readme.md +75 -25
- package/source/channel.js +56 -32
- package/source/connect.js +28 -2
- package/source/connection.js +24 -17
- package/source/decode.js +1 -1
- package/source/events.js +7 -0
- package/source/io.js +21 -14
- package/source/shards/channel.js +219 -0
- package/source/shards/connection.js +56 -0
- package/source/shards/index.js +5 -0
- package/test/channel.test.js +240 -145
- package/test/connect.test.js +39 -11
- package/test/connection.mock.js +17 -13
- package/test/connection.test.js +65 -12
- package/test/decode.test.js +5 -5
- package/test/io.close.test.js +2 -2
- package/test/io.consume.test.js +1 -1
- package/test/io.reply.test.js +7 -7
- package/test/io.request.test.js +27 -1
- package/test/presets.test.js +1 -1
- package/test/shards/channel.test.js +367 -0
- package/test/shards/connection.test.js +106 -0
- package/types/amqp.d.ts +3 -1
- package/types/channel.d.ts +8 -5
- package/types/connection.d.ts +3 -1
- package/types/diagnostic.d.ts +1 -1
- package/types/topology.d.ts +4 -0
- /package/source/{presets → topology}/event.json +0 -0
- /package/source/{presets → topology}/index.js +0 -0
- /package/source/{presets → topology}/reply.json +0 -0
- /package/source/{presets → topology}/request.json +0 -0
package/test/channel.test.js
CHANGED
|
@@ -15,7 +15,7 @@ it('should be', async () => {
|
|
|
15
15
|
expect(create).toBeDefined()
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
/** @type {jest.MockedObject<
|
|
18
|
+
/** @type {jest.MockedObject<comq.amqp.Connection>} */
|
|
19
19
|
let connection
|
|
20
20
|
|
|
21
21
|
/** @type {comq.Topology} */
|
|
@@ -83,7 +83,7 @@ describe('consume', () => {
|
|
|
83
83
|
expect(chan.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
|
|
84
84
|
|
|
85
85
|
const content = randomBytes(8)
|
|
86
|
-
const message = /** @type {
|
|
86
|
+
const message = /** @type {comq.amqp.Message} */ { content }
|
|
87
87
|
const callback = chan.consume.mock.calls[0][1]
|
|
88
88
|
|
|
89
89
|
await callback(message)
|
|
@@ -108,7 +108,7 @@ describe('acknowledgments', () => {
|
|
|
108
108
|
|
|
109
109
|
const callback = chan.consume.mock.calls[0][1]
|
|
110
110
|
const content = randomBytes(8)
|
|
111
|
-
const message = /** @type {
|
|
111
|
+
const message = /** @type {comq.amqp.Message} */ { content }
|
|
112
112
|
|
|
113
113
|
await callback(message)
|
|
114
114
|
|
|
@@ -149,7 +149,7 @@ describe('acknowledgments', () => {
|
|
|
149
149
|
const content = randomBytes(8)
|
|
150
150
|
const properties = {}
|
|
151
151
|
const fields = {}
|
|
152
|
-
const message = /** @type {
|
|
152
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties, fields }
|
|
153
153
|
|
|
154
154
|
if (!requeue) fields.redelivered = !requeue
|
|
155
155
|
|
|
@@ -172,7 +172,7 @@ describe('acknowledgments', () => {
|
|
|
172
172
|
const content = randomBytes(8)
|
|
173
173
|
const properties = {}
|
|
174
174
|
const fields = {}
|
|
175
|
-
const message = /** @type {
|
|
175
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties, fields }
|
|
176
176
|
|
|
177
177
|
chan.ack.mockImplementation(() => { throw new Error('Channel closed') })
|
|
178
178
|
chan.nack.mockImplementation(() => { throw new Error('Channel closed') })
|
|
@@ -308,11 +308,44 @@ describe('throw', () => {
|
|
|
308
308
|
expect(call[3]).toMatchObject(options)
|
|
309
309
|
})
|
|
310
310
|
|
|
311
|
-
it('should
|
|
311
|
+
it('should ignore exceptions', async () => {
|
|
312
312
|
chan.publish.mockImplementation(() => { throw new Error() })
|
|
313
313
|
|
|
314
314
|
await expect(channel.throw(queue, buffer, options)).resolves.not.toThrow()
|
|
315
315
|
})
|
|
316
|
+
|
|
317
|
+
it('should wait for unpause', async () => {
|
|
318
|
+
jest.clearAllMocks()
|
|
319
|
+
|
|
320
|
+
topology.confirms = false
|
|
321
|
+
channel = await create(connection, topology)
|
|
322
|
+
|
|
323
|
+
const exchange = generate()
|
|
324
|
+
const queue = generate()
|
|
325
|
+
const buffer = randomBytes(8)
|
|
326
|
+
const options = { contentType: 'application/octet-stream' }
|
|
327
|
+
|
|
328
|
+
// create channel
|
|
329
|
+
await channel.publish(exchange, buffer, options)
|
|
330
|
+
|
|
331
|
+
const chan = await getCreatedChannel()
|
|
332
|
+
|
|
333
|
+
chan.publish.mockImplementation(() => false)
|
|
334
|
+
|
|
335
|
+
await channel.publish(exchange, buffer, options) // now paused
|
|
336
|
+
|
|
337
|
+
expect(chan.publish).toHaveBeenCalledTimes(2)
|
|
338
|
+
|
|
339
|
+
setImmediate(async () => {
|
|
340
|
+
expect(chan.publish).toHaveBeenCalledTimes(2)
|
|
341
|
+
|
|
342
|
+
chan.emit('drain')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
await channel.throw(queue, buffer, options)
|
|
346
|
+
|
|
347
|
+
expect(chan.publish).toHaveBeenCalledTimes(3)
|
|
348
|
+
})
|
|
316
349
|
})
|
|
317
350
|
|
|
318
351
|
describe.each(['group', 'exclusive'])('%s subscribe', (option) => {
|
|
@@ -447,6 +480,9 @@ describe('publish', () => {
|
|
|
447
480
|
})
|
|
448
481
|
|
|
449
482
|
describe('seal', () => {
|
|
483
|
+
const queue = generate()
|
|
484
|
+
const consumer = jest.fn()
|
|
485
|
+
|
|
450
486
|
beforeEach(async () => {
|
|
451
487
|
channel = await create(connection, topology)
|
|
452
488
|
chan = await getCreatedChannel()
|
|
@@ -460,9 +496,6 @@ describe('seal', () => {
|
|
|
460
496
|
const tags = []
|
|
461
497
|
|
|
462
498
|
for (let i = 0; i < random(5) + 3; i++) {
|
|
463
|
-
const queue = generate()
|
|
464
|
-
const consumer = jest.fn()
|
|
465
|
-
|
|
466
499
|
await channel.consume(queue, consumer)
|
|
467
500
|
|
|
468
501
|
const { consumerTag: tag } = await chan.consume.mock.results[i].value
|
|
@@ -477,6 +510,14 @@ describe('seal', () => {
|
|
|
477
510
|
|
|
478
511
|
for (const tag of tags) expect(chan.cancel).toHaveBeenCalledWith(tag)
|
|
479
512
|
})
|
|
513
|
+
|
|
514
|
+
it('should ignore exceptions', async () => {
|
|
515
|
+
await channel.consume(queue, consumer)
|
|
516
|
+
|
|
517
|
+
chan.cancel.mockImplementationOnce(async () => { throw new Error() })
|
|
518
|
+
|
|
519
|
+
await expect(channel.seal()).resolves.not.toThrow()
|
|
520
|
+
})
|
|
480
521
|
})
|
|
481
522
|
|
|
482
523
|
describe('back pressure', () => {
|
|
@@ -528,224 +569,238 @@ describe('recovery', () => {
|
|
|
528
569
|
await expect(channel.consume(queue, consumer)).rejects.toStrictEqual(exception)
|
|
529
570
|
})
|
|
530
571
|
|
|
531
|
-
|
|
532
|
-
|
|
572
|
+
describe.each(/** @type {[string, boolean][]} */ [
|
|
573
|
+
['', false],
|
|
574
|
+
[' not', true]
|
|
575
|
+
])('should%s restore incoming (sealed: %s)', (not, sealed) => {
|
|
576
|
+
beforeEach(async () => {
|
|
577
|
+
if (sealed) await channel.seal()
|
|
578
|
+
})
|
|
533
579
|
|
|
534
|
-
|
|
535
|
-
|
|
580
|
+
it('should re-assert queue after recovery', async () => {
|
|
581
|
+
chan.assertQueue.mockImplementation(async () => { throw new Error('Channel closed') })
|
|
536
582
|
|
|
537
|
-
|
|
538
|
-
|
|
583
|
+
/** @type {comq.amqp.Connection} */
|
|
584
|
+
let replacement
|
|
539
585
|
|
|
540
|
-
|
|
586
|
+
setImmediate(async () => {
|
|
587
|
+
chan.assertQueue.mockImplementation(async () => {})
|
|
541
588
|
|
|
542
|
-
|
|
589
|
+
replacement = await amqplib.connect()
|
|
590
|
+
|
|
591
|
+
await channel.recover(replacement)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
await channel.consume(queue, consumer)
|
|
595
|
+
|
|
596
|
+
const repl = await getCreatedChannel(replacement)
|
|
597
|
+
|
|
598
|
+
expect(repl.assertQueue).toHaveBeenCalled()
|
|
543
599
|
})
|
|
544
600
|
|
|
545
|
-
|
|
601
|
+
it('should re-assert exchange after exception', async () => {
|
|
602
|
+
chan.assertExchange.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
546
603
|
|
|
547
|
-
|
|
604
|
+
/** @type {comq.amqp.Connection} */
|
|
605
|
+
let replacement
|
|
548
606
|
|
|
549
|
-
|
|
550
|
-
|
|
607
|
+
setImmediate(async () => {
|
|
608
|
+
chan.assertExchange.mockImplementation(async () => {})
|
|
551
609
|
|
|
552
|
-
|
|
553
|
-
chan.assertExchange.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
610
|
+
replacement = await amqplib.connect()
|
|
554
611
|
|
|
555
|
-
|
|
556
|
-
|
|
612
|
+
await channel.recover(replacement)
|
|
613
|
+
})
|
|
557
614
|
|
|
558
|
-
|
|
559
|
-
chan.assertExchange.mockImplementation(async () => {})
|
|
615
|
+
await channel.subscribe(exchange, queue, consumer)
|
|
560
616
|
|
|
561
|
-
|
|
617
|
+
const repl = await getCreatedChannel(replacement)
|
|
562
618
|
|
|
563
|
-
|
|
619
|
+
expect(repl.assertExchange).toHaveBeenCalled()
|
|
564
620
|
})
|
|
565
621
|
|
|
566
|
-
|
|
622
|
+
it('should re-bind queue after exception', async () => {
|
|
623
|
+
chan.bindQueue.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
567
624
|
|
|
568
|
-
|
|
625
|
+
/** @type {comq.amqp.Connection} */
|
|
626
|
+
let replacement
|
|
569
627
|
|
|
570
|
-
|
|
571
|
-
|
|
628
|
+
setImmediate(async () => {
|
|
629
|
+
chan.bindQueue.mockImplementation(async () => {})
|
|
572
630
|
|
|
573
|
-
|
|
574
|
-
chan.bindQueue.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
631
|
+
replacement = await amqplib.connect()
|
|
575
632
|
|
|
576
|
-
|
|
577
|
-
|
|
633
|
+
await channel.recover(replacement)
|
|
634
|
+
})
|
|
578
635
|
|
|
579
|
-
|
|
580
|
-
chan.bindQueue.mockImplementation(async () => {})
|
|
636
|
+
await channel.subscribe(exchange, queue, consumer)
|
|
581
637
|
|
|
582
|
-
replacement
|
|
638
|
+
expect(replacement).toBeDefined()
|
|
583
639
|
|
|
584
|
-
await
|
|
640
|
+
const repl = await getCreatedChannel(replacement)
|
|
641
|
+
|
|
642
|
+
expect(repl.bindQueue).toHaveBeenCalled()
|
|
585
643
|
})
|
|
586
644
|
|
|
587
|
-
|
|
645
|
+
it(`should${not} re-consume after recovery`, async () => {
|
|
646
|
+
await channel.consume(queue, consumer)
|
|
588
647
|
|
|
589
|
-
|
|
648
|
+
const replacement = await amqplib.connect()
|
|
590
649
|
|
|
591
|
-
|
|
650
|
+
await channel.recover(replacement)
|
|
592
651
|
|
|
593
|
-
|
|
594
|
-
})
|
|
652
|
+
const chan = await getCreatedChannel(replacement)
|
|
595
653
|
|
|
596
|
-
|
|
597
|
-
|
|
654
|
+
if (sealed) expect(chan.consume).not.toHaveBeenCalled()
|
|
655
|
+
else expect(chan.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
|
|
656
|
+
})
|
|
598
657
|
|
|
599
|
-
|
|
658
|
+
it(`should${not} re-consume after exception `, async () => {
|
|
659
|
+
chan.consume.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
600
660
|
|
|
601
|
-
|
|
661
|
+
/** @type {comq.amqp.Connection} */
|
|
662
|
+
let replacement
|
|
602
663
|
|
|
603
|
-
|
|
664
|
+
setTimeout(async () => {
|
|
665
|
+
// noinspection JSCheckFunctionSignatures
|
|
666
|
+
chan.consume.mockImplementation(async () => ({ consumerTag: generate() }))
|
|
667
|
+
replacement = await amqplib.connect()
|
|
604
668
|
|
|
605
|
-
|
|
606
|
-
|
|
669
|
+
await channel.recover(replacement)
|
|
670
|
+
}, 1)
|
|
607
671
|
|
|
608
|
-
|
|
609
|
-
chan.consume.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
672
|
+
await channel.consume(queue, consumer)
|
|
610
673
|
|
|
611
|
-
|
|
612
|
-
let replacement
|
|
674
|
+
const repl = await getCreatedChannel(replacement)
|
|
613
675
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
replacement = await amqplib.connect()
|
|
676
|
+
if (sealed) expect(repl.consume).not.toHaveBeenCalled()
|
|
677
|
+
else expect(repl.consume).toHaveBeenCalled()
|
|
678
|
+
})
|
|
618
679
|
|
|
619
|
-
|
|
620
|
-
|
|
680
|
+
it(`should${not} re-subscribe after recovery`, async () => {
|
|
681
|
+
await channel.subscribe(exchange, queue, consumer)
|
|
621
682
|
|
|
622
|
-
|
|
683
|
+
const replacement = await amqplib.connect()
|
|
623
684
|
|
|
624
|
-
|
|
685
|
+
await channel.recover(replacement)
|
|
625
686
|
|
|
626
|
-
|
|
627
|
-
})
|
|
687
|
+
const repl = await getCreatedChannel(replacement)
|
|
628
688
|
|
|
629
|
-
|
|
630
|
-
|
|
689
|
+
if (sealed) expect(repl.consume).not.toHaveBeenCalled()
|
|
690
|
+
else expect(repl.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
|
|
691
|
+
})
|
|
631
692
|
|
|
632
|
-
|
|
693
|
+
it('should re-send after exception', async () => {
|
|
694
|
+
const queue = generate()
|
|
695
|
+
const buffer = Buffer.from(generate())
|
|
633
696
|
|
|
634
|
-
|
|
697
|
+
/** @type {comq.amqp.Connection} */
|
|
698
|
+
let replacement
|
|
635
699
|
|
|
636
|
-
|
|
700
|
+
chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
|
|
637
701
|
|
|
638
|
-
|
|
639
|
-
|
|
702
|
+
setImmediate(async () => {
|
|
703
|
+
expect(chan.publish).toHaveBeenCalled()
|
|
640
704
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const buffer = Buffer.from(generate())
|
|
705
|
+
chan.publish.mockImplementation(async () => {})
|
|
706
|
+
replacement = await amqplib.connect()
|
|
644
707
|
|
|
645
|
-
|
|
646
|
-
|
|
708
|
+
await channel.recover(replacement)
|
|
709
|
+
})
|
|
647
710
|
|
|
648
|
-
|
|
711
|
+
await channel.send(queue, buffer)
|
|
649
712
|
|
|
650
|
-
|
|
651
|
-
expect(chan.publish).toHaveBeenCalled()
|
|
713
|
+
const repl = await getCreatedChannel(replacement)
|
|
652
714
|
|
|
653
|
-
|
|
654
|
-
|
|
715
|
+
expect(repl.publish).toHaveBeenCalled()
|
|
716
|
+
})
|
|
655
717
|
|
|
656
|
-
|
|
657
|
-
|
|
718
|
+
it('should re-publish unconfirmed messages', async () => {
|
|
719
|
+
jest.clearAllMocks()
|
|
658
720
|
|
|
659
|
-
|
|
721
|
+
const exchange = generate()
|
|
722
|
+
const buffer = randomBytes(8)
|
|
723
|
+
const options = { contentType: 'application/octet-stream' }
|
|
660
724
|
|
|
661
|
-
|
|
725
|
+
topology.confirms = true
|
|
726
|
+
channel = await create(connection, topology)
|
|
662
727
|
|
|
663
|
-
|
|
664
|
-
|
|
728
|
+
// create channel
|
|
729
|
+
await channel.consume(generate(), () => undefined)
|
|
665
730
|
|
|
666
|
-
|
|
667
|
-
jest.clearAllMocks()
|
|
731
|
+
const chan = await getCreatedChannel()
|
|
668
732
|
|
|
669
|
-
|
|
670
|
-
const buffer = randomBytes(8)
|
|
671
|
-
const options = { contentType: 'application/octet-stream' }
|
|
733
|
+
chan.publish.mockImplementation(() => true)
|
|
672
734
|
|
|
673
|
-
|
|
674
|
-
|
|
735
|
+
/** @type {jest.MockedObject<comq.amqp.Connection>} */
|
|
736
|
+
let replacement
|
|
675
737
|
|
|
676
|
-
|
|
677
|
-
|
|
738
|
+
setImmediate(async () => {
|
|
739
|
+
replacement = await amqplib.connect()
|
|
678
740
|
|
|
679
|
-
|
|
741
|
+
await channel.recover(replacement)
|
|
742
|
+
})
|
|
680
743
|
|
|
681
|
-
|
|
744
|
+
await channel.publish(exchange, buffer, options)
|
|
682
745
|
|
|
683
|
-
|
|
684
|
-
let replacement
|
|
746
|
+
expect(replacement).toBeDefined()
|
|
685
747
|
|
|
686
|
-
|
|
687
|
-
replacement = await amqplib.connect()
|
|
748
|
+
const repl = await getCreatedChannel(replacement)
|
|
688
749
|
|
|
689
|
-
|
|
750
|
+
expect(repl.publish).toHaveBeenCalledWith(
|
|
751
|
+
exchange, '', buffer, expect.objectContaining(options), expect.any(Function)
|
|
752
|
+
)
|
|
690
753
|
})
|
|
691
754
|
|
|
692
|
-
|
|
755
|
+
it('should unpause on recovery', async () => {
|
|
756
|
+
jest.clearAllMocks()
|
|
693
757
|
|
|
694
|
-
|
|
758
|
+
topology.confirms = false
|
|
759
|
+
channel = await create(connection, topology)
|
|
695
760
|
|
|
696
|
-
|
|
761
|
+
const exchange = generate()
|
|
762
|
+
const buffer = randomBytes(8)
|
|
763
|
+
const options = { contentType: 'application/octet-stream' }
|
|
697
764
|
|
|
698
|
-
|
|
699
|
-
exchange,
|
|
700
|
-
)
|
|
701
|
-
})
|
|
765
|
+
// create channel
|
|
766
|
+
await channel.publish(exchange, buffer, options)
|
|
702
767
|
|
|
703
|
-
|
|
704
|
-
jest.clearAllMocks()
|
|
768
|
+
const chan = await getCreatedChannel()
|
|
705
769
|
|
|
706
|
-
|
|
707
|
-
channel = await create(connection, topology)
|
|
770
|
+
chan.publish.mockImplementation(() => false)
|
|
708
771
|
|
|
709
|
-
|
|
710
|
-
const buffer = randomBytes(8)
|
|
711
|
-
const options = { contentType: 'application/octet-stream' }
|
|
772
|
+
await channel.publish(exchange, buffer, options) // now paused
|
|
712
773
|
|
|
713
|
-
|
|
714
|
-
await channel.publish(exchange, buffer, options)
|
|
774
|
+
expect(chan.publish).toHaveBeenCalledTimes(2)
|
|
715
775
|
|
|
716
|
-
|
|
776
|
+
/** @type {jest.MockedObject<comq.amqp.Connection>} */
|
|
777
|
+
let replacement
|
|
717
778
|
|
|
718
|
-
|
|
779
|
+
setImmediate(async () => {
|
|
780
|
+
replacement = await amqplib.connect()
|
|
719
781
|
|
|
720
|
-
|
|
782
|
+
await channel.recover(replacement)
|
|
783
|
+
})
|
|
721
784
|
|
|
722
|
-
|
|
785
|
+
await channel.publish(exchange, buffer, options)
|
|
723
786
|
|
|
724
|
-
|
|
725
|
-
let replacement
|
|
787
|
+
expect(replacement).toBeDefined()
|
|
726
788
|
|
|
727
|
-
|
|
728
|
-
replacement = await amqplib.connect()
|
|
789
|
+
const repl = await getCreatedChannel(replacement)
|
|
729
790
|
|
|
730
|
-
|
|
791
|
+
expect(repl.publish).toHaveBeenCalled()
|
|
731
792
|
})
|
|
732
793
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const repl = await getCreatedChannel(replacement)
|
|
794
|
+
it('should emit recover event', async () => {
|
|
795
|
+
const listener = /** @type {Function} */ jest.fn()
|
|
736
796
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
it('should emit recover event', async () => {
|
|
741
|
-
const listener = /** @type {Function} */ jest.fn()
|
|
742
|
-
|
|
743
|
-
channel = await create(connection, topology)
|
|
744
|
-
channel.diagnose('recover', listener)
|
|
797
|
+
channel = await create(connection, topology)
|
|
798
|
+
channel.diagnose('recover', listener)
|
|
745
799
|
|
|
746
|
-
|
|
800
|
+
await channel.recover(connection)
|
|
747
801
|
|
|
748
|
-
|
|
802
|
+
expect(listener).toHaveBeenCalled()
|
|
803
|
+
})
|
|
749
804
|
})
|
|
750
805
|
})
|
|
751
806
|
|
|
@@ -804,7 +859,7 @@ describe('diagnostics', () => {
|
|
|
804
859
|
const content = randomBytes(8)
|
|
805
860
|
const properties = {}
|
|
806
861
|
const fields = { redelivered }
|
|
807
|
-
const message = /** @type {
|
|
862
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties, fields }
|
|
808
863
|
|
|
809
864
|
await callback(message)
|
|
810
865
|
|
|
@@ -813,6 +868,46 @@ describe('diagnostics', () => {
|
|
|
813
868
|
})
|
|
814
869
|
})
|
|
815
870
|
|
|
871
|
+
describe('transient', () => {
|
|
872
|
+
const exchange = generate()
|
|
873
|
+
const queue = generate()
|
|
874
|
+
const buffer = randomBytes(8)
|
|
875
|
+
const index = random()
|
|
876
|
+
|
|
877
|
+
beforeEach(async () => {
|
|
878
|
+
channel = await create(connection, topology, index)
|
|
879
|
+
chan = await getCreatedChannel()
|
|
880
|
+
})
|
|
881
|
+
|
|
882
|
+
it('should throw transient exceptions on publish', async () => {
|
|
883
|
+
chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
|
|
884
|
+
|
|
885
|
+
await expect(channel.publish(exchange, buffer)).rejects.toThrow()
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
it('should throw transient exceptions on send', async () => {
|
|
889
|
+
chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
|
|
890
|
+
|
|
891
|
+
await expect(channel.send(queue, buffer)).rejects.toThrow()
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
it('should throw on back pressure', async () => {
|
|
895
|
+
chan.publish.mockImplementationOnce(backpressure.publish)
|
|
896
|
+
|
|
897
|
+
await expect(channel.publish(exchange, buffer)).rejects.toBeDefined()
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
it('should not ignore exceptions on `throw()`', async () => {
|
|
901
|
+
chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
|
|
902
|
+
|
|
903
|
+
await expect(channel.throw(queue, buffer)).rejects.toThrow()
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
it('should expose index', async () => {
|
|
907
|
+
expect(channel.index).toStrictEqual(index)
|
|
908
|
+
})
|
|
909
|
+
})
|
|
910
|
+
|
|
816
911
|
/**
|
|
817
912
|
* @param {comq.amqp.Connection} [conn]
|
|
818
913
|
* @return {jest.MockedObject<comq.amqp.Channel>}
|
package/test/connect.test.js
CHANGED
|
@@ -6,6 +6,7 @@ const { connect } = require('../')
|
|
|
6
6
|
|
|
7
7
|
jest.mock('../source/io')
|
|
8
8
|
jest.mock('../source/connection')
|
|
9
|
+
jest.mock('../source/shards/connection')
|
|
9
10
|
|
|
10
11
|
const { IO } = require('../source/io')
|
|
11
12
|
|
|
@@ -14,6 +15,9 @@ const {
|
|
|
14
15
|
Connection
|
|
15
16
|
} = require('../source/connection')
|
|
16
17
|
|
|
18
|
+
const shards = /** @type {{ Connection: jest.MockedClass<comq.Connection>}} */
|
|
19
|
+
require('../source/shards')
|
|
20
|
+
|
|
17
21
|
it('should be', async () => {
|
|
18
22
|
expect(connect).toBeDefined()
|
|
19
23
|
})
|
|
@@ -23,22 +27,46 @@ const url = generate()
|
|
|
23
27
|
/** @type {comq.IO} */
|
|
24
28
|
let io
|
|
25
29
|
|
|
26
|
-
beforeEach(
|
|
30
|
+
beforeEach(() => {
|
|
27
31
|
jest.clearAllMocks()
|
|
28
|
-
|
|
29
|
-
io = await connect(url)
|
|
30
32
|
})
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
describe('single connection', () => {
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
io = await connect(url)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should return IO', async () => {
|
|
40
|
+
expect(io).toBeInstanceOf(IO)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should pass active connection', async () => {
|
|
44
|
+
expect(Connection).toHaveBeenCalledWith(url)
|
|
45
|
+
|
|
46
|
+
/** @type {jest.MockedObject<comq.Connection>} */
|
|
47
|
+
const instance = Connection.mock.instances[0]
|
|
48
|
+
|
|
49
|
+
expect(instance.open).toHaveBeenCalled()
|
|
50
|
+
expect(IO).toHaveBeenCalledWith(instance)
|
|
51
|
+
})
|
|
34
52
|
})
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
describe('sharded connection', () => {
|
|
55
|
+
const urls = [generate(), generate()]
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
io = await connect(...urls)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should create sharded connection', async () => {
|
|
62
|
+
expect(shards.Connection).toHaveBeenCalled()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should pass single connection instances', async () => {
|
|
66
|
+
urls.forEach((url) => expect(Connection).toHaveBeenCalledWith(url))
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
const instance = Connection.mock.instances[0]
|
|
68
|
+
const instances = Connection.mock.instances
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
expect(shards.Connection).toHaveBeenCalledWith(instances)
|
|
71
|
+
})
|
|
44
72
|
})
|
package/test/connection.mock.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const channel = jest.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
const channel = /** @type {jest.MockedFunction<(sharded?: boolean, index?: number) => comq.Channel>} */ jest.fn(
|
|
4
|
+
(sharded = false, index = undefined) => ({
|
|
5
|
+
index,
|
|
6
|
+
sharded,
|
|
7
|
+
consume: jest.fn(async () => undefined),
|
|
8
|
+
deliver: jest.fn(async () => undefined),
|
|
9
|
+
send: jest.fn(async () => undefined),
|
|
10
|
+
throw: jest.fn(async () => undefined),
|
|
11
|
+
subscribe: jest.fn(async () => undefined),
|
|
12
|
+
publish: jest.fn(async () => undefined),
|
|
13
|
+
diagnose: jest.fn(async () => undefined),
|
|
14
|
+
seal: jest.fn(async () => undefined),
|
|
15
|
+
recover: jest.fn(async () => undefined)
|
|
16
|
+
}))
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* @returns {jest.MockedObject<comq.Connection>}
|
|
17
20
|
*/
|
|
18
|
-
const connection = () => (/** @type {jest.MockedObject<comq.Connection>} */ {
|
|
19
|
-
createChannel: jest.fn(async () => channel()),
|
|
21
|
+
const connection = (sharded = false) => (/** @type {jest.MockedObject<comq.Connection>} */ {
|
|
22
|
+
createChannel: jest.fn(async (type, index) => channel(sharded, index)),
|
|
23
|
+
open: jest.fn(async () => undefined),
|
|
20
24
|
close: jest.fn(async () => undefined),
|
|
21
25
|
diagnose: jest.fn(() => undefined)
|
|
22
26
|
})
|