UNPKG

dl

Version:

DreamLab Libs

544 lines (402 loc) 21 kB
var core = require('core'); var events = require('events'); var rewire = require('rewire'); var WebSocket = require('ws'); var Event = core.event.Event; var EventEmitter = events.EventEmitter; var MockDataProvider = require('../../lib/dataprovider/MockDataProvider.js').MockDataProvider; var WebSocketMockInstance = function () { EventEmitter.call(this); this.close = jasmine.createSpy('ws.close'); this.send = jasmine.createSpy('ws.send'); this.ping = jasmine.createSpy('ws.ping'); }; WebSocketMockInstance.prototype = Object.create(EventEmitter.prototype); WebSocketMockInstance.prototype.onMessage = function (id, method, params) { this.emit('message', JSON.stringify({ id: id, method: method, params: params })); }; describe('ForwardingConfigurationManager', function () { var ForwardingConfigurationManager; var WebSocketMock; var wsInstances; var instance; var driver; var wsParams = { compress: false, binary: false }; var sendCb; var ws1; var ws2; beforeEach(function () { expect(function () { ForwardingConfigurationManager = require('../../lib/configuration/ForwardingConfigurationManager.js').ForwardingConfigurationManager; // jshint ignore: line }).not.toThrow(); jasmine.Clock.useMock(); var rewiredForwardingConfigurationManager = rewire('../../lib/configuration/ForwardingConfigurationManager.js'); sendCb = jasmine.createSpy('send callback'); wsInstances = {}; WebSocketMock = jasmine.createSpy('WebSocket'); WebSocketMock.andCallFake(function (endpoint) { wsInstances[endpoint] = new WebSocketMockInstance(endpoint); return wsInstances[endpoint]; }); rewiredForwardingConfigurationManager.__set__('WebSocket', WebSocketMock); ForwardingConfigurationManager = rewiredForwardingConfigurationManager.ForwardingConfigurationManager; driver = new MockDataProvider('data'); spyOn(driver, 'init').andCallThrough(); spyOn(driver, 'exists').andCallThrough(); spyOn(driver, 'get').andCallThrough(); spyOn(driver, 'remove').andCallThrough(); spyOn(driver, 'set').andCallThrough(); spyOn(driver, 'sync').andCallThrough(); spyOn(driver, 'watch').andCallThrough(); spyOn(driver, 'isConnected').andCallThrough(); instance = new ForwardingConfigurationManager(driver); }); describe('initialize', function () { it('should connect to added clients on initialize', function () { instance.addClient('endpoint'); instance.initialize(); expect(WebSocketMock).toHaveBeenCalledWith('endpoint'); }); it('should not initialize when ready', function () { instance.initialize(); instance.initialize(); expect(driver.init.callCount).toBe(1); }); it('should reload all open endpoints when reloaded', function () { instance.initialize(); instance.setClients(['endpoint1', 'endpoint2', 'endpoint3']); wsInstances['endpoint3'].readyState = WebSocket.CLOSED; instance.dispatchEvent(new Event(ForwardingConfigurationManager.Event.RELOADED)); expect(wsInstances['endpoint1'].send).toHaveBeenCalledWith('{"method":"reloaded"}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(wsInstances['endpoint2'].send).toHaveBeenCalledWith('{"method":"reloaded"}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(wsInstances['endpoint3'].send).not.toHaveBeenCalled(); }); }); describe('addClient', function () { beforeEach(function () { instance.initialize(); }); it('should connect to new client', function () { var res = instance.addClient('endpoint'); expect(WebSocketMock).toHaveBeenCalledWith('endpoint'); expect(res).toBe(true); }); it('should not connect to duplicated client', function () { var res1 = instance.addClient('endpoint'); var res2 = instance.addClient('endpoint'); expect(WebSocketMock.callCount).toBe(1); expect(res1).toBe(true); expect(res2).toBe(false); }); it('should not connect until ready', function () { var instance2 = new ForwardingConfigurationManager(driver); instance2.addClient('endpoint'); expect(WebSocketMock).not.toHaveBeenCalled(); }); }); describe('removeClient', function () { beforeEach(function () { instance.initialize(); }); it('should disconnect existing client', function () { instance.addClient('endpoint'); var res = instance.removeClient('endpoint'); expect(wsInstances['endpoint'].close).toHaveBeenCalled(); expect(res).toBe(true); }); it('should not disconnect not existing client', function () { var res; instance.addClient('endpoint'); expect(function () { res = instance.removeClient('endpoint2'); }).not.toThrow(); expect(res).toBe(false); }); it('should not reconnect to removed client', function () { instance.addClient('endpoint'); wsInstances['endpoint'].close.andCallFake(function () { wsInstances['endpoint'].emit('close'); }); instance.removeClient('endpoint'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_INTERVAL + 1); expect(WebSocketMock.callCount).toBe(1); }); }); describe('setClients', function () { beforeEach(function () { instance.initialize(); }); it('should connect to new clients and disconnect missing clients', function () { instance.addClient('endpoint1'); instance.addClient('endpoint2'); instance.setClients(['endpoint3', 'endpoint2']); expect(wsInstances['endpoint1'].close).toHaveBeenCalled(); expect(wsInstances['endpoint2'].close).not.toHaveBeenCalled(); expect(WebSocketMock).toHaveBeenCalledWith('endpoint3'); }); it('should not connect until ready', function () { var instance2 = new ForwardingConfigurationManager(driver); instance2.setClients(['endpoint1', 'endpoint2']); expect(WebSocketMock).not.toHaveBeenCalled(); }); }); describe('closing connection', function () { beforeEach(function () { instance.initialize(); instance.addClient('endpoint'); ws1 = wsInstances['endpoint']; }); it('should remove ephemeral nodes on close', function () { ws1.onMessage(13, 'set', { key: '/key', value: 'value', options: { flags: 1 } }); ws1.emit('close'); expect(driver.remove).toHaveBeenCalledWith('/key', {}, jasmine.any(Function)); }); it('should not try to remove ephemeral nodes removed before when closing connection', function () { ws1.onMessage(13, 'set', { key: '/key', value: 'value', options: { flags: 1 } }); ws1.onMessage(14, 'remove', { key: '/key' }); ws1.emit('close'); expect(driver.remove.callCount).toBe(1); }); it('should try to reconnect', function () { ws1.emit('close'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_INTERVAL + 1); expect(WebSocketMock.callCount).toBe(2); }); it('should increate reconnect interval up to RECONNECT_MAX_INTERVAL', function () { for (var i = 0; i < 20; i++) { wsInstances['endpoint'].emit('close'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_MAX_INTERVAL + 1); } wsInstances['endpoint'].emit('close'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_MAX_INTERVAL - 1); expect(WebSocketMock.callCount).toBe(21); jasmine.Clock.tick(2); expect(WebSocketMock.callCount).toBe(22); }); }); describe('connection error', function () { beforeEach(function () { instance.initialize(); instance.addClient('endpoint'); ws1 = wsInstances['endpoint']; }); it('should reconnect on error', function () { ws1.emit('error'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_INTERVAL + 1); expect(WebSocketMock.callCount).toBe(2); }); it('should reconnect only once on multiple errors', function () { ws1.emit('error'); ws1.emit('error'); jasmine.Clock.tick(ForwardingConfigurationManager.RECONNECT_INTERVAL + 1); expect(WebSocketMock.callCount).toBe(2); }); }); describe('ping clients', function () { beforeEach(function () { instance.initialize(); instance.setClients(['endpoint1', 'endpoint2']); ws1 = wsInstances['endpoint1']; ws2 = wsInstances['endpoint2']; }); it('should ping clients on each interval', function () { instance.setPingInterval(13); jasmine.Clock.tick(28); expect(ws1.ping).toHaveBeenCalledWith(jasmine.any(Number), {}, true); expect(ws2.ping).toHaveBeenCalledWith(jasmine.any(Number), {}, true); expect(ws1.ping.callCount).toBe(2); expect(ws2.ping.callCount).toBe(2); }); it('should not ping through closed socket', function () { instance.setPingInterval(13); ws2.readyState = WebSocket.CLOSED; jasmine.Clock.tick(15); expect(ws1.ping).toHaveBeenCalled(); expect(ws2.ping).not.toHaveBeenCalled(); }); it('should not disconnect after first ping timeout', function () { instance.setPingInterval(13); instance.setPingTimeout(10); jasmine.Clock.tick(13 + 10 + 1); expect(ws1.close).not.toHaveBeenCalled(); expect(ws2.close).not.toHaveBeenCalled(); }); it('should reconnect after MAX_PING_TIMEOUTS', function () { instance.setPingInterval(13); instance.setPingTimeout(10); jasmine.Clock.tick((ForwardingConfigurationManager.MAX_PING_TIMEOUTS * (10 + 13)) + 1); expect(ws1.close).toHaveBeenCalled(); expect(ws2.close).toHaveBeenCalled(); }); it('should not disconnect when client replies with pong', function () { instance.setPingInterval(13); instance.setPingTimeout(10); for (var i = 0; i < ForwardingConfigurationManager.MAX_PING_TIMEOUTS + 1; i++) { jasmine.Clock.tick(13 + 1); ws1.emit('pong'); ws2.emit('pong'); } expect(ws1.close).not.toHaveBeenCalled(); expect(ws2.close).not.toHaveBeenCalled(); }); }); describe('client methods', function () { beforeEach(function () { instance.initialize(); instance.setClients(['endpoint1', 'endpoint2']); ws1 = wsInstances['endpoint1']; ws2 = wsInstances['endpoint2']; }); describe('exists', function () { it('should check in dataprovider and return result', function () { driver.exists.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(1, 'exists', { key: '/key1' }); expect(driver.exists).toHaveBeenCalledWith('/key1', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":1,"method":"exists","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); }); describe('get', function () { it('should get from dataprovider and return result', function () { driver.get.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(2, 'get', { key: '/key2' }); expect(driver.get).toHaveBeenCalledWith('/key2', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":2,"method":"get","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); }); describe('remove', function () { it('should remove from dataprovider and return result', function () { driver.remove.andCallFake(function (key, options, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(3, 'remove', { key: '/key3', options: 'options' }); expect(driver.remove).toHaveBeenCalledWith('/key3', 'options', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":3,"method":"remove","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); }); describe('set', function () { it('should set to dataprovider and return result', function () { driver.set.andCallFake(function (key, value, options, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(4, 'set', { key: '/key4', value: 'value', options: 'options' }); expect(driver.set).toHaveBeenCalledWith('/key4', 'value', 'options', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":4,"method":"set","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); }); describe('sync', function () { it('should sync via dataprovider and return result', function () { driver.sync.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(5, 'sync', { key: '/key5' }); expect(driver.sync).toHaveBeenCalledWith('/key5', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":5,"method":"sync","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); }); describe('watch', function () { it('should watch on dataprovider and return result for the first watch', function () { driver.watch.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(6, 'watch', { key: '/key6' }); expect(driver.watch).toHaveBeenCalledWith('/key6', jasmine.any(Function)); expect(ws1.send).toHaveBeenCalledWith('{"id":6,"method":"client-watch-init","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); it('should get from dataprovider and return result for second watch on the same endpoint', function () { driver.get.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(7, 'watch', { key: '/key7' }); ws1.onMessage(8, 'watch', { key: '/key7' }); expect(driver.watch).toHaveBeenCalledWith('/key7', jasmine.any(Function)); expect(driver.watch.callCount).toBe(1); expect(ws1.send).toHaveBeenCalledWith('{"id":8,"method":"client-watch-init","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).not.toHaveBeenCalled(); }); it('should get from dataprovider and return result for second watch on new endpoint', function () { driver.get.andCallFake(function (key, cb) { return cb('rc', 'data', 'stat'); }); ws1.onMessage(9, 'watch', { key: '/key8' }); ws2.onMessage(10, 'watch', { key: '/key8' }); expect(driver.watch).toHaveBeenCalledWith('/key8', jasmine.any(Function)); expect(driver.watch.callCount).toBe(1); expect(ws2.send).toHaveBeenCalledWith('{"id":10,"method":"client-watch-init","rc":"rc","data":"data","stat":"stat","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line }); it('should send notification on change to all endpoints', function () { var watchCb; driver.get.andCallFake(function (key, cb) { return cb('rc1', 'data1', 'stat1'); }); driver.watch.andCallFake(function (key, cb) { watchCb = cb; return cb('rc2', 'data2', 'stat2'); }); ws1.onMessage(11, 'watch', { key: '/key9' }); ws2.onMessage(12, 'watch', { key: '/key9' }); watchCb('rc3', 'data3', 'stat3'); expect(ws1.send).toHaveBeenCalledWith('{"method":"notify","params":{"key":"/key9","rc":"rc3","data":"data3","stat":"stat3","error":null}}', wsParams, jasmine.any(Function)); // jshint ignore: line expect(ws2.send).toHaveBeenCalledWith('{"method":"notify","params":{"key":"/key9","rc":"rc3","data":"data3","stat":"stat3","error":null}}', wsParams, jasmine.any(Function)); // jshint ignore: line }); it('should not throw on change when endpoints disconnected', function () { var watchCb; driver.watch.andCallFake(function (key, cb) { watchCb = cb; return cb('rc', 'data', 'stat'); }); ws1.onMessage(11, 'watch', { key: '/key9' }); instance.removeClient('endpoint1'); expect(function () { watchCb('rc1', 'data1', 'stat1'); }).not.toThrow(); }); }); it('should not throw on method with wrong params', function () { expect(function () { ws1.onMessage(7, 'sync', null); expect(ws1.send).toHaveBeenCalledWith('{"id":7,"method":"sync","rc":-1002,"data":"Wrong params","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line }).not.toThrow(); }); it('should not throw on unknown method', function () { expect(function () { ws1.onMessage(7, 'unknown', { key: '/key10' }); expect(ws1.send).toHaveBeenCalledWith('{"id":7,"method":"unknown","rc":-1001,"data":"Unknown method","error":null}', wsParams, jasmine.any(Function)); // jshint ignore: line }).not.toThrow(); }); it('should not throw on malformed message', function () { expect(function () { ws1.emit('message', '@$%@#@$@#'); }).not.toThrow(); }); it('should return with ZCONNECTIONLOSS when driver not connected', function () { driver.isConnected.andReturn(false); ws1.onMessage(8, 'get', { key: '/key11' }); expect(ws1.send).toHaveBeenCalledWith('{"id":8,"method":"get","rc":-4,"error":null}', wsParams, jasmine.any(Function)); }); it('should queue client calls', function () { var callbacks = {}; driver.get.andCallFake(function (key, _callback) { callbacks[key] = _callback; }); instance.setMaxConcurrentQueries(2); ws1.onMessage(101, 'get', { 'key': '/key101' }); ws1.onMessage(102, 'get', { 'key': '/key102' }); ws1.onMessage(103, 'get', { 'key': '/key103' }); expect(driver.get.callCount).toBe(2); callbacks['/key101'](); callbacks['/key102'](); callbacks['/key103'](); expect(driver.get.callCount).toBe(3); }); }); });