comq 0.10.0 → 0.10.2

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.
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { promex, sample } = require('@toa.io/generic')
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, toa.generic.Promex>} */
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 = promex()
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, promex())
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 = promex()
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 = sample(this.#pool)
212
+ const channel = this.#pool[Math.floor(Math.random() * this.#pool.length)]
213
213
 
214
214
  try {
215
215
  return await fn(channel)
package/types/io.d.ts CHANGED
@@ -22,18 +22,15 @@ declare namespace comq {
22
22
  buffer: Buffer
23
23
  emitter: ReplyEmitter
24
24
  properties: _amqp.Properties
25
- reply: any
26
25
  }
27
26
 
28
27
  interface IO extends _diagnostics.Diagnosable {
29
28
  reply (queue: string, produce: Producer): Promise<void>
30
29
 
31
- request<Reply = any, Request = any> (queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply>
30
+ request<Reply = any, Request = any> (queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
32
31
 
33
32
  request (queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
34
33
 
35
- fetch (queue: string, payload: any, encoding?: _encoding.Encoding): Promise<Readable>
36
-
37
34
  consume<T = any> (exchange: string, group: string, consumer: Consumer<T>): Promise<void>
38
35
 
39
36
  consume<T = any> (exchange: string, consumer: Consumer<T>): Promise<void>
@@ -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
- })
@@ -1,5 +0,0 @@
1
- 'use strict'
2
-
3
- const execute = jest.fn(async () => undefined)
4
-
5
- exports.command = { execute }
@@ -1,10 +0,0 @@
1
- 'use strict'
2
-
3
- const io = () => ({
4
- diagnose: jest.fn(() => undefined),
5
- close: jest.fn(async () => undefined)
6
- })
7
-
8
- const connect = jest.fn(async () => io())
9
-
10
- exports.comq = { connect }
@@ -1,285 +0,0 @@
1
- 'use strict'
2
-
3
- const { AssertionError } = require('node:assert')
4
- const { generate } = require('randomstring')
5
- const { random, promex } = require('@toa.io/generic')
6
- const tomato = require('@toa.io/tomato')
7
- const { comq } = require('./comq.mock')
8
- const world = require('./context.mock')
9
- const mock = { tomato, comq }
10
-
11
- jest.mock('@cucumber/cucumber', () => mock.tomato)
12
- jest.mock('comq', () => mock.comq)
13
-
14
- require('../connection')
15
-
16
- /** @type {jest.MockedObject<comq.features.Context>} */
17
- let context
18
-
19
- beforeEach(() => {
20
- jest.clearAllMocks()
21
-
22
- context = world.context()
23
- })
24
-
25
- describe('Given an active connection to the broker', () => {
26
- const step = tomato.steps.Gi('an active connection to the broker')
27
-
28
- it('should be', async () => undefined)
29
-
30
- beforeEach(async () => {
31
- await step.call(context)
32
- })
33
-
34
- it('should connect', async () => {
35
- expect(context.connect).toHaveBeenCalled()
36
- })
37
-
38
- it('should store promise', async () => {
39
- expect(context.connecting).toBeInstanceOf(Promise)
40
- })
41
- })
42
-
43
- describe('Given an active sharded connection', () => {
44
- const step = tomato.steps.Gi('an active sharded connection')
45
-
46
- it('should be', async () => undefined)
47
-
48
- beforeEach(async () => {
49
- await step.call(context)
50
- })
51
-
52
- it('should set sharded to true', async () => {
53
- expect(context.sharded).toStrictEqual(true)
54
- })
55
-
56
- it('should connect', async () => {
57
- expect(context.connect).toHaveBeenCalled()
58
- })
59
-
60
- it('should store promise', async () => {
61
- expect(context.connecting).toStrictEqual(expect.any(Promise))
62
- })
63
- })
64
-
65
- describe('When I attempt to connect to the broker for {number} second(s)', () => {
66
- const step = tomato.steps.Wh('I attempt to connect to the broker for {number} second(s)')
67
-
68
- it('should be', async () => undefined)
69
-
70
- const interval = (random(2) + 1) / 10
71
-
72
- describe('broker available', () => {
73
- beforeEach(async () => {
74
- await step.call(context, interval)
75
- })
76
-
77
- it('should call connect', async () => {
78
- expect(context.connect).toHaveBeenCalled()
79
- })
80
- })
81
-
82
- describe('broker unavailable', () => {
83
- /** @type {toa.generic.Promex} */
84
- let connection
85
-
86
- beforeEach(() => {
87
- connection = promex()
88
-
89
- context.connect.mockImplementationOnce(async () => connection)
90
- })
91
-
92
- it('should quit after given interval', async () => {
93
- const start = new Date().getTime()
94
-
95
- await step.call(context, interval)
96
-
97
- const end = new Date().getTime()
98
- const inaccuracy = 1 // setTimeout is inaccurate
99
-
100
- expect(end - start + inaccuracy).toBeGreaterThanOrEqual(interval * 1000)
101
- })
102
- })
103
-
104
- describe('exceptions', () => {
105
- it('should catch exception', async () => {
106
- const exception = new Error(generate())
107
-
108
- context.connect.mockImplementationOnce(async () => { throw exception })
109
-
110
- await step.call(context, interval)
111
-
112
- expect(context.exception).toStrictEqual(exception)
113
- })
114
- })
115
- })
116
-
117
- describe('When I attempt to connect to the broker', () => {
118
- const step = tomato.steps.Wh('I attempt to connect to the broker')
119
-
120
- it('should be', async () => undefined)
121
-
122
- it('should connect', async () => {
123
- await step.call(context)
124
-
125
- expect(context.connect).toHaveBeenCalled()
126
- })
127
-
128
- it('should store exception', async () => {
129
- const exception = new Error(generate())
130
-
131
- context.connect.mockImplementationOnce(async () => { throw exception })
132
-
133
- await step.call(context)
134
-
135
- expect(context.exception).toStrictEqual(exception)
136
- })
137
- })
138
-
139
- describe('When I attempt to connect to the broker as {string} with password {string}', () => {
140
- const step = tomato.steps.Wh('I attempt to connect to the broker as {string} with password {string}')
141
-
142
- it('should be', async () => undefined)
143
-
144
- const user = generate()
145
- const password = generate()
146
-
147
- it('should connect with credentials', async () => {
148
- await step.call(context, user, password)
149
-
150
- expect(context.connect).toHaveBeenCalledWith(user, password)
151
- })
152
- })
153
-
154
- describe('When I attempt to establish sharded connection', () => {
155
- const step = tomato.steps.Wh('I attempt to establish sharded connection')
156
-
157
- it('should be', async () => undefined)
158
-
159
- it('should set context.sharded to true', async () => {
160
- await step.call(context)
161
-
162
- expect(context.sharded).toStrictEqual(true)
163
- })
164
-
165
- it('should connect', async () => {
166
- await step.call(context)
167
-
168
- expect(context.connect).toHaveBeenCalled()
169
- })
170
- })
171
-
172
- describe('When I attempt to establish a sharded connection as {string} with password {string}', () => {
173
- const step = tomato.steps.Wh('I attempt to establish a sharded connection as {string} with password {string}')
174
-
175
- it('should be', async () => undefined)
176
-
177
- const user = generate()
178
- const password = generate()
179
-
180
- it('should connect with credentials', async () => {
181
- await step.call(context, user, password)
182
-
183
- expect(context.sharded).toStrictEqual(true)
184
- expect(context.connect).toHaveBeenCalledWith(user, password)
185
- })
186
- })
187
-
188
- describe.each([['', true], [' not', false]])('Then the connection is%s established',
189
- (not, connected) => {
190
- const step = tomato.steps.Th(`the connection is${not} established`)
191
-
192
- it('should be', async () => undefined)
193
-
194
- it(`should fail if is${connected ? 'n\'t' : ''} connected`, async () => {
195
- context.connected = !connected
196
-
197
- await expect(step.call(context)).rejects.toThrow(AssertionError)
198
- })
199
-
200
- it(`should pass if is${connected ? '' : 'n\'t'} connected`, async () => {
201
- context.connected = connected
202
-
203
- await expect(step.call(context)).resolves.not.toThrow()
204
- })
205
- })
206
-
207
- describe('Then no exceptions are thrown', () => {
208
- const step = tomato.steps.Th('no exceptions are thrown')
209
-
210
- it('should be', async () => undefined)
211
-
212
- it(`should fail if exception is defined`, async () => {
213
- context.exception = new Error()
214
-
215
- expect(() => step.call(context)).toThrow(AssertionError)
216
- })
217
-
218
- it(`should pass if exception isn't defined`, async () => {
219
- expect(() => step.call(context)).not.toThrow()
220
- })
221
- })
222
-
223
- describe('Then an exception is thrown: {string}', () => {
224
- const step = tomato.steps.Th('an exception is thrown: {string}')
225
-
226
- const message = generate()
227
-
228
- it('should be', async () => undefined)
229
-
230
- it(`should pass if exception is defined and message matches`, async () => {
231
- context.exception = new Error(message)
232
-
233
- const slice = message.slice(2, 6)
234
-
235
- expect(() => step.call(context, slice)).not.toThrow()
236
- })
237
-
238
- it(`should fail if exception isn't defined`, async () => {
239
- expect(() => step.call(context)).toThrow(AssertionError)
240
- })
241
-
242
- it('should fail if message doesn\'t match', async () => {
243
- context.exception = new Error(generate())
244
-
245
- expect(() => step.call(context)).toThrow(AssertionError)
246
- })
247
- })
248
-
249
- describe('Given the connection has started sealing', () => {
250
- const step = tomato.steps.Gi('the connection has started sealing')
251
-
252
- it('should be', async () => undefined)
253
-
254
- it('should seal the connection', async () => {
255
- context.io = /** @type {jest.MockedObject<comq.IO>} */ {
256
- seal: jest.fn(async () => undefined)
257
- }
258
-
259
- await step.call(context)
260
-
261
- expect(context.io.seal).toHaveBeenCalled()
262
- expect(context.sealing).toBeInstanceOf(Promise)
263
- })
264
- })
265
-
266
- describe('Then the connection is sealed', () => {
267
- const step = tomato.steps.Th('the connection is sealed')
268
-
269
- it('should be', async () => undefined)
270
-
271
- it('should await for sealing', async () => {
272
- context.sealing = promex()
273
-
274
- let sealed = false
275
-
276
- setImmediate(() => {
277
- context.sealing.resolve()
278
- sealed = true
279
- })
280
-
281
- await step.call(context)
282
-
283
- expect(sealed).toStrictEqual(true)
284
- })
285
- })
@@ -1,14 +0,0 @@
1
- 'use strict'
2
-
3
- const { comq } = require('./comq.mock')
4
-
5
- const connect = jest.fn(() => comq.connect())
6
- const disconnect = jest.fn(async () => undefined)
7
-
8
- /**
9
- * @return {jest.MockedObject<comq.features.Context>}
10
- */
11
- exports.context = () => (/** @type {jest.MockedObject<comq.features.Context>} */{
12
- connect,
13
- disconnect
14
- })
@@ -1,74 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
- const { comq } = require('./comq.mock')
5
- const mock = { comq }
6
-
7
- jest.mock('comq', () => mock.comq)
8
-
9
- const { Context } = require('../context')
10
-
11
- it('should be', async () => {
12
- expect(Context).toBeDefined()
13
- })
14
-
15
- /** @type {comq.features.Context} */
16
- let context
17
-
18
- beforeAll(async () => {
19
- const _ = {}
20
-
21
- // noinspection JSValidateTypes
22
- context = new Context(_)
23
- })
24
-
25
- describe.each([false, true])('connect (shards: %s)', (sharded) => {
26
- it('should be', async () => {
27
- expect(context.connect).toBeDefined()
28
- })
29
-
30
- /** @type {jest.MockedObject<comq.IO>} */
31
- let io
32
-
33
- beforeEach(async () => {
34
- context.sharded = sharded
35
-
36
- await context.connect()
37
-
38
- io = await comq.connect.mock.results[0].value
39
- })
40
-
41
- it('should connect', async () => {
42
- const shards = ['amqp://developer:secret@localhost:5673', 'amqp://developer:secret@localhost:5674']
43
-
44
- if (sharded) expect(comq.connect).toHaveBeenCalledWith(...shards)
45
- else expect(comq.connect).toHaveBeenCalledWith(shards[0])
46
- })
47
-
48
- it('should connect with credentials', async () => {
49
- const user = generate()
50
- const password = generate()
51
-
52
- await context.connect(user, password)
53
-
54
- const shards = [`amqp://${user}:${password}@localhost:5673`, `amqp://${user}:${password}@localhost:5674`]
55
-
56
- if (sharded) expect(comq.connect).toHaveBeenCalledWith(...shards)
57
- else expect(comq.connect).toHaveBeenCalledWith(shards[0])
58
- })
59
-
60
- it.each(['open', 'close', 'flow', 'discard'])('should store %s event',
61
- /**
62
- * @param {comq.diagnostics.Event} event
63
- */
64
- async (event) => {
65
- expect(io.diagnose).toHaveBeenCalledWith(event, expect.any(Function))
66
- expect(context.events[event]).not.toStrictEqual(true)
67
-
68
- const listener = io.diagnose.mock.calls.find((call) => call[0] === event)[1]
69
-
70
- listener()
71
-
72
- expect(context.events[event]).toStrictEqual(true)
73
- })
74
- })