@webex/internal-plugin-mobius-socket 0.0.0-next.1
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/.eslintrc.js +6 -0
- package/README.md +131 -0
- package/babel.config.js +3 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.js +106 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/mercury.js +916 -0
- package/dist/mercury.js.map +1 -0
- package/dist/socket/constants.js +16 -0
- package/dist/socket/constants.js.map +1 -0
- package/dist/socket/index.js +15 -0
- package/dist/socket/index.js.map +1 -0
- package/dist/socket/socket-base.js +537 -0
- package/dist/socket/socket-base.js.map +1 -0
- package/dist/socket/socket.js +19 -0
- package/dist/socket/socket.js.map +1 -0
- package/dist/socket/socket.shim.js +36 -0
- package/dist/socket/socket.shim.js.map +1 -0
- package/jest.config.js +3 -0
- package/package.json +68 -0
- package/process +1 -0
- package/src/config.js +40 -0
- package/src/errors.js +66 -0
- package/src/index.js +32 -0
- package/src/mercury.js +1059 -0
- package/src/socket/constants.js +6 -0
- package/src/socket/index.js +5 -0
- package/src/socket/socket-base.js +558 -0
- package/src/socket/socket.js +13 -0
- package/src/socket/socket.shim.js +31 -0
- package/test/integration/spec/mercury.js +117 -0
- package/test/integration/spec/sharable-mercury.js +59 -0
- package/test/integration/spec/webex.js +44 -0
- package/test/unit/lib/promise-tick.js +19 -0
- package/test/unit/spec/mercury-events.js +492 -0
- package/test/unit/spec/mercury.js +1787 -0
- package/test/unit/spec/socket.js +1037 -0
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {forEach} from 'lodash';
|
|
6
|
+
import {assert} from '@webex/test-helper-chai';
|
|
7
|
+
import MockWebSocket from '@webex/test-helper-mock-web-socket';
|
|
8
|
+
import sinon from 'sinon';
|
|
9
|
+
import {
|
|
10
|
+
BadRequest,
|
|
11
|
+
NotAuthorized,
|
|
12
|
+
Forbidden,
|
|
13
|
+
// NotFound,
|
|
14
|
+
config,
|
|
15
|
+
ConnectionError,
|
|
16
|
+
Socket,
|
|
17
|
+
} from '@webex/internal-plugin-mercury';
|
|
18
|
+
import uuid from 'uuid';
|
|
19
|
+
import FakeTimers from '@sinonjs/fake-timers';
|
|
20
|
+
|
|
21
|
+
describe('plugin-mercury', () => {
|
|
22
|
+
describe('Socket', () => {
|
|
23
|
+
let clock, mockWebSocket, socket;
|
|
24
|
+
|
|
25
|
+
const mockoptions = Object.assign(
|
|
26
|
+
{
|
|
27
|
+
logger: console,
|
|
28
|
+
token: 'mocktoken',
|
|
29
|
+
trackingId: 'mocktrackingid',
|
|
30
|
+
},
|
|
31
|
+
config.mercury
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
clock = FakeTimers.install({now: Date.now()});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
clock.uninstall();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
sinon.stub(Socket, 'getWebSocketConstructor').callsFake(
|
|
44
|
+
() =>
|
|
45
|
+
function (...args) {
|
|
46
|
+
mockWebSocket = new MockWebSocket(...args);
|
|
47
|
+
|
|
48
|
+
return mockWebSocket;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
sinon.spy(Socket.prototype, '_ping');
|
|
53
|
+
|
|
54
|
+
socket = new Socket();
|
|
55
|
+
const promise = socket.open('ws://example.com', mockoptions);
|
|
56
|
+
|
|
57
|
+
mockWebSocket.open();
|
|
58
|
+
|
|
59
|
+
return promise;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
Socket.getWebSocketConstructor.restore();
|
|
64
|
+
if (Socket.prototype._ping.restore) {
|
|
65
|
+
Socket.prototype._ping.restore();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Promise.resolve(socket && socket.close()).then(() => {
|
|
69
|
+
mockWebSocket = undefined;
|
|
70
|
+
socket = undefined;
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('#open()', () => {
|
|
75
|
+
let socket;
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
socket = new Socket();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(() => socket.close().catch(() => console.log()));
|
|
82
|
+
|
|
83
|
+
it('requires a url', () => assert.isRejected(socket.open(), /`url` is required/));
|
|
84
|
+
|
|
85
|
+
it('requires a forceCloseDelay option', () =>
|
|
86
|
+
assert.isRejected(
|
|
87
|
+
socket.open('ws://example.com'),
|
|
88
|
+
/missing required property forceCloseDelay/
|
|
89
|
+
));
|
|
90
|
+
|
|
91
|
+
it('requires a pingInterval option', () =>
|
|
92
|
+
assert.isRejected(
|
|
93
|
+
socket.open('ws://example.com', {
|
|
94
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
95
|
+
}),
|
|
96
|
+
/missing required property pingInterval/
|
|
97
|
+
));
|
|
98
|
+
|
|
99
|
+
it('requires a pongTimeout option', () =>
|
|
100
|
+
assert.isRejected(
|
|
101
|
+
socket.open('ws://example.com', {
|
|
102
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
103
|
+
pingInterval: mockoptions.pingInterval,
|
|
104
|
+
}),
|
|
105
|
+
/missing required property pongTimeout/
|
|
106
|
+
));
|
|
107
|
+
|
|
108
|
+
it('requires a token option', () =>
|
|
109
|
+
assert.isRejected(
|
|
110
|
+
socket.open('ws://example.com', {
|
|
111
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
112
|
+
pingInterval: mockoptions.pingInterval,
|
|
113
|
+
pongTimeout: mockoptions.pongTimeout,
|
|
114
|
+
}),
|
|
115
|
+
/missing required property token/
|
|
116
|
+
));
|
|
117
|
+
|
|
118
|
+
it('requires a trackingId option', () =>
|
|
119
|
+
assert.isRejected(
|
|
120
|
+
socket.open('ws://example.com', {
|
|
121
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
122
|
+
pingInterval: mockoptions.pingInterval,
|
|
123
|
+
pongTimeout: mockoptions.pongTimeout,
|
|
124
|
+
token: 'mocktoken',
|
|
125
|
+
}),
|
|
126
|
+
/missing required property trackingId/
|
|
127
|
+
));
|
|
128
|
+
|
|
129
|
+
it('requires a logger option', () =>
|
|
130
|
+
assert.isRejected(
|
|
131
|
+
socket.open('ws://example.com', {
|
|
132
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
133
|
+
pingInterval: mockoptions.pingInterval,
|
|
134
|
+
pongTimeout: mockoptions.pongTimeout,
|
|
135
|
+
token: 'mocktoken',
|
|
136
|
+
trackingId: 'mocktrackingid',
|
|
137
|
+
}),
|
|
138
|
+
/missing required property logger/
|
|
139
|
+
));
|
|
140
|
+
|
|
141
|
+
it('accepts a logLevelToken option', () => {
|
|
142
|
+
const promise = socket.open('ws://example.com', {
|
|
143
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
144
|
+
pingInterval: mockoptions.pingInterval,
|
|
145
|
+
pongTimeout: mockoptions.pongTimeout,
|
|
146
|
+
logger: console,
|
|
147
|
+
token: 'mocktoken',
|
|
148
|
+
trackingId: 'mocktrackingid',
|
|
149
|
+
logLevelToken: 'mocklogleveltoken',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
mockWebSocket.readyState = 1;
|
|
153
|
+
mockWebSocket.emit('open');
|
|
154
|
+
|
|
155
|
+
mockWebSocket.emit('message', {
|
|
156
|
+
data: JSON.stringify({
|
|
157
|
+
id: uuid.v4(),
|
|
158
|
+
data: {
|
|
159
|
+
eventType: 'mercury.buffer_state',
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return promise.then(() => {
|
|
165
|
+
assert.equal(socket.logLevelToken, 'mocklogleveltoken');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('#binaryType', () => {
|
|
171
|
+
it('proxies to the underlying socket', () => {
|
|
172
|
+
assert.notEqual(socket.binaryType, 'test');
|
|
173
|
+
mockWebSocket.binaryType = 'test';
|
|
174
|
+
assert.equal(socket.binaryType, 'test');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('#bufferedAmount', () => {
|
|
179
|
+
it('proxies to the underlying socket', () => {
|
|
180
|
+
assert.notEqual(socket.bufferedAmount, 'test');
|
|
181
|
+
mockWebSocket.bufferedAmount = 'test';
|
|
182
|
+
assert.equal(socket.bufferedAmount, 'test');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('#extensions', () => {
|
|
187
|
+
it('proxies to the underlying socket', () => {
|
|
188
|
+
assert.notEqual(socket.extensions, 'test');
|
|
189
|
+
mockWebSocket.extensions = 'test';
|
|
190
|
+
assert.equal(socket.extensions, 'test');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('#protocol', () => {
|
|
195
|
+
it('proxies to the underlying socket', () => {
|
|
196
|
+
assert.notEqual(socket.protocol, 'test');
|
|
197
|
+
mockWebSocket.protocol = 'test';
|
|
198
|
+
assert.equal(socket.protocol, 'test');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('#readyState', () => {
|
|
203
|
+
it('proxies to the underlying socket', () => {
|
|
204
|
+
assert.notEqual(socket.readyState, 'test');
|
|
205
|
+
mockWebSocket.readyState = 'test';
|
|
206
|
+
assert.equal(socket.readyState, 'test');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('#url', () => {
|
|
211
|
+
it('proxies to the underlying socket', () => {
|
|
212
|
+
assert.notEqual(socket.url, 'test');
|
|
213
|
+
mockWebSocket.url = 'test';
|
|
214
|
+
assert.equal(socket.url, 'test');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('#open()', () => {
|
|
219
|
+
it('requires a url parameter', () => {
|
|
220
|
+
const s = new Socket();
|
|
221
|
+
|
|
222
|
+
return assert.isRejected(s.open(), /`url` is required/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('cannot be called more than once', () =>
|
|
226
|
+
assert.isRejected(
|
|
227
|
+
socket.open('ws://example.com'),
|
|
228
|
+
/Socket#open\(\) can only be called once/
|
|
229
|
+
));
|
|
230
|
+
|
|
231
|
+
it("sets the underlying socket's binary type", () =>
|
|
232
|
+
assert.equal(socket.binaryType, 'arraybuffer'));
|
|
233
|
+
|
|
234
|
+
describe('when connection fails because this is a service account', () => {
|
|
235
|
+
it('rejects with a BadRequest', () => {
|
|
236
|
+
const s = new Socket();
|
|
237
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
238
|
+
|
|
239
|
+
mockWebSocket.readyState = 1;
|
|
240
|
+
mockWebSocket.emit('open');
|
|
241
|
+
|
|
242
|
+
const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
243
|
+
|
|
244
|
+
assert.equal(firstCallArgs.type, 'authorization');
|
|
245
|
+
|
|
246
|
+
mockWebSocket.emit('close', {
|
|
247
|
+
code: 4400,
|
|
248
|
+
reason: "Service accounts can't use this endpoint",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return assert.isRejected(promise).then((reason) => {
|
|
252
|
+
assert.instanceOf(reason, BadRequest);
|
|
253
|
+
assert.match(reason.code, 4400);
|
|
254
|
+
assert.match(reason.reason, /Service accounts can't use this endpoint/);
|
|
255
|
+
assert.match(reason.message, /Service accounts can't use this endpoint/);
|
|
256
|
+
|
|
257
|
+
return s.close();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('when connection fails because of an invalid token', () => {
|
|
263
|
+
it('rejects with a NotAuthorized', () => {
|
|
264
|
+
const s = new Socket();
|
|
265
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
266
|
+
|
|
267
|
+
mockWebSocket.readyState = 1;
|
|
268
|
+
mockWebSocket.emit('open');
|
|
269
|
+
|
|
270
|
+
const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
271
|
+
|
|
272
|
+
assert.equal(firstCallArgs.type, 'authorization');
|
|
273
|
+
|
|
274
|
+
mockWebSocket.emit('close', {
|
|
275
|
+
code: 4401,
|
|
276
|
+
reason: 'Authorization Failed',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return assert.isRejected(promise).then((reason) => {
|
|
280
|
+
assert.instanceOf(reason, NotAuthorized);
|
|
281
|
+
assert.match(reason.code, 4401);
|
|
282
|
+
assert.match(reason.reason, /Authorization Failed/);
|
|
283
|
+
assert.match(reason.message, /Authorization Failed/);
|
|
284
|
+
|
|
285
|
+
return s.close();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('when connection fails because of a missing entitlement', () => {
|
|
291
|
+
it('rejects with a Forbidden', () => {
|
|
292
|
+
const s = new Socket();
|
|
293
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
294
|
+
|
|
295
|
+
mockWebSocket.readyState = 1;
|
|
296
|
+
mockWebSocket.emit('open');
|
|
297
|
+
|
|
298
|
+
const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
299
|
+
|
|
300
|
+
assert.equal(firstCallArgs.type, 'authorization');
|
|
301
|
+
|
|
302
|
+
mockWebSocket.emit('close', {
|
|
303
|
+
code: 4403,
|
|
304
|
+
reason: 'Not entitled',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return assert.isRejected(promise).then((reason) => {
|
|
308
|
+
assert.instanceOf(reason, Forbidden);
|
|
309
|
+
assert.match(reason.code, 4403);
|
|
310
|
+
assert.match(reason.reason, /Not entitled/);
|
|
311
|
+
assert.match(reason.message, /Not entitled/);
|
|
312
|
+
|
|
313
|
+
return s.close();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// describe(`when connection fails because the websocket registation has expired`, () => {
|
|
319
|
+
// it(`rejects with a NotFound`, () => {
|
|
320
|
+
// const s = new Socket();
|
|
321
|
+
// const promise = s.open(`ws://example.com`, mockoptions);
|
|
322
|
+
// mockWebSocket.readyState = 1;
|
|
323
|
+
// mockWebSocket.emit(`open`);
|
|
324
|
+
//
|
|
325
|
+
// const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
326
|
+
// assert.equal(firstCallArgs.type, `authorization`);
|
|
327
|
+
//
|
|
328
|
+
// mockWebSocket.emit(`close`, {
|
|
329
|
+
// code: 4404,
|
|
330
|
+
// reason: `Expired registration`
|
|
331
|
+
// });
|
|
332
|
+
//
|
|
333
|
+
// return assert.isRejected(promise)
|
|
334
|
+
// .then((reason) => {
|
|
335
|
+
// assert.instanceOf(reason, NotFound);
|
|
336
|
+
// assert.match(reason.code, 4404);
|
|
337
|
+
// assert.match(reason.reason, /Expired registration/);
|
|
338
|
+
// assert.match(reason.message, /Expired registration/);
|
|
339
|
+
// return s.close();
|
|
340
|
+
// });
|
|
341
|
+
// });
|
|
342
|
+
// });
|
|
343
|
+
|
|
344
|
+
describe('when connection fails for non-authorization reasons', () => {
|
|
345
|
+
it("rejects with the close event's reason", () => {
|
|
346
|
+
const s = new Socket();
|
|
347
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
348
|
+
|
|
349
|
+
mockWebSocket.emit('close', {
|
|
350
|
+
code: 4001,
|
|
351
|
+
reason: 'No',
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return assert.isRejected(promise).then((reason) => {
|
|
355
|
+
assert.instanceOf(reason, ConnectionError);
|
|
356
|
+
assert.match(reason.code, 4001);
|
|
357
|
+
assert.match(reason.reason, /No/);
|
|
358
|
+
assert.match(reason.message, /No/);
|
|
359
|
+
|
|
360
|
+
return s.close();
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('when the connection succeeds', () => {
|
|
366
|
+
it('sends an auth message up the socket', () => {
|
|
367
|
+
const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
368
|
+
|
|
369
|
+
assert.property(firstCallArgs, 'id');
|
|
370
|
+
assert.equal(firstCallArgs.type, 'authorization');
|
|
371
|
+
assert.property(firstCallArgs, 'data');
|
|
372
|
+
assert.property(firstCallArgs.data, 'token');
|
|
373
|
+
assert.equal(firstCallArgs.data.token, 'mocktoken');
|
|
374
|
+
assert.equal(firstCallArgs.trackingId, 'mocktrackingid');
|
|
375
|
+
assert.notProperty(firstCallArgs, 'logLevelToken');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('when logLevelToken is set', () => {
|
|
379
|
+
it('includes the logLevelToken in the authorization payload', () => {
|
|
380
|
+
const s = new Socket();
|
|
381
|
+
|
|
382
|
+
s.open('ws://example.com', {
|
|
383
|
+
forceCloseDelay: mockoptions.forceCloseDelay,
|
|
384
|
+
pingInterval: mockoptions.pingInterval,
|
|
385
|
+
pongTimeout: mockoptions.pongTimeout,
|
|
386
|
+
logger: console,
|
|
387
|
+
token: 'mocktoken',
|
|
388
|
+
trackingId: 'mocktrackingid',
|
|
389
|
+
logLevelToken: 'mocklogleveltoken',
|
|
390
|
+
}).catch((reason) => console.error(reason));
|
|
391
|
+
mockWebSocket.readyState = 1;
|
|
392
|
+
mockWebSocket.emit('open');
|
|
393
|
+
|
|
394
|
+
const firstCallArgs = JSON.parse(mockWebSocket.send.firstCall.args[0]);
|
|
395
|
+
|
|
396
|
+
assert.property(firstCallArgs, 'id');
|
|
397
|
+
assert.equal(firstCallArgs.type, 'authorization');
|
|
398
|
+
assert.property(firstCallArgs, 'data');
|
|
399
|
+
assert.property(firstCallArgs.data, 'token');
|
|
400
|
+
assert.equal(firstCallArgs.data.token, 'mocktoken');
|
|
401
|
+
assert.equal(firstCallArgs.trackingId, 'mocktrackingid');
|
|
402
|
+
assert.equal(firstCallArgs.logLevelToken, 'mocklogleveltoken');
|
|
403
|
+
|
|
404
|
+
return s.close();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('kicks off ping/ping', () => assert.calledOnce(socket._ping));
|
|
409
|
+
|
|
410
|
+
it('resolves upon successful authorization', () => {
|
|
411
|
+
const s = new Socket();
|
|
412
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
413
|
+
|
|
414
|
+
mockWebSocket.readyState = 1;
|
|
415
|
+
mockWebSocket.emit('open');
|
|
416
|
+
mockWebSocket.emit('message', {
|
|
417
|
+
data: JSON.stringify({
|
|
418
|
+
id: uuid.v4(),
|
|
419
|
+
data: {
|
|
420
|
+
eventType: 'mercury.buffer_state',
|
|
421
|
+
},
|
|
422
|
+
}),
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return promise.then(() => s.close());
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('resolves upon receiving registration status', () => {
|
|
429
|
+
const s = new Socket();
|
|
430
|
+
const promise = s.open('ws://example.com', mockoptions);
|
|
431
|
+
|
|
432
|
+
mockWebSocket.readyState = 1;
|
|
433
|
+
mockWebSocket.emit('open');
|
|
434
|
+
mockWebSocket.emit('message', {
|
|
435
|
+
data: JSON.stringify({
|
|
436
|
+
id: uuid.v4(),
|
|
437
|
+
data: {
|
|
438
|
+
eventType: 'mercury.registration_status',
|
|
439
|
+
},
|
|
440
|
+
}),
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return promise.then(() => s.close());
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('#close()', () => {
|
|
449
|
+
it('closes the socket', () => socket.close().then(() => assert.called(mockWebSocket.close)));
|
|
450
|
+
|
|
451
|
+
it('only accepts valid close codes', () =>
|
|
452
|
+
Promise.all([
|
|
453
|
+
assert.isRejected(
|
|
454
|
+
socket.close({code: 1001}),
|
|
455
|
+
/`options.code` must be 1000 or between 3000 and 4999 \(inclusive\)/
|
|
456
|
+
),
|
|
457
|
+
socket.close({code: 1000}),
|
|
458
|
+
]));
|
|
459
|
+
|
|
460
|
+
it('accepts a reason', () =>
|
|
461
|
+
socket
|
|
462
|
+
.close({
|
|
463
|
+
code: 3001,
|
|
464
|
+
reason: 'Custom Normal',
|
|
465
|
+
})
|
|
466
|
+
.then(() => assert.calledWith(mockWebSocket.close, 3001, 'Custom Normal')));
|
|
467
|
+
|
|
468
|
+
it('accepts the logout reason', () =>
|
|
469
|
+
socket
|
|
470
|
+
.close({
|
|
471
|
+
code: 3050,
|
|
472
|
+
reason: 'done (permanent)',
|
|
473
|
+
})
|
|
474
|
+
.then(() => assert.calledWith(mockWebSocket.close, 3050, 'done (permanent)')));
|
|
475
|
+
|
|
476
|
+
it('can safely be called called multiple times', () => {
|
|
477
|
+
const p1 = socket.close();
|
|
478
|
+
|
|
479
|
+
mockWebSocket.readyState = 2;
|
|
480
|
+
const p2 = socket.close();
|
|
481
|
+
|
|
482
|
+
return Promise.all([p1, p2]);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('signals closure if no close frame is received within the specified window', () => {
|
|
486
|
+
const socket = new Socket();
|
|
487
|
+
const promise = socket.open('ws://example.com', mockoptions);
|
|
488
|
+
|
|
489
|
+
mockWebSocket.readyState = 1;
|
|
490
|
+
mockWebSocket.emit('open');
|
|
491
|
+
mockWebSocket.emit('message', {
|
|
492
|
+
data: JSON.stringify({
|
|
493
|
+
id: uuid.v4(),
|
|
494
|
+
data: {
|
|
495
|
+
eventType: 'mercury.buffer_state',
|
|
496
|
+
},
|
|
497
|
+
}),
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
return promise.then(() => {
|
|
501
|
+
const spy = sinon.spy();
|
|
502
|
+
|
|
503
|
+
socket.on('close', spy);
|
|
504
|
+
mockWebSocket.close = () =>
|
|
505
|
+
new Promise(() => {
|
|
506
|
+
/* eslint no-inline-comments: [0] */
|
|
507
|
+
});
|
|
508
|
+
mockWebSocket.removeAllListeners('close');
|
|
509
|
+
|
|
510
|
+
const promise = socket.close();
|
|
511
|
+
|
|
512
|
+
clock.tick(mockoptions.forceCloseDelay);
|
|
513
|
+
|
|
514
|
+
return promise.then(() => {
|
|
515
|
+
assert.called(spy);
|
|
516
|
+
assert.calledWith(spy, {
|
|
517
|
+
code: 1000,
|
|
518
|
+
reason: 'Done (forced)',
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('signals closure if no close frame is received within the specified window, but uses the initial options as 3050 if specified by options call', () => {
|
|
525
|
+
const socket = new Socket();
|
|
526
|
+
const promise = socket.open('ws://example.com', mockoptions);
|
|
527
|
+
|
|
528
|
+
mockWebSocket.readyState = 1;
|
|
529
|
+
mockWebSocket.emit('open');
|
|
530
|
+
mockWebSocket.emit('message', {
|
|
531
|
+
data: JSON.stringify({
|
|
532
|
+
id: uuid.v4(),
|
|
533
|
+
data: {
|
|
534
|
+
eventType: 'mercury.buffer_state',
|
|
535
|
+
},
|
|
536
|
+
}),
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return promise.then(() => {
|
|
540
|
+
const spy = sinon.spy();
|
|
541
|
+
|
|
542
|
+
socket.on('close', spy);
|
|
543
|
+
mockWebSocket.close = () =>
|
|
544
|
+
new Promise(() => {
|
|
545
|
+
/* eslint no-inline-comments: [0] */
|
|
546
|
+
});
|
|
547
|
+
mockWebSocket.removeAllListeners('close');
|
|
548
|
+
|
|
549
|
+
const promise = socket.close({code: 3050, reason: 'done (permanent)'});
|
|
550
|
+
|
|
551
|
+
clock.tick(mockoptions.forceCloseDelay);
|
|
552
|
+
|
|
553
|
+
return promise.then(() => {
|
|
554
|
+
assert.called(spy);
|
|
555
|
+
assert.calledWith(spy, {
|
|
556
|
+
code: 3050,
|
|
557
|
+
reason: 'done (permanent)',
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('signals closure if no close frame is received within the specified window, and uses default options as 1000 if the code is not 3050', () => {
|
|
564
|
+
const socket = new Socket();
|
|
565
|
+
const promise = socket.open('ws://example.com', mockoptions);
|
|
566
|
+
|
|
567
|
+
mockWebSocket.readyState = 1;
|
|
568
|
+
mockWebSocket.emit('open');
|
|
569
|
+
mockWebSocket.emit('message', {
|
|
570
|
+
data: JSON.stringify({
|
|
571
|
+
id: uuid.v4(),
|
|
572
|
+
data: {
|
|
573
|
+
eventType: 'mercury.buffer_state',
|
|
574
|
+
},
|
|
575
|
+
}),
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
return promise.then(() => {
|
|
579
|
+
const spy = sinon.spy();
|
|
580
|
+
|
|
581
|
+
socket.on('close', spy);
|
|
582
|
+
mockWebSocket.close = () =>
|
|
583
|
+
new Promise(() => {
|
|
584
|
+
/* eslint no-inline-comments: [0] */
|
|
585
|
+
});
|
|
586
|
+
mockWebSocket.removeAllListeners('close');
|
|
587
|
+
|
|
588
|
+
const promise = socket.close({code: 1000, reason: 'test'});
|
|
589
|
+
|
|
590
|
+
clock.tick(mockoptions.forceCloseDelay);
|
|
591
|
+
|
|
592
|
+
return promise.then(() => {
|
|
593
|
+
assert.called(spy);
|
|
594
|
+
assert.calledWith(spy, {
|
|
595
|
+
code: 1000,
|
|
596
|
+
reason: 'test',
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('signals closure if no close frame is received within the specified window, and uses default options as 1000 if the code is not 3050', () => {
|
|
603
|
+
const socket = new Socket();
|
|
604
|
+
const promise = socket.open('ws://example.com', mockoptions);
|
|
605
|
+
|
|
606
|
+
mockWebSocket.readyState = 1;
|
|
607
|
+
mockWebSocket.emit('open');
|
|
608
|
+
mockWebSocket.emit('message', {
|
|
609
|
+
data: JSON.stringify({
|
|
610
|
+
id: uuid.v4(),
|
|
611
|
+
data: {
|
|
612
|
+
eventType: 'mercury.buffer_state',
|
|
613
|
+
},
|
|
614
|
+
}),
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
return promise.then(() => {
|
|
618
|
+
const spy = sinon.spy();
|
|
619
|
+
|
|
620
|
+
socket.on('close', spy);
|
|
621
|
+
mockWebSocket.close = () =>
|
|
622
|
+
new Promise(() => {
|
|
623
|
+
/* eslint no-inline-comments: [0] */
|
|
624
|
+
});
|
|
625
|
+
mockWebSocket.removeAllListeners('close');
|
|
626
|
+
|
|
627
|
+
const promise = socket.close({code: 1000});
|
|
628
|
+
|
|
629
|
+
clock.tick(mockoptions.forceCloseDelay);
|
|
630
|
+
|
|
631
|
+
return promise.then(() => {
|
|
632
|
+
assert.called(spy);
|
|
633
|
+
assert.calledWith(spy, {
|
|
634
|
+
code: 1000,
|
|
635
|
+
reason: 'Done (unknown)',
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it('cancels any outstanding ping/pong timers', () => {
|
|
642
|
+
mockWebSocket.send = sinon.stub();
|
|
643
|
+
socket._ping.resetHistory();
|
|
644
|
+
const spy = sinon.spy();
|
|
645
|
+
|
|
646
|
+
socket.on('close', spy);
|
|
647
|
+
socket._ping();
|
|
648
|
+
socket.close();
|
|
649
|
+
clock.tick(2 * mockoptions.pingInterval);
|
|
650
|
+
assert.neverCalledWith(spy, {
|
|
651
|
+
code: 1000,
|
|
652
|
+
reason: 'Pong not received',
|
|
653
|
+
});
|
|
654
|
+
assert.calledOnce(socket._ping);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
[
|
|
658
|
+
{
|
|
659
|
+
description: 'manually triggers close handler when socket is still connecting',
|
|
660
|
+
closeOptions: {code: 3001, reason: 'Custom close while connecting'},
|
|
661
|
+
expectedCode: 3001,
|
|
662
|
+
expectedReason: 'Custom close while connecting',
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
description:
|
|
666
|
+
'manually triggers close handler with default code when socket is connecting',
|
|
667
|
+
closeOptions: undefined,
|
|
668
|
+
expectedCode: 1000,
|
|
669
|
+
expectedReason: 'Done',
|
|
670
|
+
},
|
|
671
|
+
].forEach(({description, closeOptions, expectedCode, expectedReason}) => {
|
|
672
|
+
it(description, async () => {
|
|
673
|
+
const s = new Socket();
|
|
674
|
+
let socketInstance;
|
|
675
|
+
|
|
676
|
+
// Save the current stub and replace it
|
|
677
|
+
const previousStub = Socket.getWebSocketConstructor;
|
|
678
|
+
Socket.getWebSocketConstructor = sinon.stub().callsFake(
|
|
679
|
+
() =>
|
|
680
|
+
function (...args) {
|
|
681
|
+
socketInstance = new MockWebSocket(...args);
|
|
682
|
+
return socketInstance;
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
// open the socket
|
|
687
|
+
s.open('ws://example.com', mockoptions);
|
|
688
|
+
|
|
689
|
+
// Keep socket in CONNECTING state (readyState 0)
|
|
690
|
+
socketInstance.readyState = 0;
|
|
691
|
+
|
|
692
|
+
const closeSpy = sinon.spy();
|
|
693
|
+
s.on('close', closeSpy);
|
|
694
|
+
|
|
695
|
+
// Call close and await the result
|
|
696
|
+
const result = await s.close(closeOptions);
|
|
697
|
+
|
|
698
|
+
// Verify the promise resolved with the correct close event
|
|
699
|
+
assert.equal(result.code, expectedCode);
|
|
700
|
+
assert.equal(result.reason, expectedReason);
|
|
701
|
+
|
|
702
|
+
// Verify close handler was called with expected code/reason
|
|
703
|
+
assert.calledOnce(closeSpy);
|
|
704
|
+
assert.calledWith(closeSpy, {
|
|
705
|
+
code: expectedCode,
|
|
706
|
+
reason: expectedReason,
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Verify the underlying socket.close was called with the correct params
|
|
710
|
+
assert.calledOnce(socketInstance.close);
|
|
711
|
+
assert.calledWith(socketInstance.close, expectedCode, expectedReason);
|
|
712
|
+
|
|
713
|
+
// Restore the previous stub
|
|
714
|
+
Socket.getWebSocketConstructor = previousStub;
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
describe('#send()', () => {
|
|
720
|
+
describe('when the socket is not in the OPEN state', () => {
|
|
721
|
+
it('fails', () => {
|
|
722
|
+
mockWebSocket.readyState = 0;
|
|
723
|
+
|
|
724
|
+
return assert
|
|
725
|
+
.isRejected(socket.send('test0'), /INVALID_STATE_ERROR/)
|
|
726
|
+
.then(() => {
|
|
727
|
+
mockWebSocket.readyState = 2;
|
|
728
|
+
|
|
729
|
+
return assert.isRejected(socket.send('test2'), /INVALID_STATE_ERROR/);
|
|
730
|
+
})
|
|
731
|
+
.then(() => {
|
|
732
|
+
mockWebSocket.readyState = 3;
|
|
733
|
+
|
|
734
|
+
return assert.isRejected(socket.send('test3'), /INVALID_STATE_ERROR/);
|
|
735
|
+
})
|
|
736
|
+
.then(() => {
|
|
737
|
+
mockWebSocket.readyState = 1;
|
|
738
|
+
|
|
739
|
+
return socket.send('test1');
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it('sends strings', () => {
|
|
745
|
+
socket.send('this is a string');
|
|
746
|
+
assert.calledWith(mockWebSocket.send, 'this is a string');
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('sends JSON.stringifyable object', () => {
|
|
750
|
+
socket.send({
|
|
751
|
+
json: true,
|
|
752
|
+
});
|
|
753
|
+
assert.calledWith(mockWebSocket.send, '{"json":true}');
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
describe('#onclose()', () => {
|
|
758
|
+
it('stops further ping checks', () => {
|
|
759
|
+
socket._ping.resetHistory();
|
|
760
|
+
assert.notCalled(socket._ping);
|
|
761
|
+
const spy = sinon.spy();
|
|
762
|
+
|
|
763
|
+
assert.notCalled(socket._ping);
|
|
764
|
+
socket.on('close', spy);
|
|
765
|
+
assert.notCalled(socket._ping);
|
|
766
|
+
socket._ping();
|
|
767
|
+
assert.calledOnce(socket._ping);
|
|
768
|
+
mockWebSocket.emit('close', {
|
|
769
|
+
code: 1000,
|
|
770
|
+
reason: 'Done',
|
|
771
|
+
});
|
|
772
|
+
assert.calledOnce(socket._ping);
|
|
773
|
+
clock.tick(5 * mockoptions.pingInterval);
|
|
774
|
+
assert.neverCalledWith(spy, {
|
|
775
|
+
code: 1000,
|
|
776
|
+
reason: 'Pong not received',
|
|
777
|
+
});
|
|
778
|
+
assert.calledOnce(socket._ping);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
describe('when it receives close code 1005', () => {
|
|
782
|
+
forEach(
|
|
783
|
+
{
|
|
784
|
+
Replaced: 4000,
|
|
785
|
+
'Authentication Failed': 1008,
|
|
786
|
+
'Authentication did not happen within the timeout window of 30000 seconds.': 1008,
|
|
787
|
+
},
|
|
788
|
+
(code, reason) => {
|
|
789
|
+
it(`emits code ${code} for reason ${reason}`, () => {
|
|
790
|
+
const spy = sinon.spy();
|
|
791
|
+
|
|
792
|
+
socket.on('close', spy);
|
|
793
|
+
|
|
794
|
+
mockWebSocket.emit('close', {
|
|
795
|
+
code: 1005,
|
|
796
|
+
reason,
|
|
797
|
+
});
|
|
798
|
+
assert.called(spy);
|
|
799
|
+
assert.calledWith(spy, {
|
|
800
|
+
code,
|
|
801
|
+
reason,
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
describe('when it receives close code 3050', () => {
|
|
809
|
+
it(`emits code 3050 for code 3050`, () => {
|
|
810
|
+
const code = 3050;
|
|
811
|
+
const reason = 'done (permanent)';
|
|
812
|
+
const spy = sinon.spy();
|
|
813
|
+
|
|
814
|
+
socket.on('close', spy);
|
|
815
|
+
|
|
816
|
+
mockWebSocket.emit('close', {
|
|
817
|
+
code,
|
|
818
|
+
reason,
|
|
819
|
+
});
|
|
820
|
+
assert.called(spy);
|
|
821
|
+
assert.calledWith(spy, {
|
|
822
|
+
code,
|
|
823
|
+
reason,
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
describe('#onmessage()', () => {
|
|
830
|
+
let spy;
|
|
831
|
+
|
|
832
|
+
beforeEach(() => {
|
|
833
|
+
spy = sinon.spy();
|
|
834
|
+
socket.on('message', spy);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it('emits messages from the underlying socket', () => {
|
|
838
|
+
mockWebSocket.emit('message', {
|
|
839
|
+
data: JSON.stringify({
|
|
840
|
+
sequenceNumber: 3,
|
|
841
|
+
id: 'mockid',
|
|
842
|
+
}),
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
assert.called(spy);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('parses received messages', () => {
|
|
849
|
+
mockWebSocket.emit('message', {
|
|
850
|
+
data: JSON.stringify({
|
|
851
|
+
sequenceNumber: 3,
|
|
852
|
+
id: 'mockid',
|
|
853
|
+
}),
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
assert.calledWith(spy, {
|
|
857
|
+
data: {
|
|
858
|
+
sequenceNumber: 3,
|
|
859
|
+
id: 'mockid',
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('emits skipped sequence numbers', () => {
|
|
865
|
+
const spy2 = sinon.spy();
|
|
866
|
+
|
|
867
|
+
socket.on('sequence-mismatch', spy2);
|
|
868
|
+
|
|
869
|
+
mockWebSocket.emit('message', {
|
|
870
|
+
data: JSON.stringify({
|
|
871
|
+
sequenceNumber: 2,
|
|
872
|
+
id: 'mockid',
|
|
873
|
+
}),
|
|
874
|
+
});
|
|
875
|
+
assert.notCalled(spy2);
|
|
876
|
+
|
|
877
|
+
mockWebSocket.emit('message', {
|
|
878
|
+
data: JSON.stringify({
|
|
879
|
+
sequenceNumber: 4,
|
|
880
|
+
id: 'mockid',
|
|
881
|
+
}),
|
|
882
|
+
});
|
|
883
|
+
assert.calledOnce(spy2);
|
|
884
|
+
assert.calledWith(spy2, 4, 3);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('acknowledges received messages', () => {
|
|
888
|
+
sinon.spy(socket, '_acknowledge');
|
|
889
|
+
mockWebSocket.emit('message', {
|
|
890
|
+
data: JSON.stringify({
|
|
891
|
+
sequenceNumber: 5,
|
|
892
|
+
id: 'mockid',
|
|
893
|
+
}),
|
|
894
|
+
});
|
|
895
|
+
assert.called(socket._acknowledge);
|
|
896
|
+
assert.calledWith(socket._acknowledge, {
|
|
897
|
+
data: {
|
|
898
|
+
sequenceNumber: 5,
|
|
899
|
+
id: 'mockid',
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('emits pongs separately from other messages', () => {
|
|
905
|
+
const pongSpy = sinon.spy();
|
|
906
|
+
|
|
907
|
+
socket.on('pong', pongSpy);
|
|
908
|
+
|
|
909
|
+
mockWebSocket.emit('message', {
|
|
910
|
+
data: JSON.stringify({
|
|
911
|
+
sequenceNumber: 5,
|
|
912
|
+
id: 'mockid1',
|
|
913
|
+
type: 'pong',
|
|
914
|
+
}),
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
assert.calledOnce(pongSpy);
|
|
918
|
+
assert.notCalled(spy);
|
|
919
|
+
|
|
920
|
+
mockWebSocket.emit('message', {
|
|
921
|
+
data: JSON.stringify({
|
|
922
|
+
sequenceNumber: 6,
|
|
923
|
+
id: 'mockid2',
|
|
924
|
+
}),
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
assert.calledOnce(pongSpy);
|
|
928
|
+
assert.calledOnce(spy);
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
describe('#_acknowledge', () => {
|
|
933
|
+
it('requires an event', () =>
|
|
934
|
+
assert.isRejected(socket._acknowledge(), /`event` is required/));
|
|
935
|
+
|
|
936
|
+
it('requires a message id', () =>
|
|
937
|
+
assert.isRejected(socket._acknowledge({}), /`event.data.id` is required/));
|
|
938
|
+
|
|
939
|
+
it('acknowledges the specified message', () => {
|
|
940
|
+
const id = 'mockuuid';
|
|
941
|
+
|
|
942
|
+
return socket
|
|
943
|
+
._acknowledge({
|
|
944
|
+
data: {
|
|
945
|
+
type: 'not an ack',
|
|
946
|
+
id,
|
|
947
|
+
},
|
|
948
|
+
})
|
|
949
|
+
.then(() => {
|
|
950
|
+
assert.calledWith(
|
|
951
|
+
mockWebSocket.send,
|
|
952
|
+
JSON.stringify({
|
|
953
|
+
messageId: id,
|
|
954
|
+
type: 'ack',
|
|
955
|
+
})
|
|
956
|
+
);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
describe('#_ping()', () => {
|
|
962
|
+
let id;
|
|
963
|
+
|
|
964
|
+
beforeEach(() => {
|
|
965
|
+
id = uuid.v4();
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
it('sends a ping up the socket', () =>
|
|
969
|
+
socket._ping(id).then(() => {
|
|
970
|
+
assert.calledWith(
|
|
971
|
+
mockWebSocket.send,
|
|
972
|
+
JSON.stringify({
|
|
973
|
+
id,
|
|
974
|
+
type: 'ping',
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
}));
|
|
978
|
+
|
|
979
|
+
it('considers the socket closed if no pong is received in an acceptable time period', () => {
|
|
980
|
+
const spy = sinon.spy();
|
|
981
|
+
|
|
982
|
+
socket.on('close', spy);
|
|
983
|
+
|
|
984
|
+
mockWebSocket.send = sinon.stub();
|
|
985
|
+
socket._ping(id);
|
|
986
|
+
clock.tick(2 * mockoptions.pongTimeout);
|
|
987
|
+
assert.called(spy);
|
|
988
|
+
assert.calledWith(spy, {
|
|
989
|
+
code: 1000,
|
|
990
|
+
reason: 'Pong not received',
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('schedules a future ping', () => {
|
|
995
|
+
assert.callCount(socket._ping, 1);
|
|
996
|
+
clock.tick(mockoptions.pingInterval);
|
|
997
|
+
assert.callCount(socket._ping, 2);
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it('closes the socket when an unexpected pong is received', () => {
|
|
1001
|
+
const spy = sinon.spy();
|
|
1002
|
+
|
|
1003
|
+
socket.on('close', spy);
|
|
1004
|
+
|
|
1005
|
+
socket._ping(2);
|
|
1006
|
+
mockWebSocket.emit('message', {
|
|
1007
|
+
data: JSON.stringify({
|
|
1008
|
+
type: 'pong',
|
|
1009
|
+
id: 1,
|
|
1010
|
+
}),
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
assert.calledWith(spy, {
|
|
1014
|
+
code: 1000,
|
|
1015
|
+
reason: 'Pong mismatch',
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
it('emits ping pong latency correctly', () => {
|
|
1020
|
+
const spy = sinon.spy();
|
|
1021
|
+
|
|
1022
|
+
socket.on('ping-pong-latency', spy);
|
|
1023
|
+
|
|
1024
|
+
socket._ping(123);
|
|
1025
|
+
mockWebSocket.emit('message', {
|
|
1026
|
+
data: JSON.stringify({
|
|
1027
|
+
type: 'pong',
|
|
1028
|
+
id: 123,
|
|
1029
|
+
}),
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
assert.calledWith(spy, 0);
|
|
1033
|
+
assert.calledOnce(spy);
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
1037
|
+
});
|