comq 0.25.1 → 0.26.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/docs/headers.md +16 -1
- package/features/properties.feature +39 -0
- package/features/shards.virtual.feature +89 -0
- package/features/steps/context.js +29 -2
- package/features/steps/hooks.js +1 -0
- package/features/steps/names.js +245 -0
- package/features/steps/networks.js +23 -5
- package/features/steps/rpc.js +14 -2
- package/package.json +1 -1
- package/readme.md +102 -4
- package/source/channel.js +147 -2
- package/source/connect.js +18 -15
- package/source/connection.js +8 -5
- package/source/events.js +7 -0
- package/source/io.js +31 -9
- package/source/shards/channel.js +289 -19
- package/source/shards/connection.js +404 -18
- package/source/shards/expand.js +57 -0
- package/source/shards/index.js +2 -0
- package/source/shards/resolve.js +134 -0
- package/source/shards/watch.js +257 -0
- package/source/singleton.js +3 -2
- package/test/amqplib.mock.js +1 -0
- package/test/assert.test.js +7 -2
- package/test/channel.quiet.test.js +301 -0
- package/test/connect.test.js +49 -8
- package/test/connection.mock.js +3 -0
- package/test/connection.test.js +15 -0
- package/test/io.call.test.js +1 -1
- package/test/io.diagnostics.test.js +1 -1
- package/test/io.reply.test.js +12 -1
- package/test/io.request.test.js +18 -0
- package/test/io.shards.test.js +85 -0
- package/test/shards/channel.test.js +411 -0
- package/test/shards/connection.test.js +372 -17
- package/test/shards/expand.test.js +58 -0
- package/test/shards/resolve.test.js +73 -0
- package/test/shards/watch.test.js +264 -0
- package/test/singleton.test.js +1 -1
- package/types/channel.d.ts +23 -0
- package/types/connection.d.ts +13 -2
- package/types/diagnostic.d.ts +2 -1
- package/types/io.d.ts +17 -1
- package/types/shards.d.ts +58 -0
package/test/connection.test.js
CHANGED
|
@@ -42,6 +42,21 @@ describe('initial connection', () => {
|
|
|
42
42
|
expect.objectContaining({ timeout: expect.any(Number) }))
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
+
it('should connect without a name to verify the broker by', async () => {
|
|
46
|
+
await connection.open()
|
|
47
|
+
|
|
48
|
+
expect(amqplib.connect.mock.calls[0][1]).not.toHaveProperty('servername')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should verify the broker by the name its address was resolved from', async () => {
|
|
52
|
+
connection = new Connection(url, {}, 'rmq.example.com')
|
|
53
|
+
|
|
54
|
+
await connection.open()
|
|
55
|
+
|
|
56
|
+
expect(amqplib.connect).toHaveBeenCalledWith(expect.stringContaining(url),
|
|
57
|
+
expect.objectContaining({ servername: 'rmq.example.com', timeout: expect.any(Number) }))
|
|
58
|
+
})
|
|
59
|
+
|
|
45
60
|
it.each(/** @type {[string, Partial<Error>][]} */[
|
|
46
61
|
['Socket closed', { message: 'Socket closed abruptly during opening handshake' }],
|
|
47
62
|
['TLS disconnect', { message: 'Client network socket disconnected before secure TLS connection was established' }],
|
package/test/io.call.test.js
CHANGED
|
@@ -122,7 +122,7 @@ describe('back', () => {
|
|
|
122
122
|
|
|
123
123
|
replies = await findChannel('reply')
|
|
124
124
|
|
|
125
|
-
expect(producer).toHaveBeenCalledWith(payload)
|
|
125
|
+
expect(producer).toHaveBeenCalledWith(payload, properties)
|
|
126
126
|
|
|
127
127
|
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, expect.any(Buffer),
|
|
128
128
|
expect.objectContaining({ correlationId: properties.correlationId }), request)
|
|
@@ -61,7 +61,7 @@ it('should not throw on error without listeners', async () => {
|
|
|
61
61
|
expect(() => emit(new Error(generate()))).not.toThrow()
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
-
it.each(['open', 'close', 'error', 'reconnect', 'exhausted'])('should re-emit %s from connection',
|
|
64
|
+
it.each(['open', 'close', 'error', 'reconnect', 'exhausted', 'join', 'retire', 'leave'])('should re-emit %s from connection',
|
|
65
65
|
/**
|
|
66
66
|
* @param {comq.diagnostics.Event} event
|
|
67
67
|
*/
|
package/test/io.reply.test.js
CHANGED
|
@@ -102,6 +102,17 @@ describe('reply', () => {
|
|
|
102
102
|
expect(replies.fire).toHaveBeenCalledWith(properties.replyTo, buffer, expect.anything(), message)
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
+
it('should pass the properties of the request to the producer', async () => {
|
|
106
|
+
const content = randomBytes(10)
|
|
107
|
+
const properties = { contentType: 'text/plain', headers: { [generate()]: generate() } }
|
|
108
|
+
const message = /** @type {comq.amqp.Message} */ { content, properties }
|
|
109
|
+
const producer = requests.consume.mock.calls[0][1]
|
|
110
|
+
|
|
111
|
+
await producer(message)
|
|
112
|
+
|
|
113
|
+
expect(produce).toHaveBeenCalledWith(expect.anything(), properties)
|
|
114
|
+
})
|
|
115
|
+
|
|
105
116
|
it('should throw if producer returned undefined', async () => {
|
|
106
117
|
jest.clearAllMocks()
|
|
107
118
|
|
|
@@ -161,7 +172,7 @@ describe('encoding', () => {
|
|
|
161
172
|
|
|
162
173
|
await producer(message)
|
|
163
174
|
|
|
164
|
-
expect(produce).toHaveBeenCalledWith(value)
|
|
175
|
+
expect(produce).toHaveBeenCalledWith(value, properties)
|
|
165
176
|
})
|
|
166
177
|
|
|
167
178
|
it.each(encodings)('should encode reply with same encoding (%s)', async (encoding) => {
|
package/test/io.request.test.js
CHANGED
|
@@ -120,6 +120,24 @@ describe('send', () => {
|
|
|
120
120
|
expect(call[2].expiration).toBeUndefined()
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
it('should send the properties it is given, as a Request still', async () => {
|
|
124
|
+
requests.send.mockClear()
|
|
125
|
+
|
|
126
|
+
const headers = { [generate()]: generate() }
|
|
127
|
+
|
|
128
|
+
setImmediate(reply)
|
|
129
|
+
|
|
130
|
+
await io.request(queue, payload, { headers, contentType: 'text/plain', replyTo: generate() })
|
|
131
|
+
|
|
132
|
+
const [, buffer, properties] = requests.send.mock.calls[0]
|
|
133
|
+
|
|
134
|
+
expect(buffer).toStrictEqual(encode(payload, 'text/plain'))
|
|
135
|
+
expect(properties.headers).toStrictEqual(headers)
|
|
136
|
+
expect(properties.contentType).toStrictEqual('text/plain')
|
|
137
|
+
expect(properties.replyTo).toStrictEqual(call[2].replyTo)
|
|
138
|
+
expect(properties.correlationId).toBeDefined()
|
|
139
|
+
})
|
|
140
|
+
|
|
123
141
|
it('should refuse options other than the encoding', async () => {
|
|
124
142
|
requests.send.mockClear()
|
|
125
143
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { immediate } = require('./helpers')
|
|
5
|
+
const { encode } = require('../source/encode')
|
|
6
|
+
|
|
7
|
+
const mock = require('./connection.mock')
|
|
8
|
+
const { IO } = require('../source/io')
|
|
9
|
+
|
|
10
|
+
/** @type {comq.IO} */
|
|
11
|
+
let io
|
|
12
|
+
|
|
13
|
+
/** @type {jest.MockedObject<comq.Connection>} */
|
|
14
|
+
let connection
|
|
15
|
+
|
|
16
|
+
/** @type {jest.MockedObject<comq.Channel>} */
|
|
17
|
+
let requests
|
|
18
|
+
|
|
19
|
+
/** @type {jest.MockedObject<comq.Channel>} */
|
|
20
|
+
let replies
|
|
21
|
+
|
|
22
|
+
/** @type {(index: number) => boolean} */
|
|
23
|
+
let occupied
|
|
24
|
+
|
|
25
|
+
let promise
|
|
26
|
+
|
|
27
|
+
const findChannel = (type) => {
|
|
28
|
+
const index = connection.createChannel.mock.calls.findIndex(([t]) => (t === type))
|
|
29
|
+
|
|
30
|
+
return connection.createChannel.mock.results[index].value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
jest.clearAllMocks()
|
|
35
|
+
|
|
36
|
+
connection = mock.connection(true)
|
|
37
|
+
io = new IO(connection)
|
|
38
|
+
promise = io.request(generate(), { [generate()]: generate() })
|
|
39
|
+
|
|
40
|
+
await immediate()
|
|
41
|
+
|
|
42
|
+
requests = await findChannel('request')
|
|
43
|
+
replies = await findChannel('reply')
|
|
44
|
+
occupied = requests.occupy.mock.calls[0][0]
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should tell the request channel which shards owe a Reply', async () => {
|
|
48
|
+
expect(requests.occupy).toHaveBeenCalledWith(expect.any(Function))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should say so of the shard an unanswered Request went through', async () => {
|
|
52
|
+
expect(occupied(0)).toStrictEqual(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should not say so of another shard', async () => {
|
|
56
|
+
expect(occupied(1)).toStrictEqual(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should not say so once the Request is answered', async () => {
|
|
60
|
+
const { correlationId, contentType } = requests.send.mock.calls[0][2]
|
|
61
|
+
const consumer = replies.consume.mock.calls[0][1]
|
|
62
|
+
|
|
63
|
+
consumer({ content: encode(generate(), contentType), properties: { correlationId, contentType } })
|
|
64
|
+
|
|
65
|
+
await promise
|
|
66
|
+
|
|
67
|
+
expect(occupied(0)).toStrictEqual(false)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should not tell a channel that is not sharded', async () => {
|
|
71
|
+
connection = mock.connection()
|
|
72
|
+
connection.createChannel.mockImplementation(async (type, index) => {
|
|
73
|
+
const channel = mock.channel(false, index)
|
|
74
|
+
|
|
75
|
+
delete channel.occupy
|
|
76
|
+
|
|
77
|
+
return channel
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
io = new IO(connection)
|
|
81
|
+
|
|
82
|
+
expect(() => io.request(generate(), generate())).not.toThrow()
|
|
83
|
+
|
|
84
|
+
await immediate()
|
|
85
|
+
})
|
|
@@ -985,3 +985,414 @@ function emitRecover (chan) {
|
|
|
985
985
|
|
|
986
986
|
for (const call of calls) call[1]()
|
|
987
987
|
}
|
|
988
|
+
|
|
989
|
+
describe('a shard that joins', () => {
|
|
990
|
+
/** @type {jest.MockedObject<comq.Connection>} */
|
|
991
|
+
let joiner
|
|
992
|
+
|
|
993
|
+
/** @type {jest.MockedObject<comq.Channel>} */
|
|
994
|
+
let joined
|
|
995
|
+
|
|
996
|
+
const consumer = jest.fn()
|
|
997
|
+
|
|
998
|
+
const channelOf = async (connection) => await connection.createChannel.mock.results.at(-1).value
|
|
999
|
+
|
|
1000
|
+
const sendMany = async () => {
|
|
1001
|
+
for (let i = 0; i < 100; i++) await channel.send(generate(), randomBytes(8))
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
beforeEach(async () => {
|
|
1005
|
+
channel = await create(connections, type)
|
|
1006
|
+
joiner = mock.connection()
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
it('should tell whether a shard is its own', async () => {
|
|
1010
|
+
expect(channel.has(1)).toStrictEqual(true)
|
|
1011
|
+
expect(channel.has(2)).toStrictEqual(false)
|
|
1012
|
+
|
|
1013
|
+
await channel.join(joiner, 2)
|
|
1014
|
+
|
|
1015
|
+
expect(channel.has(2)).toStrictEqual(true)
|
|
1016
|
+
})
|
|
1017
|
+
|
|
1018
|
+
it('should create a channel of the type, numbered as the shard is', async () => {
|
|
1019
|
+
await channel.join(joiner, 2)
|
|
1020
|
+
|
|
1021
|
+
expect(joiner.createChannel).toHaveBeenCalledWith(type, 2)
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
it('should not join twice', async () => {
|
|
1025
|
+
await channel.join(joiner, 2)
|
|
1026
|
+
await channel.join(joiner, 2)
|
|
1027
|
+
|
|
1028
|
+
expect(joiner.createChannel).toHaveBeenCalledTimes(1)
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
it('should consume what has been consumed', async () => {
|
|
1032
|
+
await channel.consume('q', consumer)
|
|
1033
|
+
await channel.subscribe('x', 'g', consumer)
|
|
1034
|
+
await channel.bound('x', 'q', 'k', consumer)
|
|
1035
|
+
await channel.held('x', 'q', 'k', consumer)
|
|
1036
|
+
|
|
1037
|
+
await channel.join(joiner, 2)
|
|
1038
|
+
|
|
1039
|
+
joined = await channelOf(joiner)
|
|
1040
|
+
|
|
1041
|
+
expect(joined.consume).toHaveBeenCalledWith('q', expect.any(Function))
|
|
1042
|
+
expect(joined.subscribe).toHaveBeenCalledWith('x', 'g', consumer)
|
|
1043
|
+
expect(joined.bound).toHaveBeenCalledWith('x', 'q', 'k', consumer)
|
|
1044
|
+
expect(joined.held).toHaveBeenCalledWith('x', 'q', 'k', expect.any(Function))
|
|
1045
|
+
})
|
|
1046
|
+
|
|
1047
|
+
it('should consume what is consumed afterwards', async () => {
|
|
1048
|
+
await channel.join(joiner, 2)
|
|
1049
|
+
await channel.consume('q', consumer)
|
|
1050
|
+
|
|
1051
|
+
joined = await channelOf(joiner)
|
|
1052
|
+
|
|
1053
|
+
expect(joined.consume).toHaveBeenCalledTimes(1)
|
|
1054
|
+
})
|
|
1055
|
+
|
|
1056
|
+
it('should not be published through before it consumes', async () => {
|
|
1057
|
+
const consuming = new Promex()
|
|
1058
|
+
|
|
1059
|
+
await channel.consume('q', consumer)
|
|
1060
|
+
|
|
1061
|
+
joiner.createChannel.mockImplementationOnce(async (type, index) => {
|
|
1062
|
+
const chan = mock.channel(false, index)
|
|
1063
|
+
|
|
1064
|
+
chan.consume.mockImplementation(() => consuming)
|
|
1065
|
+
|
|
1066
|
+
return chan
|
|
1067
|
+
})
|
|
1068
|
+
|
|
1069
|
+
const joining = channel.join(joiner, 2)
|
|
1070
|
+
|
|
1071
|
+
await immediate()
|
|
1072
|
+
await sendMany()
|
|
1073
|
+
|
|
1074
|
+
joined = await channelOf(joiner)
|
|
1075
|
+
|
|
1076
|
+
expect(joined.consume).toHaveBeenCalled()
|
|
1077
|
+
expect(joined.send).not.toHaveBeenCalled()
|
|
1078
|
+
|
|
1079
|
+
consuming.resolve()
|
|
1080
|
+
|
|
1081
|
+
await joining
|
|
1082
|
+
await sendMany()
|
|
1083
|
+
|
|
1084
|
+
expect(joined.send).toHaveBeenCalled()
|
|
1085
|
+
})
|
|
1086
|
+
|
|
1087
|
+
it('should not wait for a key that is held by another', async () => {
|
|
1088
|
+
await channel.held('x', 'q', 'k', consumer)
|
|
1089
|
+
|
|
1090
|
+
joiner.createChannel.mockImplementationOnce(async (type, index) => {
|
|
1091
|
+
const chan = mock.channel(false, index)
|
|
1092
|
+
|
|
1093
|
+
chan.held.mockImplementation(() => new Promex())
|
|
1094
|
+
|
|
1095
|
+
return chan
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
await channel.join(joiner, 2)
|
|
1099
|
+
await sendMany()
|
|
1100
|
+
|
|
1101
|
+
joined = await channelOf(joiner)
|
|
1102
|
+
|
|
1103
|
+
expect(joined.send).toHaveBeenCalled()
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
it('should not consume what a sealed channel consumed', async () => {
|
|
1107
|
+
await channel.consume('q', consumer)
|
|
1108
|
+
await channel.seal()
|
|
1109
|
+
await channel.join(joiner, 2)
|
|
1110
|
+
|
|
1111
|
+
joined = await channelOf(joiner)
|
|
1112
|
+
|
|
1113
|
+
expect(joined.consume).not.toHaveBeenCalled()
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
it('should not be its shard when no channel can be made for it', async () => {
|
|
1117
|
+
const exception = new Error('No channels left to allocate')
|
|
1118
|
+
|
|
1119
|
+
joiner.createChannel.mockImplementationOnce(async () => { throw exception })
|
|
1120
|
+
|
|
1121
|
+
await expect(channel.join(joiner, 2)).rejects.toThrow(exception)
|
|
1122
|
+
|
|
1123
|
+
expect(channel.has(2)).toStrictEqual(false)
|
|
1124
|
+
|
|
1125
|
+
// and may be joined again
|
|
1126
|
+
await channel.join(joiner, 2)
|
|
1127
|
+
|
|
1128
|
+
expect(channel.has(2)).toStrictEqual(true)
|
|
1129
|
+
})
|
|
1130
|
+
|
|
1131
|
+
it('should not join when it cannot consume what the rest do', async () => {
|
|
1132
|
+
const exception = new Error('PRECONDITION_FAILED')
|
|
1133
|
+
|
|
1134
|
+
await channel.consume('q', consumer)
|
|
1135
|
+
|
|
1136
|
+
joiner.createChannel.mockImplementationOnce(async (type, index) => {
|
|
1137
|
+
const chan = mock.channel(false, index)
|
|
1138
|
+
|
|
1139
|
+
chan.consume.mockImplementation(async () => { throw exception })
|
|
1140
|
+
|
|
1141
|
+
return chan
|
|
1142
|
+
})
|
|
1143
|
+
|
|
1144
|
+
await expect(channel.join(joiner, 2)).rejects.toThrow(exception)
|
|
1145
|
+
|
|
1146
|
+
joined = await channelOf(joiner)
|
|
1147
|
+
|
|
1148
|
+
await sendMany()
|
|
1149
|
+
|
|
1150
|
+
expect(channel.has(2)).toStrictEqual(false)
|
|
1151
|
+
expect(joined.close).toHaveBeenCalled()
|
|
1152
|
+
expect(joined.send).not.toHaveBeenCalled()
|
|
1153
|
+
})
|
|
1154
|
+
|
|
1155
|
+
it('should not consume what no shard could', async () => {
|
|
1156
|
+
const chans = await Promise.all(connections.map((conn) => channelOf(conn)))
|
|
1157
|
+
|
|
1158
|
+
for (const chan of chans) chan.consume.mockImplementationOnce(async () => { throw new Error('oops') })
|
|
1159
|
+
|
|
1160
|
+
await expect(channel.consume('q', consumer)).rejects.toThrow()
|
|
1161
|
+
await channel.join(joiner, 2)
|
|
1162
|
+
|
|
1163
|
+
joined = await channelOf(joiner)
|
|
1164
|
+
|
|
1165
|
+
expect(joined.consume).not.toHaveBeenCalled()
|
|
1166
|
+
})
|
|
1167
|
+
|
|
1168
|
+
it.each(['seal', 'close'])('should %s a shard that is joining with the rest', async (method) => {
|
|
1169
|
+
const consuming = new Promex()
|
|
1170
|
+
|
|
1171
|
+
await channel.consume('q', consumer)
|
|
1172
|
+
|
|
1173
|
+
joiner.createChannel.mockImplementationOnce(async (type, index) => {
|
|
1174
|
+
const chan = mock.channel(false, index)
|
|
1175
|
+
|
|
1176
|
+
chan.consume.mockImplementation(() => consuming)
|
|
1177
|
+
|
|
1178
|
+
return chan
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
const joining = channel.join(joiner, 2)
|
|
1182
|
+
|
|
1183
|
+
await immediate()
|
|
1184
|
+
await channel[method]()
|
|
1185
|
+
|
|
1186
|
+
joined = await channelOf(joiner)
|
|
1187
|
+
|
|
1188
|
+
expect(joined[method]).toHaveBeenCalled()
|
|
1189
|
+
|
|
1190
|
+
consuming.resolve()
|
|
1191
|
+
|
|
1192
|
+
await joining
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
it('should not be published through when closed while joining', async () => {
|
|
1196
|
+
const consuming = new Promex()
|
|
1197
|
+
|
|
1198
|
+
await channel.consume('q', consumer)
|
|
1199
|
+
|
|
1200
|
+
joiner.createChannel.mockImplementationOnce(async (type, index) => {
|
|
1201
|
+
const chan = mock.channel(false, index)
|
|
1202
|
+
|
|
1203
|
+
chan.consume.mockImplementation(() => consuming)
|
|
1204
|
+
|
|
1205
|
+
return chan
|
|
1206
|
+
})
|
|
1207
|
+
|
|
1208
|
+
const joining = channel.join(joiner, 2)
|
|
1209
|
+
|
|
1210
|
+
await immediate()
|
|
1211
|
+
await channel.close()
|
|
1212
|
+
|
|
1213
|
+
consuming.resolve()
|
|
1214
|
+
|
|
1215
|
+
await joining
|
|
1216
|
+
|
|
1217
|
+
joined = await channelOf(joiner)
|
|
1218
|
+
|
|
1219
|
+
expect(joined.close).toHaveBeenCalled()
|
|
1220
|
+
expect(joined.send).not.toHaveBeenCalled()
|
|
1221
|
+
})
|
|
1222
|
+
|
|
1223
|
+
it('should tell which shard a message arrived on', async () => {
|
|
1224
|
+
await channel.consume('q', consumer)
|
|
1225
|
+
await channel.join(joiner, 2)
|
|
1226
|
+
|
|
1227
|
+
joined = await channelOf(joiner)
|
|
1228
|
+
|
|
1229
|
+
const message = { content: randomBytes(8) }
|
|
1230
|
+
|
|
1231
|
+
await joined.consume.mock.calls[0][1](message)
|
|
1232
|
+
await channel.fire('reply', randomBytes(8), {}, message)
|
|
1233
|
+
|
|
1234
|
+
expect(joined.fire).toHaveBeenCalled()
|
|
1235
|
+
})
|
|
1236
|
+
})
|
|
1237
|
+
|
|
1238
|
+
describe('a shard that retires', () => {
|
|
1239
|
+
/** @type {jest.MockedObject<comq.Channel>[]} */
|
|
1240
|
+
let chans
|
|
1241
|
+
|
|
1242
|
+
const sendMany = async () => {
|
|
1243
|
+
for (let i = 0; i < 100; i++) await channel.send(generate(), randomBytes(8))
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
beforeEach(async () => {
|
|
1247
|
+
channel = await create(connections, type)
|
|
1248
|
+
chans = await Promise.all(connections.map((conn) => conn.createChannel.mock.results.at(-1).value))
|
|
1249
|
+
})
|
|
1250
|
+
|
|
1251
|
+
it('should not be published through', async () => {
|
|
1252
|
+
channel.retire(1)
|
|
1253
|
+
|
|
1254
|
+
await sendMany()
|
|
1255
|
+
|
|
1256
|
+
expect(chans[0].send).toHaveBeenCalledTimes(100)
|
|
1257
|
+
expect(chans[1].send).not.toHaveBeenCalled()
|
|
1258
|
+
})
|
|
1259
|
+
|
|
1260
|
+
it('should still take back what answers a message that arrived on it', async () => {
|
|
1261
|
+
await channel.consume('q', jest.fn())
|
|
1262
|
+
|
|
1263
|
+
const message = { content: randomBytes(8) }
|
|
1264
|
+
|
|
1265
|
+
await chans[1].consume.mock.calls[0][1](message)
|
|
1266
|
+
|
|
1267
|
+
channel.retire(1)
|
|
1268
|
+
|
|
1269
|
+
for (let i = 0; i < 20; i++) await channel.fire('reply', randomBytes(8), {}, message)
|
|
1270
|
+
|
|
1271
|
+
expect(chans[1].fire).toHaveBeenCalledTimes(20)
|
|
1272
|
+
})
|
|
1273
|
+
|
|
1274
|
+
it('should go on being consumed from', async () => {
|
|
1275
|
+
channel.retire(1)
|
|
1276
|
+
|
|
1277
|
+
await channel.consume('q', jest.fn())
|
|
1278
|
+
|
|
1279
|
+
expect(chans[1].consume).toHaveBeenCalled()
|
|
1280
|
+
})
|
|
1281
|
+
|
|
1282
|
+
it('should pause once no other shard is left', async () => {
|
|
1283
|
+
const pause = jest.fn()
|
|
1284
|
+
const resume = jest.fn()
|
|
1285
|
+
|
|
1286
|
+
channel.diagnose('pause', pause)
|
|
1287
|
+
channel.diagnose('resume', resume)
|
|
1288
|
+
|
|
1289
|
+
channel.retire(0)
|
|
1290
|
+
channel.retire(1)
|
|
1291
|
+
|
|
1292
|
+
expect(pause).toHaveBeenCalled()
|
|
1293
|
+
|
|
1294
|
+
channel.restore(1)
|
|
1295
|
+
|
|
1296
|
+
expect(resume).toHaveBeenCalled()
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
it('should be published through again once restored', async () => {
|
|
1300
|
+
channel.retire(1)
|
|
1301
|
+
channel.restore(1)
|
|
1302
|
+
|
|
1303
|
+
await sendMany()
|
|
1304
|
+
|
|
1305
|
+
expect(chans[1].send).toHaveBeenCalled()
|
|
1306
|
+
})
|
|
1307
|
+
|
|
1308
|
+
it('should tell since when it has had nothing to do', async () => {
|
|
1309
|
+
chans[1].quiet.mockImplementation(async () => 42)
|
|
1310
|
+
|
|
1311
|
+
await expect(channel.quiet(1)).resolves.toStrictEqual(42)
|
|
1312
|
+
})
|
|
1313
|
+
|
|
1314
|
+
it('should have something to do while whoever publishes says so', async () => {
|
|
1315
|
+
channel.occupy((index) => index === 1)
|
|
1316
|
+
|
|
1317
|
+
const now = Date.now()
|
|
1318
|
+
|
|
1319
|
+
expect(await channel.quiet(1)).toBeGreaterThanOrEqual(now)
|
|
1320
|
+
expect(await channel.quiet(0)).toStrictEqual(0)
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
describe('and leaves', () => {
|
|
1324
|
+
it('should seal its channel, wait for what it delivered, and close it', async () => {
|
|
1325
|
+
const order = []
|
|
1326
|
+
|
|
1327
|
+
chans[1].seal.mockImplementation(async () => order.push('seal'))
|
|
1328
|
+
chans[1].settled.mockImplementation(async () => order.push('settled'))
|
|
1329
|
+
chans[1].close.mockImplementation(async () => order.push('close'))
|
|
1330
|
+
|
|
1331
|
+
await channel.leave(1)
|
|
1332
|
+
|
|
1333
|
+
expect(order).toStrictEqual(['seal', 'settled', 'close'])
|
|
1334
|
+
expect(chans[0].close).not.toHaveBeenCalled()
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
it('should be the way back until what it delivered is done with', async () => {
|
|
1338
|
+
const settled = new Promex()
|
|
1339
|
+
|
|
1340
|
+
await channel.consume('q', jest.fn())
|
|
1341
|
+
|
|
1342
|
+
const message = { content: randomBytes(8) }
|
|
1343
|
+
|
|
1344
|
+
await chans[1].consume.mock.calls[0][1](message)
|
|
1345
|
+
|
|
1346
|
+
chans[1].settled.mockImplementation(() => settled)
|
|
1347
|
+
channel.retire(1)
|
|
1348
|
+
|
|
1349
|
+
const leaving = channel.leave(1)
|
|
1350
|
+
|
|
1351
|
+
await immediate()
|
|
1352
|
+
await channel.fire('reply', randomBytes(8), {}, message)
|
|
1353
|
+
|
|
1354
|
+
expect(chans[1].fire).toHaveBeenCalled()
|
|
1355
|
+
|
|
1356
|
+
settled.resolve()
|
|
1357
|
+
|
|
1358
|
+
await leaving
|
|
1359
|
+
await channel.fire('reply', randomBytes(8), {}, message)
|
|
1360
|
+
|
|
1361
|
+
expect(chans[0].fire).toHaveBeenCalled()
|
|
1362
|
+
})
|
|
1363
|
+
|
|
1364
|
+
it('should not be its shard any longer', async () => {
|
|
1365
|
+
await channel.leave(1)
|
|
1366
|
+
|
|
1367
|
+
expect(channel.has(1)).toStrictEqual(false)
|
|
1368
|
+
|
|
1369
|
+
await sendMany()
|
|
1370
|
+
|
|
1371
|
+
expect(chans[1].send).not.toHaveBeenCalled()
|
|
1372
|
+
})
|
|
1373
|
+
|
|
1374
|
+
it('should not be consumed from', async () => {
|
|
1375
|
+
await channel.leave(1)
|
|
1376
|
+
await channel.consume('q', jest.fn())
|
|
1377
|
+
|
|
1378
|
+
expect(chans[1].consume).not.toHaveBeenCalled()
|
|
1379
|
+
})
|
|
1380
|
+
|
|
1381
|
+
it('should not come back when its channel recovers', async () => {
|
|
1382
|
+
await channel.leave(1)
|
|
1383
|
+
|
|
1384
|
+
for (const [event, listener] of chans[1].diagnose.mock.calls) if (event === 'recover') listener()
|
|
1385
|
+
|
|
1386
|
+
await sendMany()
|
|
1387
|
+
|
|
1388
|
+
expect(chans[1].send).not.toHaveBeenCalled()
|
|
1389
|
+
})
|
|
1390
|
+
|
|
1391
|
+
it('should stop listening to its connection', async () => {
|
|
1392
|
+
await channel.leave(1)
|
|
1393
|
+
|
|
1394
|
+
expect(connections[1].forget).toHaveBeenCalledWith('close', expect.any(Function))
|
|
1395
|
+
expect(connections[1].forget).toHaveBeenCalledWith('open', expect.any(Function))
|
|
1396
|
+
})
|
|
1397
|
+
})
|
|
1398
|
+
})
|