UNPKG

pusher-js

Version:

Pusher Channels JavaScript library for browsers, React Native, NodeJS and web workers

646 lines (525 loc) 19.8 kB
var ConnectionManager = require('core/connection/connection_manager').default; var Collections = require('core/utils/collections'); var Network = require('net_info').Network; var Mocks = require("mocks"); describe("ConnectionManager", function() { var connection, strategy, timeline; var managerOptions, manager; beforeAll(() => { jasmine.clock().install(); }); afterAll(() => { jasmine.clock().uninstall(); }); beforeEach(function() { connection = Mocks.getConnection(); strategy = Mocks.getStrategy(true); timeline = Mocks.getTimeline(); spyOn(Network, "isOnline").and.returnValue(true); managerOptions = { getStrategy: jasmine.createSpy("getStrategy").and.returnValue(strategy), timeline: timeline, activityTimeout: 3456, pongTimeout: 2345, unavailableTimeout: 1234, useTLS: false, }; manager = new ConnectionManager("foo", managerOptions); }); describe("on construction", function() { it("should construct a strategy", function() { expect(manager.options.getStrategy.calls.count()).toEqual(1); }); it("should pass the key to the strategy builder", function() { expect(manager.options.getStrategy.calls.first().args[0].key).toEqual("foo"); }); it("should pass a timeline to the strategy builder", function() { var getStrategy = jasmine.createSpy("getStrategy").and.callFake(function(options) { expect(options.timeline).toBe(timeline); return strategy; }); var manager = new ConnectionManager("foo", { getStrategy: getStrategy, timeline: timeline, activityTimeout: 3456, pongTimeout: 2345, unavailableTimeout: 1234 }, {}); expect(getStrategy).toHaveBeenCalled(); }); it("should transition to initialized state", function() { expect(manager.state).toEqual("initialized"); }); }); describe("#isUsingTLS", function() { it("should return false if the manager has been created with useTLS=false", function() { expect(manager.isUsingTLS()).toEqual(false); }); it("should return true if the manager has been created with useTLS=true", function() { var manager = new ConnectionManager( "foo", Object.assign({}, managerOptions, { useTLS: true }) ); expect(manager.isUsingTLS()).toEqual(true); }); }); describe("#connect", function() { it("should not re-build the strategy", function() { manager.connect(); expect(managerOptions.getStrategy.calls.count()).toEqual(1); }); it("should try to connect using the strategy", function() { manager.connect(); expect(strategy.connect).toHaveBeenCalled(); }); it("should transition to connecting", function() { var onConnecting = jasmine.createSpy("onConnecting"); var onStateChange = jasmine.createSpy("onStateChange"); manager.bind("connecting", onConnecting); manager.bind("state_change", onStateChange); manager.connect(); expect(manager.state).toEqual("connecting"); expect(onConnecting).toHaveBeenCalled(); expect(onStateChange).toHaveBeenCalledWith({ previous: "initialized", current: "connecting" }); }); }); describe("before establishing a connection", function() { beforeEach(function() { manager.connect(); }); describe("#send", function() { it("should not send data", function() { expect(manager.send("FALSE!")).toBe(false); }); }); describe("#disconnect", function() { it("should transition to disconnected", function() { var onDisconnected = jasmine.createSpy("onDisconnected"); manager.bind("disconnected", onDisconnected); manager.disconnect(); expect(onDisconnected).toHaveBeenCalled(); }); it("should abort an unfinished connection attempt", function() { manager.connect(); manager.disconnect(); expect(strategy._abort).toHaveBeenCalled(); }); it("should clear the unavailable timer", function() { manager.disconnect(); jasmine.clock().tick(10000); // if unavailable timer had worked, it would have transitioned into 'unavailable' expect(manager.state).toEqual("disconnected"); }); }); describe("on unavailable timeout", function() { it("should fire the timer and transition to unavailable", function() { var onUnavailable = jasmine.createSpy("onUnavailable"); manager.bind("unavailable", onUnavailable); jasmine.clock().tick(1233); expect(manager.state).toEqual("connecting"); jasmine.clock().tick(1); expect(manager.state).toEqual("unavailable"); expect(onUnavailable).toHaveBeenCalled(); }); }); }); describe("on handshake", function() { var handshake; beforeEach(function() { manager.connect(); }); describe("with 'tls_only' action", function() { var tlsStrategy; beforeEach(function() { tlsStrategy = Mocks.getStrategy(true); managerOptions.getStrategy.and.returnValue(tlsStrategy); handshake = { action: "tls_only" }; strategy._callback(null, handshake); }); it("should build a TLS strategy", function() { expect(managerOptions.getStrategy.calls.count()).toEqual(2); expect(managerOptions.getStrategy).toHaveBeenCalledWith({ key: "foo", useTLS: true, timeline: timeline }); }); it("should connect using the TLS strategy", function() { // connection is retried with a zero delay jasmine.clock().tick(0); expect(tlsStrategy.connect).toHaveBeenCalled(); expect(manager.state).toEqual("connecting"); }); it("should transition to 'connecting'", function() { expect(manager.state).toEqual("connecting"); }); it("#isUsingTLS should return true", function() { expect(manager.isUsingTLS()).toEqual(true); }); }); describe("with 'refused' action", function() { var handshake; beforeEach(function() { handshake = { action: "refused" }; strategy._callback(null, handshake); }); it("should transition to 'disconnected'", function() { expect(manager.state).toEqual("disconnected"); }); it("should not reconnect", function() { jasmine.clock().tick(100000); expect(manager.state).toEqual("disconnected"); }); }); describe("with 'retry' action", function() { var handshake; beforeEach(function() { handshake = { action: "retry" }; strategy._callback(null, handshake); }); it("should reconnect immediately", function() { jasmine.clock().tick(0); expect(manager.state).toEqual("connecting"); }); }); describe("with 'backoff' action", function() { var handshake; var onConnectingIn; beforeEach(function() { handshake = { action: "backoff" }; onConnectingIn = jasmine.createSpy("onConnectingIn"); manager.bind("connecting_in", onConnectingIn); strategy._callback(null, handshake); }); it("should reconnect after 1s", function() { jasmine.clock().tick(999); expect(strategy.connect.calls.count()).toEqual(1); jasmine.clock().tick(1); expect(strategy.connect.calls.count()).toEqual(2); }); it("should emit 'connecting_in' event", function() { expect(onConnectingIn.calls.count()).toEqual(1); expect(onConnectingIn).toHaveBeenCalledWith(1); }); }); describe("with 'error' action", function() { var handshake; var onConnectingIn; beforeEach(function() { handshake = { action: "error", error: "boom" }; strategy._callback(null, handshake); }); it("should log the error to the timeline", function() { expect(timeline.error).toHaveBeenCalledWith({ handshakeError: "boom" }); }); it("should not abort the strategy", function() { expect(strategy._abort).not.toHaveBeenCalled(); }); }); }); describe("after establishing a connection", function() { var handshake; var onConnected; beforeEach(function() { onConnected = jasmine.createSpy("onConnected"); manager.bind("connected", onConnected); manager.connect(); connection.id = "123.456"; handshake = { action: "connected", connection: connection, activityTimeout: 500 }; strategy._callback(null, handshake); }); it("should transition to connected", function() { expect(onConnected).toHaveBeenCalled(); }); it("should assign 'socket_id' to the manager", function() { expect(manager.socket_id).toEqual("123.456"); }); it("should abort substrategy immediately", function() { expect(strategy._abort).toHaveBeenCalled(); }); it("should clear the unavailable timer", function() { jasmine.clock().tick(1500); // if unavailable timer was not cleared, state should be unavailable expect(manager.state).toEqual("connected"); }); it("should not try to connect again", function() { expect(strategy.connect.calls.count()).toEqual(1); manager.connect(); expect(strategy.connect.calls.count()).toEqual(1); }); describe("#send", function() { it("should pass data to the connection", function() { expect(manager.send("howdy")).toBe(true); expect(connection.send).toHaveBeenCalledWith("howdy"); }); }); describe("#disconnect", function() { it("should transition to disconnected", function() { var onDisconnected = jasmine.createSpy("onDisconnected"); manager.bind("disconnected", onDisconnected); manager.disconnect(); expect(onDisconnected).toHaveBeenCalled(); }); it("should close the connection", function() { manager.disconnect(); expect(connection.close).toHaveBeenCalled(); }); it("should clear the activity check", function() { manager.disconnect(); jasmine.clock().tick(10000); // if activity check had worked, it would have sent a ping message expect(connection.ping).not.toHaveBeenCalled(); }); it("should stop emitting received messages", function() { var onMessage = jasmine.createSpy("onMessage"); manager.bind("message", onMessage); manager.disconnect(); connection.emit("message", {}); expect(onMessage).not.toHaveBeenCalled(); }); }); describe("and losing the connection", function() { var onConnecting, onDisconnected; beforeEach(function() { onConnecting = jasmine.createSpy("onConnecting"); onDisconnected = jasmine.createSpy("onDisconnected"); manager.bind("connecting", onConnecting); manager.bind("disconnected", onDisconnected); connection.emit("closed"); }); it("should transition to 'connecting' after 1s", function() { jasmine.clock().tick(999); expect(onConnecting).not.toHaveBeenCalled(); jasmine.clock().tick(1); expect(onConnecting).toHaveBeenCalled(); expect(manager.state).toEqual("connecting"); }); it("should clean up the activity check", function() { jasmine.clock().tick(10000); // if activity check had worked, it would have sent a ping message expect(connection.ping).not.toHaveBeenCalled(); }); }); describe("while reconnecting", function() { it("should re-use the strategy", function() { expect(managerOptions.getStrategy.calls.count()).toEqual(1); expect(strategy.connect.calls.count()).toEqual(1); manager.disconnect(); manager.connect(); expect(managerOptions.getStrategy.calls.count()).toEqual(1); expect(strategy.connect.calls.count()).toEqual(2); }); }); describe("on activity timeout", function() { it("should send a ping", function() { jasmine.clock().tick(499); expect(connection.ping).not.toHaveBeenCalled(); jasmine.clock().tick(1); expect(connection.ping).toHaveBeenCalled(); jasmine.clock().tick(999); expect(connection.close).not.toHaveBeenCalled(); connection.emit("activity"); // pong received, connection should not get closed jasmine.clock().tick(1000); expect(connection.close).not.toHaveBeenCalled(); }); it("should close the connection after pong timeout", function() { jasmine.clock().tick(500); expect(connection.close).not.toHaveBeenCalled(); jasmine.clock().tick(2346); expect(connection.close).toHaveBeenCalled(); }); }); describe("on connection error", function() { it("should emit an error", function() { var onError = jasmine.createSpy("onError"); manager.bind("error", onError); connection.emit("error", { type: "WebSocketError", error: { boom: "boom" } }); expect(onError).toHaveBeenCalledWith({ type: "WebSocketError", error: { boom: "boom" } }); }); }); describe("on ping", function() { it("should reply with a pusher:pong event", function() { connection.emit("ping"); expect(connection.send_event).toHaveBeenCalledWith( "pusher:pong", {}, undefined ); }); }); describe("on offline event", function() { it("should send an activity check and disconnect after no pong response", function() { Network.emit("offline"); expect(connection.ping).toHaveBeenCalled(); jasmine.clock().tick(2345); expect(manager.state).toEqual("connected"); jasmine.clock().tick(1); expect(manager.state).toEqual("connecting"); }); }); describe("on error callback", function() { var errorHandler; beforeEach(function() { errorHandler = jasmine.createSpy(); manager.bind("error", errorHandler) }); it("should emit error and disconnect upon receipt of a refused event", function() { var error = {code: 4000}; connection.emit("refused", {error: error}); expect(manager.state).toEqual("disconnected"); expect(errorHandler).toHaveBeenCalledWith({ type: 'WebSocketError', error: error }); }); it("should emit error and reconnect upon receipt of a tls_only event", function() { var error = {code: 4000}; connection.emit("tls_only", {error: error}); jasmine.clock().tick(1); expect(manager.state).toEqual("connecting"); expect(errorHandler).toHaveBeenCalledWith({ type: 'WebSocketError', error: error }); }); it("should emit error and reconnect with backoff upon receipt of a backoff event", function() { var error = {code: 4100}; connection.emit("backoff", {error: error}); jasmine.clock().tick(1000); expect(manager.state).toEqual("connecting"); expect(errorHandler).toHaveBeenCalledWith({ type: 'WebSocketError', error: error }); }); it("should emit error and reconnect upon receipt of a retry event", function() { var error = {code: 4200}; connection.emit("retry", {error: error}); jasmine.clock().tick(1); expect(manager.state).toEqual("connecting"); expect(errorHandler).toHaveBeenCalledWith({ type: 'WebSocketError', error: error }); }); }); }); describe("after establishing a connection which handles activity checks by iself", function() { beforeEach(function() { manager.connect(); connection.id = "123.456"; connection.handlesActivityChecks.and.returnValue(true); strategy._callback(null, { action: "connected", connection: connection, activityTimeout: 999999 }); }); describe("on activity timeout", function() { it("should not send a ping or close a connection", function() { jasmine.clock().tick(10000); expect(connection.ping).not.toHaveBeenCalled(); expect(connection.close).not.toHaveBeenCalled(); }); }); }); describe("on online event", function() { it("should retry when in 'connecting' state", function() { manager.connect(); expect(strategy.connect.calls.count()).toEqual(1); Network.emit("online"); expect(strategy.connect.calls.count()).toEqual(1); expect(manager.state).toEqual("connecting"); jasmine.clock().tick(1); expect(strategy.connect.calls.count()).toEqual(2); }); it("should retry when in 'unavailable' state", function() { manager.connect(); expect(strategy.connect.calls.count()).toEqual(1); jasmine.clock().tick(1234); expect(strategy.connect.calls.count()).toEqual(1); expect(manager.state).toEqual("unavailable"); Network.emit("online"); jasmine.clock().tick(1); expect(strategy.connect.calls.count()).toEqual(2); }); }); describe("on strategy error", function() { it("should connect again using the same strategy", function() { manager.connect(); expect(strategy.connect.calls.count()).toEqual(1); strategy._callback(true); expect(strategy.connect.calls.count()).toEqual(2); expect(manager.state).toEqual("connecting"); }); }); describe("with unsupported strategy", function() { it("should transition to failed on connect", function() { strategy.isSupported = jasmine.createSpy("isSupported") .and.returnValue(false); var onFailed = jasmine.createSpy("onFailed"); manager.bind("failed", onFailed); manager.connect(); expect(onFailed).toHaveBeenCalled(); }); }); describe("with adjusted activity timeouts", function() { var handshake; var onConnected; beforeEach(function() { manager.connect(); connection.id = "123.456"; }); it("should use the activity timeout value from the connection, if it's the lowest", function() { connection.activityTimeout = 2666; handshake = { action: "connected", connection: connection, activityTimeout: 2667 }; strategy._callback(null, handshake); jasmine.clock().tick(2665); expect(connection.send_event).not.toHaveBeenCalled(); jasmine.clock().tick(1); expect(connection.ping).toHaveBeenCalled(); }); it("should use the handshake activity timeout value, if it's the lowest", function() { connection.activityTimeout = 3455; handshake = { action: "connected", connection: connection, activityTimeout: 3400 }; strategy._callback(null, handshake); jasmine.clock().tick(3399); expect(connection.send_event).not.toHaveBeenCalled(); jasmine.clock().tick(1); expect(connection.ping).toHaveBeenCalled(); }); it("should use the default activity timeout value, if it's the lowest", function() { connection.activityTimeout = 5555; handshake = { action: "connected", connection: connection, activityTimeout: 3500 }; strategy._callback(null, handshake); jasmine.clock().tick(3455); expect(connection.send_event).not.toHaveBeenCalled(); jasmine.clock().tick(1); expect(connection.ping).toHaveBeenCalled(); }); }); });