@mojaloop/central-services-shared 18.26.0 → 18.26.1-snapshot.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/package.json +2 -1
- package/src/util/redis/pubSub.js +36 -30
- package/test/unit/util/redis/pubSub.test.js +350 -366
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojaloop/central-services-shared",
|
|
3
|
-
"version": "18.26.0",
|
|
3
|
+
"version": "18.26.1-snapshot.0",
|
|
4
4
|
"description": "Shared code for mojaloop central services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "ModusBox",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"openapi-backend": "5.12.0",
|
|
81
81
|
"raw-body": "3.0.0",
|
|
82
82
|
"rc": "1.2.8",
|
|
83
|
+
"redis": "^5.0.1",
|
|
83
84
|
"shins": "2.6.0",
|
|
84
85
|
"ulidx": "2.4.1",
|
|
85
86
|
"uuid4": "2.0.3",
|
package/src/util/redis/pubSub.js
CHANGED
|
@@ -28,20 +28,13 @@
|
|
|
28
28
|
******/
|
|
29
29
|
|
|
30
30
|
'use strict'
|
|
31
|
-
const
|
|
31
|
+
const { createClient, createCluster } = require('redis')
|
|
32
32
|
const { createLogger } = require('../createLogger')
|
|
33
33
|
const isClusterConfig = (config) => { return 'cluster' in config }
|
|
34
34
|
const { rethrowRedisError } = require('../rethrow')
|
|
35
|
-
const { REDIS_SUCCESS, REDIS_IS_CONNECTED_STATUSES } = require('../../constants')
|
|
36
35
|
|
|
37
36
|
class PubSub {
|
|
38
37
|
constructor (config, publisherClient, subscriberClient) {
|
|
39
|
-
// prepare redis connection instances
|
|
40
|
-
// once the client enters the subscribed state it is not supposed to issue
|
|
41
|
-
// any other commands, except for additional SUBSCRIBE, PSUBSCRIBE,
|
|
42
|
-
// UNSUBSCRIBE, PUNSUBSCRIBE, PING and QUIT commands.
|
|
43
|
-
// So we create two clients, one for subscribing another for
|
|
44
|
-
// and publishing
|
|
45
38
|
this.config = config
|
|
46
39
|
this.isCluster = isClusterConfig(config)
|
|
47
40
|
this.log = createLogger(this.constructor.name)
|
|
@@ -52,10 +45,19 @@ class PubSub {
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
createRedisClient () {
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
if (this.isCluster) {
|
|
49
|
+
// node-redis cluster config expects rootNodes: [{url: 'redis://host:port'}]
|
|
50
|
+
const rootNodes = this.config.cluster.map(
|
|
51
|
+
node => ({ url: `redis://${node.host}:${node.port}` })
|
|
52
|
+
)
|
|
53
|
+
return createCluster({ rootNodes })
|
|
54
|
+
} else {
|
|
55
|
+
// node-redis expects url: 'redis://host:port'
|
|
56
|
+
const url = this.config.host && this.config.port
|
|
57
|
+
? `redis://${this.config.host}:${this.config.port}`
|
|
58
|
+
: undefined
|
|
59
|
+
return createClient({ url, ...this.config })
|
|
60
|
+
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
async connect () {
|
|
@@ -71,12 +73,12 @@ class PubSub {
|
|
|
71
73
|
|
|
72
74
|
async disconnect () {
|
|
73
75
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.subscriberClient.removeAllListeners()
|
|
76
|
+
await this.publisherClient.quit()
|
|
77
|
+
await this.subscriberClient.quit()
|
|
78
|
+
this.subscriberClient.removeAllListeners && this.subscriberClient.removeAllListeners()
|
|
78
79
|
this.log.info('Redis clients disconnected successfully')
|
|
79
|
-
|
|
80
|
+
// node-redis returns 'OK' on quit
|
|
81
|
+
return true
|
|
80
82
|
} catch (err) {
|
|
81
83
|
this.log.error('Error disconnecting Redis clients:', err)
|
|
82
84
|
rethrowRedisError(err)
|
|
@@ -97,8 +99,9 @@ class PubSub {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
get isConnected () {
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
+
// node-redis: status is 'ready' when connected
|
|
103
|
+
const publisherConnected = this.publisherClient.isOpen
|
|
104
|
+
const subscriberConnected = this.subscriberClient.isOpen
|
|
102
105
|
this.log.debug('Redis connection status', {
|
|
103
106
|
publisherConnected,
|
|
104
107
|
subscriberConnected
|
|
@@ -107,18 +110,21 @@ class PubSub {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
addEventListeners (client) {
|
|
110
|
-
client.on('connect', () => this.log.info('Redis client
|
|
113
|
+
client.on('connect', () => this.log.info('Redis client connecting'))
|
|
114
|
+
client.on('ready', () => this.log.info('Redis client ready'))
|
|
115
|
+
client.on('end', () => this.log.info('Redis client connection closed'))
|
|
111
116
|
client.on('error', (err) => this.log.error('Redis client error:', err))
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
async publish (channel, message) {
|
|
115
120
|
try {
|
|
116
|
-
if (this.isCluster) {
|
|
121
|
+
if (this.isCluster && typeof this.publisherClient.spublish === 'function') {
|
|
117
122
|
await this.publisherClient.spublish(channel, JSON.stringify(message))
|
|
123
|
+
this.log.info(`Message SPUBLISHED to channel: ${channel}`)
|
|
118
124
|
} else {
|
|
119
125
|
await this.publisherClient.publish(channel, JSON.stringify(message))
|
|
126
|
+
this.log.info(`Message published to channel: ${channel}`)
|
|
120
127
|
}
|
|
121
|
-
this.log.info(`Message published to channel: ${channel}`)
|
|
122
128
|
} catch (err) {
|
|
123
129
|
this.log.error('Error publishing message:', err)
|
|
124
130
|
rethrowRedisError(err)
|
|
@@ -127,22 +133,21 @@ class PubSub {
|
|
|
127
133
|
|
|
128
134
|
async subscribe (channel, callback) {
|
|
129
135
|
try {
|
|
130
|
-
if (this.isCluster) {
|
|
131
|
-
await this.subscriberClient.ssubscribe(channel)
|
|
132
|
-
this.subscriberClient.on('smessage', (subscribedChannel, message) => {
|
|
136
|
+
if (this.isCluster && typeof this.subscriberClient.ssubscribe === 'function') {
|
|
137
|
+
await this.subscriberClient.ssubscribe(channel, (message, subscribedChannel) => {
|
|
133
138
|
if (subscribedChannel === channel) {
|
|
134
139
|
callback(JSON.parse(message))
|
|
135
140
|
}
|
|
136
141
|
})
|
|
142
|
+
this.log.info(`SSUBSCRIBED to channel: ${channel}`)
|
|
137
143
|
} else {
|
|
138
|
-
await this.subscriberClient.subscribe(channel)
|
|
139
|
-
this.subscriberClient.on('message', (subscribedChannel, message) => {
|
|
144
|
+
await this.subscriberClient.subscribe(channel, (message, subscribedChannel) => {
|
|
140
145
|
if (subscribedChannel === channel) {
|
|
141
146
|
callback(JSON.parse(message))
|
|
142
147
|
}
|
|
143
148
|
})
|
|
149
|
+
this.log.info(`Subscribed to channel: ${channel}`)
|
|
144
150
|
}
|
|
145
|
-
this.log.info(`Subscribed to channel: ${channel}`)
|
|
146
151
|
return channel
|
|
147
152
|
} catch (err) {
|
|
148
153
|
this.log.error('Error subscribing to channel:', err)
|
|
@@ -152,12 +157,13 @@ class PubSub {
|
|
|
152
157
|
|
|
153
158
|
async unsubscribe (channel) {
|
|
154
159
|
try {
|
|
155
|
-
if (this.isCluster) {
|
|
160
|
+
if (this.isCluster && typeof this.subscriberClient.sunsubscribe === 'function') {
|
|
156
161
|
await this.subscriberClient.sunsubscribe(channel)
|
|
162
|
+
this.log.info(`SUNSUBSCRIBED from channel: ${channel}`)
|
|
157
163
|
} else {
|
|
158
164
|
await this.subscriberClient.unsubscribe(channel)
|
|
165
|
+
this.log.info(`Unsubscribed from channel: ${channel}`)
|
|
159
166
|
}
|
|
160
|
-
this.log.info(`Unsubscribed from channel: ${channel}`)
|
|
161
167
|
} catch (err) {
|
|
162
168
|
this.log.error('Error unsubscribing from channel:', err)
|
|
163
169
|
rethrowRedisError(err)
|
|
@@ -22,502 +22,486 @@
|
|
|
22
22
|
* Mojaloop Foundation
|
|
23
23
|
- Name Surname <name.surname@mojaloop.io>
|
|
24
24
|
|
|
25
|
-
* Kevin Leyow <kevin.leyow@
|
|
25
|
+
* Kevin Leyow <kevin.leyow@infitx.com>
|
|
26
26
|
|
|
27
27
|
--------------
|
|
28
28
|
******/
|
|
29
29
|
const Test = require('tapes')(require('tape'))
|
|
30
30
|
const sinon = require('sinon')
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const { constructSystemExtensionError } = require('../../../../src/util/rethrow')
|
|
31
|
+
let PubSub = require('../../../../src/util/redis/pubSub')
|
|
32
|
+
const proxyquire = require('proxyquire')
|
|
34
33
|
|
|
35
|
-
Test('PubSub',
|
|
36
|
-
let sandbox
|
|
34
|
+
Test('PubSub', pubSubTest => {
|
|
35
|
+
let sandbox, publisherStub, subscriberStub, pubSub, logStub
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
pubSubTest.beforeEach(t => {
|
|
39
38
|
sandbox = sinon.createSandbox()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
logStub = {
|
|
40
|
+
info: sandbox.stub(),
|
|
41
|
+
error: sandbox.stub(),
|
|
42
|
+
debug: sandbox.stub()
|
|
43
|
+
}
|
|
44
|
+
publisherStub = {
|
|
45
|
+
connect: sandbox.stub().resolves(),
|
|
46
|
+
quit: sandbox.stub().resolves(),
|
|
47
|
+
ping: sandbox.stub().resolves('PONG'),
|
|
48
|
+
publish: sandbox.stub().resolves(),
|
|
49
|
+
spublish: undefined,
|
|
50
|
+
isOpen: true,
|
|
51
|
+
on: sandbox.stub().returnsThis(),
|
|
52
|
+
removeAllListeners: sandbox.stub()
|
|
53
|
+
}
|
|
54
|
+
subscriberStub = {
|
|
55
|
+
connect: sandbox.stub().resolves(),
|
|
56
|
+
quit: sandbox.stub().resolves(),
|
|
57
|
+
ping: sandbox.stub().resolves('PONG'),
|
|
58
|
+
subscribe: sandbox.stub().resolves(),
|
|
59
|
+
unsubscribe: sandbox.stub().resolves(),
|
|
60
|
+
ssubscribe: undefined,
|
|
61
|
+
sunsubscribe: undefined,
|
|
62
|
+
isOpen: true,
|
|
63
|
+
on: sandbox.stub().returnsThis(),
|
|
64
|
+
removeAllListeners: sandbox.stub()
|
|
65
|
+
}
|
|
66
|
+
sandbox.stub(require('../../../../src/util/createLogger'), 'createLogger').returns(logStub)
|
|
45
67
|
t.end()
|
|
46
68
|
})
|
|
47
69
|
|
|
48
|
-
|
|
70
|
+
pubSubTest.afterEach(t => {
|
|
49
71
|
sandbox.restore()
|
|
50
72
|
t.end()
|
|
51
73
|
})
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const pubSub = new PubSub(config)
|
|
56
|
-
t.ok(pubSub.publisherClient instanceof Redis, 'publisherClient is an instance of Redis')
|
|
57
|
-
t.ok(pubSub.subscriberClient instanceof Redis, 'subscriberClient is an instance of Redis')
|
|
58
|
-
t.end()
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
t.test('should publish a message to a channel', async (t) => {
|
|
62
|
-
const config = {}
|
|
63
|
-
const pubSub = new PubSub(config)
|
|
64
|
-
const channel = 'test-channel'
|
|
65
|
-
const message = { key: 'value' }
|
|
66
|
-
await pubSub.publish(channel, message)
|
|
75
|
+
pubSubTest.test('should connect publisher and subscriber clients', async t => {
|
|
76
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
await pubSub.connect()
|
|
79
|
+
t.ok(publisherStub.connect.called, 'publisher connect called')
|
|
80
|
+
t.ok(subscriberStub.connect.called, 'subscriber connect called')
|
|
69
81
|
t.end()
|
|
70
82
|
})
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const pubSub = new PubSub(config)
|
|
75
|
-
const channel = 'test-channel'
|
|
76
|
-
const message = { key: 'value' }
|
|
77
|
-
const error = new Error('Publish error')
|
|
78
|
-
|
|
79
|
-
pubSub.publisherClient.publish.rejects(error)
|
|
84
|
+
pubSubTest.test('should disconnect publisher and subscriber clients', async t => {
|
|
85
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
86
|
-
}
|
|
87
|
+
await pubSub.disconnect()
|
|
88
|
+
t.ok(publisherStub.quit.called, 'publisher quit called')
|
|
89
|
+
t.ok(subscriberStub.quit.called, 'subscriber quit called')
|
|
90
|
+
t.ok(subscriberStub.removeAllListeners.called, 'subscriber removeAllListeners called')
|
|
87
91
|
t.end()
|
|
88
92
|
})
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const pubSub = new PubSub(config)
|
|
93
|
-
const channel = 'test-channel'
|
|
94
|
-
const callback = sinon.stub()
|
|
95
|
-
const message = JSON.stringify({ key: 'value' })
|
|
96
|
-
|
|
97
|
-
await pubSub.subscribe(channel, callback)
|
|
98
|
-
pubSub.subscriberClient.on.callArgWith(1, channel, message)
|
|
94
|
+
pubSubTest.test('should return true on healthy healthCheck', async t => {
|
|
95
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
t.
|
|
97
|
+
const result = await pubSub.healthCheck()
|
|
98
|
+
t.equal(result, true, 'healthCheck returns true')
|
|
99
|
+
t.ok(publisherStub.ping.called, 'publisher ping called')
|
|
100
|
+
t.ok(subscriberStub.ping.called, 'subscriber ping called')
|
|
102
101
|
t.end()
|
|
103
102
|
})
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const pubSub = new PubSub(config)
|
|
108
|
-
const channel = 'test-channel'
|
|
109
|
-
const callback = sinon.stub()
|
|
110
|
-
const error = new Error('Subscribe error')
|
|
104
|
+
pubSubTest.test('should return false on unhealthy healthCheck', async t => {
|
|
105
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await pubSub.subscribe(channel, callback)
|
|
116
|
-
t.fail('Should have thrown an error')
|
|
117
|
-
} catch (err) {
|
|
118
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
119
|
-
}
|
|
107
|
+
publisherStub.ping.rejects(new Error('fail'))
|
|
108
|
+
const result = await pubSub.healthCheck()
|
|
109
|
+
t.equal(result, false, 'healthCheck returns false')
|
|
120
110
|
t.end()
|
|
121
111
|
})
|
|
122
112
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const pubSub = new PubSub(config)
|
|
126
|
-
const channel = 'test-channel'
|
|
113
|
+
pubSubTest.test('should return isConnected status', t => {
|
|
114
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
127
115
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
t.ok(pubSub.subscriberClient.unsubscribe.calledWith(channel), 'unsubscribe called with correct channel')
|
|
116
|
+
const status = pubSub.isConnected
|
|
117
|
+
t.deepEqual(status, { publisherConnected: true, subscriberConnected: true }, 'isConnected returns correct status')
|
|
131
118
|
t.end()
|
|
132
119
|
})
|
|
133
120
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const pubSub = new PubSub(config)
|
|
137
|
-
const channel = 'test-channel'
|
|
138
|
-
const error = new Error('Unsubscribe error')
|
|
139
|
-
|
|
140
|
-
pubSub.subscriberClient.unsubscribe.rejects(error)
|
|
121
|
+
pubSubTest.test('should publish message to channel', async t => {
|
|
122
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
141
123
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
t.fail('Should have thrown an error')
|
|
145
|
-
} catch (err) {
|
|
146
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
147
|
-
}
|
|
124
|
+
await pubSub.publish('test-channel', { foo: 'bar' })
|
|
125
|
+
t.ok(publisherStub.publish.calledWith('test-channel', JSON.stringify({ foo: 'bar' })), 'publish called with correct args')
|
|
148
126
|
t.end()
|
|
149
127
|
})
|
|
150
128
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const pubSub = new PubSub(config)
|
|
154
|
-
const channels = ['channel1', 'channel2']
|
|
155
|
-
const message = { key: 'value' }
|
|
156
|
-
|
|
157
|
-
await pubSub.broadcast(channels, message)
|
|
129
|
+
pubSubTest.test('should publish message using spublish in cluster mode', async t => {
|
|
130
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
158
131
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
132
|
+
pubSub.isCluster = true
|
|
133
|
+
publisherStub.spublish = sandbox.stub().resolves()
|
|
134
|
+
await pubSub.publish('test-channel', { foo: 'bar' })
|
|
135
|
+
t.ok(publisherStub.spublish.calledWith('test-channel', JSON.stringify({ foo: 'bar' })), 'spublish called with correct args')
|
|
162
136
|
t.end()
|
|
163
137
|
})
|
|
164
138
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const pubSub = new PubSub(config)
|
|
168
|
-
const channels = ['channel1', 'channel2']
|
|
169
|
-
const message = { key: 'value' }
|
|
170
|
-
const error = new Error('Broadcast error')
|
|
171
|
-
|
|
172
|
-
pubSub.publisherClient.publish.onFirstCall().rejects(error)
|
|
139
|
+
pubSubTest.test('should subscribe to channel', async t => {
|
|
140
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
173
141
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
} catch (err) {
|
|
178
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
179
|
-
}
|
|
142
|
+
const callback = sandbox.stub()
|
|
143
|
+
await pubSub.subscribe('test-channel', callback)
|
|
144
|
+
t.ok(subscriberStub.subscribe.called, 'subscribe called')
|
|
180
145
|
t.end()
|
|
181
146
|
})
|
|
182
147
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const pubSub = new PubSub(config)
|
|
186
|
-
|
|
187
|
-
sandbox.stub(pubSub.publisherClient, 'connect').resolves()
|
|
188
|
-
sandbox.stub(pubSub.subscriberClient, 'connect').resolves()
|
|
189
|
-
|
|
190
|
-
await pubSub.connect()
|
|
148
|
+
pubSubTest.test('should subscribe using ssubscribe in cluster mode', async t => {
|
|
149
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
191
150
|
|
|
192
|
-
|
|
193
|
-
|
|
151
|
+
pubSub.isCluster = true
|
|
152
|
+
subscriberStub.ssubscribe = sandbox.stub().resolves()
|
|
153
|
+
const callback = sandbox.stub()
|
|
154
|
+
await pubSub.subscribe('test-channel', callback)
|
|
155
|
+
t.ok(subscriberStub.ssubscribe.called, 'ssubscribe called')
|
|
194
156
|
t.end()
|
|
195
157
|
})
|
|
196
158
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const pubSub = new PubSub(config)
|
|
200
|
-
const error = new Error('Connect error')
|
|
201
|
-
|
|
202
|
-
sandbox.stub(pubSub.publisherClient, 'connect').rejects(error)
|
|
203
|
-
sandbox.stub(pubSub.subscriberClient, 'connect').resolves()
|
|
159
|
+
pubSubTest.test('should unsubscribe from channel', async t => {
|
|
160
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
204
161
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
t.fail('Should have thrown an error')
|
|
208
|
-
} catch (err) {
|
|
209
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
210
|
-
}
|
|
162
|
+
await pubSub.unsubscribe('test-channel')
|
|
163
|
+
t.ok(subscriberStub.unsubscribe.calledWith('test-channel'), 'unsubscribe called')
|
|
211
164
|
t.end()
|
|
212
165
|
})
|
|
213
166
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const pubSub = new PubSub(config)
|
|
167
|
+
pubSubTest.test('should unsubscribe using sunsubscribe in cluster mode', async t => {
|
|
168
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
217
169
|
|
|
218
|
-
|
|
219
|
-
|
|
170
|
+
pubSub.isCluster = true
|
|
171
|
+
subscriberStub.sunsubscribe = sandbox.stub().resolves()
|
|
172
|
+
await pubSub.unsubscribe('test-channel')
|
|
173
|
+
t.ok(subscriberStub.sunsubscribe.calledWith('test-channel'), 'sunsubscribe called')
|
|
220
174
|
t.end()
|
|
221
175
|
})
|
|
222
176
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const pubSub = new PubSub(config)
|
|
226
|
-
|
|
227
|
-
sandbox.stub(pubSub.publisherClient, 'connect').resolves()
|
|
228
|
-
sandbox.stub(pubSub.subscriberClient, 'connect').resolves()
|
|
229
|
-
|
|
230
|
-
await pubSub.connect()
|
|
177
|
+
pubSubTest.test('should broadcast message to multiple channels', async t => {
|
|
178
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
231
179
|
|
|
232
|
-
|
|
233
|
-
|
|
180
|
+
sandbox.stub(pubSub, 'publish').resolves()
|
|
181
|
+
await pubSub.broadcast(['a', 'b'], { foo: 'bar' })
|
|
182
|
+
t.ok(pubSub.publish.calledTwice, 'publish called for each channel')
|
|
234
183
|
t.end()
|
|
235
184
|
})
|
|
236
185
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const pubSub = new PubSub(config)
|
|
240
|
-
const error = new Error('Cluster connect error')
|
|
241
|
-
|
|
242
|
-
sandbox.stub(pubSub.publisherClient, 'connect').rejects(error)
|
|
243
|
-
sandbox.stub(pubSub.subscriberClient, 'connect').resolves()
|
|
186
|
+
pubSubTest.test('should handle error on connect', async t => {
|
|
187
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
244
188
|
|
|
189
|
+
publisherStub.connect.rejects(new Error('connect error'))
|
|
245
190
|
try {
|
|
246
191
|
await pubSub.connect()
|
|
247
|
-
t.fail('
|
|
192
|
+
t.fail('Expected error not thrown')
|
|
248
193
|
} catch (err) {
|
|
249
|
-
t.
|
|
194
|
+
t.match(err.message, /connect error/, 'throws error on connect failure')
|
|
250
195
|
}
|
|
251
196
|
t.end()
|
|
252
197
|
})
|
|
253
198
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const pubSub = new PubSub(config)
|
|
257
|
-
const channel = 'test-channel'
|
|
258
|
-
const callback = sinon.stub()
|
|
259
|
-
const message = JSON.stringify({ key: 'value' })
|
|
260
|
-
const otherChannel = 'other-channel'
|
|
261
|
-
|
|
262
|
-
await pubSub.subscribe(channel, callback)
|
|
263
|
-
pubSub.subscriberClient.on.callArgWith(1, otherChannel, message)
|
|
264
|
-
|
|
265
|
-
t.ok(pubSub.subscriberClient.subscribe.calledWith(channel), 'subscribe called with correct channel')
|
|
266
|
-
t.notOk(callback.called, 'callback not called when subscribedChannel does not match channel')
|
|
267
|
-
t.end()
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
t.test('should disconnect Redis clients successfully', async (t) => {
|
|
271
|
-
const config = {}
|
|
272
|
-
const pubSub = new PubSub(config)
|
|
273
|
-
|
|
274
|
-
sandbox.stub(pubSub.publisherClient, 'quit').resolves()
|
|
275
|
-
sandbox.stub(pubSub.subscriberClient, 'quit').resolves()
|
|
276
|
-
sandbox.stub(pubSub.subscriberClient, 'removeAllListeners').resolves()
|
|
277
|
-
|
|
278
|
-
await pubSub.disconnect()
|
|
279
|
-
|
|
280
|
-
t.ok(pubSub.publisherClient.quit.calledOnce, 'publisherClient quit called once')
|
|
281
|
-
t.ok(pubSub.subscriberClient.quit.calledOnce, 'subscriberClient quit called once')
|
|
282
|
-
t.ok(pubSub.subscriberClient.removeAllListeners.calledOnce, 'subscriberClient removeAllListeners called once')
|
|
283
|
-
t.end()
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
t.test('should handle error when disconnecting Redis clients', async (t) => {
|
|
287
|
-
const config = {}
|
|
288
|
-
const pubSub = new PubSub(config)
|
|
289
|
-
const error = new Error('Disconnect error')
|
|
290
|
-
|
|
291
|
-
sandbox.stub(pubSub.publisherClient, 'quit').rejects(error)
|
|
292
|
-
sandbox.stub(pubSub.subscriberClient, 'quit').resolves()
|
|
199
|
+
pubSubTest.test('should handle error on disconnect', async t => {
|
|
200
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
293
201
|
|
|
202
|
+
publisherStub.quit.rejects(new Error('disconnect error'))
|
|
294
203
|
try {
|
|
295
204
|
await pubSub.disconnect()
|
|
296
|
-
t.fail('
|
|
205
|
+
t.fail('Expected error not thrown')
|
|
297
206
|
} catch (err) {
|
|
298
|
-
t.
|
|
207
|
+
t.match(err.message, /disconnect error/, 'throws error on disconnect failure')
|
|
299
208
|
}
|
|
300
209
|
t.end()
|
|
301
210
|
})
|
|
302
211
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const pubSub = new PubSub(config)
|
|
306
|
-
|
|
307
|
-
sandbox.stub(pubSub.publisherClient, 'ping').resolves('PONG')
|
|
308
|
-
sandbox.stub(pubSub.subscriberClient, 'ping').resolves('PONG')
|
|
309
|
-
|
|
310
|
-
const isHealthy = await pubSub.healthCheck()
|
|
212
|
+
pubSubTest.test('should handle error on publish', async t => {
|
|
213
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
311
214
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
215
|
+
publisherStub.publish.rejects(new Error('publish error'))
|
|
216
|
+
try {
|
|
217
|
+
await pubSub.publish('chan', { a: 1 })
|
|
218
|
+
t.fail('Expected error not thrown')
|
|
219
|
+
} catch (err) {
|
|
220
|
+
t.match(err.message, /publish error/, 'throws error on publish failure')
|
|
221
|
+
}
|
|
315
222
|
t.end()
|
|
316
223
|
})
|
|
317
224
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const pubSub = new PubSub(config)
|
|
321
|
-
|
|
322
|
-
sandbox.stub(pubSub.publisherClient, 'ping').resolves('PONG')
|
|
323
|
-
sandbox.stub(pubSub.subscriberClient, 'ping').resolves('ERROR')
|
|
324
|
-
|
|
325
|
-
const isHealthy = await pubSub.healthCheck()
|
|
225
|
+
pubSubTest.test('should handle error on subscribe', async t => {
|
|
226
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
326
227
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
228
|
+
subscriberStub.subscribe.rejects(new Error('subscribe error'))
|
|
229
|
+
try {
|
|
230
|
+
await pubSub.subscribe('chan', () => {})
|
|
231
|
+
t.fail('Expected error not thrown')
|
|
232
|
+
} catch (err) {
|
|
233
|
+
t.match(err.message, /subscribe error/, 'throws error on subscribe failure')
|
|
234
|
+
}
|
|
330
235
|
t.end()
|
|
331
236
|
})
|
|
332
237
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const pubSub = new PubSub(config)
|
|
336
|
-
const error = new Error('Health check error')
|
|
337
|
-
|
|
338
|
-
sandbox.stub(pubSub.publisherClient, 'ping').rejects(error)
|
|
339
|
-
sandbox.stub(pubSub.subscriberClient, 'ping').resolves('PONG')
|
|
238
|
+
pubSubTest.test('should handle error on unsubscribe', async t => {
|
|
239
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
340
240
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
241
|
+
subscriberStub.unsubscribe.rejects(new Error('unsubscribe error'))
|
|
242
|
+
try {
|
|
243
|
+
await pubSub.unsubscribe('chan')
|
|
244
|
+
t.fail('Expected error not thrown')
|
|
245
|
+
} catch (err) {
|
|
246
|
+
t.match(err.message, /unsubscribe error/, 'throws error on unsubscribe failure')
|
|
247
|
+
}
|
|
346
248
|
t.end()
|
|
347
249
|
})
|
|
348
250
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const pubSub = new PubSub(config)
|
|
352
|
-
|
|
353
|
-
sandbox.stub(pubSub.publisherClient, 'status').value('ready')
|
|
354
|
-
sandbox.stub(pubSub.subscriberClient, 'status').value('ready')
|
|
355
|
-
|
|
356
|
-
const connectionStatus = pubSub.isConnected
|
|
251
|
+
pubSubTest.test('should handle error on broadcast', async t => {
|
|
252
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
357
253
|
|
|
358
|
-
|
|
254
|
+
sandbox.stub(pubSub, 'publish').rejects(new Error('broadcast error'))
|
|
255
|
+
try {
|
|
256
|
+
await pubSub.broadcast(['a', 'b'], { foo: 'bar' })
|
|
257
|
+
t.fail('Expected error not thrown')
|
|
258
|
+
} catch (err) {
|
|
259
|
+
t.match(err.message, /broadcast error/, 'throws error on broadcast failure')
|
|
260
|
+
}
|
|
359
261
|
t.end()
|
|
360
262
|
})
|
|
361
263
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const pubSub = new PubSub(config)
|
|
365
|
-
|
|
366
|
-
sandbox.stub(pubSub.publisherClient, 'status').value('disconnected')
|
|
367
|
-
sandbox.stub(pubSub.subscriberClient, 'status').value('disconnected')
|
|
368
|
-
|
|
369
|
-
const connectionStatus = pubSub.isConnected
|
|
264
|
+
pubSubTest.test('should call callback with parsed message on subscribe', async t => {
|
|
265
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
370
266
|
|
|
371
|
-
|
|
267
|
+
let received
|
|
268
|
+
subscriberStub.subscribe.callsFake(async (channel, cb) => {
|
|
269
|
+
cb(JSON.stringify({ test: 1 }), channel)
|
|
270
|
+
})
|
|
271
|
+
await pubSub.subscribe('chan', msg => { received = msg })
|
|
272
|
+
t.same(received, { test: 1 }, 'callback called with parsed message')
|
|
372
273
|
t.end()
|
|
373
274
|
})
|
|
374
|
-
t.test('should publish a message to a channel using spublish when isCluster is true', async (t) => {
|
|
375
|
-
const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
|
|
376
|
-
const pubSub = new PubSub(config)
|
|
377
|
-
const channel = 'cluster-channel'
|
|
378
|
-
const message = { key: 'cluster-value' }
|
|
379
|
-
|
|
380
|
-
sandbox.stub(pubSub.publisherClient, 'spublish').resolves()
|
|
381
275
|
|
|
382
|
-
|
|
276
|
+
pubSubTest.test('should call callback with parsed message on ssubscribe in cluster mode', async t => {
|
|
277
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
383
278
|
|
|
384
|
-
|
|
279
|
+
pubSub.isCluster = true
|
|
280
|
+
let received
|
|
281
|
+
subscriberStub.ssubscribe = sandbox.stub().callsFake(async (channel, cb) => {
|
|
282
|
+
cb(JSON.stringify({ test: 2 }), channel)
|
|
283
|
+
})
|
|
284
|
+
await pubSub.subscribe('chan', msg => { received = msg })
|
|
285
|
+
t.same(received, { test: 2 }, 'callback called with parsed message')
|
|
385
286
|
t.end()
|
|
386
287
|
})
|
|
387
288
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const pubSub = new PubSub(config)
|
|
391
|
-
const channel = 'cluster-channel'
|
|
392
|
-
const message = { key: 'cluster-value' }
|
|
393
|
-
const error = new Error('Cluster spublish error')
|
|
394
|
-
|
|
395
|
-
sandbox.stub(pubSub.publisherClient, 'spublish').rejects(error)
|
|
289
|
+
pubSubTest.test('should not call callback if subscribedChannel does not match', async t => {
|
|
290
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
396
291
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
292
|
+
let called = false
|
|
293
|
+
subscriberStub.subscribe.callsFake(async (channel, cb) => {
|
|
294
|
+
cb(JSON.stringify({ test: 3 }), 'other-channel')
|
|
295
|
+
})
|
|
296
|
+
await pubSub.subscribe('chan', () => { called = true })
|
|
297
|
+
t.notOk(called, 'callback not called for other channel')
|
|
403
298
|
t.end()
|
|
404
299
|
})
|
|
405
300
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const pubSub = new PubSub(config)
|
|
409
|
-
const channel = 'cluster-channel'
|
|
410
|
-
const callback = sinon.stub()
|
|
411
|
-
const message = JSON.stringify({ key: 'cluster-value' })
|
|
301
|
+
pubSubTest.test('should add event listeners to publisher and subscriber clients', t => {
|
|
302
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
412
303
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
304
|
+
// The event listeners are added in the constructor
|
|
305
|
+
// We check that .on was called with the correct events for both clients
|
|
306
|
+
const expectedEvents = ['connect', 'ready', 'end', 'error']
|
|
307
|
+
for (const event of expectedEvents) {
|
|
308
|
+
t.ok(
|
|
309
|
+
publisherStub.on.calledWith(event, sinon.match.func),
|
|
310
|
+
`publisherStub.on called with event '${event}'`
|
|
311
|
+
)
|
|
312
|
+
t.ok(
|
|
313
|
+
subscriberStub.on.calledWith(event, sinon.match.func),
|
|
314
|
+
`subscriberStub.on called with event '${event}'`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
420
317
|
t.end()
|
|
421
318
|
})
|
|
422
319
|
|
|
423
|
-
|
|
424
|
-
|
|
320
|
+
pubSubTest.test('should create publisher and subscriber clients if not provided', t => {
|
|
321
|
+
// Arrange
|
|
322
|
+
const config = { host: 'localhost', port: 6379 }
|
|
323
|
+
// Stub createClient and createLogger
|
|
324
|
+
const createClientStub = sandbox.stub(require('redis'), 'createClient').returns({
|
|
325
|
+
connect: sandbox.stub().resolves(),
|
|
326
|
+
quit: sandbox.stub().resolves(),
|
|
327
|
+
ping: sandbox.stub().resolves('PONG'),
|
|
328
|
+
publish: sandbox.stub().resolves(),
|
|
329
|
+
isOpen: true,
|
|
330
|
+
on: sandbox.stub().returnsThis(),
|
|
331
|
+
removeAllListeners: sandbox.stub()
|
|
332
|
+
})
|
|
333
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
334
|
+
redis: {
|
|
335
|
+
createClient: createClientStub
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
// Act
|
|
425
339
|
const pubSub = new PubSub(config)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
340
|
+
// Assert
|
|
341
|
+
t.ok(createClientStub.calledTwice, 'createClient called for both publisher and subscriber')
|
|
342
|
+
t.ok(pubSub.publisherClient, 'publisherClient is created')
|
|
343
|
+
t.ok(pubSub.subscriberClient, 'subscriberClient is created')
|
|
344
|
+
t.end()
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
pubSubTest.test('should create cluster clients if cluster config is provided and clients not provided', async t => {
|
|
348
|
+
// Arrange
|
|
349
|
+
const clusterConfig = { cluster: [{ host: 'c1', port: 7000 }, { host: 'c2', port: 7001 }] }
|
|
350
|
+
const createClusterStub = sandbox.stub().callsFake(() => ({
|
|
351
|
+
connect: sandbox.stub().resolves(),
|
|
352
|
+
quit: sandbox.stub().resolves(),
|
|
353
|
+
ping: sandbox.stub().resolves('PONG'),
|
|
354
|
+
spublish: sandbox.stub().resolves(),
|
|
355
|
+
ssubscribe: sandbox.stub().resolves(),
|
|
356
|
+
sunsubscribe: sandbox.stub().resolves(),
|
|
357
|
+
isOpen: true,
|
|
358
|
+
on: sandbox.stub().returnsThis(),
|
|
359
|
+
removeAllListeners: sandbox.stub()
|
|
360
|
+
}))
|
|
361
|
+
|
|
362
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
363
|
+
redis: {
|
|
364
|
+
createCluster: createClusterStub
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// Act
|
|
369
|
+
const pubSubCluster = new PubSub(clusterConfig)
|
|
370
|
+
// Assert
|
|
371
|
+
t.ok(createClusterStub.calledTwice, 'createCluster called for both publisher and subscriber')
|
|
372
|
+
t.ok(pubSubCluster.isCluster, 'isCluster is true')
|
|
373
|
+
t.end()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
pubSubTest.test('should log info on publisher client connect event', t => {
|
|
377
|
+
logStub = {
|
|
378
|
+
info: sandbox.stub(),
|
|
379
|
+
error: sandbox.stub(),
|
|
380
|
+
debug: sandbox.stub()
|
|
381
|
+
}
|
|
382
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
383
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
384
|
+
})
|
|
385
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
386
|
+
// Find the connect event handler
|
|
387
|
+
const connectHandler = publisherStub.on.getCalls().find(call => call.args[0] === 'connect').args[1]
|
|
388
|
+
connectHandler()
|
|
389
|
+
t.ok(logStub.info.calledWith('Redis client connecting'), 'logs info on connect')
|
|
390
|
+
t.end()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
pubSubTest.test('should log info on publisher client ready event', t => {
|
|
394
|
+
logStub = {
|
|
395
|
+
info: sandbox.stub(),
|
|
396
|
+
error: sandbox.stub(),
|
|
397
|
+
debug: sandbox.stub()
|
|
398
|
+
}
|
|
399
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
400
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
401
|
+
})
|
|
402
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
403
|
+
const readyHandler = publisherStub.on.getCalls().find(call => call.args[0] === 'ready').args[1]
|
|
404
|
+
readyHandler()
|
|
405
|
+
t.ok(logStub.info.calledWith('Redis client ready'), 'logs info on ready')
|
|
438
406
|
t.end()
|
|
439
407
|
})
|
|
440
408
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const error = new Error('Cluster subscribe error')
|
|
447
|
-
|
|
448
|
-
sandbox.stub(pubSub.subscriberClient, 'ssubscribe').rejects(error)
|
|
449
|
-
|
|
450
|
-
try {
|
|
451
|
-
await pubSub.subscribe(channel, callback)
|
|
452
|
-
t.fail('Should have thrown an error')
|
|
453
|
-
} catch (err) {
|
|
454
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
409
|
+
pubSubTest.test('should log info on publisher client end event', t => {
|
|
410
|
+
logStub = {
|
|
411
|
+
info: sandbox.stub(),
|
|
412
|
+
error: sandbox.stub(),
|
|
413
|
+
debug: sandbox.stub()
|
|
455
414
|
}
|
|
415
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
416
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
417
|
+
})
|
|
418
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
419
|
+
const endHandler = publisherStub.on.getCalls().find(call => call.args[0] === 'end').args[1]
|
|
420
|
+
endHandler()
|
|
421
|
+
t.ok(logStub.info.calledWith('Redis client connection closed'), 'logs info on end')
|
|
456
422
|
t.end()
|
|
457
423
|
})
|
|
458
424
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
425
|
+
pubSubTest.test('should log error on publisher client error event', t => {
|
|
426
|
+
logStub = {
|
|
427
|
+
info: sandbox.stub(),
|
|
428
|
+
error: sandbox.stub(),
|
|
429
|
+
debug: sandbox.stub()
|
|
430
|
+
}
|
|
431
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
432
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
433
|
+
})
|
|
434
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
435
|
+
const errorHandler = publisherStub.on.getCalls().find(call => call.args[0] === 'error').args[1]
|
|
436
|
+
const err = new Error('test error')
|
|
437
|
+
errorHandler(err)
|
|
438
|
+
t.ok(logStub.error.calledWith('Redis client error:', err), 'logs error on error event')
|
|
439
|
+
t.end()
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
pubSubTest.test('should log info on subscriber client connect event', t => {
|
|
443
|
+
logStub = {
|
|
444
|
+
info: sandbox.stub(),
|
|
445
|
+
error: sandbox.stub(),
|
|
446
|
+
debug: sandbox.stub()
|
|
447
|
+
}
|
|
448
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
449
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
450
|
+
})
|
|
451
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
452
|
+
const connectHandler = subscriberStub.on.getCalls().find(call => call.args[0] === 'connect').args[1]
|
|
453
|
+
connectHandler()
|
|
454
|
+
t.ok(logStub.info.calledWith('Redis client connecting'), 'logs info on subscriber connect')
|
|
469
455
|
t.end()
|
|
470
456
|
})
|
|
471
457
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
sandbox.stub(pubSub.subscriberClient, 'sunsubscribe').rejects(error)
|
|
479
|
-
|
|
480
|
-
try {
|
|
481
|
-
await pubSub.unsubscribe(channel)
|
|
482
|
-
t.fail('Should have thrown an error')
|
|
483
|
-
} catch (err) {
|
|
484
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
458
|
+
pubSubTest.test('should log info on subscriber client ready event', t => {
|
|
459
|
+
logStub = {
|
|
460
|
+
info: sandbox.stub(),
|
|
461
|
+
error: sandbox.stub(),
|
|
462
|
+
debug: sandbox.stub()
|
|
485
463
|
}
|
|
464
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
465
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
466
|
+
})
|
|
467
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
468
|
+
const readyHandler = subscriberStub.on.getCalls().find(call => call.args[0] === 'ready').args[1]
|
|
469
|
+
readyHandler()
|
|
470
|
+
t.ok(logStub.info.calledWith('Redis client ready'), 'logs info on subscriber ready')
|
|
486
471
|
t.end()
|
|
487
472
|
})
|
|
488
473
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
t.ok(
|
|
474
|
+
pubSubTest.test('should log info on subscriber client end event', t => {
|
|
475
|
+
logStub = {
|
|
476
|
+
info: sandbox.stub(),
|
|
477
|
+
error: sandbox.stub(),
|
|
478
|
+
debug: sandbox.stub()
|
|
479
|
+
}
|
|
480
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
481
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
482
|
+
})
|
|
483
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
484
|
+
const endHandler = subscriberStub.on.getCalls().find(call => call.args[0] === 'end').args[1]
|
|
485
|
+
endHandler()
|
|
486
|
+
t.ok(logStub.info.calledWith('Redis client connection closed'), 'logs info on subscriber end')
|
|
502
487
|
t.end()
|
|
503
488
|
})
|
|
504
489
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const error = new Error('Cluster broadcast error')
|
|
511
|
-
|
|
512
|
-
sandbox.stub(pubSub.publisherClient, 'spublish').onFirstCall().rejects(error)
|
|
513
|
-
|
|
514
|
-
try {
|
|
515
|
-
await pubSub.broadcast(channels, message)
|
|
516
|
-
t.fail('Should have thrown an error')
|
|
517
|
-
} catch (err) {
|
|
518
|
-
t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
|
|
490
|
+
pubSubTest.test('should log error on subscriber client error event', t => {
|
|
491
|
+
logStub = {
|
|
492
|
+
info: sandbox.stub(),
|
|
493
|
+
error: sandbox.stub(),
|
|
494
|
+
debug: sandbox.stub()
|
|
519
495
|
}
|
|
496
|
+
PubSub = proxyquire('../../../../src/util/redis/pubSub', {
|
|
497
|
+
'../createLogger': { createLogger: sandbox.stub().returns(logStub) }
|
|
498
|
+
})
|
|
499
|
+
pubSub = new PubSub({ host: 'localhost', port: 6379 }, publisherStub, subscriberStub)
|
|
500
|
+
const errorHandler = subscriberStub.on.getCalls().find(call => call.args[0] === 'error').args[1]
|
|
501
|
+
const err = new Error('subscriber error')
|
|
502
|
+
errorHandler(err)
|
|
503
|
+
t.ok(logStub.error.calledWith('Redis client error:', err), 'logs error on subscriber error event')
|
|
520
504
|
t.end()
|
|
521
505
|
})
|
|
522
|
-
|
|
506
|
+
pubSubTest.end()
|
|
523
507
|
})
|