comq 0.4.1-dev.3 → 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.
Files changed (61) hide show
  1. package/.github/labels.yaml +15 -0
  2. package/.github/workflows/labels.yaml +18 -0
  3. package/.github/workflows/release.yaml +20 -8
  4. package/docker-compose.yaml +20 -2
  5. package/docs/notes.md +14 -8
  6. package/examples/const.js +1 -3
  7. package/examples/events/consumer.js +5 -6
  8. package/examples/events/producer.js +3 -3
  9. package/examples/package.json +3 -0
  10. package/examples/rpc/consumer.js +3 -3
  11. package/examples/rpc/producer.js +3 -3
  12. package/features/connection.feature +3 -0
  13. package/features/events.feature +1 -1
  14. package/features/shards.feature +61 -0
  15. package/features/steps/.test/backpressure.test.js +2 -2
  16. package/features/steps/.test/broker.test.js +32 -6
  17. package/features/steps/.test/connection.test.js +105 -1
  18. package/features/steps/.test/context.mock.js +2 -2
  19. package/features/steps/.test/context.test.js +13 -5
  20. package/features/steps/.test/events.test.js +66 -3
  21. package/features/steps/.test/rpc.test.js +99 -1
  22. package/features/steps/.types/context.d.ts +11 -1
  23. package/features/steps/backpressure.js +1 -1
  24. package/features/steps/broker.js +26 -9
  25. package/features/steps/connection.js +69 -2
  26. package/features/steps/context.js +28 -12
  27. package/features/steps/events.js +41 -2
  28. package/features/steps/rpc.js +58 -1
  29. package/lerna.json +1 -1
  30. package/package.json +5 -5
  31. package/readme.md +75 -25
  32. package/source/channel.js +56 -32
  33. package/source/connect.js +28 -2
  34. package/source/connection.js +24 -17
  35. package/source/decode.js +1 -1
  36. package/source/events.js +7 -0
  37. package/source/io.js +21 -14
  38. package/source/shards/channel.js +219 -0
  39. package/source/shards/connection.js +56 -0
  40. package/source/shards/index.js +5 -0
  41. package/test/channel.test.js +240 -145
  42. package/test/connect.test.js +39 -11
  43. package/test/connection.mock.js +17 -13
  44. package/test/connection.test.js +65 -12
  45. package/test/decode.test.js +5 -5
  46. package/test/io.close.test.js +2 -2
  47. package/test/io.consume.test.js +1 -1
  48. package/test/io.reply.test.js +7 -7
  49. package/test/io.request.test.js +27 -1
  50. package/test/presets.test.js +1 -1
  51. package/test/shards/channel.test.js +367 -0
  52. package/test/shards/connection.test.js +106 -0
  53. package/types/amqp.d.ts +3 -1
  54. package/types/channel.d.ts +8 -5
  55. package/types/connection.d.ts +3 -1
  56. package/types/diagnostic.d.ts +1 -1
  57. package/types/topology.d.ts +4 -0
  58. /package/source/{presets → topology}/event.json +0 -0
  59. /package/source/{presets → topology}/index.js +0 -0
  60. /package/source/{presets → topology}/reply.json +0 -0
  61. /package/source/{presets → topology}/request.json +0 -0
@@ -15,7 +15,7 @@ it('should be', async () => {
15
15
  expect(create).toBeDefined()
16
16
  })
17
17
 
18
- /** @type {jest.MockedObject<import('amqplib').Connection>} */
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 {import('amqplib').ConsumeMessage} */ { content }
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 {import('amqplib').ConsumeMessage} */ { content }
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 {import('amqplib').ConsumeMessage} */ { content, properties, fields }
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 {import('amqplib').ConsumeMessage} */ { content, properties, fields }
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 catch exceptions', async () => {
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
- it('should assert queue after recovery', async () => {
532
- chan.assertQueue.mockImplementation(async () => { throw new Error('Channel closed') })
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
- /** @type {comq.amqp.Connection} */
535
- let replacement
580
+ it('should re-assert queue after recovery', async () => {
581
+ chan.assertQueue.mockImplementation(async () => { throw new Error('Channel closed') })
536
582
 
537
- setImmediate(async () => {
538
- chan.assertQueue.mockImplementation(async () => {})
583
+ /** @type {comq.amqp.Connection} */
584
+ let replacement
539
585
 
540
- replacement = await amqplib.connect()
586
+ setImmediate(async () => {
587
+ chan.assertQueue.mockImplementation(async () => {})
541
588
 
542
- await channel.recover(replacement)
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
- await channel.consume(queue, consumer)
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
- const repl = await getCreatedChannel(replacement)
604
+ /** @type {comq.amqp.Connection} */
605
+ let replacement
548
606
 
549
- expect(repl.assertQueue).toHaveBeenCalled()
550
- })
607
+ setImmediate(async () => {
608
+ chan.assertExchange.mockImplementation(async () => {})
551
609
 
552
- it('should re-assert exchange after exception', async () => {
553
- chan.assertExchange.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
610
+ replacement = await amqplib.connect()
554
611
 
555
- /** @type {comq.amqp.Connection} */
556
- let replacement
612
+ await channel.recover(replacement)
613
+ })
557
614
 
558
- setImmediate(async () => {
559
- chan.assertExchange.mockImplementation(async () => {})
615
+ await channel.subscribe(exchange, queue, consumer)
560
616
 
561
- replacement = await amqplib.connect()
617
+ const repl = await getCreatedChannel(replacement)
562
618
 
563
- await channel.recover(replacement)
619
+ expect(repl.assertExchange).toHaveBeenCalled()
564
620
  })
565
621
 
566
- await channel.subscribe(exchange, queue, consumer)
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
- const repl = await getCreatedChannel(replacement)
625
+ /** @type {comq.amqp.Connection} */
626
+ let replacement
569
627
 
570
- expect(repl.assertExchange).toHaveBeenCalled()
571
- })
628
+ setImmediate(async () => {
629
+ chan.bindQueue.mockImplementation(async () => {})
572
630
 
573
- it('should re-bind queue after exception', async () => {
574
- chan.bindQueue.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
631
+ replacement = await amqplib.connect()
575
632
 
576
- /** @type {comq.amqp.Connection} */
577
- let replacement
633
+ await channel.recover(replacement)
634
+ })
578
635
 
579
- setImmediate(async () => {
580
- chan.bindQueue.mockImplementation(async () => {})
636
+ await channel.subscribe(exchange, queue, consumer)
581
637
 
582
- replacement = await amqplib.connect()
638
+ expect(replacement).toBeDefined()
583
639
 
584
- await channel.recover(replacement)
640
+ const repl = await getCreatedChannel(replacement)
641
+
642
+ expect(repl.bindQueue).toHaveBeenCalled()
585
643
  })
586
644
 
587
- await channel.subscribe(exchange, queue, consumer)
645
+ it(`should${not} re-consume after recovery`, async () => {
646
+ await channel.consume(queue, consumer)
588
647
 
589
- expect(replacement).toBeDefined()
648
+ const replacement = await amqplib.connect()
590
649
 
591
- const repl = await getCreatedChannel(replacement)
650
+ await channel.recover(replacement)
592
651
 
593
- expect(repl.bindQueue).toHaveBeenCalled()
594
- })
652
+ const chan = await getCreatedChannel(replacement)
595
653
 
596
- it('should re-consume after recovery', async () => {
597
- await channel.consume(queue, consumer)
654
+ if (sealed) expect(chan.consume).not.toHaveBeenCalled()
655
+ else expect(chan.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
656
+ })
598
657
 
599
- const replacement = await amqplib.connect()
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
- await channel.recover(replacement)
661
+ /** @type {comq.amqp.Connection} */
662
+ let replacement
602
663
 
603
- const chan = await getCreatedChannel(replacement)
664
+ setTimeout(async () => {
665
+ // noinspection JSCheckFunctionSignatures
666
+ chan.consume.mockImplementation(async () => ({ consumerTag: generate() }))
667
+ replacement = await amqplib.connect()
604
668
 
605
- expect(chan.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
606
- })
669
+ await channel.recover(replacement)
670
+ }, 1)
607
671
 
608
- it('should re-consume after exception ', async () => {
609
- chan.consume.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
672
+ await channel.consume(queue, consumer)
610
673
 
611
- /** @type {comq.amqp.Connection} */
612
- let replacement
674
+ const repl = await getCreatedChannel(replacement)
613
675
 
614
- setTimeout(async () => {
615
- // noinspection JSCheckFunctionSignatures
616
- chan.consume.mockImplementation(async () => ({ consumerTag: generate() }))
617
- replacement = await amqplib.connect()
676
+ if (sealed) expect(repl.consume).not.toHaveBeenCalled()
677
+ else expect(repl.consume).toHaveBeenCalled()
678
+ })
618
679
 
619
- await channel.recover(replacement)
620
- }, 1)
680
+ it(`should${not} re-subscribe after recovery`, async () => {
681
+ await channel.subscribe(exchange, queue, consumer)
621
682
 
622
- await channel.consume(queue, consumer)
683
+ const replacement = await amqplib.connect()
623
684
 
624
- const repl = await getCreatedChannel(replacement)
685
+ await channel.recover(replacement)
625
686
 
626
- expect(repl.consume).toHaveBeenCalled()
627
- })
687
+ const repl = await getCreatedChannel(replacement)
628
688
 
629
- it('should re-subscribe after recovery', async () => {
630
- await channel.subscribe(exchange, queue, consumer)
689
+ if (sealed) expect(repl.consume).not.toHaveBeenCalled()
690
+ else expect(repl.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
691
+ })
631
692
 
632
- const replacement = await amqplib.connect()
693
+ it('should re-send after exception', async () => {
694
+ const queue = generate()
695
+ const buffer = Buffer.from(generate())
633
696
 
634
- await channel.recover(replacement)
697
+ /** @type {comq.amqp.Connection} */
698
+ let replacement
635
699
 
636
- const repl = await getCreatedChannel(replacement)
700
+ chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
637
701
 
638
- expect(repl.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
639
- })
702
+ setImmediate(async () => {
703
+ expect(chan.publish).toHaveBeenCalled()
640
704
 
641
- it('should re-send after exception', async () => {
642
- const queue = generate()
643
- const buffer = Buffer.from(generate())
705
+ chan.publish.mockImplementation(async () => {})
706
+ replacement = await amqplib.connect()
644
707
 
645
- /** @type {comq.amqp.Connection} */
646
- let replacement
708
+ await channel.recover(replacement)
709
+ })
647
710
 
648
- chan.publish.mockImplementation(() => { throw new Error('Channel closed') })
711
+ await channel.send(queue, buffer)
649
712
 
650
- setTimeout(async () => {
651
- expect(chan.publish).toHaveBeenCalled()
713
+ const repl = await getCreatedChannel(replacement)
652
714
 
653
- chan.publish.mockImplementation(async () => {})
654
- replacement = await amqplib.connect()
715
+ expect(repl.publish).toHaveBeenCalled()
716
+ })
655
717
 
656
- await channel.recover(replacement)
657
- }, 1)
718
+ it('should re-publish unconfirmed messages', async () => {
719
+ jest.clearAllMocks()
658
720
 
659
- await channel.send(queue, buffer)
721
+ const exchange = generate()
722
+ const buffer = randomBytes(8)
723
+ const options = { contentType: 'application/octet-stream' }
660
724
 
661
- const repl = await getCreatedChannel(replacement)
725
+ topology.confirms = true
726
+ channel = await create(connection, topology)
662
727
 
663
- expect(repl.publish).toHaveBeenCalled()
664
- })
728
+ // create channel
729
+ await channel.consume(generate(), () => undefined)
665
730
 
666
- it('should re-publish unconfirmed messages', async () => {
667
- jest.clearAllMocks()
731
+ const chan = await getCreatedChannel()
668
732
 
669
- const exchange = generate()
670
- const buffer = randomBytes(8)
671
- const options = { contentType: 'application/octet-stream' }
733
+ chan.publish.mockImplementation(() => true)
672
734
 
673
- topology.confirms = true
674
- channel = await create(connection, topology)
735
+ /** @type {jest.MockedObject<comq.amqp.Connection>} */
736
+ let replacement
675
737
 
676
- // create channel
677
- await channel.consume(generate(), () => undefined)
738
+ setImmediate(async () => {
739
+ replacement = await amqplib.connect()
678
740
 
679
- const chan = await getCreatedChannel()
741
+ await channel.recover(replacement)
742
+ })
680
743
 
681
- chan.publish.mockImplementation(() => true)
744
+ await channel.publish(exchange, buffer, options)
682
745
 
683
- /** @type {jest.MockedObject<comq.amqp.Connection>} */
684
- let replacement
746
+ expect(replacement).toBeDefined()
685
747
 
686
- setImmediate(async () => {
687
- replacement = await amqplib.connect()
748
+ const repl = await getCreatedChannel(replacement)
688
749
 
689
- await channel.recover(replacement)
750
+ expect(repl.publish).toHaveBeenCalledWith(
751
+ exchange, '', buffer, expect.objectContaining(options), expect.any(Function)
752
+ )
690
753
  })
691
754
 
692
- await channel.publish(exchange, buffer, options)
755
+ it('should unpause on recovery', async () => {
756
+ jest.clearAllMocks()
693
757
 
694
- expect(replacement).toBeDefined()
758
+ topology.confirms = false
759
+ channel = await create(connection, topology)
695
760
 
696
- const repl = await getCreatedChannel(replacement)
761
+ const exchange = generate()
762
+ const buffer = randomBytes(8)
763
+ const options = { contentType: 'application/octet-stream' }
697
764
 
698
- expect(repl.publish).toHaveBeenCalledWith(
699
- exchange, '', buffer, expect.objectContaining(options), expect.any(Function)
700
- )
701
- })
765
+ // create channel
766
+ await channel.publish(exchange, buffer, options)
702
767
 
703
- it('should unpause on recovery', async () => {
704
- jest.clearAllMocks()
768
+ const chan = await getCreatedChannel()
705
769
 
706
- topology.confirms = false
707
- channel = await create(connection, topology)
770
+ chan.publish.mockImplementation(() => false)
708
771
 
709
- const exchange = generate()
710
- const buffer = randomBytes(8)
711
- const options = { contentType: 'application/octet-stream' }
772
+ await channel.publish(exchange, buffer, options) // now paused
712
773
 
713
- // create channel
714
- await channel.publish(exchange, buffer, options)
774
+ expect(chan.publish).toHaveBeenCalledTimes(2)
715
775
 
716
- const chan = await getCreatedChannel()
776
+ /** @type {jest.MockedObject<comq.amqp.Connection>} */
777
+ let replacement
717
778
 
718
- chan.publish.mockImplementation(() => false)
779
+ setImmediate(async () => {
780
+ replacement = await amqplib.connect()
719
781
 
720
- await channel.publish(exchange, buffer, options) // now paused
782
+ await channel.recover(replacement)
783
+ })
721
784
 
722
- expect(chan.publish).toHaveBeenCalledTimes(2)
785
+ await channel.publish(exchange, buffer, options)
723
786
 
724
- /** @type {jest.MockedObject<comq.amqp.Connection>} */
725
- let replacement
787
+ expect(replacement).toBeDefined()
726
788
 
727
- setImmediate(async () => {
728
- replacement = await amqplib.connect()
789
+ const repl = await getCreatedChannel(replacement)
729
790
 
730
- await channel.recover(replacement)
791
+ expect(repl.publish).toHaveBeenCalled()
731
792
  })
732
793
 
733
- await channel.publish(exchange, buffer, options)
734
-
735
- const repl = await getCreatedChannel(replacement)
794
+ it('should emit recover event', async () => {
795
+ const listener = /** @type {Function} */ jest.fn()
736
796
 
737
- expect(repl.publish).toHaveBeenCalled()
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
- await channel.recover(connection)
800
+ await channel.recover(connection)
747
801
 
748
- expect(listener).toHaveBeenCalled()
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 {import('amqplib').ConsumeMessage} */ { content, properties, fields }
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>}
@@ -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(async () => {
30
+ beforeEach(() => {
27
31
  jest.clearAllMocks()
28
-
29
- io = await connect(url)
30
32
  })
31
33
 
32
- it('should return IO', async () => {
33
- expect(io).toBeInstanceOf(IO)
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
- it('should pass active connection', async () => {
37
- expect(Connection).toHaveBeenCalledWith(url)
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
- /** @type {jest.MockedObject<comq.Connection>} */
40
- const instance = Connection.mock.instances[0]
68
+ const instances = Connection.mock.instances
41
69
 
42
- expect(instance.open).toHaveBeenCalled()
43
- expect(IO).toHaveBeenCalledWith(instance)
70
+ expect(shards.Connection).toHaveBeenCalledWith(instances)
71
+ })
44
72
  })
@@ -1,22 +1,26 @@
1
1
  'use strict'
2
2
 
3
- const channel = jest.fn(() => ({
4
- consume: jest.fn(async () => undefined),
5
- deliver: jest.fn(async () => undefined),
6
- send: jest.fn(async () => undefined),
7
- throw: jest.fn(async () => undefined),
8
- subscribe: jest.fn(async () => undefined),
9
- publish: jest.fn(async () => undefined),
10
- diagnose: jest.fn(async () => undefined),
11
- seal: jest.fn(async () => undefined),
12
- recover: jest.fn(async () => undefined)
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
  })