UNPKG

dl

Version:

DreamLab Libs

801 lines (603 loc) 26.4 kB
var events = require('events'); var rewire = require('rewire'); var WebSocket = require('ws'); var EventEmitter = events.EventEmitter; var WebSocketConnectionMock = function () { EventEmitter.call(this); this.readyState = WebSocket.OPEN; }; WebSocketConnectionMock.prototype = Object.create(EventEmitter.prototype); WebSocketConnectionMock.prototype.send = function (data, opts, cb) { return cb(); }; WebSocketConnectionMock.prototype.onMessage = function (data, cb) { this.emit('message', JSON.stringify(data)); if (cb) { process.nextTick(cb); } }; WebSocketConnectionMock.prototype.onClose = function () { this.readyState = WebSocket.CLOSED; this.emit('close'); }; var WebSocketServerMock = function () { EventEmitter.call(this); this.connections = []; }; WebSocketServerMock.prototype = Object.create(EventEmitter.prototype); WebSocketServerMock.prototype.onConnect = function () { var ws = new WebSocketConnectionMock(); spyOn(ws, 'send').andCallThrough(); this.connections.push(ws); this.emit('connection', ws); return ws; }; describe('WebSocketDataProvider', function () { var WebSocketDataProvider; var instance; var params = { compress: false, binary: false, server: 'server' }; var wssMock; var cb1; var cb2; var cb3; var conn; beforeEach(function () { expect(function () { WebSocketDataProvider = require('../../lib/dataprovider/WebSocketDataProvider.js').WebSocketDataProvider; }).not.toThrow(); var rewiredWebSocketDataProvider = rewire('../../lib/dataprovider/WebSocketDataProvider.js'); var WebSocketServerSpy = jasmine.createSpy('wss'); WebSocketServerSpy.andCallFake(function (p) { expect(p).toEqual(params); wssMock = new WebSocketServerMock(p); return wssMock; }); WebSocketServerSpy.prototype = Object.create(WebSocketServerMock.prototype); rewiredWebSocketDataProvider.__set__('WebSocketServer', WebSocketServerSpy); cb1 = jasmine.createSpy('cb1'); cb2 = jasmine.createSpy('cb2'); cb3 = jasmine.createSpy('cb3'); instance = new rewiredWebSocketDataProvider.WebSocketDataProvider(params); jasmine.Clock.useMock(); }); describe('new connection', function () { beforeEach(function () { instance.init(cb1, cb2); }); it('should call ready callback', function () { wssMock.onConnect(); expect(cb1).toHaveBeenCalledWith(null, true); }); it('should call reloaded callback', function () { wssMock.onConnect(); expect(cb2).toHaveBeenCalledWith(null, true); }); it('should call reloaded but no ready on reconnect', function () { wssMock.onConnect(); wssMock.onConnect(); expect(cb1.callCount).toBe(1); expect(cb2.callCount).toBe(2); }); }); describe('reloaded', function () { beforeEach(function () { instance.init(function () {}, cb2); conn = wssMock.onConnect(); }); it('should call reloaded calback on reloaded', function (done) { conn.onMessage({ method: 'reloaded' }, function () { expect(cb2.callCount).toBe(2); done(); }); }); }); describe('dropping connection', function () { beforeEach(function () { instance.init(cb1, cb2); conn = wssMock.onConnect(); }); it('should call reloaded callback when there is another open connection', function () { wssMock.onConnect(); conn.onClose(); expect(cb1.callCount).toBe(1); expect(cb2.callCount).toBe(3); }); it('should not call reloaded callback when there is no another open connection', function () { conn.onClose(); expect(cb1.callCount).toBe(1); expect(cb2.callCount).toBe(1); }); it('should reassign watchers when there is another open connection', function (done) { var newConn1 = wssMock.onConnect(); var newConn2 = wssMock.onConnect(); instance.watch('/key', function () {}); instance.watch('/key2', function () {}); conn.onMessage({ id: 1, rc: 0, error: null }, function () { newConn1.onMessage({ id: 2, rc: 0, error: null }, function () { expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"watch","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); conn.onClose(); expect(newConn1.send).toHaveBeenCalledWith('{"id":3,"method":"watch","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); expect(newConn1.send).not.toHaveBeenCalledWith('{"id":4,"method":"watch","params":{"key":"/key2"}}', jasmine.any(Object), jasmine.any(Function)); expect(newConn2.send).not.toHaveBeenCalledWith('{"id":4,"method":"watch","params":{"key":"/key2"}}', jasmine.any(Object), jasmine.any(Function)); done(); }); }); }); }); describe('handling incorrect message', function () { beforeEach(function () { instance.init(cb1, cb2); conn = wssMock.onConnect(); }); it('should not throw when failed to parse message', function () { expect(function () { conn.emit('message', 'xxxxx{xx"xx'); }).not.toThrow(); }); it('should not throw when message is unknown', function () { expect(function () { conn.onMessage({}); }).not.toThrow(); }); it('should not throw when there is no callback for message', function () { expect(function () { conn.onMessage({ id: 123 }); }).not.toThrow(); }); }); describe('exists', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error when checked', function (done) { instance.exists('/key', function (rc, stat) { expect(rc).toBe(null); expect(stat).toBe('stat'); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"exists","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null, data: 'stat' }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, 'stat'); expect(cb1.callCount).toBe(1); done(); }); instance.exists('/key2', cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null, data: 'stat' }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.exists('/key3', cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should return unhandled error', function (done) { instance.exists('/key4.1', function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should return connection error', function (done) { conn.send.andCallFake(function (_data, _opts, cb) { return cb('error'); }); instance.exists('/key4.2', cb1); conn.onMessage({ id: 1, rc: 0 }, function () { expect(cb1).toHaveBeenCalledWith('error', undefined); expect(cb1.callCount).toBe(1); done(); }); }); it('should use all connections', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.exists('/key5'); instance.exists('/key5'); instance.exists('/key5'); expect(conn.send.callCount).toBe(1); expect(conn2.send.callCount).toBe(1); expect(conn3.send.callCount).toBe(1); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.exists('/key6'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); }); describe('get', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error when get', function (done) { instance.get('/key', function (rc, data, stat) { expect(rc).toBe(null); expect(data).toBe('data'); expect(stat).toBe('stat'); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"get","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null, stat: 'stat', data: 'data' }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, 'data', 'stat'); expect(cb1.callCount).toBe(1); done(); }); instance.get('/key2', cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null, stat: 'stat', data: 'data' }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.get('/key3', cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should return unhandled error', function (done) { instance.get('/key4', function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should use all connections', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.get('/key5'); instance.get('/key5'); instance.get('/key5'); expect(conn.send.callCount).toBe(1); expect(conn2.send.callCount).toBe(1); expect(conn3.send.callCount).toBe(1); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.get('/key6'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); }); describe('remove', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error when removed', function (done) { instance.remove('/key', { 'a': 'b' }, function (rc, data) { expect(rc).toBe(null); expect(data).toBe(null); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"remove","params":{"key":"/key","options":{"a":"b"}}}', // jshint ignore: line jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, null); expect(cb1.callCount).toBe(1); done(); }); instance.remove('/key2', {}, cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.remove('/key3', {}, cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should return unhandled error', function (done) { instance.remove('/key4', {}, function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should use all connections', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.remove('/key5'); instance.remove('/key5'); instance.remove('/key5'); expect(conn.send.callCount).toBe(1); expect(conn2.send.callCount).toBe(1); expect(conn3.send.callCount).toBe(1); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.remove('/key6'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); }); describe('set', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error when set', function (done) { instance.set('/key', 'value', {'a': 'b'}, function (rc, stat) { expect(rc).toBe(null); expect(stat).toBe('stat'); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"set","params":{"key":"/key","value":"value","options":{"a":"b"}}}', // jshint ignore: line jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null, data: 'stat' }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, 'stat'); expect(cb1.callCount).toBe(1); done(); }); instance.set('/key2', 'value', {}, cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null, data: 'stat' }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.set('/key3', 'value', {}, cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should timeout when not connected longer than for DEFER_TIMEOUT', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function (err) { expect(err).toBeTruthy(); expect(cb1.callCount).toBe(1); done(); }); instance.set('/whatever', 'value', {}, cb1); jasmine.Clock.tick(WebSocketDataProvider.DEFER_TIMEOUT + 1); }); it('should return unhandled error', function (done) { instance.set('/key4', 'value', {}, function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should use all connections', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key5', 'value'); instance.set('/key5', 'value'); instance.set('/key5', 'value'); expect(conn.send.callCount).toBe(1); expect(conn2.send.callCount).toBe(1); expect(conn3.send.callCount).toBe(1); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.set('/key6', 'value'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); }); describe('sync', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error when synced', function (done) { instance.sync('/key', function (rc, data) { expect(rc).toBe(null); expect(data).toBe(null); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"sync","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, null); expect(cb1.callCount).toBe(1); done(); }); instance.sync('/key2', cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.sync('/key3', cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should return unhandled error', function (done) { instance.sync('/key4', function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should use all connections', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.sync('/key5'); instance.sync('/key5'); instance.sync('/key5'); expect(conn.send.callCount).toBe(1); expect(conn2.send.callCount).toBe(1); expect(conn3.send.callCount).toBe(1); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.sync('/key6'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); }); describe('watch', function () { beforeEach(function () { instance.init(function () {}, function () {}); conn = wssMock.onConnect(); }); it('should return callback without error and with data on success', function (done) { instance.watch('/key', function (rc, data, stat) { expect(rc).toBe(null); expect(data).toBe('data'); expect(stat).toBe('stat'); done(); }); expect(conn.send).toHaveBeenCalledWith('{"id":1,"method":"watch","params":{"key":"/key"}}', jasmine.any(Object), jasmine.any(Function)); conn.onMessage({ id: 1, rc: 0, error: null, stat: 'stat', data: 'data' }); }); it('should retry on ZCONNECTIONLOSS', function (done) { cb1.andCallFake(function () { expect(cb1).toHaveBeenCalledWith(null, 'data', 'stat'); expect(cb1.callCount).toBe(1); done(); }); instance.watch('/key2', cb1); conn.onClose(); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 2, rc: 0, error: null, stat: 'stat', data: 'data' }); }); it('should retry when not connected', function (done) { conn.readyState = WebSocket.CLOSED; cb1.andCallFake(function () { expect(cb1.callCount).toBe(1); done(); }); instance.watch('/key3', cb1); var newConn = wssMock.onConnect(); newConn.onMessage({ id: 1, rc: 0, error: null }); }); it('should return unhandled error', function (done) { instance.watch('/key4', function (rc, data) { expect(rc).toBe(123); expect(data).toBe('some error'); done(); }); conn.onMessage({ id: 1, rc: 123, error: 'some error' }); }); it('should stick to one connection', function () { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.watch('/key5'); instance.watch('/key5'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); }); it('should use sticky connection when using ephemeral key', function (done) { var conn2 = wssMock.onConnect(); var conn3 = wssMock.onConnect(); instance.set('/key6', 'value', { flags: 1 }, function () { instance.watch('/key6'); expect(conn.send.callCount).toBe(2); expect(conn2.send.callCount).toBe(0); expect(conn3.send.callCount).toBe(0); done(); }); conn.onMessage({ id: 1, rc: 0, data: { createdInThisSession: true } }); }); it('should call callback with data on watch notify', function (done) { instance.watch('/key7', cb1); instance.watch('/key7', cb2); instance.watch('/key8', cb3); conn.onMessage({ id: 1, rc: 0, error: null, stat: 'stat', data: 'data' }, function () { conn.onMessage({ id: 2, rc: 0, error: null, stat: 'stat', data: 'data' }, function () { conn.onMessage({ method: 'notify', params: { key: '/key7', rc: 0, stat: 'stat1', data: 'data1' }}, function () { // jshint ignore: line expect(cb1).toHaveBeenCalledWith(null, 'data1', 'stat1'); expect(cb2).toHaveBeenCalledWith(null, 'data1', 'stat1'); expect(cb1.callCount).toBe(2); expect(cb2.callCount).toBe(2); expect(cb3.callCount).toBe(0); done(); }); }); }); }); it('should call callback with error on watch notify', function (done) { instance.watch('/key9', cb1); conn.onMessage({ id: 1, rc: 0, error: null, stat: 'stat', data: 'data' }, function () { conn.onMessage({ method: 'notify', params: { key: '/key9', rc: 1234, error: 'error' } }, function () { expect(cb1).toHaveBeenCalledWith(1234, 'error'); expect(cb1.callCount).toBe(2); done(); }); }); }); }); });