comq 0.24.0 → 0.25.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/.husky/commit-msg +5 -0
- package/benchmarks/run.js +66 -4
- package/benchmarks/server.js +14 -0
- package/commitlint.config.js +6 -0
- package/features/heartbeat.feature +16 -0
- package/features/rpc.call.recovery.feature +2 -2
- package/features/rpc.shards.retransmission.feature +46 -0
- package/features/rpc.streams.feature +28 -0
- package/features/shards.feature +1 -1
- package/features/silence.feature +3 -2
- package/features/steps/.types/context.d.ts +6 -1
- package/features/steps/brokers.js +1 -1
- package/features/steps/connection.js +23 -0
- package/features/steps/context.js +1 -2
- package/features/steps/hooks.js +0 -1
- package/features/steps/network.js +11 -2
- package/features/steps/networks.js +31 -2
- package/features/steps/retransmission.js +165 -0
- package/features/steps/rpc.js +31 -7
- package/package.json +31 -9
- package/readme.md +15 -4
- package/source/.io/ReplyPipe.js +130 -11
- package/source/.io/ReplyStream.js +41 -2
- package/source/.io/const.js +6 -0
- package/source/attributes/track.test.js +1 -1
- package/source/channel.js +5 -3
- package/source/connection.js +11 -88
- package/source/io.js +91 -16
- package/source/promex.js +47 -0
- package/source/promex.test.js +79 -0
- package/source/retry.js +42 -0
- package/source/retry.test.js +120 -0
- package/source/shards/channel.js +74 -13
- package/test/.types/jest.d.ts +22 -0
- package/test/ReplyPipe.test.js +71 -2
- package/test/ReplyStream.test.js +33 -0
- package/test/connection.mock.js +3 -2
- package/test/connection.test.js +16 -78
- package/test/globals.js +143 -0
- package/test/io.call.test.js +5 -3
- package/test/io.close.test.js +1 -1
- package/test/io.reply.test.js +5 -3
- package/test/io.request.test.js +264 -2
- package/test/memory.test.js +0 -2
- package/test/shards/channel.test.js +169 -3
- package/test/shards/connection.test.js +2 -3
- package/types/channel.d.ts +13 -4
- package/types/io.d.ts +9 -0
package/test/ReplyPipe.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Readable } = require('node:stream')
|
|
4
|
-
const { Promex } = require('promex')
|
|
4
|
+
const { Promex } = require('../source/promex')
|
|
5
5
|
const { ReplyPipe } = require('../source/.io/ReplyPipe')
|
|
6
6
|
const { createReplyEmitter } = require('../source/.io/createReplyEmitter')
|
|
7
7
|
const { control, FLOW_HEADER } = require('../source/.io/const')
|
|
@@ -96,10 +96,14 @@ it('should pull the source no faster than the channel takes', async () => {
|
|
|
96
96
|
})
|
|
97
97
|
|
|
98
98
|
it('should hold the source while the consumer has asked for a pause', async () => {
|
|
99
|
-
|
|
99
|
+
// the pause is asked for while the confirmation is still being published: the pipe does not
|
|
100
|
+
// read its source until that has been published, so nothing can be in flight yet
|
|
101
|
+
const opening = ReplyPipe.create(request, Readable.from([1, 2, 3]), channel, feedback, reply)
|
|
100
102
|
|
|
101
103
|
feedback.emit(request.properties.correlationId, control.pause, {})
|
|
102
104
|
|
|
105
|
+
const pipe = await opening
|
|
106
|
+
|
|
103
107
|
await timeout(10)
|
|
104
108
|
|
|
105
109
|
expect(messages()).toStrictEqual([control.ok])
|
|
@@ -208,3 +212,68 @@ it('should destroy the source if the confirmation cannot be sent', async () => {
|
|
|
208
212
|
|
|
209
213
|
expect(source.destroyed).toStrictEqual(true)
|
|
210
214
|
})
|
|
215
|
+
|
|
216
|
+
describe('batches', () => {
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
request.properties.contentType = 'application/json'
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should send the values its source holds as one message', async () => {
|
|
222
|
+
const pipe = await ReplyPipe.create(request, Readable.from([1, 2, 3]), channel, feedback, reply)
|
|
223
|
+
|
|
224
|
+
await closing(pipe)
|
|
225
|
+
|
|
226
|
+
expect(messages()).toStrictEqual([control.ok, [1, 2, 3], control.end])
|
|
227
|
+
expect(sent[1][1].type).toStrictEqual('batch')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should send each value that arrives on its own as it arrives', async () => {
|
|
231
|
+
async function * source () {
|
|
232
|
+
yield 1
|
|
233
|
+
await timeout(10)
|
|
234
|
+
yield 2
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const pipe = await ReplyPipe.create(request, Readable.from(source()), channel, feedback, reply)
|
|
238
|
+
|
|
239
|
+
await closing(pipe)
|
|
240
|
+
|
|
241
|
+
expect(messages()).toStrictEqual([control.ok, 1, 2, control.end])
|
|
242
|
+
expect(sent[1][1].type).toBeUndefined()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should send buffers as one message, each prefixed with its length', async () => {
|
|
246
|
+
const source = Readable.from([Buffer.from('ab'), Buffer.from('cde')])
|
|
247
|
+
const pipe = await ReplyPipe.create(request, source, channel, feedback, reply)
|
|
248
|
+
|
|
249
|
+
await closing(pipe)
|
|
250
|
+
|
|
251
|
+
const expected = Buffer.from([0, 0, 0, 2, 0x61, 0x62, 0, 0, 0, 3, 0x63, 0x64, 0x65])
|
|
252
|
+
|
|
253
|
+
expect(messages()[1]).toStrictEqual(expected)
|
|
254
|
+
expect(sent[1][1].type).toStrictEqual('buffers')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should send values one at a time in an encoding that holds no list', async () => {
|
|
258
|
+
request.properties.contentType = 'text/plain'
|
|
259
|
+
|
|
260
|
+
const pipe = await ReplyPipe.create(request, Readable.from(['a', 'b']), channel, feedback, reply)
|
|
261
|
+
|
|
262
|
+
await closing(pipe)
|
|
263
|
+
|
|
264
|
+
expect(messages()).toStrictEqual([control.ok, 'a', 'b', control.end])
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should keep a batch within its limit', async () => {
|
|
268
|
+
const values = Array.from({ length: 300 }, (_, i) => i)
|
|
269
|
+
const source = Readable.from(values, { highWaterMark: 1000 })
|
|
270
|
+
const pipe = await ReplyPipe.create(request, source, channel, feedback, reply)
|
|
271
|
+
|
|
272
|
+
await closing(pipe)
|
|
273
|
+
|
|
274
|
+
const batches = messages().slice(1, -1)
|
|
275
|
+
|
|
276
|
+
expect(batches.flat()).toStrictEqual(values)
|
|
277
|
+
expect(Math.max(...batches.map((batch) => [batch].flat().length))).toBeLessThanOrEqual(128)
|
|
278
|
+
})
|
|
279
|
+
})
|
package/test/ReplyStream.test.js
CHANGED
|
@@ -204,3 +204,36 @@ describe('flow', () => {
|
|
|
204
204
|
}
|
|
205
205
|
})
|
|
206
206
|
})
|
|
207
|
+
|
|
208
|
+
describe('batches', () => {
|
|
209
|
+
const ok = { headers: { index: 0, [FLOW_HEADER]: true }, type: 'control' }
|
|
210
|
+
const end = (index) => [control.end, { headers: { index }, type: 'control' }]
|
|
211
|
+
|
|
212
|
+
it('should yield each value of a batch, in order', async () => {
|
|
213
|
+
stream.arrange(control.ok, ok)
|
|
214
|
+
stream.arrange([1, 2, 3], { headers: { index: 1 }, type: 'batch' })
|
|
215
|
+
stream.arrange(4, { headers: { index: 2 } })
|
|
216
|
+
stream.arrange(...end(3))
|
|
217
|
+
|
|
218
|
+
expect(await stream.toArray()).toStrictEqual([1, 2, 3, 4])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should yield each buffer of a batch of buffers', async () => {
|
|
222
|
+
const buffers = Buffer.from([0, 0, 0, 2, 0x61, 0x62, 0, 0, 0, 0, 0, 0, 0, 1, 0x63])
|
|
223
|
+
|
|
224
|
+
stream.arrange(control.ok, ok)
|
|
225
|
+
stream.arrange(buffers, { headers: { index: 1 }, type: 'buffers' })
|
|
226
|
+
stream.arrange(...end(2))
|
|
227
|
+
|
|
228
|
+
expect(await stream.toArray()).toStrictEqual([Buffer.from('ab'), Buffer.alloc(0), Buffer.from('c')])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should hold a batch that arrives out of order until the gap is filled', async () => {
|
|
232
|
+
stream.arrange(control.ok, ok)
|
|
233
|
+
stream.arrange([3, 4], { headers: { index: 2 }, type: 'batch' })
|
|
234
|
+
stream.arrange([1, 2], { headers: { index: 1 }, type: 'batch' })
|
|
235
|
+
stream.arrange(...end(3))
|
|
236
|
+
|
|
237
|
+
expect(await stream.toArray()).toStrictEqual([1, 2, 3, 4])
|
|
238
|
+
})
|
|
239
|
+
})
|
package/test/connection.mock.js
CHANGED
|
@@ -6,13 +6,14 @@ const channel = /** @type {jest.MockedFunction<(sharded?: boolean, index?: numbe
|
|
|
6
6
|
sharded,
|
|
7
7
|
consume: jest.fn(async () => undefined),
|
|
8
8
|
deliver: jest.fn(async () => undefined),
|
|
9
|
-
|
|
9
|
+
// a sharded channel tells which shard a message goes through, the first one unless told otherwise
|
|
10
|
+
send: jest.fn(async (_queue, _buffer, _options, via) => { if (sharded) via?.(0) }),
|
|
10
11
|
fire: jest.fn(async () => undefined),
|
|
11
12
|
subscribe: jest.fn(async () => undefined),
|
|
12
13
|
bound: jest.fn(async () => undefined),
|
|
13
14
|
held: jest.fn(async () => undefined),
|
|
14
15
|
publish: jest.fn(async () => undefined),
|
|
15
|
-
route: jest.fn(async () =>
|
|
16
|
+
route: jest.fn(async (_exchange, _key, _buffer, _options, via) => { if (sharded) via?.(0) }),
|
|
16
17
|
diagnose: jest.fn(async () => undefined),
|
|
17
18
|
forget: jest.fn(() => undefined),
|
|
18
19
|
seal: jest.fn(async () => undefined),
|
package/test/connection.test.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { generate } = require('randomstring')
|
|
6
6
|
|
|
7
|
-
const { Promex } = require('promex')
|
|
7
|
+
const { Promex } = require('../source/promex')
|
|
8
8
|
const { timeout, random } = require('./helpers')
|
|
9
9
|
const { amqplib, connect } = require('./amqplib.mock')
|
|
10
10
|
const { channel: create } = require('./connection.mock')
|
|
@@ -543,109 +543,47 @@ describe('channel exhaustion', () => {
|
|
|
543
543
|
})
|
|
544
544
|
})
|
|
545
545
|
|
|
546
|
-
describe('
|
|
546
|
+
describe('silence', () => {
|
|
547
547
|
afterEach(() => {
|
|
548
548
|
jest.useRealTimers()
|
|
549
549
|
})
|
|
550
550
|
|
|
551
|
-
|
|
551
|
+
// amqplib reads the socket through a callback rather than 'data' events, and tells a
|
|
552
|
+
// connection gone silent by its heartbeat
|
|
553
|
+
it('should keep an idle connection', async () => {
|
|
552
554
|
jest.useFakeTimers()
|
|
553
555
|
|
|
554
556
|
await connection.open()
|
|
555
557
|
|
|
556
558
|
const conn = await amqplib.connect.mock.results[0].value
|
|
557
559
|
|
|
558
|
-
await jest.advanceTimersByTimeAsync(
|
|
559
|
-
|
|
560
|
-
expect(conn.connection.stream.destroy).toHaveBeenCalled()
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
it('should reconnect after the watchdog destroys a silent connection', async () => {
|
|
564
|
-
jest.useFakeTimers()
|
|
565
|
-
|
|
566
|
-
await connection.open()
|
|
567
|
-
|
|
568
|
-
await jest.advanceTimersByTimeAsync(46_000)
|
|
569
|
-
|
|
570
|
-
// a socket destroyed without an error is a socket amqplib never reports,
|
|
571
|
-
// which leaves the connection silently dead instead of recovering
|
|
572
|
-
expect(amqplib.connect).toHaveBeenCalledTimes(2)
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
it('should not let a replaced socket disarm the watchdog', async () => {
|
|
576
|
-
jest.useFakeTimers()
|
|
577
|
-
|
|
578
|
-
await connection.open()
|
|
579
|
-
|
|
580
|
-
const stale = await amqplib.connect.mock.results[0].value
|
|
581
|
-
|
|
582
|
-
stale.emit('close', new Error('lost'))
|
|
583
|
-
|
|
584
|
-
await jest.advanceTimersByTimeAsync(1)
|
|
585
|
-
|
|
586
|
-
const live = await amqplib.connect.mock.results[1].value
|
|
587
|
-
|
|
588
|
-
await jest.advanceTimersByTimeAsync(30_000)
|
|
589
|
-
|
|
590
|
-
stale.connection.stream.emit('data', Buffer.alloc(0))
|
|
591
|
-
|
|
592
|
-
await jest.advanceTimersByTimeAsync(30_000)
|
|
593
|
-
|
|
594
|
-
expect(live.connection.stream.destroy).toHaveBeenCalled()
|
|
595
|
-
})
|
|
596
|
-
|
|
597
|
-
it('should reset watchdog on socket data', async () => {
|
|
598
|
-
jest.useFakeTimers()
|
|
599
|
-
|
|
600
|
-
await connection.open()
|
|
601
|
-
|
|
602
|
-
const conn = await amqplib.connect.mock.results[0].value
|
|
603
|
-
|
|
604
|
-
await jest.advanceTimersByTimeAsync(40_000)
|
|
605
|
-
|
|
606
|
-
conn.connection.stream.emit('data', Buffer.alloc(0))
|
|
607
|
-
|
|
608
|
-
await jest.advanceTimersByTimeAsync(40_000)
|
|
560
|
+
await jest.advanceTimersByTimeAsync(600_000)
|
|
609
561
|
|
|
610
562
|
expect(conn.connection.stream.destroy).not.toHaveBeenCalled()
|
|
611
|
-
|
|
612
|
-
await jest.advanceTimersByTimeAsync(10_000)
|
|
613
|
-
|
|
614
|
-
expect(conn.connection.stream.destroy).toHaveBeenCalled()
|
|
563
|
+
expect(amqplib.connect).toHaveBeenCalledTimes(1)
|
|
615
564
|
})
|
|
616
565
|
|
|
617
|
-
it('should
|
|
618
|
-
jest.useFakeTimers()
|
|
619
|
-
|
|
620
|
-
const connection = new Connection('amqp://localhost?heartbeat=30')
|
|
621
|
-
|
|
566
|
+
it('should destroy the socket of a connection lost with an error', async () => {
|
|
622
567
|
await connection.open()
|
|
623
568
|
|
|
624
569
|
const conn = await amqplib.connect.mock.results[0].value
|
|
570
|
+
const stream = conn.connection.stream
|
|
625
571
|
|
|
626
|
-
|
|
627
|
-
await jest.advanceTimersByTimeAsync(60_000)
|
|
628
|
-
|
|
629
|
-
expect(conn.connection.stream.destroy).not.toHaveBeenCalled()
|
|
630
|
-
|
|
631
|
-
await jest.advanceTimersByTimeAsync(31_000)
|
|
572
|
+
conn.emit('close', new Error('Heartbeat timeout'))
|
|
632
573
|
|
|
633
|
-
expect(
|
|
574
|
+
expect(stream.destroy).toHaveBeenCalled()
|
|
634
575
|
})
|
|
635
576
|
|
|
636
|
-
it('should
|
|
637
|
-
jest.useFakeTimers()
|
|
638
|
-
|
|
639
|
-
const connection = new Connection('amqp://localhost?heartbeat=0')
|
|
640
|
-
|
|
577
|
+
it('should reconnect once a connection is lost with an error', async () => {
|
|
641
578
|
await connection.open()
|
|
642
579
|
|
|
643
580
|
const conn = await amqplib.connect.mock.results[0].value
|
|
644
581
|
|
|
645
|
-
|
|
646
|
-
await jest.advanceTimersByTimeAsync(600_000)
|
|
582
|
+
conn.emit('close', new Error('Heartbeat timeout'))
|
|
647
583
|
|
|
648
|
-
|
|
584
|
+
await timeout(0)
|
|
585
|
+
|
|
586
|
+
expect(amqplib.connect).toHaveBeenCalledTimes(2)
|
|
649
587
|
})
|
|
650
588
|
})
|
|
651
589
|
|
package/test/globals.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Jest's globals, on top of node:test.
|
|
4
|
+
//
|
|
5
|
+
// The runner is Node's own; the assertions, the mocks and the fake timers are still Jest's,
|
|
6
|
+
// taken as the standalone packages they are published as. Loaded with `--import`, so that
|
|
7
|
+
// the test files themselves keep seeing `it`, `expect` and `jest` as globals.
|
|
8
|
+
|
|
9
|
+
const { createRequire } = require('node:module')
|
|
10
|
+
const { format } = require('node:util')
|
|
11
|
+
const nodeTest = require('node:test')
|
|
12
|
+
const { expect } = require('expect')
|
|
13
|
+
const { ModuleMocker } = require('jest-mock')
|
|
14
|
+
const { ModernFakeTimers } = require('@jest/fake-timers')
|
|
15
|
+
|
|
16
|
+
// node:test drives its own queue with process.nextTick: faking it, as Jest does inside its
|
|
17
|
+
// sandbox, stops the runner mid-file and the tests left behind are never reported as missing
|
|
18
|
+
const DO_NOT_FAKE = ['nextTick']
|
|
19
|
+
|
|
20
|
+
const mocker = new ModuleMocker(globalThis)
|
|
21
|
+
const timers = new ModernFakeTimers({ global: globalThis, config: { rootDir: process.cwd() } })
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Where `jest.mock` was called from, so that its specifier resolves
|
|
25
|
+
* relative to the test file rather than to this one.
|
|
26
|
+
*
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function origin () {
|
|
30
|
+
const prepare = Error.prepareStackTrace
|
|
31
|
+
|
|
32
|
+
Error.prepareStackTrace = (_, frames) => frames
|
|
33
|
+
|
|
34
|
+
const { stack } = new Error('origin')
|
|
35
|
+
|
|
36
|
+
Error.prepareStackTrace = prepare
|
|
37
|
+
|
|
38
|
+
for (const frame of stack) {
|
|
39
|
+
const file = frame.getFileName()
|
|
40
|
+
|
|
41
|
+
if (file !== null && file !== undefined && file !== __filename && !file.startsWith('node:')) {
|
|
42
|
+
return file
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new Error('cannot tell which file has called jest.mock')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Replaces a module, as `jest.mock` does. Without a factory the module is automocked:
|
|
51
|
+
* every function it exports becomes a mock function.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} specifier
|
|
54
|
+
* @param {() => object} [factory]
|
|
55
|
+
*/
|
|
56
|
+
function mockModule (specifier, factory) {
|
|
57
|
+
if (nodeTest.mock.module === undefined) {
|
|
58
|
+
throw new Error('jest.mock requires node --experimental-test-module-mocks')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const path = createRequire(origin()).resolve(specifier)
|
|
62
|
+
|
|
63
|
+
const namedExports = factory === undefined
|
|
64
|
+
? mocker.generateFromMetadata(mocker.getMetadata(require(path)))
|
|
65
|
+
: factory()
|
|
66
|
+
|
|
67
|
+
nodeTest.mock.module(path, { namedExports, cache: false })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const jest = {
|
|
71
|
+
fn: (implementation) => mocker.fn(implementation),
|
|
72
|
+
spyOn: (object, property, accessType) => mocker.spyOn(object, property, accessType),
|
|
73
|
+
clearAllMocks: () => mocker.clearAllMocks(),
|
|
74
|
+
resetAllMocks: () => mocker.resetAllMocks(),
|
|
75
|
+
restoreAllMocks: () => mocker.restoreAllMocks(),
|
|
76
|
+
mock: mockModule,
|
|
77
|
+
useFakeTimers: (config) => timers.useFakeTimers({ doNotFake: DO_NOT_FAKE, ...config }),
|
|
78
|
+
useRealTimers: () => timers.useRealTimers(),
|
|
79
|
+
advanceTimersByTime: (ms) => timers.advanceTimersByTime(ms),
|
|
80
|
+
advanceTimersByTimeAsync: (ms) => timers.advanceTimersByTimeAsync(ms),
|
|
81
|
+
runAllTimers: () => timers.runAllTimers(),
|
|
82
|
+
runAllTimersAsync: () => timers.runAllTimersAsync(),
|
|
83
|
+
runOnlyPendingTimers: () => timers.runOnlyPendingTimers(),
|
|
84
|
+
getTimerCount: () => timers.getTimerCount(),
|
|
85
|
+
setSystemTime: (now) => timers.setSystemTime(now)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `it.each` / `describe.each`: one test per row, the row spread into the callback,
|
|
90
|
+
* the title formatted with the row's values.
|
|
91
|
+
*
|
|
92
|
+
* @param {Function} declare
|
|
93
|
+
* @returns {(table: any[]) => (title: string, fn: Function, options?: object) => void}
|
|
94
|
+
*/
|
|
95
|
+
const each = (declare) => (table) => (title, fn, options) => {
|
|
96
|
+
const placeholders = (title.replace(/%%/g, '').match(/%[sdifjoOc]/g) ?? []).length
|
|
97
|
+
|
|
98
|
+
for (const row of table) {
|
|
99
|
+
const values = Array.isArray(row) ? row : [row]
|
|
100
|
+
|
|
101
|
+
declare(format(title, ...values.slice(0, placeholders)), options, () => fn(...values))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Jest's `it` and `describe` carry `.each`; node:test's do not.
|
|
107
|
+
*
|
|
108
|
+
* @param {Function} declare
|
|
109
|
+
* @returns {Function}
|
|
110
|
+
*/
|
|
111
|
+
function extend (declare) {
|
|
112
|
+
// Jest takes a timeout where node:test takes options
|
|
113
|
+
const call = (...args) => declare(...(typeof args[2] === 'number'
|
|
114
|
+
? [args[0], { timeout: args[2] }, args[1]]
|
|
115
|
+
: args))
|
|
116
|
+
|
|
117
|
+
const extended = Object.assign(call, declare, { each: each(call) })
|
|
118
|
+
|
|
119
|
+
extended.only = Object.assign((...args) => declare.only(...args), { each: each(declare.only) })
|
|
120
|
+
extended.skip = Object.assign((...args) => declare.skip(...args), { each: each(declare.skip) })
|
|
121
|
+
extended.todo = (...args) => declare.todo(...args)
|
|
122
|
+
|
|
123
|
+
return extended
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Object.assign(globalThis, {
|
|
127
|
+
jest,
|
|
128
|
+
expect,
|
|
129
|
+
it: extend(nodeTest.it),
|
|
130
|
+
test: extend(nodeTest.test),
|
|
131
|
+
describe: extend(nodeTest.describe),
|
|
132
|
+
beforeEach: nodeTest.beforeEach,
|
|
133
|
+
afterEach: nodeTest.afterEach,
|
|
134
|
+
beforeAll: nodeTest.before,
|
|
135
|
+
afterAll: nodeTest.after
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// `expect.assertions` is only enforced by whoever runs the test
|
|
139
|
+
nodeTest.afterEach(() => {
|
|
140
|
+
const errors = expect.extractExpectedAssertionsErrors()
|
|
141
|
+
|
|
142
|
+
if (errors.length > 0) throw errors[0].error
|
|
143
|
+
})
|
package/test/io.call.test.js
CHANGED
|
@@ -52,7 +52,7 @@ describe('call', () => {
|
|
|
52
52
|
|
|
53
53
|
it('should publish to the exchange under the key', async () => {
|
|
54
54
|
expect(requests.route).toHaveBeenCalledWith(exchange, key, expect.any(Buffer),
|
|
55
|
-
expect.objectContaining({ mandatory: true }))
|
|
55
|
+
expect.objectContaining({ mandatory: true }), expect.any(Function))
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
it('should declare no queue to publish to', async () => {
|
|
@@ -116,14 +116,16 @@ describe('back', () => {
|
|
|
116
116
|
const contentType = 'application/json'
|
|
117
117
|
const properties = { contentType, correlationId: generate(), replyTo: generate() }
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
const request = { content: encode(payload, contentType), properties }
|
|
120
|
+
|
|
121
|
+
await consumer(request)
|
|
120
122
|
|
|
121
123
|
replies = await findChannel('reply')
|
|
122
124
|
|
|
123
125
|
expect(producer).toHaveBeenCalledWith(payload)
|
|
124
126
|
|
|
125
127
|
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, expect.any(Buffer),
|
|
126
|
-
expect.objectContaining({ correlationId: properties.correlationId }))
|
|
128
|
+
expect.objectContaining({ correlationId: properties.correlationId }), request)
|
|
127
129
|
})
|
|
128
130
|
})
|
|
129
131
|
|
package/test/io.close.test.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { randomBytes } = require('node:crypto')
|
|
4
4
|
const { generate } = require('randomstring')
|
|
5
|
-
const { Promex } = require('promex')
|
|
5
|
+
const { Promex } = require('../source/promex')
|
|
6
6
|
const { immediate } = require('./helpers')
|
|
7
7
|
|
|
8
8
|
const mock = require('./connection.mock')
|
package/test/io.reply.test.js
CHANGED
|
@@ -98,7 +98,8 @@ describe('reply', () => {
|
|
|
98
98
|
const reply = await produce.mock.results[0].value
|
|
99
99
|
const buffer = encode(reply, properties.contentType)
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
// the request goes along, so that the reply goes back the way it came
|
|
102
|
+
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, buffer, expect.anything(), message)
|
|
102
103
|
})
|
|
103
104
|
|
|
104
105
|
it('should throw if producer returned undefined', async () => {
|
|
@@ -190,7 +191,7 @@ describe('encoding', () => {
|
|
|
190
191
|
mandatory: true
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, reply, props)
|
|
194
|
+
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, reply, props, message)
|
|
194
195
|
})
|
|
195
196
|
|
|
196
197
|
it('should set octet-stream for Buffer reply', async () => {
|
|
@@ -215,7 +216,8 @@ describe('encoding', () => {
|
|
|
215
216
|
.toHaveBeenCalledWith(
|
|
216
217
|
expect.any(String),
|
|
217
218
|
buffer,
|
|
218
|
-
expect.objectContaining({ contentType: 'application/octet-stream' })
|
|
219
|
+
expect.objectContaining({ contentType: 'application/octet-stream' }),
|
|
220
|
+
message
|
|
219
221
|
)
|
|
220
222
|
})
|
|
221
223
|
|