comq 0.10.1 → 0.10.3
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/workflows/codeql.yml +3 -3
- package/.github/workflows/release.yaml +2 -2
- package/features/rpc.feature +13 -0
- package/features/steps/broker.js +3 -5
- package/features/steps/events.js +2 -1
- package/features/steps/rpc.js +1 -1
- package/lerna.json +1 -1
- package/package.json +12 -11
- package/readme.md +0 -9
- package/source/.io/ReplyStream.js +8 -8
- package/source/channel.js +11 -10
- package/source/connection.js +7 -14
- package/source/io.js +8 -7
- package/source/shards/channel.js +6 -6
- package/features/steps/.test/backpressure.test.js +0 -79
- package/features/steps/.test/broker.test.js +0 -101
- package/features/steps/.test/command.mock.js +0 -5
- package/features/steps/.test/comq.mock.js +0 -10
- package/features/steps/.test/connection.test.js +0 -285
- package/features/steps/.test/context.mock.js +0 -14
- package/features/steps/.test/context.test.js +0 -74
- package/features/steps/.test/events.test.js +0 -279
- package/features/steps/.test/io.mock.js +0 -14
- package/features/steps/.test/poison.test.js +0 -40
- package/features/steps/.test/rpc.test.js +0 -261
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
|
|
47
47
|
# Initializes the CodeQL tools for scanning.
|
|
48
48
|
- name: Initialize CodeQL
|
|
49
|
-
uses: github/codeql-action/init@
|
|
49
|
+
uses: github/codeql-action/init@v3
|
|
50
50
|
with:
|
|
51
51
|
languages: ${{ matrix.language }}
|
|
52
52
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
@@ -60,7 +60,7 @@ jobs:
|
|
|
60
60
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
|
61
61
|
# If this step fails, then you should remove it and run the build manually (see below)
|
|
62
62
|
- name: Autobuild
|
|
63
|
-
uses: github/codeql-action/autobuild@
|
|
63
|
+
uses: github/codeql-action/autobuild@v3
|
|
64
64
|
|
|
65
65
|
# ℹ️ Command-line programs to run using the OS shell.
|
|
66
66
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
@@ -73,6 +73,6 @@ jobs:
|
|
|
73
73
|
# ./location_of_script_within_repo/buildscript.sh
|
|
74
74
|
|
|
75
75
|
- name: Perform CodeQL Analysis
|
|
76
|
-
uses: github/codeql-action/analyze@
|
|
76
|
+
uses: github/codeql-action/analyze@v3
|
|
77
77
|
with:
|
|
78
78
|
category: "/language:${{matrix.language}}"
|
package/features/rpc.feature
CHANGED
|
@@ -18,6 +18,19 @@ Feature: Request-reply (RPC)
|
|
|
18
18
|
3
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
Scenario: Getting `null` reply
|
|
22
|
+
Given function replying `add_numbers` queue:
|
|
23
|
+
"""
|
|
24
|
+
() => { return null }
|
|
25
|
+
"""
|
|
26
|
+
When the consumer sends the following request to the `add_numbers` queue:
|
|
27
|
+
"""yaml
|
|
28
|
+
"""
|
|
29
|
+
Then the consumer receives the reply:
|
|
30
|
+
"""yaml
|
|
31
|
+
null
|
|
32
|
+
"""
|
|
33
|
+
|
|
21
34
|
Scenario: Request causing an exception is discarded
|
|
22
35
|
Given function replying `throw_error` queue:
|
|
23
36
|
"""
|
package/features/steps/broker.js
CHANGED
|
@@ -32,7 +32,7 @@ Given('one of the brokers is/has {status}',
|
|
|
32
32
|
const actions = {
|
|
33
33
|
up: async (n = 0) => {
|
|
34
34
|
await execute('docker start comq-rmq-' + n)
|
|
35
|
-
await healthy()
|
|
35
|
+
await healthy(n)
|
|
36
36
|
},
|
|
37
37
|
down: async (n = 0) => {
|
|
38
38
|
await execute('docker stop comq-rmq-' + n)
|
|
@@ -42,16 +42,14 @@ const actions = {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async function healthy () {
|
|
45
|
+
async function healthy (n) {
|
|
46
46
|
let process
|
|
47
47
|
|
|
48
48
|
do {
|
|
49
49
|
await timeout(HEALTHCHECK_INTERVAL)
|
|
50
50
|
|
|
51
|
-
process = await execute('docker inspect -f {{.State.Health.Status}} comq-rmq-
|
|
51
|
+
process = await execute('docker inspect -f {{.State.Health.Status}} comq-rmq-' + n)
|
|
52
52
|
} while (process.output !== 'healthy')
|
|
53
|
-
|
|
54
|
-
await timeout(500)
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
const BROKERS_AMOUNT = 2
|
package/features/steps/events.js
CHANGED
|
@@ -4,7 +4,7 @@ const stream = require('node:stream')
|
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const { randomBytes } = require('node:crypto')
|
|
6
6
|
const { timeout, quantity, match } = require('@toa.io/generic')
|
|
7
|
-
const { parse } = require('
|
|
7
|
+
const { load: parse } = require('js-yaml')
|
|
8
8
|
const { Given, When, Then } = require('@cucumber/cucumber')
|
|
9
9
|
|
|
10
10
|
Given('(that ){token} is consuming events from the {token} exchange',
|
|
@@ -64,6 +64,7 @@ When('an event is emitted to the {token} exchange with properties:',
|
|
|
64
64
|
|
|
65
65
|
When('a stream of {quantity} events is emitted to the {token} exchange',
|
|
66
66
|
/**
|
|
67
|
+
* @param {string} amountQ
|
|
67
68
|
* @param {string} exchange
|
|
68
69
|
* @this {comq.features.Context}
|
|
69
70
|
*/
|
package/features/steps/rpc.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const stream = require('node:stream')
|
|
4
4
|
const assert = require('node:assert')
|
|
5
5
|
const { randomBytes } = require('node:crypto')
|
|
6
|
-
const { parse } = require('
|
|
6
|
+
const { load: parse } = require('js-yaml')
|
|
7
7
|
const { match, timeout, quantity } = require('@toa.io/generic')
|
|
8
8
|
const { Given, When, Then } = require('@cucumber/cucumber')
|
|
9
9
|
const { Readable } = require('node:stream')
|
package/lerna.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comq",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "ComQ",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Artem Gurtovoi",
|
|
@@ -27,27 +27,28 @@
|
|
|
27
27
|
"main": "source/index.js",
|
|
28
28
|
"types": "types/index.d.ts",
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@cucumber/cucumber": "^
|
|
30
|
+
"@cucumber/cucumber": "^10.0.1",
|
|
31
31
|
"@toa.io/command": "^0.4.0",
|
|
32
|
-
"@toa.io/
|
|
33
|
-
"@
|
|
34
|
-
"@types/
|
|
35
|
-
"
|
|
36
|
-
"husky": "^8.0.3",
|
|
32
|
+
"@toa.io/generic": "^1.0.0-alpha.0",
|
|
33
|
+
"@types/amqplib": "^0.10.3",
|
|
34
|
+
"@types/jest": "^29.5.8",
|
|
35
|
+
"husky": "^9.0.11",
|
|
37
36
|
"jest": "^29.5.0",
|
|
37
|
+
"js-yaml": "^4.1.0",
|
|
38
38
|
"randomstring": "^1.3.0",
|
|
39
39
|
"snazzy": "^9.0.0",
|
|
40
40
|
"standard": "^17.1.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
43
|
+
"amqplib": "^0.10.4",
|
|
44
|
+
"msgpackr": "^1.10.2",
|
|
45
|
+
"promex": "^0.1.1",
|
|
46
|
+
"reretry": "^0.1.0"
|
|
46
47
|
},
|
|
47
48
|
"scripts": {
|
|
48
49
|
"prepare": "echo run: husky install",
|
|
49
50
|
"test": "standard && jest",
|
|
50
|
-
"features": "cucumber-js --
|
|
51
|
+
"features": "cucumber-js --fail-fast",
|
|
51
52
|
"lint": "standard --fix --verbose | snazzy"
|
|
52
53
|
},
|
|
53
54
|
"standard": {
|
package/readme.md
CHANGED
|
@@ -137,12 +137,6 @@ auto-generated name.
|
|
|
137
137
|
// with a consumer function
|
|
138
138
|
await io.consume('numbers_added', 'logger',
|
|
139
139
|
({ a, b }) => console.log(`${a} was added to ${b}`))
|
|
140
|
-
|
|
141
|
-
// as a stream
|
|
142
|
-
const events = await io.consume('numbers_added', 'logger')
|
|
143
|
-
|
|
144
|
-
for await (const { a, b } of events)
|
|
145
|
-
console.log(`${a} was added to ${b}`)
|
|
146
140
|
```
|
|
147
141
|
|
|
148
142
|
## Emission
|
|
@@ -350,9 +344,6 @@ and it will be closed when the last instance of `IO` using that connection is [d
|
|
|
350
344
|
Topology is designed to deliver maximum performance while ensuring that the **at least once**
|
|
351
345
|
guarantee provided by RabbitMQ is maintained.
|
|
352
346
|
|
|
353
|
-
> Topology design assumes that unanswered Requests and unconfirmed Events will be retransmitted
|
|
354
|
-
> upon reconnection.
|
|
355
|
-
|
|
356
347
|
### Dynamic
|
|
357
348
|
|
|
358
349
|
Static topology refers to the process of defining the complete topology declaration along with the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Readable } = require('node:stream')
|
|
4
|
-
const {
|
|
4
|
+
const { Promex } = require('promex')
|
|
5
5
|
const { IDLE_INTERVAL, control } = require('./const')
|
|
6
6
|
|
|
7
7
|
class ReplyStream extends Readable {
|
|
8
|
-
confirmation =
|
|
8
|
+
confirmation = new Promex()
|
|
9
9
|
|
|
10
10
|
/** @type {ReturnType<setTimeout> | null} */
|
|
11
11
|
#timeout = null
|
|
@@ -26,8 +26,8 @@ class ReplyStream extends Readable {
|
|
|
26
26
|
|
|
27
27
|
#buffered = 0
|
|
28
28
|
|
|
29
|
-
/** @type {
|
|
30
|
-
#queue =
|
|
29
|
+
/** @type {Map<number, unknown>} */
|
|
30
|
+
#queue = new Map()
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* @param {comq.Request} request
|
|
@@ -70,12 +70,12 @@ class ReplyStream extends Readable {
|
|
|
70
70
|
if (this.#buffered > 0) {
|
|
71
71
|
let message
|
|
72
72
|
|
|
73
|
-
while ((message = this.#queue
|
|
74
|
-
|
|
73
|
+
while ((message = this.#queue.get(this.#index))) {
|
|
74
|
+
this.#queue.delete(this.#index)
|
|
75
75
|
this._add(message.payload, message.properties)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
this.#buffered =
|
|
78
|
+
this.#buffered = this.#queue.size
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -98,7 +98,7 @@ class ReplyStream extends Readable {
|
|
|
98
98
|
if (this.#buffered > MAX_BUFFER_SIZE) this.destroy()
|
|
99
99
|
|
|
100
100
|
this.#buffered++
|
|
101
|
-
this.#queue
|
|
101
|
+
this.#queue.set(properties.headers.index, { payload, properties })
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
package/source/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { Promex } = require('promex')
|
|
4
4
|
const { failsafe, lazy, recall } = require('./attributes')
|
|
5
5
|
const emitter = require('./emitter')
|
|
6
6
|
|
|
@@ -25,16 +25,16 @@ class Channel {
|
|
|
25
25
|
/** @type {string[]} */
|
|
26
26
|
#tags = []
|
|
27
27
|
|
|
28
|
-
/** @type {
|
|
28
|
+
/** @type {Promex | null} */
|
|
29
29
|
#paused = null
|
|
30
30
|
|
|
31
31
|
/** @type {boolean} */
|
|
32
32
|
#sealed = false
|
|
33
33
|
|
|
34
|
-
/** @type {
|
|
35
|
-
#recovery =
|
|
34
|
+
/** @type {Promex} */
|
|
35
|
+
#recovery = new Promex()
|
|
36
36
|
|
|
37
|
-
/** @type {Set<
|
|
37
|
+
/** @type {Set<Promex>} */
|
|
38
38
|
#confirmations = new Set()
|
|
39
39
|
|
|
40
40
|
#diagnostics = emitter.create()
|
|
@@ -149,10 +149,11 @@ class Channel {
|
|
|
149
149
|
|
|
150
150
|
for (const confirmation of this.#confirmations) confirmation.reject(INTERRUPTION)
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
// handle interruptions
|
|
153
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
153
154
|
|
|
154
155
|
this.#recovery.resolve()
|
|
155
|
-
this.#recovery =
|
|
156
|
+
this.#recovery = new Promex()
|
|
156
157
|
this.#diagnostics.emit('recover')
|
|
157
158
|
}
|
|
158
159
|
|
|
@@ -222,10 +223,10 @@ class Channel {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
/**
|
|
225
|
-
* @return {
|
|
226
|
+
* @return {Promex}
|
|
226
227
|
*/
|
|
227
228
|
#confirmation () {
|
|
228
|
-
const confirmation =
|
|
229
|
+
const confirmation = new Promex()
|
|
229
230
|
|
|
230
231
|
this.#confirmations.add(confirmation)
|
|
231
232
|
|
|
@@ -292,7 +293,7 @@ class Channel {
|
|
|
292
293
|
#pause () {
|
|
293
294
|
if (this.#paused !== null) return
|
|
294
295
|
|
|
295
|
-
this.#paused =
|
|
296
|
+
this.#paused = new Promex()
|
|
296
297
|
this.#diagnostics.emit('flow')
|
|
297
298
|
this.#diagnostics.emit('pause')
|
|
298
299
|
}
|
package/source/connection.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const amqp = require('amqplib')
|
|
4
|
-
const {
|
|
4
|
+
const { Promex } = require('promex')
|
|
5
|
+
const { retry } = require('reretry')
|
|
5
6
|
|
|
6
7
|
const { failsafe } = require('./attributes')
|
|
7
8
|
const presets = require('./topology')
|
|
@@ -21,8 +22,8 @@ class Connection {
|
|
|
21
22
|
/** @type {comq.Channel[]} */
|
|
22
23
|
#channels = []
|
|
23
24
|
|
|
24
|
-
/** @type {
|
|
25
|
-
#recovery =
|
|
25
|
+
/** @type {Promex} */
|
|
26
|
+
#recovery = new Promex()
|
|
26
27
|
|
|
27
28
|
/** @type {boolean} */
|
|
28
29
|
#running = false
|
|
@@ -37,7 +38,7 @@ class Connection {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
async open () {
|
|
40
|
-
await retry(this.#open
|
|
41
|
+
await retry(this.#open)
|
|
41
42
|
|
|
42
43
|
this.#running = true
|
|
43
44
|
}
|
|
@@ -69,14 +70,11 @@ class Connection {
|
|
|
69
70
|
this.#diagnostics.on(event, listener)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
/**
|
|
73
|
-
* @type {toa.generic.retry.Task}
|
|
74
|
-
*/
|
|
75
73
|
#open = async (retry) => {
|
|
76
74
|
try {
|
|
77
75
|
this.#connection = await amqp.connect(this.#url)
|
|
78
76
|
} catch (exception) {
|
|
79
|
-
if (this.#transient(exception)) return retry
|
|
77
|
+
if (this.#transient(exception)) return retry
|
|
80
78
|
else throw exception
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -90,7 +88,7 @@ class Connection {
|
|
|
90
88
|
for (const channel of this.#channels) await channel.recover(this.#connection)
|
|
91
89
|
|
|
92
90
|
this.#recovery.resolve()
|
|
93
|
-
this.#recovery =
|
|
91
|
+
this.#recovery = new Promex()
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
/**
|
|
@@ -117,9 +115,4 @@ class Connection {
|
|
|
117
115
|
|
|
118
116
|
function noop () {}
|
|
119
117
|
|
|
120
|
-
/** @type {toa.generic.retry.Options} */
|
|
121
|
-
const RETRY = {
|
|
122
|
-
retries: Infinity
|
|
123
|
-
}
|
|
124
|
-
|
|
125
118
|
exports.Connection = Connection
|
package/source/io.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const stream = require('node:stream')
|
|
4
4
|
const { EventEmitter } = require('node:events')
|
|
5
5
|
const { randomBytes } = require('node:crypto')
|
|
6
|
-
const {
|
|
6
|
+
const { setTimeout } = require('node:timers/promises')
|
|
7
|
+
const { Promex } = require('promex')
|
|
7
8
|
const { memo, failsafe, lazy, track } = require('./attributes')
|
|
8
9
|
|
|
9
10
|
const { decode } = require('./decode')
|
|
@@ -34,7 +35,7 @@ class IO {
|
|
|
34
35
|
/** @type {comq.ReplyEmitter | null} */
|
|
35
36
|
#control = null
|
|
36
37
|
|
|
37
|
-
/** @type {Set<
|
|
38
|
+
/** @type {Set<Promex>} */
|
|
38
39
|
#pendingReplies = new Set()
|
|
39
40
|
|
|
40
41
|
/** @type {Set<comq.Destroyable>} */
|
|
@@ -217,7 +218,7 @@ class IO {
|
|
|
217
218
|
|
|
218
219
|
if (request.properties.replyTo === undefined) return
|
|
219
220
|
|
|
220
|
-
const iterator = typeof reply === 'object' &&
|
|
221
|
+
const iterator = typeof reply === 'object' && reply !== null &&
|
|
221
222
|
(Symbol.asyncIterator in reply ||
|
|
222
223
|
(Symbol.iterator in reply && !Array.isArray(reply) && !Buffer.isBuffer(reply)))
|
|
223
224
|
|
|
@@ -279,7 +280,7 @@ class IO {
|
|
|
279
280
|
|
|
280
281
|
/**
|
|
281
282
|
* @param {comq.Request} request
|
|
282
|
-
* @return {
|
|
283
|
+
* @return {Promex<any>}
|
|
283
284
|
*/
|
|
284
285
|
#createReply (request) {
|
|
285
286
|
const reply = this.#createPendingReply()
|
|
@@ -290,10 +291,10 @@ class IO {
|
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
/**
|
|
293
|
-
* @return {
|
|
294
|
+
* @return {Promex}
|
|
294
295
|
*/
|
|
295
296
|
#createPendingReply () {
|
|
296
|
-
const reply =
|
|
297
|
+
const reply = new Promex()
|
|
297
298
|
|
|
298
299
|
this.#pendingReplies.add(reply)
|
|
299
300
|
|
|
@@ -383,7 +384,7 @@ class IO {
|
|
|
383
384
|
Even if these messages are lost, the reply stream will be closed anyway,
|
|
384
385
|
either due to missing heartbeat or the deletion of the stream queue.
|
|
385
386
|
*/
|
|
386
|
-
await
|
|
387
|
+
await setTimeout(50)
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
/**
|
package/source/shards/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { Promex } = require('promex')
|
|
4
4
|
const events = require('../events')
|
|
5
5
|
const emitter = require('../emitter')
|
|
6
6
|
|
|
@@ -22,13 +22,13 @@ class Channel {
|
|
|
22
22
|
/** @type {Set<Promise<comq.Channel>>} */
|
|
23
23
|
#pending = new Set()
|
|
24
24
|
|
|
25
|
-
/** @type {Map<comq.Channel,
|
|
25
|
+
/** @type {Map<comq.Channel, Promex>} */
|
|
26
26
|
#bench = new Map()
|
|
27
27
|
|
|
28
28
|
/** @type {comq.topology.type} */
|
|
29
29
|
#type
|
|
30
30
|
|
|
31
|
-
#recovery =
|
|
31
|
+
#recovery = new Promex()
|
|
32
32
|
|
|
33
33
|
#diagnostics = emitter.create()
|
|
34
34
|
|
|
@@ -134,7 +134,7 @@ class Channel {
|
|
|
134
134
|
#remove (channel) {
|
|
135
135
|
if (!this.#channels.has(channel)) return
|
|
136
136
|
|
|
137
|
-
this.#bench.set(channel,
|
|
137
|
+
this.#bench.set(channel, new Promex())
|
|
138
138
|
this.#channels.delete(channel)
|
|
139
139
|
this.#update()
|
|
140
140
|
this.#diagnostics.emit('remove', channel.index)
|
|
@@ -162,7 +162,7 @@ class Channel {
|
|
|
162
162
|
this.#add(channel)
|
|
163
163
|
|
|
164
164
|
this.#recovery.resolve()
|
|
165
|
-
this.#recovery =
|
|
165
|
+
this.#recovery = new Promex()
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
#comeback (channel) {
|
|
@@ -209,7 +209,7 @@ class Channel {
|
|
|
209
209
|
async #one (fn) {
|
|
210
210
|
if (this.#pool.length === 0) await this.#recovery
|
|
211
211
|
|
|
212
|
-
const channel =
|
|
212
|
+
const channel = this.#pool[Math.floor(Math.random() * this.#pool.length)]
|
|
213
213
|
|
|
214
214
|
try {
|
|
215
215
|
return await fn(channel)
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { AssertionError } = require('node:assert')
|
|
4
|
-
const { generate } = require('randomstring')
|
|
5
|
-
const { random, quantity } = require('@toa.io/generic')
|
|
6
|
-
const tomato = require('@toa.io/tomato')
|
|
7
|
-
const { io } = require('./io.mock')
|
|
8
|
-
const mock = { tomato }
|
|
9
|
-
|
|
10
|
-
jest.mock('@cucumber/cucumber', () => mock.tomato)
|
|
11
|
-
|
|
12
|
-
require('../backpressure')
|
|
13
|
-
|
|
14
|
-
/** @type {comq.features.Context} */
|
|
15
|
-
let context
|
|
16
|
-
|
|
17
|
-
describe('When I\'m sending {quantity}B requests to the {token} queue at {quantity}Hz for {number} second(s)', () => {
|
|
18
|
-
const step = tomato.steps.Wh('I\'m sending {quantity}B requests to the {token} queue at {quantity}Hz for {number} second(s)')
|
|
19
|
-
|
|
20
|
-
it('should be', async () => undefined)
|
|
21
|
-
|
|
22
|
-
const bytesQ = (random(5) + 1) + 'k'
|
|
23
|
-
const frequencyQ = String((random(5) + 1) / 10) + 'k' // not too high
|
|
24
|
-
const seconds = (random(3) + 1) / 10
|
|
25
|
-
const queue = generate()
|
|
26
|
-
|
|
27
|
-
const bytes = quantity(bytesQ)
|
|
28
|
-
const frequency = quantity(frequencyQ)
|
|
29
|
-
|
|
30
|
-
beforeAll(async () => {
|
|
31
|
-
jest.clearAllMocks()
|
|
32
|
-
|
|
33
|
-
context = /** @type {comq.features.Context} */ { io, requestCount: 0, replyCount: 0 }
|
|
34
|
-
|
|
35
|
-
await step.call(context, bytesQ, queue, frequencyQ, seconds)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('should send request', async () => {
|
|
39
|
-
expect(io.request).toHaveBeenCalled()
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should sent request to specified queue', async () => {
|
|
43
|
-
const call = io.request.mock.calls[0]
|
|
44
|
-
|
|
45
|
-
expect(call[0]).toStrictEqual(queue)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should send buffer of given size', async () => {
|
|
49
|
-
const buffer = io.request.mock.calls[0][1]
|
|
50
|
-
|
|
51
|
-
expect(Buffer.byteLength(buffer)).toStrictEqual(bytes)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('should send expected amount of requests', async () => {
|
|
55
|
-
const times = Math.ceil(seconds * frequency)
|
|
56
|
-
|
|
57
|
-
expect(io.request).toHaveBeenCalledTimes(times)
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('Then back pressure was applied', () => {
|
|
62
|
-
const step = tomato.steps.Th('back pressure was applied')
|
|
63
|
-
|
|
64
|
-
beforeEach(() => {
|
|
65
|
-
context = /** @type {comq.features.Context} */ { io, events: {} }
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('should be', async () => undefined)
|
|
69
|
-
|
|
70
|
-
it('should fail if flag is not set', async () => {
|
|
71
|
-
expect(() => step.call(context)).toThrow(AssertionError)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should pass if flag is true', async () => {
|
|
75
|
-
context.events = { flow: true }
|
|
76
|
-
|
|
77
|
-
expect(() => step.call(context)).not.toThrow()
|
|
78
|
-
})
|
|
79
|
-
})
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { generate } = require('randomstring')
|
|
4
|
-
const tomato = require('@toa.io/tomato')
|
|
5
|
-
const { command } = require('./command.mock')
|
|
6
|
-
const mock = { tomato, command }
|
|
7
|
-
|
|
8
|
-
jest.mock('@cucumber/cucumber', () => mock.tomato)
|
|
9
|
-
jest.mock('@toa.io/command', () => mock.command)
|
|
10
|
-
|
|
11
|
-
jest.setTimeout(10000)
|
|
12
|
-
|
|
13
|
-
global.COMQ_TESTING_HEALTHCHECK_INTERVAL = 0
|
|
14
|
-
|
|
15
|
-
require('../broker')
|
|
16
|
-
|
|
17
|
-
const context = /** @type {comq.features.Context} */ {}
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
jest.clearAllMocks()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
describe.each([false, true])('Given the broker is/has {status} #shards:%s', (sharded) => {
|
|
24
|
-
const step = tomato.steps.Gi('the broker is/has {status}')
|
|
25
|
-
|
|
26
|
-
const containerNumber = sharded ? 1 : 0
|
|
27
|
-
|
|
28
|
-
it('should be', async () => undefined)
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
context.io = generate()
|
|
32
|
-
|
|
33
|
-
if (sharded) {
|
|
34
|
-
context.sharded = true
|
|
35
|
-
context.shard = containerNumber
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should start comq-rmq container', async () => {
|
|
40
|
-
command.execute.mockImplementationOnce(async () => undefined)
|
|
41
|
-
command.execute.mockImplementationOnce(async () => ({ output: 'healthy' }))
|
|
42
|
-
|
|
43
|
-
await step.call(context, 'up')
|
|
44
|
-
|
|
45
|
-
expect(command.execute).toHaveBeenCalledWith('docker start comq-rmq-' + containerNumber)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should wait for healthy state', async () => {
|
|
49
|
-
let starting = false
|
|
50
|
-
let healthy = false
|
|
51
|
-
|
|
52
|
-
command.execute.mockImplementationOnce(async () => undefined)
|
|
53
|
-
|
|
54
|
-
command.execute.mockImplementationOnce(async () => {
|
|
55
|
-
starting = true
|
|
56
|
-
|
|
57
|
-
return { output: 'starting' }
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
command.execute.mockImplementationOnce(async () => {
|
|
61
|
-
healthy = true
|
|
62
|
-
|
|
63
|
-
return { output: 'healthy' }
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
await step.call(context, 'up')
|
|
67
|
-
|
|
68
|
-
expect(command.execute).toHaveBeenCalledTimes(3)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should stop comq-rmq container', async () => {
|
|
72
|
-
await step.call(context, 'down')
|
|
73
|
-
|
|
74
|
-
expect(command.execute).toHaveBeenCalledWith('docker stop comq-rmq-' + containerNumber)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('should kill comq-rmq-0 container', async () => {
|
|
78
|
-
await step.call(context, 'crashed')
|
|
79
|
-
|
|
80
|
-
expect(command.execute).toHaveBeenCalledWith('docker kill comq-rmq-' + containerNumber)
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('Given one of the brokers is/has {status}', () => {
|
|
85
|
-
const step = tomato.steps.Gi('one of the brokers is/has {status}')
|
|
86
|
-
|
|
87
|
-
it('should store picked broker', async () => {
|
|
88
|
-
await step.call(context, 'down')
|
|
89
|
-
|
|
90
|
-
expect(context.shard).toStrictEqual(expect.any(Number))
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('should start one of the containers', async () => {
|
|
94
|
-
command.execute.mockImplementationOnce(async () => undefined)
|
|
95
|
-
command.execute.mockImplementationOnce(async () => ({ output: 'healthy' }))
|
|
96
|
-
|
|
97
|
-
await step.call(context, 'up')
|
|
98
|
-
|
|
99
|
-
expect(command.execute).toHaveBeenCalledWith('docker start comq-rmq-' + context.shard)
|
|
100
|
-
})
|
|
101
|
-
})
|