@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 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",
@@ -28,20 +28,13 @@
28
28
  ******/
29
29
 
30
30
  'use strict'
31
- const Redis = require('ioredis')
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.config.lazyConnect ??= true
56
- return this.isCluster
57
- ? new Redis.Cluster(this.config.cluster, { ...this.config, shardedSubscribers: true })
58
- : new Redis(this.config)
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
- const publisherResponse = await this.publisherClient.quit()
75
- const subscriberResponse = await this.subscriberClient.quit()
76
- const isDisconnected = publisherResponse === REDIS_SUCCESS && subscriberResponse === REDIS_SUCCESS
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
- return isDisconnected
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
- const publisherConnected = REDIS_IS_CONNECTED_STATUSES.includes(this.publisherClient.status)
101
- const subscriberConnected = REDIS_IS_CONNECTED_STATUSES.includes(this.subscriberClient.status)
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 connected'))
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@modusbox.com>
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
- const Redis = require('ioredis')
32
- const PubSub = require('../../../../src/util/redis/pubSub')
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', (t) => {
36
- let sandbox
34
+ Test('PubSub', pubSubTest => {
35
+ let sandbox, publisherStub, subscriberStub, pubSub, logStub
37
36
 
38
- t.beforeEach((t) => {
37
+ pubSubTest.beforeEach(t => {
39
38
  sandbox = sinon.createSandbox()
40
- sandbox.stub(Redis.prototype, 'publish')
41
- sandbox.stub(Redis.prototype, 'subscribe')
42
- sandbox.stub(Redis.prototype, 'unsubscribe')
43
- sandbox.stub(Redis.prototype, 'on')
44
- sandbox.stub(Redis.Cluster.prototype, 'on')
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
- t.afterEach((t) => {
70
+ pubSubTest.afterEach(t => {
49
71
  sandbox.restore()
50
72
  t.end()
51
73
  })
52
74
 
53
- t.test('should create a Redis client and subscriber', (t) => {
54
- const config = { lazyConnect: true }
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
- t.ok(pubSub.publisherClient.publish.calledWith(channel, JSON.stringify(message)), 'publish called with correct arguments')
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
- t.test('should handle error when publishing a message', async (t) => {
73
- const config = {}
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
- try {
82
- await pubSub.publish(channel, message)
83
- t.fail('Should have thrown an error')
84
- } catch (err) {
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
- t.test('should subscribe to a channel and handle messages', async (t) => {
91
- const config = {}
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
- t.ok(pubSub.subscriberClient.subscribe.calledWith(channel), 'subscribe called with correct channel')
101
- t.ok(callback.calledWith(JSON.parse(message)), 'callback called with parsed message')
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
- t.test('should handle error when subscribing to a channel', async (t) => {
106
- const config = {}
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
- pubSub.subscriberClient.subscribe.rejects(error)
113
-
114
- try {
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
- t.test('should unsubscribe from a channel', async (t) => {
124
- const config = {}
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
- await pubSub.unsubscribe(channel)
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
- t.test('should handle error when unsubscribing from a channel', async (t) => {
135
- const config = {}
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
- try {
143
- await pubSub.unsubscribe(channel)
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
- t.test('should broadcast a message to multiple channels', async (t) => {
152
- const config = {}
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
- t.ok(pubSub.publisherClient.publish.calledTwice, 'publish called twice')
160
- t.ok(pubSub.publisherClient.publish.firstCall.calledWith(channels[0], JSON.stringify(message)), 'publish called with first channel and message')
161
- t.ok(pubSub.publisherClient.publish.secondCall.calledWith(channels[1], JSON.stringify(message)), 'publish called with second channel and message')
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
- t.test('should handle error when broadcasting a message', async (t) => {
166
- const config = {}
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
- try {
175
- await pubSub.broadcast(channels, message)
176
- t.fail('Should have thrown an error')
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
- t.test('should connect Redis clients successfully', async (t) => {
184
- const config = {}
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
- t.ok(pubSub.publisherClient.connect.calledOnce, 'publisherClient connect called once')
193
- t.ok(pubSub.subscriberClient.connect.calledOnce, 'subscriberClient connect called once')
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
- t.test('should handle error when connecting Redis clients', async (t) => {
198
- const config = {}
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
- try {
206
- await pubSub.connect()
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
- t.test('should create a Redis Cluster client when cluster config is provided', (t) => {
215
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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
- t.ok(pubSub.publisherClient instanceof Redis.Cluster, 'publisherClient is an instance of Redis.Cluster')
219
- t.ok(pubSub.subscriberClient instanceof Redis.Cluster, 'subscriberClient is an instance of Redis.Cluster')
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
- t.test('should connect Redis Cluster clients successfully', async (t) => {
224
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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
- t.ok(pubSub.publisherClient.connect.calledOnce, 'publisherClient connect called once')
233
- t.ok(pubSub.subscriberClient.connect.calledOnce, 'subscriberClient connect called once')
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
- t.test('should handle error when connecting Redis Cluster clients', async (t) => {
238
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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('Should have thrown an error')
192
+ t.fail('Expected error not thrown')
248
193
  } catch (err) {
249
- t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
194
+ t.match(err.message, /connect error/, 'throws error on connect failure')
250
195
  }
251
196
  t.end()
252
197
  })
253
198
 
254
- t.test('should not call callback if subscribedChannel does not match channel', async (t) => {
255
- const config = {}
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('Should have thrown an error')
205
+ t.fail('Expected error not thrown')
297
206
  } catch (err) {
298
- t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
207
+ t.match(err.message, /disconnect error/, 'throws error on disconnect failure')
299
208
  }
300
209
  t.end()
301
210
  })
302
211
 
303
- t.test('should perform health check and return true if both clients are healthy', async (t) => {
304
- const config = {}
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
- t.equal(isHealthy, true, 'healthCheck returns true when both clients are healthy')
313
- t.ok(pubSub.publisherClient.ping.calledOnce, 'publisherClient ping called once')
314
- t.ok(pubSub.subscriberClient.ping.calledOnce, 'subscriberClient ping called once')
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
- t.test('should perform health check and return false if any client is unhealthy', async (t) => {
319
- const config = {}
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
- t.equal(isHealthy, false, 'healthCheck returns false when any client is unhealthy')
328
- t.ok(pubSub.publisherClient.ping.calledOnce, 'publisherClient ping called once')
329
- t.ok(pubSub.subscriberClient.ping.calledOnce, 'subscriberClient ping called once')
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
- t.test('should handle error during health check and return false', async (t) => {
334
- const config = {}
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
- const isHealthy = await pubSub.healthCheck()
342
-
343
- t.equal(isHealthy, false, 'healthCheck returns false when an error occurs')
344
- t.ok(pubSub.publisherClient.ping.calledOnce, 'publisherClient ping called once')
345
- t.notOk(pubSub.subscriberClient.ping.calledOnce, 'subscriberClient ping not called once')
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
- t.test('should return correct connection statuses for isConnected', (t) => {
350
- const config = {}
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
- t.deepEqual(connectionStatus, { publisherConnected: true, subscriberConnected: true }, 'isConnected returns correct statuses')
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
- t.test('should return false connection statuses for isConnected when clients are not connected', (t) => {
363
- const config = {}
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
- t.deepEqual(connectionStatus, { publisherConnected: false, subscriberConnected: false }, 'isConnected returns false statuses when clients are not connected')
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
- await pubSub.publish(channel, message)
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
- t.ok(pubSub.publisherClient.spublish.calledWith(channel, JSON.stringify(message)), 'spublish called with correct arguments')
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
- t.test('should handle error when publishing a message with spublish in cluster mode', async (t) => {
389
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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
- try {
398
- await pubSub.publish(channel, message)
399
- t.fail('Should have thrown an error')
400
- } catch (err) {
401
- t.deepEqual(err, constructSystemExtensionError(error, '["redis"]'), 'Error thrown and rethrown correctly')
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
- t.test('should subscribe to a channel and handle smessage in cluster mode', async (t) => {
407
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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
- sandbox.stub(pubSub.subscriberClient, 'ssubscribe').resolves()
414
- pubSub.subscriberClient.on.withArgs('smessage').yields(channel, message)
415
-
416
- await pubSub.subscribe(channel, callback)
417
-
418
- t.ok(pubSub.subscriberClient.ssubscribe.calledWith(channel), 'ssubscribe called with correct channel')
419
- t.ok(callback.calledWith(JSON.parse(message)), 'callback called with parsed message')
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
- t.test('should not call callback if smessage subscribedChannel does not match channel in cluster mode', async (t) => {
424
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
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
- const channel = 'cluster-channel'
427
- const callback = sinon.stub()
428
- const message = JSON.stringify({ key: 'cluster-value' })
429
- const otherChannel = 'other-cluster-channel'
430
-
431
- sandbox.stub(pubSub.subscriberClient, 'ssubscribe').resolves()
432
- pubSub.subscriberClient.on.withArgs('smessage').yields(otherChannel, message)
433
-
434
- await pubSub.subscribe(channel, callback)
435
-
436
- t.ok(pubSub.subscriberClient.ssubscribe.calledWith(channel), 'ssubscribe called with correct channel')
437
- t.notOk(callback.called, 'callback not called when smessage channel does not match')
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
- t.test('should handle error when subscribing to a channel in cluster mode', async (t) => {
442
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
443
- const pubSub = new PubSub(config)
444
- const channel = 'cluster-channel'
445
- const callback = sinon.stub()
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
- t.test('should unsubscribe from a channel using sunsubscribe in cluster mode', async (t) => {
460
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
461
- const pubSub = new PubSub(config)
462
- const channel = 'cluster-channel'
463
-
464
- sandbox.stub(pubSub.subscriberClient, 'sunsubscribe').resolves()
465
-
466
- await pubSub.unsubscribe(channel)
467
-
468
- t.ok(pubSub.subscriberClient.sunsubscribe.calledWith(channel), 'sunsubscribe called with correct channel')
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
- t.test('should handle error when unsubscribing from a channel in cluster mode', async (t) => {
473
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
474
- const pubSub = new PubSub(config)
475
- const channel = 'cluster-channel'
476
- const error = new Error('Cluster unsubscribe error')
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
- t.test('should broadcast a message to multiple channels using spublish in cluster mode', async (t) => {
490
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
491
- const pubSub = new PubSub(config)
492
- const channels = ['cluster1', 'cluster2']
493
- const message = { key: 'cluster-broadcast' }
494
-
495
- sandbox.stub(pubSub.publisherClient, 'spublish').resolves()
496
-
497
- await pubSub.broadcast(channels, message)
498
-
499
- t.ok(pubSub.publisherClient.spublish.calledTwice, 'spublish called twice')
500
- t.ok(pubSub.publisherClient.spublish.firstCall.calledWith(channels[0], JSON.stringify(message)), 'spublish called with first channel and message')
501
- t.ok(pubSub.publisherClient.spublish.secondCall.calledWith(channels[1], JSON.stringify(message)), 'spublish called with second channel and message')
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
- t.test('should handle error when broadcasting a message in cluster mode', async (t) => {
506
- const config = { cluster: [{ host: '127.0.0.1', port: 6379 }] }
507
- const pubSub = new PubSub(config)
508
- const channels = ['cluster1', 'cluster2']
509
- const message = { key: 'cluster-broadcast' }
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
- t.end()
506
+ pubSubTest.end()
523
507
  })