comq 0.22.0 → 0.24.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/discussions/amqp-1.0.md +127 -0
- package/discussions/one-parking-queue.md +63 -0
- package/discussions/one-reply-queue.md +44 -0
- package/discussions/poison-message-handling.md +2 -1
- package/docs/headers.md +1 -1
- package/features/parking.feature +32 -0
- package/features/rpc.call.feature +23 -1
- package/features/rpc.call.recovery.feature +0 -21
- package/features/rpc.parked.feature +6 -6
- package/features/rpc.replies.feature +26 -0
- package/features/steps/brokers.js +25 -1
- package/features/steps/call.js +90 -30
- package/features/steps/events.js +1 -1
- package/features/steps/parked.js +7 -18
- package/features/steps/parking.js +113 -0
- package/features/steps/poison.js +17 -19
- package/features/steps/replies.js +35 -0
- package/package.json +6 -4
- package/readme.md +54 -75
- package/source/.io/ReplyPipe.js +16 -1
- package/source/channel.js +19 -27
- package/source/connection.js +1 -4
- package/source/index.js +0 -2
- package/source/io.js +48 -59
- package/test/ReplyPipe.test.js +40 -0
- package/test/amqplib.mock.js +1 -1
- package/test/channel.test.js +29 -18
- package/test/connection.test.js +1 -1
- package/test/{io.request.terms.test.js → io.call.terms.test.js} +31 -30
- package/test/io.call.test.js +0 -10
- package/test/io.request.test.js +26 -3
- package/test/memory.test.js +1 -1
- package/types/amqp.d.ts +1 -1
- package/types/channel.d.ts +1 -1
- package/types/index.d.ts +0 -6
- package/types/io.d.ts +9 -14
- package/features/closing.feature +0 -32
- package/features/rpc.timeout.feature +0 -19
- package/features/steps/closing.js +0 -109
- package/source/abandoned.js +0 -15
- package/source/batch.js +0 -62
- package/test/batch.test.js +0 -94
package/test/io.request.test.js
CHANGED
|
@@ -41,7 +41,7 @@ let replies
|
|
|
41
41
|
|
|
42
42
|
let promise
|
|
43
43
|
|
|
44
|
-
const REPLY =
|
|
44
|
+
const REPLY = /^comq\.reply\.\.[0-9a-f]+$/
|
|
45
45
|
|
|
46
46
|
beforeEach(async () => {
|
|
47
47
|
promise = io.request(queue, payload)
|
|
@@ -62,6 +62,17 @@ it('should consume replies queue', async () => {
|
|
|
62
62
|
expect(replies.consume).toHaveBeenCalledWith(expect.stringMatching(REPLY), expect.any(Function))
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
+
it('should consume one replies queue however many queues are requested', async () => {
|
|
66
|
+
// a reply is found by its correlation identifier, so a queue per target would
|
|
67
|
+
// name what the identifier already tells apart
|
|
68
|
+
io.request(generate(), payload)
|
|
69
|
+
io.request(generate(), payload)
|
|
70
|
+
|
|
71
|
+
await immediate()
|
|
72
|
+
|
|
73
|
+
expect(replies.consume).toHaveBeenCalledTimes(1)
|
|
74
|
+
})
|
|
75
|
+
|
|
65
76
|
describe('send', () => {
|
|
66
77
|
let call
|
|
67
78
|
|
|
@@ -84,9 +95,8 @@ describe('send', () => {
|
|
|
84
95
|
|
|
85
96
|
it('should set replyTo', async () => {
|
|
86
97
|
const properties = call[2]
|
|
87
|
-
const rx = new RegExp(`^${queue}..[a-z0-9]+`)
|
|
88
98
|
|
|
89
|
-
expect(properties.replyTo).toMatch(
|
|
99
|
+
expect(properties.replyTo).toMatch(REPLY)
|
|
90
100
|
})
|
|
91
101
|
|
|
92
102
|
it('should consume replyTo', async () => {
|
|
@@ -105,6 +115,19 @@ describe('send', () => {
|
|
|
105
115
|
expect(call[2]).toMatchObject({ contentType })
|
|
106
116
|
})
|
|
107
117
|
|
|
118
|
+
it('should not expire', async () => {
|
|
119
|
+
expect(call[2].expiration).toBeUndefined()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should refuse options other than the encoding', async () => {
|
|
123
|
+
requests.send.mockClear()
|
|
124
|
+
|
|
125
|
+
await expect(io.request(queue, payload, { timeout: 1000 })).rejects.toBeInstanceOf(TypeError)
|
|
126
|
+
await expect(io.request(queue, payload, { signal: new AbortController().signal })).rejects.toBeInstanceOf(TypeError)
|
|
127
|
+
|
|
128
|
+
expect(requests.send).not.toHaveBeenCalled()
|
|
129
|
+
})
|
|
130
|
+
|
|
108
131
|
it('should throw if encoding is not supported', async () => {
|
|
109
132
|
const encoding = /** @type {comq.Encoding} */ 'wtf/' + generate()
|
|
110
133
|
|
package/test/memory.test.js
CHANGED
|
@@ -12,7 +12,7 @@ const { timeout } = require('./helpers')
|
|
|
12
12
|
const mock = require('./amqplib.mock')
|
|
13
13
|
const { amqplib, connect } = mock
|
|
14
14
|
|
|
15
|
-
jest.mock('amqplib', () => mock.amqplib)
|
|
15
|
+
jest.mock('@toa.io/amqplib', () => mock.amqplib)
|
|
16
16
|
|
|
17
17
|
const { connect: open, assert } = require('../source')
|
|
18
18
|
const { SingletonConnection } = require('../source/singleton')
|
package/types/amqp.d.ts
CHANGED
package/types/channel.d.ts
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -38,9 +38,3 @@ export class Unroutable extends Error {
|
|
|
38
38
|
export class Interrupted extends Error {
|
|
39
39
|
readonly reason: string
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* A Reply nobody is waiting for any more, or a Request made once the waiting has stopped.
|
|
44
|
-
* See `IO.abandon`.
|
|
45
|
-
*/
|
|
46
|
-
export class Abandoned extends Error {}
|
package/types/io.d.ts
CHANGED
|
@@ -37,19 +37,19 @@ declare namespace comq {
|
|
|
37
37
|
signal?: AbortSignal
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** How long a caller waits for a
|
|
41
|
-
interface
|
|
40
|
+
/** How long a caller waits for the Reply to a call. */
|
|
41
|
+
interface CallOptions {
|
|
42
42
|
encoding?: _encoding.Encoding
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Milliseconds to wait for the Reply. A
|
|
45
|
+
* Milliseconds to wait for the Reply. A call nobody has taken by then is dropped by the
|
|
46
46
|
* broker; one already taken may still be processed, and its Reply is discarded.
|
|
47
47
|
*/
|
|
48
48
|
timeout?: number
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Ends the wait when aborted, within the timeout. The
|
|
52
|
-
* timeout passes, or, without one, until
|
|
51
|
+
* Ends the wait when aborted, within the timeout. The call stays in its queue until the
|
|
52
|
+
* timeout passes, or, without one, until its holder takes it.
|
|
53
53
|
*/
|
|
54
54
|
signal?: AbortSignal
|
|
55
55
|
}
|
|
@@ -64,15 +64,16 @@ declare namespace comq {
|
|
|
64
64
|
interface IO extends _diagnostics.Diagnosable {
|
|
65
65
|
reply(queue: string, produce: Producer): Promise<void>
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
/** Waits for the Reply for as long as it takes. */
|
|
68
|
+
request<Reply = any, Request = any>(queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
|
|
68
69
|
|
|
69
|
-
request(queue: string, stream: Readable,
|
|
70
|
+
request(queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Sends a Request to whoever holds `key` on the routed `exchange`, through `back`. One that
|
|
73
74
|
* nobody holds is refused with `Unroutable`.
|
|
74
75
|
*/
|
|
75
|
-
call<Reply = any, Request = any>(exchange: string, key: string, payload: Request, options?: _encoding.Encoding |
|
|
76
|
+
call<Reply = any, Request = any>(exchange: string, key: string, payload: Request, options?: _encoding.Encoding | CallOptions): Promise<Reply> | Promise<Readable>
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
79
|
* Answers the Requests `call` sends under `key`, from a queue this connection alone holds.
|
|
@@ -119,12 +120,6 @@ declare namespace comq {
|
|
|
119
120
|
|
|
120
121
|
seal(): Promise<void>
|
|
121
122
|
|
|
122
|
-
/**
|
|
123
|
-
* Stops waiting for Replies: every one still outstanding is rejected with `Abandoned`, and a
|
|
124
|
-
* Request made from now on is refused with it. Callable while a close is already under way.
|
|
125
|
-
*/
|
|
126
|
-
abandon(): Promise<void>
|
|
127
|
-
|
|
128
123
|
close(): Promise<void>
|
|
129
124
|
|
|
130
125
|
diagnose(event: 'open', listener: (index?: number) => void): void
|
package/features/closing.feature
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
Feature: Abandoning the Replies a close is waiting for
|
|
2
|
-
|
|
3
|
-
A consumer callback may call out while it runs, and a close waits for every callback to return.
|
|
4
|
-
Where the answer that callback waits for is the one thing going away with the close, the two
|
|
5
|
-
wait for each other. Abandoning ends that wait.
|
|
6
|
-
|
|
7
|
-
Background:
|
|
8
|
-
Given an active connection to the broker
|
|
9
|
-
|
|
10
|
-
Scenario: Abandoning lets a close finish
|
|
11
|
-
Given a producer replying `upstream` queue by requesting the `downstream` queue
|
|
12
|
-
When the consumer sends a request to the `upstream` queue
|
|
13
|
-
And the producer is handling the request
|
|
14
|
-
And the connection abandons its replies
|
|
15
|
-
Then the connection closes within 1000ms
|
|
16
|
-
And the producer was told its reply is abandoned
|
|
17
|
-
And the consumer was told its own reply is abandoned
|
|
18
|
-
|
|
19
|
-
Scenario: A close already under way is told to stop waiting
|
|
20
|
-
Given a producer replying `upstream` queue by requesting the `downstream` queue
|
|
21
|
-
When the consumer sends a request to the `upstream` queue
|
|
22
|
-
And the producer is handling the request
|
|
23
|
-
And the connection has started closing
|
|
24
|
-
And the connection abandons its replies
|
|
25
|
-
Then the connection closes within 1000ms
|
|
26
|
-
|
|
27
|
-
Scenario: A Request made once the Replies are abandoned is refused
|
|
28
|
-
Given a producer replying `upstream` queue by requesting the `downstream` queue
|
|
29
|
-
When the consumer sends a request to the `upstream` queue
|
|
30
|
-
And the producer is handling the request
|
|
31
|
-
And the connection abandons its replies
|
|
32
|
-
Then a request to the `downstream` queue is refused as abandoned
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
Feature: Request timeout
|
|
2
|
-
|
|
3
|
-
A caller may say how long it waits for a reply. A request nobody took by then is dropped by the
|
|
4
|
-
broker; one already taken is still processed, and its reply is discarded.
|
|
5
|
-
|
|
6
|
-
Background:
|
|
7
|
-
Given an active connection to the broker
|
|
8
|
-
|
|
9
|
-
Scenario: A caller stops waiting at its timeout
|
|
10
|
-
Given a producer replying `slow` queue in 1000ms
|
|
11
|
-
When the consumer sends a request to the `slow` queue with a 200ms timeout
|
|
12
|
-
Then the consumer stops waiting
|
|
13
|
-
|
|
14
|
-
Scenario: A request nobody took in time is never processed
|
|
15
|
-
When the consumer sends a request to the `later` queue with a 200ms timeout
|
|
16
|
-
Then the consumer stops waiting
|
|
17
|
-
When after 300ms
|
|
18
|
-
And a producer counting requests to the `later` queue
|
|
19
|
-
Then the producer receives nothing
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const assert = require('node:assert')
|
|
4
|
-
const { Given, When, Then } = require('@cucumber/cucumber')
|
|
5
|
-
const { Abandoned } = require('../../')
|
|
6
|
-
const { timeout } = require('../../test/helpers')
|
|
7
|
-
|
|
8
|
-
Given('a producer replying {token} queue by requesting the {token} queue',
|
|
9
|
-
/**
|
|
10
|
-
* @param {string} queue
|
|
11
|
-
* @param {string} downstream
|
|
12
|
-
* @this {comq.features.Context}
|
|
13
|
-
*/
|
|
14
|
-
async function (queue, downstream) {
|
|
15
|
-
this.handling = new Promise((resolve) => { this.handled = resolve })
|
|
16
|
-
|
|
17
|
-
const producer = async (request) => {
|
|
18
|
-
const reply = this.io.request(downstream, request)
|
|
19
|
-
|
|
20
|
-
this.handled()
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
return await reply
|
|
24
|
-
} catch (error) {
|
|
25
|
-
// recorded rather than rethrown: what this asserts is what the caller was told,
|
|
26
|
-
// and a rejected producer would have the delivery retried on top of it
|
|
27
|
-
this.abandoned = error
|
|
28
|
-
|
|
29
|
-
return null
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
await this.io.reply(queue, producer)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
When('the producer is handling the request',
|
|
37
|
-
/**
|
|
38
|
-
* @this {comq.features.Context}
|
|
39
|
-
*/
|
|
40
|
-
async function () {
|
|
41
|
-
await this.handling
|
|
42
|
-
|
|
43
|
-
// the consumer's own Request is outstanding as well, and abandoning abandons that one too;
|
|
44
|
-
// its outcome is taken here so that it is asserted rather than left to surface as an
|
|
45
|
-
// unhandled rejection
|
|
46
|
-
this.reply.catch((error) => { this.refused = error })
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
When('the connection has started closing',
|
|
50
|
-
/**
|
|
51
|
-
* @this {comq.features.Context}
|
|
52
|
-
*/
|
|
53
|
-
async function () {
|
|
54
|
-
this.closing = this.io.close()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
When('the connection abandons its replies',
|
|
58
|
-
/**
|
|
59
|
-
* @this {comq.features.Context}
|
|
60
|
-
*/
|
|
61
|
-
async function () {
|
|
62
|
-
await this.io.abandon()
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
Then('the connection closes within {number}ms',
|
|
66
|
-
/**
|
|
67
|
-
* @param {number} ms
|
|
68
|
-
* @this {comq.features.Context}
|
|
69
|
-
*/
|
|
70
|
-
async function (ms) {
|
|
71
|
-
const io = this.io
|
|
72
|
-
|
|
73
|
-
// taken off the world so that the hook on the way out does not await a close
|
|
74
|
-
// this may have just proven does not end
|
|
75
|
-
this.io = undefined
|
|
76
|
-
this.connected = false
|
|
77
|
-
|
|
78
|
-
const closing = io.close().then(() => true, () => true)
|
|
79
|
-
const closed = await Promise.race([closing, timeout(ms).then(() => false)])
|
|
80
|
-
|
|
81
|
-
assert.ok(closed, `The connection did not close within ${ms}ms`)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
Then('the producer was told its reply is abandoned',
|
|
85
|
-
/**
|
|
86
|
-
* @this {comq.features.Context}
|
|
87
|
-
*/
|
|
88
|
-
async function () {
|
|
89
|
-
assert.ok(this.abandoned instanceof Abandoned,
|
|
90
|
-
`The producer was told ${this.abandoned ?? 'nothing'}`)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
Then('the consumer was told its own reply is abandoned',
|
|
94
|
-
/**
|
|
95
|
-
* @this {comq.features.Context}
|
|
96
|
-
*/
|
|
97
|
-
async function () {
|
|
98
|
-
assert.ok(this.refused instanceof Abandoned,
|
|
99
|
-
`The consumer was told ${this.refused ?? 'nothing'}`)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
Then('a request to the {token} queue is refused as abandoned',
|
|
103
|
-
/**
|
|
104
|
-
* @param {string} queue
|
|
105
|
-
* @this {comq.features.Context}
|
|
106
|
-
*/
|
|
107
|
-
async function (queue) {
|
|
108
|
-
await assert.rejects(() => this.io.request(queue, null), Abandoned)
|
|
109
|
-
})
|
package/source/abandoned.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A Reply nobody is waiting for any more, or a Request made once the waiting has stopped. The
|
|
5
|
-
* Request itself was sent and may well be processed; what was given up is the answer.
|
|
6
|
-
*/
|
|
7
|
-
class Abandoned extends Error {
|
|
8
|
-
constructor () {
|
|
9
|
-
super('The connection no longer waits for a Reply')
|
|
10
|
-
|
|
11
|
-
this.name = 'Abandoned'
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
exports.Abandoned = Abandoned
|
package/source/batch.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Writes as one what a connection sends in one turn of the event loop.
|
|
5
|
-
*
|
|
6
|
-
* amqplib hands its frames to the socket one write at a time, and a request served is two of
|
|
7
|
-
* them: the reply, and the acknowledgement of the request it answers. With Nagle's algorithm off,
|
|
8
|
-
* which is what a socket sending without delay runs under, each is a syscall and a packet of its
|
|
9
|
-
* own. Corked, they are held in the socket's buffer and leave together, in one `writev`, at the
|
|
10
|
-
* end of the same turn: nothing waits for a timer, and nothing waits for a write that may never
|
|
11
|
-
* come.
|
|
12
|
-
*
|
|
13
|
-
* This belongs in amqplib, whose `Mux` writes the frames one at a time, and where corking that
|
|
14
|
-
* loop is six lines and needs no wrapping of a socket the library does not own. It is done here
|
|
15
|
-
* until amqplib carries it, and goes when it does.
|
|
16
|
-
*
|
|
17
|
-
* @param {import('node:net').Socket} socket
|
|
18
|
-
*/
|
|
19
|
-
function batch (socket) {
|
|
20
|
-
// whatever a connection is carried over is left as it is unless it is a stream that corks
|
|
21
|
-
if (!corkable(socket) || socket[BATCHED] === true) return
|
|
22
|
-
|
|
23
|
-
socket[BATCHED] = true
|
|
24
|
-
|
|
25
|
-
const write = socket.write.bind(socket)
|
|
26
|
-
|
|
27
|
-
const flush = () => {
|
|
28
|
-
socket[CORKED] = false
|
|
29
|
-
socket.uncork()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const cork = () => {
|
|
33
|
-
if (socket[CORKED] === true) return
|
|
34
|
-
|
|
35
|
-
socket[CORKED] = true
|
|
36
|
-
socket.cork()
|
|
37
|
-
|
|
38
|
-
process.nextTick(flush)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
socket.write = (...args) => {
|
|
42
|
-
cork()
|
|
43
|
-
|
|
44
|
-
return write(...args)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @param {any} socket
|
|
50
|
-
* @returns {boolean}
|
|
51
|
-
*/
|
|
52
|
-
function corkable (socket) {
|
|
53
|
-
return socket !== null && socket !== undefined &&
|
|
54
|
-
typeof socket.write === 'function' &&
|
|
55
|
-
typeof socket.cork === 'function' &&
|
|
56
|
-
typeof socket.uncork === 'function'
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const BATCHED = Symbol('comq.batched')
|
|
60
|
-
const CORKED = Symbol('comq.corked')
|
|
61
|
-
|
|
62
|
-
exports.batch = batch
|
package/test/batch.test.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { Writable } = require('node:stream')
|
|
4
|
-
|
|
5
|
-
const { batch } = require('../source/batch')
|
|
6
|
-
|
|
7
|
-
/** A socket, as far as writing to it goes: what it was given, and how many times. */
|
|
8
|
-
function socket () {
|
|
9
|
-
const writes = []
|
|
10
|
-
|
|
11
|
-
return Object.assign(new Writable({
|
|
12
|
-
write (chunk, _encoding, callback) {
|
|
13
|
-
writes.push([chunk.toString()])
|
|
14
|
-
callback()
|
|
15
|
-
},
|
|
16
|
-
writev (chunks, callback) {
|
|
17
|
-
writes.push(chunks.map(({ chunk }) => chunk.toString()))
|
|
18
|
-
callback()
|
|
19
|
-
}
|
|
20
|
-
}), { writes })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const tick = () => new Promise((resolve) => process.nextTick(resolve))
|
|
24
|
-
|
|
25
|
-
it('should be', async () => {
|
|
26
|
-
expect(batch).toBeDefined()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('should write what a turn wrote as one', async () => {
|
|
30
|
-
const stream = socket()
|
|
31
|
-
|
|
32
|
-
batch(stream)
|
|
33
|
-
|
|
34
|
-
stream.write('a')
|
|
35
|
-
stream.write('b')
|
|
36
|
-
|
|
37
|
-
await tick()
|
|
38
|
-
|
|
39
|
-
expect(stream.writes).toStrictEqual([['a', 'b']])
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should write what a later turn wrote on its own', async () => {
|
|
43
|
-
const stream = socket()
|
|
44
|
-
|
|
45
|
-
batch(stream)
|
|
46
|
-
|
|
47
|
-
stream.write('a')
|
|
48
|
-
|
|
49
|
-
await tick()
|
|
50
|
-
|
|
51
|
-
stream.write('b')
|
|
52
|
-
|
|
53
|
-
await tick()
|
|
54
|
-
|
|
55
|
-
expect(stream.writes).toStrictEqual([['a'], ['b']])
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should not hold a write past its turn', async () => {
|
|
59
|
-
const stream = socket()
|
|
60
|
-
|
|
61
|
-
batch(stream)
|
|
62
|
-
|
|
63
|
-
stream.write('a')
|
|
64
|
-
|
|
65
|
-
await tick()
|
|
66
|
-
|
|
67
|
-
expect(stream.writes).toStrictEqual([['a']])
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should take a socket once', async () => {
|
|
71
|
-
const stream = socket()
|
|
72
|
-
|
|
73
|
-
batch(stream)
|
|
74
|
-
|
|
75
|
-
const wrapped = stream.write
|
|
76
|
-
|
|
77
|
-
batch(stream)
|
|
78
|
-
|
|
79
|
-
expect(stream.write).toStrictEqual(wrapped)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('should leave alone what does not cork', async () => {
|
|
83
|
-
for (const value of [undefined, null, {}, { write: () => undefined }]) {
|
|
84
|
-
expect(() => batch(value)).not.toThrow()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const written = []
|
|
88
|
-
const plain = { write: (chunk) => written.push(chunk) }
|
|
89
|
-
|
|
90
|
-
batch(plain)
|
|
91
|
-
plain.write('a')
|
|
92
|
-
|
|
93
|
-
expect(written).toStrictEqual(['a'])
|
|
94
|
-
})
|