@nxtedition/deepstream.io-client-js 32.0.26 → 32.0.28
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 +1 -1
- package/src/event/event-handler.js +4 -0
- package/src/message/connection.js +3 -2
- package/src/record/record-handler.js +19 -13
- package/src/record/record.js +77 -42
- package/src/rpc/rpc-handler.js +3 -2
- package/src/utils/legacy-listener.js +7 -4
- package/src/utils/unicast-listener.js +7 -3
- package/test/legacy-listener.test.js +450 -0
- package/test/unicast-listener.test.js +366 -0
- package/src/utils/multicast-listener.js +0 -252
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import * as C from '../src/constants/constants.js'
|
|
4
|
+
|
|
5
|
+
// Minimal mock helpers
|
|
6
|
+
function createMockConnection(connected = true) {
|
|
7
|
+
return {
|
|
8
|
+
connected,
|
|
9
|
+
messages: [],
|
|
10
|
+
sendMsg(topic, action, data) {
|
|
11
|
+
this.messages.push({ topic, action, data })
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createMockClient() {
|
|
17
|
+
return {
|
|
18
|
+
errors: [],
|
|
19
|
+
_$onError(topic, event, err, data) {
|
|
20
|
+
this.errors.push({ topic, event, err, data })
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createMockHandler(connection, client) {
|
|
26
|
+
return {
|
|
27
|
+
_client: client,
|
|
28
|
+
_connection: connection,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function msg(action, data) {
|
|
33
|
+
return { action, data }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// We can't import the Listener directly because it depends on xxhash-wasm (top-level await).
|
|
37
|
+
// Instead, we dynamically import after setting up mocks.
|
|
38
|
+
let Listener
|
|
39
|
+
|
|
40
|
+
describe('LegacyListener', async () => {
|
|
41
|
+
Listener = (await import('../src/utils/legacy-listener.js')).default
|
|
42
|
+
|
|
43
|
+
describe('constructor', () => {
|
|
44
|
+
it('sends LISTEN on construction when connected', () => {
|
|
45
|
+
const connection = createMockConnection(true)
|
|
46
|
+
const client = createMockClient()
|
|
47
|
+
const handler = createMockHandler(connection, client)
|
|
48
|
+
|
|
49
|
+
new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
50
|
+
|
|
51
|
+
assert.equal(connection.messages.length, 1)
|
|
52
|
+
assert.deepEqual(connection.messages[0], {
|
|
53
|
+
topic: C.TOPIC.RECORD,
|
|
54
|
+
action: C.ACTIONS.LISTEN,
|
|
55
|
+
data: ['test/.*'],
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('does not send LISTEN when not connected', () => {
|
|
60
|
+
const connection = createMockConnection(false)
|
|
61
|
+
const client = createMockClient()
|
|
62
|
+
const handler = createMockHandler(connection, client)
|
|
63
|
+
|
|
64
|
+
new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
65
|
+
|
|
66
|
+
assert.equal(connection.messages.length, 0)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe('_$onMessage - not connected', () => {
|
|
71
|
+
it('returns true and reports error with correct topic', () => {
|
|
72
|
+
const connection = createMockConnection(true)
|
|
73
|
+
const client = createMockClient()
|
|
74
|
+
const handler = createMockHandler(connection, client)
|
|
75
|
+
|
|
76
|
+
const listener = new Listener(C.TOPIC.EVENT, 'test/.*', () => null, handler)
|
|
77
|
+
|
|
78
|
+
// Simulate disconnect without calling _$onConnectionStateChange
|
|
79
|
+
connection.connected = false
|
|
80
|
+
|
|
81
|
+
const result = listener._$onMessage(
|
|
82
|
+
msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
assert.equal(result, true)
|
|
86
|
+
assert.equal(client.errors.length, 1)
|
|
87
|
+
assert.equal(client.errors[0].topic, C.TOPIC.EVENT) // Bug 1: was hardcoded to RECORD
|
|
88
|
+
assert.equal(client.errors[0].event, C.EVENT.NOT_CONNECTED)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('_$onMessage - SUBSCRIPTION_FOR_PATTERN_FOUND', () => {
|
|
93
|
+
it('creates a provider and sends LISTEN_ACCEPT via microtask', async () => {
|
|
94
|
+
const connection = createMockConnection(true)
|
|
95
|
+
const client = createMockClient()
|
|
96
|
+
const handler = createMockHandler(connection, client)
|
|
97
|
+
const { of } = await import('rxjs')
|
|
98
|
+
|
|
99
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => of({ key: 'value' }), handler)
|
|
100
|
+
connection.messages.length = 0
|
|
101
|
+
|
|
102
|
+
const result = listener._$onMessage(
|
|
103
|
+
msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
assert.equal(result, true)
|
|
107
|
+
assert.equal(listener.stats.subscriptions, 1)
|
|
108
|
+
|
|
109
|
+
// Wait for microtask to fire provider.send()
|
|
110
|
+
await new Promise((r) => queueMicrotask(r))
|
|
111
|
+
|
|
112
|
+
const acceptMsg = connection.messages.find((m) => m.action === C.ACTIONS.LISTEN_ACCEPT)
|
|
113
|
+
assert.ok(acceptMsg, 'should send LISTEN_ACCEPT')
|
|
114
|
+
assert.deepEqual(acceptMsg.data, ['test/.*', 'test/1'])
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('rejects duplicate subscription names', async () => {
|
|
118
|
+
const connection = createMockConnection(true)
|
|
119
|
+
const client = createMockClient()
|
|
120
|
+
const handler = createMockHandler(connection, client)
|
|
121
|
+
const rxjs = await import('rxjs')
|
|
122
|
+
|
|
123
|
+
const listener = new Listener(
|
|
124
|
+
C.TOPIC.RECORD,
|
|
125
|
+
'test/.*',
|
|
126
|
+
() => rxjs.of({ key: 'val' }),
|
|
127
|
+
handler,
|
|
128
|
+
)
|
|
129
|
+
connection.messages.length = 0
|
|
130
|
+
|
|
131
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
132
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
133
|
+
|
|
134
|
+
assert.equal(client.errors.length, 1)
|
|
135
|
+
assert.ok(String(client.errors[0].err).includes('invalid add'))
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('cleans up provider when callback throws (Bug 3)', async () => {
|
|
139
|
+
const connection = createMockConnection(true)
|
|
140
|
+
const client = createMockClient()
|
|
141
|
+
const handler = createMockHandler(connection, client)
|
|
142
|
+
|
|
143
|
+
const listener = new Listener(
|
|
144
|
+
C.TOPIC.RECORD,
|
|
145
|
+
'test/.*',
|
|
146
|
+
() => {
|
|
147
|
+
throw new Error('callback failed')
|
|
148
|
+
},
|
|
149
|
+
handler,
|
|
150
|
+
)
|
|
151
|
+
connection.messages.length = 0
|
|
152
|
+
|
|
153
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
154
|
+
|
|
155
|
+
// Provider should be removed from map after callback throws
|
|
156
|
+
assert.equal(listener.stats.subscriptions, 0)
|
|
157
|
+
assert.equal(client.errors.length, 1)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('sends LISTEN_REJECT when callback returns null', async () => {
|
|
161
|
+
const connection = createMockConnection(true)
|
|
162
|
+
const client = createMockClient()
|
|
163
|
+
const handler = createMockHandler(connection, client)
|
|
164
|
+
|
|
165
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
166
|
+
connection.messages.length = 0
|
|
167
|
+
|
|
168
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
169
|
+
|
|
170
|
+
// rxjs.of(null) emits null synchronously, provider.next(null) sets value$ to null
|
|
171
|
+
// provider.send is queued via microtask — wait for it, then another for delivery
|
|
172
|
+
await new Promise((r) => queueMicrotask(r))
|
|
173
|
+
await new Promise((r) => queueMicrotask(r))
|
|
174
|
+
|
|
175
|
+
// With null callback, value$ stays null, so accepted=false matches initial accepted=false
|
|
176
|
+
// The provider never sends ACCEPT in the first place, so no REJECT is sent either.
|
|
177
|
+
// This is correct behavior: null means "don't provide", so we don't accept or reject.
|
|
178
|
+
assert.equal(listener.stats.subscriptions, 1)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('_$onMessage - SUBSCRIPTION_FOR_PATTERN_REMOVED', () => {
|
|
183
|
+
it('stops and removes the provider', async () => {
|
|
184
|
+
const connection = createMockConnection(true)
|
|
185
|
+
const client = createMockClient()
|
|
186
|
+
const handler = createMockHandler(connection, client)
|
|
187
|
+
|
|
188
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
189
|
+
connection.messages.length = 0
|
|
190
|
+
|
|
191
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
192
|
+
assert.equal(listener.stats.subscriptions, 1)
|
|
193
|
+
|
|
194
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED, ['test/.*', 'test/1']))
|
|
195
|
+
assert.equal(listener.stats.subscriptions, 0)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('errors on removal of unknown subscription', () => {
|
|
199
|
+
const connection = createMockConnection(true)
|
|
200
|
+
const client = createMockClient()
|
|
201
|
+
const handler = createMockHandler(connection, client)
|
|
202
|
+
|
|
203
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
204
|
+
|
|
205
|
+
listener._$onMessage(
|
|
206
|
+
msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_REMOVED, ['test/.*', 'test/unknown']),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert.equal(client.errors.length, 1)
|
|
210
|
+
assert.ok(String(client.errors[0].err).includes('invalid remove'))
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('_$onMessage - LISTEN_ACCEPT', () => {
|
|
215
|
+
it('starts value subscription when provider has value$', async () => {
|
|
216
|
+
const connection = createMockConnection(true)
|
|
217
|
+
const client = createMockClient()
|
|
218
|
+
const handler = createMockHandler(connection, client)
|
|
219
|
+
const rxjs = await import('rxjs')
|
|
220
|
+
|
|
221
|
+
const value$ = new rxjs.BehaviorSubject({ data: 'hello' })
|
|
222
|
+
|
|
223
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => value$, handler)
|
|
224
|
+
connection.messages.length = 0
|
|
225
|
+
|
|
226
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
227
|
+
|
|
228
|
+
// Wait for microtask to send LISTEN_ACCEPT
|
|
229
|
+
await new Promise((r) => queueMicrotask(r))
|
|
230
|
+
|
|
231
|
+
// Server sends back LISTEN_ACCEPT
|
|
232
|
+
listener._$onMessage(msg(C.ACTIONS.LISTEN_ACCEPT, ['test/.*', 'test/1']))
|
|
233
|
+
|
|
234
|
+
// Should have sent an UPDATE with the value
|
|
235
|
+
const updateMsg = connection.messages.find((m) => m.action === C.ACTIONS.UPDATE)
|
|
236
|
+
assert.ok(updateMsg, 'should send UPDATE after LISTEN_ACCEPT')
|
|
237
|
+
assert.equal(updateMsg.data[0], 'test/1')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('ignores LISTEN_ACCEPT when provider has no value$', async () => {
|
|
241
|
+
const connection = createMockConnection(true)
|
|
242
|
+
const client = createMockClient()
|
|
243
|
+
const handler = createMockHandler(connection, client)
|
|
244
|
+
|
|
245
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
246
|
+
connection.messages.length = 0
|
|
247
|
+
|
|
248
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
249
|
+
|
|
250
|
+
listener._$onMessage(msg(C.ACTIONS.LISTEN_ACCEPT, ['test/.*', 'test/1']))
|
|
251
|
+
|
|
252
|
+
// No error, no crash
|
|
253
|
+
assert.equal(client.errors.length, 0)
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('_$onMessage - unknown action', () => {
|
|
258
|
+
it('returns false for unrecognized actions', () => {
|
|
259
|
+
const connection = createMockConnection(true)
|
|
260
|
+
const client = createMockClient()
|
|
261
|
+
const handler = createMockHandler(connection, client)
|
|
262
|
+
|
|
263
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
264
|
+
|
|
265
|
+
const result = listener._$onMessage(msg(C.ACTIONS.UPDATE, ['test/.*', 'test/1']))
|
|
266
|
+
assert.equal(result, false)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('_$onConnectionStateChange', () => {
|
|
271
|
+
it('re-sends LISTEN on reconnect', () => {
|
|
272
|
+
const connection = createMockConnection(true)
|
|
273
|
+
const client = createMockClient()
|
|
274
|
+
const handler = createMockHandler(connection, client)
|
|
275
|
+
|
|
276
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
277
|
+
connection.messages.length = 0
|
|
278
|
+
|
|
279
|
+
// Simulate disconnect
|
|
280
|
+
connection.connected = false
|
|
281
|
+
listener._$onConnectionStateChange()
|
|
282
|
+
|
|
283
|
+
// Simulate reconnect
|
|
284
|
+
connection.connected = true
|
|
285
|
+
listener._$onConnectionStateChange()
|
|
286
|
+
|
|
287
|
+
assert.equal(connection.messages.length, 1)
|
|
288
|
+
assert.deepEqual(connection.messages[0], {
|
|
289
|
+
topic: C.TOPIC.RECORD,
|
|
290
|
+
action: C.ACTIONS.LISTEN,
|
|
291
|
+
data: ['test/.*'],
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('clears all subscriptions on disconnect', () => {
|
|
296
|
+
const connection = createMockConnection(true)
|
|
297
|
+
const client = createMockClient()
|
|
298
|
+
const handler = createMockHandler(connection, client)
|
|
299
|
+
|
|
300
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
301
|
+
|
|
302
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
303
|
+
assert.equal(listener.stats.subscriptions, 1)
|
|
304
|
+
|
|
305
|
+
connection.connected = false
|
|
306
|
+
listener._$onConnectionStateChange()
|
|
307
|
+
|
|
308
|
+
assert.equal(listener.stats.subscriptions, 0)
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
describe('_$destroy', () => {
|
|
313
|
+
it('sends UNLISTEN and cleans up when connected', () => {
|
|
314
|
+
const connection = createMockConnection(true)
|
|
315
|
+
const client = createMockClient()
|
|
316
|
+
const handler = createMockHandler(connection, client)
|
|
317
|
+
|
|
318
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
319
|
+
|
|
320
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
321
|
+
connection.messages.length = 0
|
|
322
|
+
|
|
323
|
+
listener._$destroy()
|
|
324
|
+
|
|
325
|
+
assert.equal(listener.stats.subscriptions, 0)
|
|
326
|
+
const unlistenMsg = connection.messages.find((m) => m.action === C.ACTIONS.UNLISTEN)
|
|
327
|
+
assert.ok(unlistenMsg, 'should send UNLISTEN')
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('does not send UNLISTEN when not connected', () => {
|
|
331
|
+
const connection = createMockConnection(false)
|
|
332
|
+
const client = createMockClient()
|
|
333
|
+
const handler = createMockHandler(connection, client)
|
|
334
|
+
|
|
335
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => null, handler)
|
|
336
|
+
|
|
337
|
+
listener._$destroy()
|
|
338
|
+
|
|
339
|
+
const unlistenMsg = connection.messages.find((m) => m.action === C.ACTIONS.UNLISTEN)
|
|
340
|
+
assert.equal(unlistenMsg, undefined)
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
describe('provider.observer - record values', () => {
|
|
345
|
+
it('deduplicates identical values by hash', async () => {
|
|
346
|
+
const connection = createMockConnection(true)
|
|
347
|
+
const client = createMockClient()
|
|
348
|
+
const handler = createMockHandler(connection, client)
|
|
349
|
+
const rxjs = await import('rxjs')
|
|
350
|
+
|
|
351
|
+
const subject = new rxjs.BehaviorSubject({ key: 'value' })
|
|
352
|
+
|
|
353
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => subject, handler)
|
|
354
|
+
connection.messages.length = 0
|
|
355
|
+
|
|
356
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
357
|
+
await new Promise((r) => queueMicrotask(r))
|
|
358
|
+
|
|
359
|
+
listener._$onMessage(msg(C.ACTIONS.LISTEN_ACCEPT, ['test/.*', 'test/1']))
|
|
360
|
+
|
|
361
|
+
const updateCount = connection.messages.filter((m) => m.action === C.ACTIONS.UPDATE).length
|
|
362
|
+
|
|
363
|
+
// Emit same value again
|
|
364
|
+
subject.next({ key: 'value' })
|
|
365
|
+
|
|
366
|
+
const updateCount2 = connection.messages.filter((m) => m.action === C.ACTIONS.UPDATE).length
|
|
367
|
+
assert.equal(updateCount2, updateCount, 'should not send duplicate UPDATE for same hash')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('handles null values from observer by calling provider.next(null)', async () => {
|
|
371
|
+
const connection = createMockConnection(true)
|
|
372
|
+
const client = createMockClient()
|
|
373
|
+
const handler = createMockHandler(connection, client)
|
|
374
|
+
const rxjs = await import('rxjs')
|
|
375
|
+
|
|
376
|
+
const subject = new rxjs.Subject()
|
|
377
|
+
|
|
378
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => subject, handler)
|
|
379
|
+
connection.messages.length = 0
|
|
380
|
+
|
|
381
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
382
|
+
await new Promise((r) => queueMicrotask(r))
|
|
383
|
+
|
|
384
|
+
listener._$onMessage(msg(C.ACTIONS.LISTEN_ACCEPT, ['test/.*', 'test/1']))
|
|
385
|
+
|
|
386
|
+
// Emit null - should trigger provider.next(null) which rejects
|
|
387
|
+
subject.next(null)
|
|
388
|
+
await new Promise((r) => queueMicrotask(r))
|
|
389
|
+
|
|
390
|
+
const rejectMsg = connection.messages.find((m) => m.action === C.ACTIONS.LISTEN_REJECT)
|
|
391
|
+
assert.ok(rejectMsg, 'should send LISTEN_REJECT when value becomes null')
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('errors on invalid non-object non-string values', async () => {
|
|
395
|
+
const connection = createMockConnection(true)
|
|
396
|
+
const client = createMockClient()
|
|
397
|
+
const handler = createMockHandler(connection, client)
|
|
398
|
+
const rxjs = await import('rxjs')
|
|
399
|
+
|
|
400
|
+
const subject = new rxjs.Subject()
|
|
401
|
+
|
|
402
|
+
const listener = new Listener(C.TOPIC.RECORD, 'test/.*', () => subject, handler)
|
|
403
|
+
connection.messages.length = 0
|
|
404
|
+
|
|
405
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
406
|
+
await new Promise((r) => queueMicrotask(r))
|
|
407
|
+
|
|
408
|
+
listener._$onMessage(msg(C.ACTIONS.LISTEN_ACCEPT, ['test/.*', 'test/1']))
|
|
409
|
+
|
|
410
|
+
subject.next(42)
|
|
411
|
+
|
|
412
|
+
assert.equal(client.errors.length, 1)
|
|
413
|
+
assert.ok(String(client.errors[0].err).includes('invalid value'))
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('provider.error - retry behavior', () => {
|
|
418
|
+
it('retries after error with timeout', async () => {
|
|
419
|
+
const connection = createMockConnection(true)
|
|
420
|
+
const client = createMockClient()
|
|
421
|
+
const handler = createMockHandler(connection, client)
|
|
422
|
+
const rxjs = await import('rxjs')
|
|
423
|
+
|
|
424
|
+
let callCount = 0
|
|
425
|
+
const listener = new Listener(
|
|
426
|
+
C.TOPIC.RECORD,
|
|
427
|
+
'test/.*',
|
|
428
|
+
() => {
|
|
429
|
+
callCount++
|
|
430
|
+
if (callCount === 1) {
|
|
431
|
+
return rxjs.throwError(() => new Error('temporary'))
|
|
432
|
+
}
|
|
433
|
+
return rxjs.of({ ok: true })
|
|
434
|
+
},
|
|
435
|
+
handler,
|
|
436
|
+
{ recursive: true },
|
|
437
|
+
)
|
|
438
|
+
connection.messages.length = 0
|
|
439
|
+
|
|
440
|
+
listener._$onMessage(msg(C.ACTIONS.SUBSCRIPTION_FOR_PATTERN_FOUND, ['test/.*', 'test/1']))
|
|
441
|
+
|
|
442
|
+
assert.equal(callCount, 1)
|
|
443
|
+
assert.equal(client.errors.length, 1)
|
|
444
|
+
|
|
445
|
+
// The retry timeout is 10 seconds, we don't want to actually wait
|
|
446
|
+
// Just verify the provider is still in the map (will be retried)
|
|
447
|
+
assert.equal(listener.stats.subscriptions, 1)
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
})
|