comq 0.6.0 → 0.7.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/features/connection.feature +0 -8
- package/features/shards.feature +11 -14
- package/features/steps/.test/connection.test.js +0 -6
- package/features/steps/connection.js +3 -21
- package/lerna.json +1 -1
- package/package.json +1 -1
- package/source/channel.js +2 -3
- package/source/connection.js +13 -13
- package/source/shards/connection.js +21 -2
- package/test/channel.test.js +8 -12
- package/test/connection.test.js +10 -13
- package/test/shards/channel.test.js +6 -5
- package/test/shards/connection.test.js +39 -12
|
@@ -3,14 +3,6 @@ Feature: Connection Tolerance
|
|
|
3
3
|
Scenario: Connect
|
|
4
4
|
Given an active connection to the broker
|
|
5
5
|
|
|
6
|
-
Scenario: Connecting to a broker before it was started
|
|
7
|
-
Given the broker is down
|
|
8
|
-
When I attempt to connect to the broker for 0.2 seconds
|
|
9
|
-
Then the connection is not established
|
|
10
|
-
And no exceptions are thrown
|
|
11
|
-
When the broker is up
|
|
12
|
-
Then the connection is established
|
|
13
|
-
|
|
14
6
|
Scenario: Restoring connection after broker restart
|
|
15
7
|
Given an active connection to the broker
|
|
16
8
|
When the broker is down
|
package/features/shards.feature
CHANGED
|
@@ -3,25 +3,26 @@ Feature: Sharded Connection
|
|
|
3
3
|
Scenario: Establishing sharded connection
|
|
4
4
|
Given an active sharded connection
|
|
5
5
|
|
|
6
|
-
Scenario: Connecting and waiting for 'open' event
|
|
7
|
-
Given the connection to both shards is established
|
|
8
|
-
|
|
9
6
|
Scenario: Connecting while a shard is down
|
|
10
7
|
Given one of the brokers has crashed
|
|
11
8
|
When I attempt to establish sharded connection
|
|
12
|
-
Then
|
|
9
|
+
Then an exception is thrown: "ECONNREFUSED"
|
|
13
10
|
Then the broker is up
|
|
14
11
|
|
|
12
|
+
Scenario: Connecting with wrong credentials
|
|
13
|
+
When I attempt to establish a sharded connection as "someone" with password "whatever"
|
|
14
|
+
Then an exception is thrown: "Handshake terminated by server: 403 (ACCESS-REFUSED)"
|
|
15
|
+
|
|
15
16
|
Scenario: Sending a request to a sharded connection while one broker is down
|
|
16
|
-
Given
|
|
17
|
-
And
|
|
17
|
+
Given an active sharded connection
|
|
18
|
+
And one of the brokers has crashed
|
|
18
19
|
And a producer replying `add_numbers` queue
|
|
19
20
|
When the consumer sends a request to the `add_numbers` queue
|
|
20
21
|
Then the consumer receives the reply
|
|
21
22
|
Then the broker is up
|
|
22
23
|
|
|
23
24
|
Scenario: Publishing an event to a sharded connection missing one broker
|
|
24
|
-
Given
|
|
25
|
+
Given an active sharded connection
|
|
25
26
|
And events are exclusively consumed from the `numbers_added` exchange
|
|
26
27
|
And one of the brokers has crashed
|
|
27
28
|
When an event is emitted to the `numbers_added` exchange
|
|
@@ -29,7 +30,7 @@ Feature: Sharded Connection
|
|
|
29
30
|
Then the broker is up
|
|
30
31
|
|
|
31
32
|
Scenario: Shard crashes while sending requests
|
|
32
|
-
Given
|
|
33
|
+
Given an active sharded connection
|
|
33
34
|
And a producer replying `flood` queue
|
|
34
35
|
And I'm sending 1kB requests to the `flood` queue at 1kHz
|
|
35
36
|
When one of the brokers has crashed
|
|
@@ -38,7 +39,7 @@ Feature: Sharded Connection
|
|
|
38
39
|
Then the broker is up
|
|
39
40
|
|
|
40
41
|
Scenario: Shard crashes while publishing events
|
|
41
|
-
Given
|
|
42
|
+
Given an active sharded connection
|
|
42
43
|
And events are exclusively consumed from the `flood` exchange
|
|
43
44
|
And I'm publishing 1kB events to the `flood` exchange at 100Hz
|
|
44
45
|
When one of the brokers has crashed
|
|
@@ -47,7 +48,7 @@ Feature: Sharded Connection
|
|
|
47
48
|
Then the broker is up
|
|
48
49
|
|
|
49
50
|
Scenario: Sealing sharded connection while one broker is down
|
|
50
|
-
Given
|
|
51
|
+
Given an active sharded connection
|
|
51
52
|
And a producer replying `echo` queue
|
|
52
53
|
And one of the brokers has crashed
|
|
53
54
|
And the connection has started sealing
|
|
@@ -55,7 +56,3 @@ Feature: Sharded Connection
|
|
|
55
56
|
And the broker is up
|
|
56
57
|
Then the connection is sealed
|
|
57
58
|
And the consumer does not receive the reply
|
|
58
|
-
|
|
59
|
-
Scenario: Connecting with wrong credentials
|
|
60
|
-
When I attempt to establish a sharded connection as "someone" with password "whatever"
|
|
61
|
-
Then an exception is thrown: "Handshake terminated by server: 403 (ACCESS-REFUSED)"
|
|
@@ -62,12 +62,6 @@ describe('Given an active sharded connection', () => {
|
|
|
62
62
|
})
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
describe('Given the connection to both shards is established', () => {
|
|
66
|
-
tomato.steps.Gi('the connection to both shards is established')
|
|
67
|
-
|
|
68
|
-
it('should be', async () => undefined)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
65
|
describe('When I attempt to connect to the broker for {number} second(s)', () => {
|
|
72
66
|
const step = tomato.steps.Wh('I attempt to connect to the broker for {number} second(s)')
|
|
73
67
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
|
-
const { timeout
|
|
4
|
+
const { timeout } = require('@toa.io/generic')
|
|
5
5
|
|
|
6
6
|
const { Given, When, Then } = require('@cucumber/cucumber')
|
|
7
7
|
|
|
@@ -26,24 +26,6 @@ Given('an active sharded connection',
|
|
|
26
26
|
await this.connecting
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
Given('the connection to both shards is established',
|
|
30
|
-
/**
|
|
31
|
-
* @this {comq.features.Context}
|
|
32
|
-
*/
|
|
33
|
-
async function () {
|
|
34
|
-
const promise = promex()
|
|
35
|
-
|
|
36
|
-
this.sharded = true
|
|
37
|
-
this.connecting = this.connect()
|
|
38
|
-
|
|
39
|
-
await this.connecting
|
|
40
|
-
|
|
41
|
-
// second shard is connected
|
|
42
|
-
this.io.diagnose('open', () => promise.resolve())
|
|
43
|
-
|
|
44
|
-
return promise
|
|
45
|
-
})
|
|
46
|
-
|
|
47
29
|
When('I attempt to connect to the broker for {number} second(s)',
|
|
48
30
|
/**
|
|
49
31
|
* @param {number} interval
|
|
@@ -185,6 +167,6 @@ const CONNECTION_EVENTS = {
|
|
|
185
167
|
}
|
|
186
168
|
|
|
187
169
|
const CONNECTION_GAPS = {
|
|
188
|
-
lost:
|
|
189
|
-
restored:
|
|
170
|
+
lost: 10,
|
|
171
|
+
restored: 5000
|
|
190
172
|
}
|
package/lerna.json
CHANGED
package/package.json
CHANGED
package/source/channel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require('node:events')
|
|
4
|
-
const { lazy, recall, promex, failsafe,
|
|
4
|
+
const { lazy, recall, promex, failsafe, timeout } = require('@toa.io/generic')
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @implements {comq.Channel}
|
|
@@ -137,8 +137,7 @@ class Channel {
|
|
|
137
137
|
|
|
138
138
|
for (const confirmation of this.#confirmations) confirmation.reject(INTERRUPTION)
|
|
139
139
|
|
|
140
|
-
// let unpause and confirmation interruptions be handled
|
|
141
|
-
await immediate()
|
|
140
|
+
await timeout(0) // let unpause and confirmation interruptions be handled
|
|
142
141
|
|
|
143
142
|
this.#recovery.resolve()
|
|
144
143
|
this.#recovery = promex()
|
package/source/connection.js
CHANGED
|
@@ -23,6 +23,9 @@ class Connection {
|
|
|
23
23
|
/** @type {toa.generic.Promex} */
|
|
24
24
|
#recovery = promex()
|
|
25
25
|
|
|
26
|
+
/** @type {boolean} */
|
|
27
|
+
#running = false
|
|
28
|
+
|
|
26
29
|
#diagnostics = new EventEmitter()
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -34,6 +37,8 @@ class Connection {
|
|
|
34
37
|
|
|
35
38
|
async open () {
|
|
36
39
|
await retry(this.#open, RETRY)
|
|
40
|
+
|
|
41
|
+
this.#running = true
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
async close () {
|
|
@@ -70,11 +75,11 @@ class Connection {
|
|
|
70
75
|
try {
|
|
71
76
|
this.#connection = await amqp.connect(this.#url)
|
|
72
77
|
} catch (exception) {
|
|
73
|
-
if (transient(exception)) return retry()
|
|
78
|
+
if (this.#transient(exception)) return retry()
|
|
74
79
|
else throw exception
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
// prevents process crash, 'close' will be emitted next
|
|
82
|
+
// prevents the process crash, 'close' will be emitted next
|
|
78
83
|
// https://amqp-node.github.io/amqplib/channel_api.html#model_events
|
|
79
84
|
this.#connection.on('error', noop)
|
|
80
85
|
|
|
@@ -101,6 +106,12 @@ class Connection {
|
|
|
101
106
|
#recover () {
|
|
102
107
|
return this.#recovery
|
|
103
108
|
}
|
|
109
|
+
|
|
110
|
+
#transient (exception) {
|
|
111
|
+
const abruptly = exception.message === 'Socket closed abruptly during opening handshake'
|
|
112
|
+
|
|
113
|
+
return this.#running || abruptly
|
|
114
|
+
}
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
/** @type {toa.generic.retry.Options} */
|
|
@@ -108,17 +119,6 @@ const RETRY = {
|
|
|
108
119
|
retries: Infinity
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
/**
|
|
112
|
-
* @param {Error} exception
|
|
113
|
-
* @returns {boolean}
|
|
114
|
-
*/
|
|
115
|
-
const transient = (exception) => {
|
|
116
|
-
const refused = exception.code === 'ECONNREFUSED'
|
|
117
|
-
const handshake = exception.message === 'Socket closed abruptly during opening handshake'
|
|
118
|
-
|
|
119
|
-
return refused || handshake
|
|
120
|
-
}
|
|
121
|
-
|
|
122
122
|
function noop () {}
|
|
123
123
|
|
|
124
124
|
exports.Connection = Connection
|
|
@@ -23,9 +23,13 @@ class Connection {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async open () {
|
|
26
|
-
const
|
|
26
|
+
const openings = this.#connections.map((connection) => connection.open())
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
try {
|
|
29
|
+
await Promise.all(openings)
|
|
30
|
+
} catch (exception) {
|
|
31
|
+
await this.#cancel(openings, exception)
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
async close () {
|
|
@@ -51,6 +55,21 @@ class Connection {
|
|
|
51
55
|
connection.diagnose(event, (...args) => this.#diagnostics.emit(event, ...args, index))
|
|
52
56
|
}
|
|
53
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {Promise<any>[]} openings
|
|
61
|
+
* @param {Error} exception
|
|
62
|
+
*/
|
|
63
|
+
async #cancel (openings, exception) {
|
|
64
|
+
const settled = await Promise.allSettled(openings)
|
|
65
|
+
const indexes = settled.map((result, index) => result.status === 'fulfilled' ? index : -1)
|
|
66
|
+
const opened = indexes.filter((index) => index !== -1)
|
|
67
|
+
const closings = opened.map((index) => this.#connections[index].close())
|
|
68
|
+
|
|
69
|
+
await Promise.all(closings)
|
|
70
|
+
|
|
71
|
+
throw exception
|
|
72
|
+
}
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
exports.Connection = Connection
|
package/test/channel.test.js
CHANGED
|
@@ -577,7 +577,7 @@ describe('recovery', () => {
|
|
|
577
577
|
if (sealed) await channel.seal()
|
|
578
578
|
})
|
|
579
579
|
|
|
580
|
-
it('should re-assert queue
|
|
580
|
+
it('should re-assert queue', async () => {
|
|
581
581
|
chan.assertQueue.mockImplementation(async () => { throw new Error('Channel closed') })
|
|
582
582
|
|
|
583
583
|
/** @type {comq.amqp.Connection} */
|
|
@@ -598,7 +598,7 @@ describe('recovery', () => {
|
|
|
598
598
|
expect(repl.assertQueue).toHaveBeenCalled()
|
|
599
599
|
})
|
|
600
600
|
|
|
601
|
-
it('should re-assert exchange
|
|
601
|
+
it('should re-assert exchange', async () => {
|
|
602
602
|
chan.assertExchange.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
603
603
|
|
|
604
604
|
/** @type {comq.amqp.Connection} */
|
|
@@ -619,7 +619,7 @@ describe('recovery', () => {
|
|
|
619
619
|
expect(repl.assertExchange).toHaveBeenCalled()
|
|
620
620
|
})
|
|
621
621
|
|
|
622
|
-
it('should re-bind queue
|
|
622
|
+
it('should re-bind queue', async () => {
|
|
623
623
|
chan.bindQueue.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
624
624
|
|
|
625
625
|
/** @type {comq.amqp.Connection} */
|
|
@@ -642,7 +642,7 @@ describe('recovery', () => {
|
|
|
642
642
|
expect(repl.bindQueue).toHaveBeenCalled()
|
|
643
643
|
})
|
|
644
644
|
|
|
645
|
-
it(`should${not} re-consume
|
|
645
|
+
it(`should${not} re-consume`, async () => {
|
|
646
646
|
await channel.consume(queue, consumer)
|
|
647
647
|
|
|
648
648
|
const replacement = await amqplib.connect()
|
|
@@ -655,7 +655,7 @@ describe('recovery', () => {
|
|
|
655
655
|
else expect(chan.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
|
|
656
656
|
})
|
|
657
657
|
|
|
658
|
-
it(`should${not} re-consume
|
|
658
|
+
it(`should${not} re-consume`, async () => {
|
|
659
659
|
chan.consume.mockImplementation(async () => { throw new Error('Channel ended, no reply will be forthcoming') })
|
|
660
660
|
|
|
661
661
|
/** @type {comq.amqp.Connection} */
|
|
@@ -677,7 +677,7 @@ describe('recovery', () => {
|
|
|
677
677
|
else expect(repl.consume).toHaveBeenCalled()
|
|
678
678
|
})
|
|
679
679
|
|
|
680
|
-
it(`should${not} re-subscribe
|
|
680
|
+
it(`should${not} re-subscribe`, async () => {
|
|
681
681
|
await channel.subscribe(exchange, queue, consumer)
|
|
682
682
|
|
|
683
683
|
const replacement = await amqplib.connect()
|
|
@@ -690,7 +690,7 @@ describe('recovery', () => {
|
|
|
690
690
|
else expect(repl.consume).toHaveBeenCalledWith(queue, expect.any(Function), expect.anything())
|
|
691
691
|
})
|
|
692
692
|
|
|
693
|
-
it('should re-send
|
|
693
|
+
it('should re-send', async () => {
|
|
694
694
|
const queue = generate()
|
|
695
695
|
const buffer = Buffer.from(generate())
|
|
696
696
|
|
|
@@ -702,7 +702,6 @@ describe('recovery', () => {
|
|
|
702
702
|
setImmediate(async () => {
|
|
703
703
|
expect(chan.publish).toHaveBeenCalled()
|
|
704
704
|
|
|
705
|
-
chan.publish.mockImplementation(async () => {})
|
|
706
705
|
replacement = await amqplib.connect()
|
|
707
706
|
|
|
708
707
|
await channel.recover(replacement)
|
|
@@ -712,9 +711,6 @@ describe('recovery', () => {
|
|
|
712
711
|
|
|
713
712
|
const repl = await getCreatedChannel(replacement)
|
|
714
713
|
|
|
715
|
-
// await recovery effect
|
|
716
|
-
await timeout(5)
|
|
717
|
-
|
|
718
714
|
expect(repl.publish).toHaveBeenCalled()
|
|
719
715
|
})
|
|
720
716
|
|
|
@@ -757,7 +753,7 @@ describe('recovery', () => {
|
|
|
757
753
|
)
|
|
758
754
|
})
|
|
759
755
|
|
|
760
|
-
it('should unpause
|
|
756
|
+
it('should unpause', async () => {
|
|
761
757
|
jest.clearAllMocks()
|
|
762
758
|
|
|
763
759
|
topology.confirms = false
|
package/test/connection.test.js
CHANGED
|
@@ -30,12 +30,6 @@ beforeEach(() => {
|
|
|
30
30
|
connection = new Connection(url)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
/** @type {[string, Partial<Error>][]} */
|
|
34
|
-
const TRANSIENT_ERRORS = [
|
|
35
|
-
['ECONNREFUSED', { code: 'ECONNREFUSED' }],
|
|
36
|
-
['Socket closed', { message: 'Socket closed abruptly during opening handshake' }]
|
|
37
|
-
]
|
|
38
|
-
|
|
39
33
|
// endregion
|
|
40
34
|
|
|
41
35
|
describe('initial connection', () => {
|
|
@@ -45,7 +39,9 @@ describe('initial connection', () => {
|
|
|
45
39
|
expect(amqplib.connect).toHaveBeenCalledWith(url)
|
|
46
40
|
})
|
|
47
41
|
|
|
48
|
-
it.each(
|
|
42
|
+
it.each(/** @type {[string, Partial<Error>][]} */[
|
|
43
|
+
['Socket closed', { message: 'Socket closed abruptly during opening handshake' }]
|
|
44
|
+
])('should reconnect on %s',
|
|
49
45
|
async (_, error) => {
|
|
50
46
|
amqplib.connect.mockImplementationOnce(async () => { throw error })
|
|
51
47
|
|
|
@@ -54,12 +50,13 @@ describe('initial connection', () => {
|
|
|
54
50
|
expect(amqplib.connect).toHaveBeenCalledTimes(2)
|
|
55
51
|
})
|
|
56
52
|
|
|
57
|
-
it(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
it.each(/** @type {[string, Partial<Error>][]} */[
|
|
54
|
+
['ECONNREFUSED', { code: 'ECONNREFUSED' }],
|
|
55
|
+
['any exception', new Error(generate())]
|
|
56
|
+
])('should throw if error is %s', async (_, exception) => {
|
|
57
|
+
amqplib.connect.mockImplementationOnce(async () => { throw exception })
|
|
61
58
|
|
|
62
|
-
await expect(connection.open()).rejects.
|
|
59
|
+
await expect(connection.open()).rejects.toStrictEqual(exception)
|
|
63
60
|
})
|
|
64
61
|
})
|
|
65
62
|
|
|
@@ -77,7 +74,7 @@ describe('reconnection', () => {
|
|
|
77
74
|
expect(amqplib.connect).toHaveBeenCalledTimes(1)
|
|
78
75
|
|
|
79
76
|
// const clear = jest.spyOn(conn, 'removeAllListeners')
|
|
80
|
-
const error = {
|
|
77
|
+
const error = { code: 'ECONNREFUSED' }
|
|
81
78
|
|
|
82
79
|
conn.emit('close', error)
|
|
83
80
|
|
|
@@ -374,20 +374,21 @@ it('should remove channel with back pressure from the pool', async () => {
|
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
await send()
|
|
377
|
+
|
|
377
378
|
expect(chan.send).toHaveBeenCalled()
|
|
379
|
+
|
|
378
380
|
chan.send.mockClear()
|
|
379
381
|
|
|
380
|
-
|
|
381
|
-
for (const listener of flowListeners) listener()
|
|
382
|
+
for (const flow of flowListeners) flow()
|
|
382
383
|
|
|
383
384
|
await send()
|
|
385
|
+
|
|
384
386
|
expect(chan.send).not.toHaveBeenCalled()
|
|
385
|
-
chan.send.mockClear()
|
|
386
387
|
|
|
387
|
-
|
|
388
|
-
for (const listener of drainListeners) listener()
|
|
388
|
+
for (const drain of drainListeners) drain()
|
|
389
389
|
|
|
390
390
|
await send()
|
|
391
|
+
|
|
391
392
|
expect(chan.send).toHaveBeenCalled()
|
|
392
393
|
})
|
|
393
394
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { generate } = require('randomstring')
|
|
4
|
-
const { random, promex,
|
|
4
|
+
const { random, promex, immediate } = require('@toa.io/generic')
|
|
5
5
|
|
|
6
6
|
const { Connection } = require('../../source/shards')
|
|
7
7
|
|
|
@@ -16,7 +16,7 @@ it('should be', async () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
/** @type {jest.MockedObject<comq.Connection>[]} */
|
|
19
|
-
|
|
19
|
+
let connections
|
|
20
20
|
|
|
21
21
|
/** @type {comq.Connection} */
|
|
22
22
|
let connection
|
|
@@ -24,12 +24,13 @@ let connection
|
|
|
24
24
|
beforeEach(() => {
|
|
25
25
|
jest.clearAllMocks()
|
|
26
26
|
|
|
27
|
+
connections = [mock.connection(), mock.connection()]
|
|
27
28
|
connection = new Connection(connections)
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
describe('open', () => {
|
|
31
|
-
it('should resolve when
|
|
32
|
-
expect.assertions(
|
|
32
|
+
it('should resolve when all of the connections are established', async () => {
|
|
33
|
+
expect.assertions(2)
|
|
33
34
|
|
|
34
35
|
/** @type {toa.generic.Promex[]} */
|
|
35
36
|
const promises = []
|
|
@@ -41,19 +42,45 @@ describe('open', () => {
|
|
|
41
42
|
promises.push(promise)
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
let
|
|
45
|
+
let resolved = false
|
|
45
46
|
|
|
46
|
-
setImmediate(() => {
|
|
47
|
-
expect(
|
|
47
|
+
setImmediate(async () => {
|
|
48
|
+
expect(resolved).toStrictEqual(false)
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
+
const first = promises.shift()
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
first.resolve()
|
|
53
|
+
|
|
54
|
+
await immediate()
|
|
55
|
+
|
|
56
|
+
promises.forEach((promise) => promise.resolve())
|
|
57
|
+
|
|
58
|
+
resolved = true
|
|
52
59
|
})
|
|
53
60
|
|
|
54
61
|
await connection.open()
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
expect(resolved).toStrictEqual(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should close opened connection if one fails', async () => {
|
|
67
|
+
connections.push(mock.connection())
|
|
68
|
+
|
|
69
|
+
const [one, bad, two] = connections
|
|
70
|
+
const exception = new Error(generate())
|
|
71
|
+
|
|
72
|
+
one.open.mockImplementation(() => Promise.resolve())
|
|
73
|
+
two.open.mockImplementation(() => Promise.resolve())
|
|
74
|
+
|
|
75
|
+
bad.open.mockImplementation(async () => {
|
|
76
|
+
await immediate()
|
|
77
|
+
throw exception
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
await expect(connection.open()).rejects.toThrow(exception)
|
|
81
|
+
|
|
82
|
+
expect(one.close).toHaveBeenCalled()
|
|
83
|
+
expect(two.close).toHaveBeenCalled()
|
|
57
84
|
})
|
|
58
85
|
})
|
|
59
86
|
|
|
@@ -81,9 +108,9 @@ describe('createChannel', () => {
|
|
|
81
108
|
|
|
82
109
|
describe.each(/** @type {comq.diagnostics.event[]} */ ['open', 'close'])('diagnose %s event',
|
|
83
110
|
(event) => {
|
|
84
|
-
const index = random(connections.length)
|
|
85
|
-
|
|
86
111
|
it('should re-emit event', async () => {
|
|
112
|
+
const index = random(connections.length)
|
|
113
|
+
|
|
87
114
|
for (const conn of connections) {
|
|
88
115
|
expect(conn.diagnose).toHaveBeenCalledWith(event, expect.any(Function))
|
|
89
116
|
}
|