"use strict";

var _typeof = require("@babel/runtime-corejs2/helpers/typeof");
var _Object$keys = require("@babel/runtime-corejs2/core-js/object/keys");
var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs2/core-js/object/get-own-property-symbols");
var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
var _Object$getOwnPropertyDescriptors = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptors");
var _Object$defineProperties = require("@babel/runtime-corejs2/core-js/object/define-properties");
var _Object$defineProperty2 = require("@babel/runtime-corejs2/core-js/object/define-property");
var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));
var _construct2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/construct"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/define-property"));
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
var _now = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/date/now"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
var _apply = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/apply"));
var _crypto = require("crypto");
var _sinon = _interopRequireDefault(require("sinon"));
var _testHelperChai = require("@webex/test-helper-chai");
var _testHelperMockWebex = _interopRequireDefault(require("@webex/test-helper-mock-webex"));
var _testHelperMockWebSocket = _interopRequireDefault(require("@webex/test-helper-mock-web-socket"));
var _index = _interopRequireWildcard(require("./index"));
var _errors = require("./errors");
var _config = _interopRequireDefault(require("./config"));
var _socket = _interopRequireDefault(require("./socket"));
var _mochaHelpers = require("./test/mocha-helpers");
var _constants = require("./socket/constants");
var _promiseTick = _interopRequireDefault(require("./test/promise-tick"));
function _interopRequireWildcard(e, t) { if ("function" == typeof _WeakMap) var r = new _WeakMap(), n = new _WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = _Object$defineProperty2) && _Object$getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
function ownKeys(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { _Object$defineProperty2(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; } /*!
 * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
 */
if (!crypto.randomUUID) {
  (0, _defineProperty3.default)(crypto, 'randomUUID', {
    value: _crypto.randomUUID,
    configurable: true
  });
}
describe('plugin-mobius-socket', function () {
  var createUuid = function createUuid() {
    return crypto.randomUUID();
  };
  describe('getMobiusSocketInstance', function () {
    afterEach(function () {
      (0, _index.resetMobiusSocketInstance)();
    });
    it('uses package config when consumer config is not provided', function () {
      var configuredWebex = new _testHelperMockWebex.default();
      var instance = (0, _index.getMobiusSocketInstance)(configuredWebex);
      _testHelperChai.assert.instanceOf(instance, _index.default);
      _testHelperChai.assert.deepEqual(instance.config, _config.default.mobiusSocket);
    });
    it('uses consumer config when it is provided', function () {
      var configuredWebex = new _testHelperMockWebex.default();
      var consumerConfig = {
        initialConnectionMaxRetries: 0,
        backoffTimeReset: 1234
      };
      var instance = (0, _index.getMobiusSocketInstance)(configuredWebex, consumerConfig);
      _testHelperChai.assert.instanceOf(instance, _index.default);
      _testHelperChai.assert.deepEqual(instance.config, _objectSpread(_objectSpread({}, _config.default.mobiusSocket), consumerConfig));
    });
  });
  describe('MobiusSocket', function () {
    var mobiusSocket;
    var mockWebSocket;
    var socketOpenStub;
    var usingFakeTimers;
    var webex;
    var statusStartTypingMessage = (0, _stringify.default)({
      id: createUuid(),
      data: {
        eventType: 'status.start_typing',
        actor: {
          id: 'actorId'
        },
        conversationId: createUuid()
      },
      timestamp: (0, _now.default)(),
      trackingId: "suffix_".concat(createUuid(), "_").concat((0, _now.default)())
    });
    var emitAuthResponse = function emitAuthResponse() {
      var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
        _ref$statusCode = _ref.statusCode,
        statusCode = _ref$statusCode === void 0 ? 200 : _ref$statusCode,
        _ref$statusMessage = _ref.statusMessage,
        statusMessage = _ref$statusMessage === void 0 ? 'OK' : _ref$statusMessage;
      var authRequest = JSON.parse(mockWebSocket.send.lastCall.args[0]);
      mockWebSocket.emit('message', {
        data: (0, _stringify.default)({
          type: 'response_event',
          subtype: _constants.MESSAGE_TYPES.AUTH,
          trackingId: authRequest.trackingId,
          statusCode: statusCode,
          statusMessage: statusMessage
        })
      });
    };
    var createAsyncEvent = function createAsyncEvent(eventId) {
      var eventType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'custom.event';
      return {
        data: {
          type: 'async_event',
          eventId: eventId,
          data: {
            eventType: eventType
          }
        }
      };
    };
    var createSessionSocket = function createSessionSocket() {
      return {
        close: _sinon.default.stub().returns(_promise.default.resolve()),
        removeAllListeners: _sinon.default.stub()
      };
    };
    var countGenericEventEmits = function countGenericEventEmits(emitSpy) {
      return emitSpy.getCalls().filter(function (call) {
        return call.args[0] === 'event';
      }).length;
    };
    beforeEach(function () {
      jest.useFakeTimers({
        doNotFake: ['nextTick']
      });
      usingFakeTimers = true;
    });
    beforeEach(function () {
      webex = new _testHelperMockWebex.default();
      webex.credentials = {
        refresh: _sinon.default.stub().returns(_promise.default.resolve()),
        getUserToken: _sinon.default.stub().returns(_promise.default.resolve({
          toString: function toString() {
            return 'Bearer FAKE';
          }
        }))
      };
      webex.internal.device = {
        registered: true,
        register: _sinon.default.stub().returns(_promise.default.resolve()),
        refresh: _sinon.default.stub().returns(_promise.default.resolve()),
        webSocketUrl: 'ws://example.com',
        getWebSocketUrl: _sinon.default.stub().returns(_promise.default.resolve('ws://example-2.com')),
        useServiceCatalogUrl: _sinon.default.stub().returns(_promise.default.resolve('https://service-catalog-url.com'))
      };
      webex.internal.services = {
        convertUrlToPriorityHostUrl: _sinon.default.stub().returns(_promise.default.resolve('ws://example-2.com')),
        markFailedUrl: _sinon.default.stub().returns(_promise.default.resolve()),
        switchActiveClusterIds: _sinon.default.stub(),
        invalidateCache: _sinon.default.stub(),
        isValidHost: _sinon.default.stub().returns(_promise.default.resolve(true))
      };
      webex.internal.metrics.submitClientMetrics = _sinon.default.stub();
      webex.trackingId = 'fakeTrackingId';
      webex.logger = console;
      _sinon.default.stub(_socket.default, 'getWebSocketConstructor').callsFake(function () {
        return function () {
          for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
          }
          mockWebSocket = (0, _construct2.default)(_testHelperMockWebSocket.default, args);
          return mockWebSocket;
        };
      });
      var origOpen = _socket.default.prototype.open;
      socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').callsFake(function () {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        var promise = (0, _apply.default)(origOpen, this, args);
        process.nextTick(function () {
          mockWebSocket.open();
          // Simulate Mobius auth response after socket open
          process.nextTick(function () {
            emitAuthResponse();
          });
        });
        return promise;
      });
      mobiusSocket = new _index.default(webex, _objectSpread({}, _config.default.mobiusSocket));
      mobiusSocket.logger = {
        debug: jest.fn(),
        error: jest.fn(),
        info: jest.fn(),
        log: jest.fn(),
        warn: jest.fn()
      };
    });
    afterEach(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee() {
      var _t;
      return _regenerator.default.wrap(function (_context) {
        while (1) switch (_context.prev = _context.next) {
          case 0:
            if (usingFakeTimers) {
              jest.useRealTimers();
              usingFakeTimers = false;
            }

            // Clean up MobiusSocket connections and internal state
            if (!mobiusSocket) {
              _context.next = 4;
              break;
            }
            _context.prev = 1;
            _context.next = 2;
            return mobiusSocket.disconnect();
          case 2:
            _context.next = 4;
            break;
          case 3:
            _context.prev = 3;
            _t = _context["catch"](1);
          case 4:
            // Ensure mock socket is properly closed
            if (mockWebSocket && typeof mockWebSocket.close === 'function') {
              try {
                mockWebSocket.close();
              } catch (e) {
                // Ignore cleanup errors
              }
            }
            if (socketOpenStub) {
              socketOpenStub.restore();
            }
            if (_socket.default.getWebSocketConstructor.restore) {
              _socket.default.getWebSocketConstructor.restore();
            }

            // Small delay to ensure all async operations complete
            _context.next = 5;
            return new _promise.default(function (resolve) {
              setTimeout(resolve, 10);
            });
          case 5:
          case "end":
            return _context.stop();
        }
      }, _callee, null, [[1, 3]]);
    })));
    describe('#connect()', function () {
      it('lazily registers the device', function () {
        webex.internal.device.registered = false;
        _testHelperChai.assert.notCalled(webex.internal.device.register);
        var promise = mobiusSocket.connect();
        return promise.then(function () {
          _testHelperChai.assert.calledOnce(webex.internal.device.register);
        });
      });
      it('connects to MobiusSocket using default url', function () {
        var promise = mobiusSocket.connect();
        _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
        _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
        mockWebSocket.open();
        return promise.then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match.any);
        });
      });
      describe('when `maxRetries` is set', function () {
        var check = function check() {
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open');
          socketOpenStub.returns(_promise.default.reject(new _errors.ConnectionError()));
          _testHelperChai.assert.notCalled(_socket.default.prototype.open);
          var promise = mobiusSocket.connect();
          return (0, _promiseTick.default)(5).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            jest.advanceTimersByTime(mobiusSocket.config.backoffTimeReset);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            _testHelperChai.assert.calledTwice(_socket.default.prototype.open);
            jest.advanceTimersByTime(2 * mobiusSocket.config.backoffTimeReset);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            _testHelperChai.assert.calledThrice(_socket.default.prototype.open);
            jest.advanceTimersByTime(5 * mobiusSocket.config.backoffTimeReset);
            return _testHelperChai.assert.isRejected(promise);
          }).then(function () {
            _testHelperChai.assert.calledThrice(_socket.default.prototype.open);
          });
        };

        // skipping due to apparent bug with lolex in all browsers but Chrome.
        (0, _mochaHelpers.skipInBrowser)(it)('fails after configured `initialConnectionMaxRetries` attempts', function () {
          mobiusSocket.config.maxRetries = 0;
          mobiusSocket.config.initialConnectionMaxRetries = 2;
          return check();
        });

        // skipping due to apparent bug with lolex in all browsers but Chrome.
        // if initial retries is zero and mobiusSocket has never connected, do not retry
        (0, _mochaHelpers.skipInBrowser)(it)('fails immediately when `initialConnectionMaxRetries` is 0', function () {
          mobiusSocket.config.maxRetries = 2;
          mobiusSocket.config.initialConnectionMaxRetries = 0;
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open');
          socketOpenStub.returns(_promise.default.reject(new _errors.ConnectionError()));
          _testHelperChai.assert.notCalled(_socket.default.prototype.open);
          var promise = mobiusSocket.connect();
          return (0, _promiseTick.default)(5).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            return _testHelperChai.assert.isRejected(promise);
          }).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
          });
        });

        // initial retries is non-zero so takes precedence over maxRetries when mobiusSocket has never connected
        (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
          mobiusSocket.config.maxRetries = 0;
          mobiusSocket.config.initialConnectionMaxRetries = 2;
          return check();
        });

        // initial retries is non-zero so takes precedence over maxRetries when mobiusSocket has never connected
        (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
          mobiusSocket.config.initialConnectionMaxRetries = 2;
          mobiusSocket.config.maxRetries = 5;
          return check();
        });

        // when mobiusSocket has connected maxRetries is used and the initialConnectionMaxRetries is ignored
        (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
          mobiusSocket.config.initialConnectionMaxRetries = 5;
          mobiusSocket.config.maxRetries = 2;
          mobiusSocket.hasEverConnected = true;
          return check();
        });
      });
      it('can safely be called multiple times', function () {
        var promise = _promise.default.all([mobiusSocket.connect(), mobiusSocket.connect(), mobiusSocket.connect(), mobiusSocket.connect()]);
        mockWebSocket.open();
        return promise.then(function () {
          _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
        });
      });

      // skipping due to apparent bug with lolex in all browsers but Chrome.
      (0, _mochaHelpers.skipInBrowser)(describe)('when the connection fails', function () {
        it('backs off exponentially', function () {
          mobiusSocket.config.initialConnectionMaxRetries = 2;
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open');
          socketOpenStub.returns(_promise.default.reject(new _errors.ConnectionError({
            code: 4001
          })));
          // Note: onCall is zero-based
          socketOpenStub.onCall(2).returns(_promise.default.resolve(new _testHelperMockWebSocket.default()));
          _testHelperChai.assert.notCalled(_socket.default.prototype.open);
          var promise = mobiusSocket.connect();
          return (0, _promiseTick.default)(5).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);

            // I'm not sure why, but it's important the clock doesn't advance
            // until a tick happens
            return (0, _promiseTick.default)(5);
          }).then(function () {
            jest.advanceTimersByTime(mobiusSocket.config.backoffTimeReset);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            _testHelperChai.assert.calledTwice(_socket.default.prototype.open);
            jest.advanceTimersByTime(2 * mobiusSocket.config.backoffTimeReset);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            _testHelperChai.assert.calledThrice(_socket.default.prototype.open);
            jest.advanceTimersByTime(5 * mobiusSocket.config.backoffTimeReset);
            return promise;
          }).then(function () {
            _testHelperChai.assert.calledThrice(_socket.default.prototype.open);
            jest.advanceTimersByTime(8 * mobiusSocket.config.backoffTimeReset);
            return (0, _promiseTick.default)(5);
          }).then(function () {
            _testHelperChai.assert.calledThrice(_socket.default.prototype.open);
          });
        });
        describe('with `BadRequest`', function () {
          it('fails permanently', function () {
            jest.useRealTimers();
            usingFakeTimers = false;
            socketOpenStub.restore();
            socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.reject(new _errors.BadRequest({
              code: 4400
            })));
            return _testHelperChai.assert.isRejected(mobiusSocket.connect());
          });
        });
        describe('with `UnknownResponse`', function () {
          it('triggers a device refresh', function () {
            mobiusSocket.config.initialConnectionMaxRetries = 1;
            socketOpenStub.restore();
            socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.resolve());
            socketOpenStub.onCall(0).returns(_promise.default.reject(new _errors.UnknownResponse({
              code: 4444
            })));
            _testHelperChai.assert.notCalled(webex.credentials.refresh);
            _testHelperChai.assert.notCalled(webex.internal.device.refresh);
            var promise = mobiusSocket.connect();
            return (0, _promiseTick.default)(7).then(function () {
              _testHelperChai.assert.notCalled(webex.credentials.refresh);
              _testHelperChai.assert.called(webex.internal.device.refresh);
              jest.advanceTimersByTime(1000);
              return promise;
            });
          });
        });
        describe('with `NotAuthorized`', function () {
          it('triggers a token refresh', function () {
            mobiusSocket.config.initialConnectionMaxRetries = 1;
            socketOpenStub.restore();
            socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.resolve());
            socketOpenStub.onCall(0).returns(_promise.default.reject(new _errors.NotAuthorized({
              code: 4401
            })));
            _testHelperChai.assert.notCalled(webex.credentials.refresh);
            _testHelperChai.assert.notCalled(webex.internal.device.refresh);
            var promise = mobiusSocket.connect();
            return (0, _promiseTick.default)(7).then(function () {
              _testHelperChai.assert.called(webex.credentials.refresh);
              _testHelperChai.assert.notCalled(webex.internal.device.refresh);
              jest.advanceTimersByTime(1000);
              return promise;
            });
          });
        });
        describe('with `Forbidden`', function () {
          it('fails permanently', function () {
            jest.useRealTimers();
            usingFakeTimers = false;
            socketOpenStub.restore();
            socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.reject(new _errors.Forbidden({
              code: 4403
            })));
            return _testHelperChai.assert.isRejected(mobiusSocket.connect());
          });
        });

        // describe(`with \`NotFound\``, () => {
        //   it(`triggers a device refresh`, () => {
        //     socketOpenStub.restore();
        //     socketOpenStub = sinon.stub(Socket.prototype, `open`).returns(Promise.resolve());
        //     socketOpenStub.onCall(0).returns(Promise.reject(new NotFound({code: 4404})));
        //     assert.notCalled(webex.credentials.refresh);
        //     assert.notCalled(webex.internal.device.refresh);
        //     const promise = mobiusSocket.connect();
        //     return promiseTick(6)
        //       .then(() => {
        //         assert.notCalled(webex.credentials.refresh);
        //         assert.called(webex.internal.device.refresh);
        //         clock.tick(1000);
        //         return assert.isFulfilled(promise);
        //       });
        //   });
        // });
      });
      describe('when connected', function () {
        it('resolves immediately', function () {
          return mobiusSocket.connect().then(function () {
            _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
            _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
            var promise = mobiusSocket.connect();
            _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
            _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
            return promise;
          });
        });

        // skipping due to apparent bug with lolex in all browsers but Chrome.
        (0, _mochaHelpers.skipInBrowser)(it)('does not continue attempting to connect', function () {
          var promise = mobiusSocket.connect();

          // Wait for the connection to be established before proceeding
          mockWebSocket.open();
          return promise.then(function () {
            return (0, _promiseTick.default)(2).then(function () {
              jest.advanceTimersByTime(6 * mobiusSocket.config.backoffTimeReset);
              return (0, _promiseTick.default)(2);
            }).then(function () {
              _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            });
          });
        });
      });
      describe('when webSocketUrl is provided', function () {
        it('connects to MobiusSocket with provided url', function () {
          var webSocketUrl = 'ws://providedurl.com';
          var promise = mobiusSocket.connect(webSocketUrl);
          _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
          _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
          mockWebSocket.open();
          return promise.then(function () {
            _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
            _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
            _testHelperChai.assert.calledWith(_socket.default.prototype.open, 'ws://providedurl.com', _sinon.default.match.any);
          });
        });
      });
      describe('when config.initialConnectionMaxRetries is set to 0', function () {
        it('connects successfully through the shared backoff flow', function () {
          var backoffSpy = _sinon.default.spy(mobiusSocket, 'connectWithBackoff');
          mobiusSocket.config.initialConnectionMaxRetries = 0;
          var promise = mobiusSocket.connect('ws://example.com');
          _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
          mockWebSocket.open();
          return promise.then(function () {
            var _backoffSpy$firstCall;
            _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
            _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
            _testHelperChai.assert.isTrue(mobiusSocket.hasEverConnected, 'hasEverConnected is true');
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            _testHelperChai.assert.calledOnce(backoffSpy);
            _testHelperChai.assert.isUndefined((_backoffSpy$firstCall = backoffSpy.firstCall.args[2]) === null || _backoffSpy$firstCall === void 0 ? void 0 : _backoffSpy$firstCall.initialConnectionMaxRetries);
            backoffSpy.restore();
          });
        });
        it('rejects immediately on failure without retrying', function () {
          jest.useRealTimers();
          usingFakeTimers = false;
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.reject(new _errors.ConnectionError({
            code: 4001
          })));
          mobiusSocket.config.initialConnectionMaxRetries = 0;
          var promise = mobiusSocket.connect('ws://example.com');
          return _testHelperChai.assert.isRejected(promise).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
          });
        });
        it('uses config-driven initial retry behavior in the shared backoff strategy', function () {
          var backoffSpy = _sinon.default.spy(mobiusSocket, 'connectWithBackoff');
          mobiusSocket.config.initialConnectionMaxRetries = 0;
          var promise = mobiusSocket.connect('ws://example.com');
          mockWebSocket.open();
          return promise.then(function () {
            var _backoffSpy$firstCall2;
            _testHelperChai.assert.calledOnce(backoffSpy);
            _testHelperChai.assert.isUndefined((_backoffSpy$firstCall2 = backoffSpy.firstCall.args[2]) === null || _backoffSpy$firstCall2 === void 0 ? void 0 : _backoffSpy$firstCall2.initialConnectionMaxRetries);
            backoffSpy.restore();
          });
        });
        it('treats a different explicit URL as a fresh initial connect', function () {
          jest.useRealTimers();
          usingFakeTimers = false;
          mobiusSocket.hasEverConnected = true;
          mobiusSocket.socketUrl = 'ws://old-url.com';
          mobiusSocket.config.initialConnectionMaxRetries = 0;
          mobiusSocket.config.maxRetries = 5;
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open').returns(_promise.default.reject(new _errors.ConnectionError({
            code: 4001
          })));
          var promise = mobiusSocket.connect('ws://new-url.com');
          return _testHelperChai.assert.isRejected(promise).then(function () {
            _testHelperChai.assert.calledOnce(_socket.default.prototype.open);
            _testHelperChai.assert.equal(mobiusSocket.socketUrl, 'ws://new-url.com');
            _testHelperChai.assert.isFalse(mobiusSocket.hasEverConnected, 'hasEverConnected is false');
          });
        });
      });
    });
    describe('Websocket proxy agent', function () {
      afterEach(function () {
        delete webex.config.defaultMobiusSocketOptions;
      });
      it('connects to MobiusSocket using proxy agent', function () {
        var testProxyUrl = 'http://proxyurl.com:80';
        webex.config.defaultMobiusSocketOptions = {
          agent: {
            proxy: {
              href: testProxyUrl
            }
          }
        };
        var promise = mobiusSocket.connect();
        _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
        _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
        mockWebSocket.open();
        return promise.then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match.has('agent', _sinon.default.match.has('proxy', _sinon.default.match.has('href', testProxyUrl))));
        });
      });
      it('connects to MobiusSocket without proxy agent', function () {
        var promise = mobiusSocket.connect();
        _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
        _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
        mockWebSocket.open();
        return promise.then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match({
            agent: undefined
          }));
        });
      });
    });
    describe('#disconnect()', function () {
      it('disconnects the WebSocket', function () {
        return mobiusSocket.connect().then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          var promise = mobiusSocket.disconnect();
          mockWebSocket.emit('close', {
            code: 1000,
            reason: 'Done'
          });
          return promise;
        }).then(function () {
          _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.isUndefined(mobiusSocket.mockWebSocket, 'MobiusSocket does not have a mockWebSocket');
        });
      });
      it('disconnects the WebSocket with code 3050', function () {
        return mobiusSocket.connect().then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          var promise = mobiusSocket.disconnect();
          mockWebSocket.emit('close', {
            code: 3050,
            reason: 'done (permanent)'
          });
          return promise;
        }).then(function () {
          _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.isUndefined(mobiusSocket.mockWebSocket, 'MobiusSocket does not have a mockWebSocket');
        });
      });
      it('stops emitting message events', function () {
        var spy = _sinon.default.spy();
        mobiusSocket.on('event:status.start_typing', spy);
        return mobiusSocket.connect().then(function () {
          _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
          _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
          _testHelperChai.assert.notCalled(spy);
          mockWebSocket.readyState = 1;
          mockWebSocket.emit('open');
          mockWebSocket.emit('message', {
            data: statusStartTypingMessage
          });
        }).then(function () {
          _testHelperChai.assert.calledOnce(spy);
          var promise = mobiusSocket.disconnect();
          mockWebSocket.readyState = 1;
          mockWebSocket.emit('open');
          mockWebSocket.emit('message', {
            data: statusStartTypingMessage
          });
          mockWebSocket.emit('close', {
            code: 1000,
            reason: 'Done'
          });
          mockWebSocket.emit('message', {
            data: statusStartTypingMessage
          });
          return promise;
        }).then(function () {
          mockWebSocket.readyState = 1;
          mockWebSocket.emit('open');
          mockWebSocket.emit('message', {
            data: statusStartTypingMessage
          });
          _testHelperChai.assert.calledOnce(spy);
        });
      });
      describe('when there is a connection attempt inflight', function () {
        it('stops the attempt when disconnect called', function () {
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open');
          socketOpenStub.onCall(0).returns(
          // Delay the opening of the socket so that disconnect is called while open
          // is in progress
          (0, _promiseTick.default)(2 * mobiusSocket.config.backoffTimeReset)
          // Pretend the socket opened successfully. Failing should be fine too but
          // it generates more console output.
          .then(function () {
            return _promise.default.resolve();
          }));
          var promise = mobiusSocket.connect();

          // Wait for the connect call to setup
          return (0, _promiseTick.default)(mobiusSocket.config.backoffTimeReset).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
            return _regenerator.default.wrap(function (_context2) {
              while (1) switch (_context2.prev = _context2.next) {
                case 0:
                  // By this time backoffCall and mobiusSocket socket should be defined by the
                  // 'connect' call
                  _testHelperChai.assert.isDefined(mobiusSocket.backoffCall, 'MobiusSocket backoffCall is not defined');
                  _testHelperChai.assert.isDefined(mobiusSocket.socket, 'MobiusSocket socket is not defined');
                  // Calling disconnect will abort the backoffCall, close the socket, and
                  // reject the connect
                  mobiusSocket.disconnect();
                  _testHelperChai.assert.isUndefined(mobiusSocket.backoffCall, 'MobiusSocket backoffCall is still defined');
                  // The socket will never be unset (which seems bad)
                  _testHelperChai.assert.isDefined(mobiusSocket.socket, 'MobiusSocket socket is not defined');
                  _context2.next = 1;
                  return _testHelperChai.assert.isRejected(promise);
                case 1:
                case "end":
                  return _context2.stop();
              }
            }, _callee2);
          })));
        });
        it('stops the attempt when backoffCall is undefined', function () {
          socketOpenStub.restore();
          socketOpenStub = _sinon.default.stub(_socket.default.prototype, 'open');
          socketOpenStub.returns(_promise.default.resolve());
          var reason;
          mobiusSocket.backoffCall = undefined;
          var promise = mobiusSocket.attemptConnection('ws://example.com', function (_reason) {
            reason = _reason;
          });
          return (0, _promiseTick.default)(mobiusSocket.config.backoffTimeReset).then(function () {
            _testHelperChai.assert.match(reason.message, /prevent socket open when backoffCall no longer defined/);

            // Ensure the promise was actually rejected (short-circuited)
            return _testHelperChai.assert.isRejected(promise);
          });
        });
      });
    });
    describe('#sendWssRequest()', function () {
      beforeEach(function () {
        mobiusSocket.config.wssResponseTimeout = 100;
      });
      it('resolves when a matching response_event arrives', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3() {
        var requestPromise, requestPayload, response;
        return _regenerator.default.wrap(function (_context3) {
          while (1) switch (_context3.prev = _context3.next) {
            case 0:
              _context3.next = 1;
              return mobiusSocket.connect();
            case 1:
              requestPromise = mobiusSocket.sendWssRequest({
                type: 'auth',
                data: {
                  token: 'test'
                }
              });
              _context3.next = 2;
              return (0, _promiseTick.default)();
            case 2:
              requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
              _testHelperChai.assert.equal(requestPayload.data.token, 'test');
              mockWebSocket.emit('message', {
                data: (0, _stringify.default)({
                  type: 'response_event',
                  subtype: 'auth',
                  trackingId: requestPayload.trackingId,
                  statusCode: 200,
                  statusMessage: 'OK'
                })
              });
              _context3.next = 3;
              return requestPromise;
            case 3:
              response = _context3.sent;
              _testHelperChai.assert.equal(response.type, 'response_event');
              _testHelperChai.assert.equal(response.subtype, 'auth');
              _testHelperChai.assert.equal(response.trackingId, requestPayload.trackingId);
              _testHelperChai.assert.equal(response.statusCode, 200);
            case 4:
            case "end":
              return _context3.stop();
          }
        }, _callee3);
      })));
      it('strips the Bearer prefix from connect-time auth token', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
        var authPayload;
        return _regenerator.default.wrap(function (_context4) {
          while (1) switch (_context4.prev = _context4.next) {
            case 0:
              _context4.next = 1;
              return mobiusSocket.connect();
            case 1:
              authPayload = JSON.parse(mockWebSocket.send.firstCall.args[0]);
              _testHelperChai.assert.equal(authPayload.type, _constants.MESSAGE_TYPES.AUTH);
              _testHelperChai.assert.equal(authPayload.data.token, 'FAKE');
            case 2:
            case "end":
              return _context4.stop();
          }
        }, _callee4);
      })));
      it('rejects when a matching response_event is non-2xx', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5() {
        var requestPromise, requestPayload, error;
        return _regenerator.default.wrap(function (_context5) {
          while (1) switch (_context5.prev = _context5.next) {
            case 0:
              _context5.next = 1;
              return mobiusSocket.connect();
            case 1:
              requestPromise = mobiusSocket.sendWssRequest({
                type: 'auth',
                data: {
                  token: 'test'
                }
              });
              _context5.next = 2;
              return (0, _promiseTick.default)();
            case 2:
              requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
              mockWebSocket.emit('message', {
                data: (0, _stringify.default)({
                  type: 'response_event',
                  subtype: 'auth',
                  trackingId: requestPayload.trackingId,
                  statusCode: 403,
                  statusMessage: 'Forbidden'
                })
              });
              _context5.next = 3;
              return _testHelperChai.assert.isRejected(requestPromise);
            case 3:
              error = _context5.sent;
              _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
              _testHelperChai.assert.equal(error.statusCode, 403);
              _testHelperChai.assert.equal(error.statusMessage, 'Forbidden');
              _testHelperChai.assert.equal(error.trackingId, requestPayload.trackingId);
            case 4:
            case "end":
              return _context5.stop();
          }
        }, _callee5);
      })));
      it('rejects when the matching response does not arrive before timeout', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6() {
        var requestPromise, error;
        return _regenerator.default.wrap(function (_context6) {
          while (1) switch (_context6.prev = _context6.next) {
            case 0:
              _context6.next = 1;
              return mobiusSocket.connect();
            case 1:
              requestPromise = mobiusSocket.sendWssRequest({
                type: 'auth',
                data: {
                  token: 'test'
                }
              });
              jest.advanceTimersByTime(101);
              _context6.next = 2;
              return (0, _promiseTick.default)();
            case 2:
              _context6.next = 3;
              return _testHelperChai.assert.isRejected(requestPromise);
            case 3:
              error = _context6.sent;
              _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
              _testHelperChai.assert.equal(error.statusCode, 408);
              _testHelperChai.assert.equal(error.statusMessage, 'Mobius websocket response timed out');
            case 4:
            case "end":
              return _context6.stop();
          }
        }, _callee6);
      })));
      it('rejects with a clear error when the matching response is missing status code', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee7() {
        var requestPromise, requestPayload, error;
        return _regenerator.default.wrap(function (_context7) {
          while (1) switch (_context7.prev = _context7.next) {
            case 0:
              _context7.next = 1;
              return mobiusSocket.connect();
            case 1:
              requestPromise = mobiusSocket.sendWssRequest({
                type: 'auth',
                data: {
                  token: 'test'
                }
              });
              _context7.next = 2;
              return (0, _promiseTick.default)();
            case 2:
              requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
              mockWebSocket.emit('message', {
                data: (0, _stringify.default)({
                  type: 'response_event',
                  subtype: 'auth',
                  trackingId: requestPayload.trackingId
                })
              });
              _context7.next = 3;
              return _testHelperChai.assert.isRejected(requestPromise);
            case 3:
              error = _context7.sent;
              _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
              _testHelperChai.assert.isUndefined(error.statusCode);
              _testHelperChai.assert.equal(error.statusMessage, 'Socket response missing status code');
            case 4:
            case "end":
              return _context7.stop();
          }
        }, _callee7);
      })));
      it('rejects pending requests when the active socket closes', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee8() {
        var requestPromise, error;
        return _regenerator.default.wrap(function (_context8) {
          while (1) switch (_context8.prev = _context8.next) {
            case 0:
              _context8.next = 1;
              return mobiusSocket.connect();
            case 1:
              requestPromise = mobiusSocket.sendWssRequest({
                type: 'auth',
                data: {
                  token: 'test'
                }
              });
              mockWebSocket.emit('close', {
                code: 1003,
                reason: 'service rejected request'
              });
              _context8.next = 2;
              return _testHelperChai.assert.isRejected(requestPromise);
            case 2:
              error = _context8.sent;
              _testHelperChai.assert.instanceOf(error, _errors.ConnectionError);
              _testHelperChai.assert.equal(error.code, 1003);
              _testHelperChai.assert.equal(error.reason, 'service rejected request');
            case 3:
            case "end":
              return _context8.stop();
          }
        }, _callee8);
      })));
      it('rejects array payloads', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee9() {
        var error;
        return _regenerator.default.wrap(function (_context9) {
          while (1) switch (_context9.prev = _context9.next) {
            case 0:
              _context9.next = 1;
              return mobiusSocket.connect();
            case 1:
              _context9.next = 2;
              return _testHelperChai.assert.isRejected(mobiusSocket.sendWssRequest([]));
            case 2:
              error = _context9.sent;
              _testHelperChai.assert.equal(error.message, '`payload` is required');
            case 3:
            case "end":
              return _context9.stop();
          }
        }, _callee9);
      })));
    });
    describe('#getConnectedWebSocketUrl()', function () {
      it('returns the connected websocket url', function () {
        mobiusSocket.socket = {
          connected: true,
          url: 'ws://connected-url.com'
        };
        _testHelperChai.assert.equal(mobiusSocket.getConnectedWebSocketUrl(), 'ws://connected-url.com');
      });
      it('returns undefined when not connected', function () {
        mobiusSocket.socket = {
          connected: false,
          url: 'ws://disconnected-url.com'
        };
        _testHelperChai.assert.isUndefined(mobiusSocket.getConnectedWebSocketUrl());
      });
    });
    describe('#_emit()', function () {
      it('emits Error-safe events and log the error with the call parameters', function () {
        var error = 'error';
        var event = {
          data: 'some data'
        };
        mobiusSocket.on('break', function () {
          throw error;
        });
        _sinon.default.stub(mobiusSocket.logger, 'error');
        return _promise.default.resolve(mobiusSocket.emitEvent('break', event)).then(function (res) {
          _testHelperChai.assert.calledWith(mobiusSocket.logger.error, 'MobiusSocket: error occurred in event handler:', error, ' with args: ', ['break', event]);
          return res;
        });
      });
    });
    describe('#_prepareUrl()', function () {
      it('returns the provided URL as-is (no Mercury URL transforms)', function () {
        return mobiusSocket.prepareUrl('ws://provided.com').then(function (wsUrl) {
          _testHelperChai.assert.equal(wsUrl, 'ws://provided.com');
        });
      });
      it('falls back to device webSocketUrl when no URL is provided', function () {
        return mobiusSocket.prepareUrl().then(function (wsUrl) {
          _testHelperChai.assert.equal(wsUrl, 'ws://example.com');
        });
      });
    });
    describe('shutdown protocol', function () {
      describe('#_handleImminentShutdown()', function () {
        var connectWithBackoffStub;
        beforeEach(function () {
          mobiusSocket.connected = true;
          mobiusSocket.socket = {
            url: 'ws://old-socket.com',
            removeAllListeners: _sinon.default.stub()
          };
          connectWithBackoffStub = _sinon.default.stub(mobiusSocket, 'connectWithBackoff');
          connectWithBackoffStub.returns(_promise.default.resolve());
          _sinon.default.stub(mobiusSocket, 'emitEvent');
        });
        afterEach(function () {
          connectWithBackoffStub.restore();
          mobiusSocket.emitEvent.restore();
        });
        it('should be idempotent - no-op if already in progress', function () {
          mobiusSocket.shutdownSwitchoverBackoffCall = {
            placeholder: true
          };
          mobiusSocket.handleImminentShutdown();
          _testHelperChai.assert.notCalled(connectWithBackoffStub);
        });
        it('should set switchover flags when called', function () {
          mobiusSocket.handleImminentShutdown();
          _testHelperChai.assert.calledOnce(connectWithBackoffStub);
          var callArgs = connectWithBackoffStub.firstCall.args;
          _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
          _testHelperChai.assert.isObject(callArgs[1]); // context
          _testHelperChai.assert.isTrue(callArgs[1].isShutdownSwitchover);
          _testHelperChai.assert.isObject(callArgs[1].attemptOptions);
          _testHelperChai.assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
        });
        it('should call _connectWithBackoff with correct parameters', function (done) {
          mobiusSocket.handleImminentShutdown();
          process.nextTick(function () {
            _testHelperChai.assert.calledOnce(connectWithBackoffStub);
            var callArgs = connectWithBackoffStub.firstCall.args;
            _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
            _testHelperChai.assert.isObject(callArgs[1]); // context
            _testHelperChai.assert.isTrue(callArgs[1].isShutdownSwitchover);
            _testHelperChai.assert.isObject(callArgs[1].attemptOptions);
            _testHelperChai.assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
            done();
          });
        });
        it('should handle exceptions during switchover', function () {
          connectWithBackoffStub.restore();
          _sinon.default.stub(mobiusSocket, 'connectWithBackoff').throws(new Error('Connection failed'));
          mobiusSocket.handleImminentShutdown();
          _testHelperChai.assert.isUndefined(mobiusSocket.shutdownSwitchoverBackoffCall);
          mobiusSocket.connectWithBackoff.restore();
        });
      });
      describe('#_onmessage() with shutdown message', function () {
        beforeEach(function () {
          _sinon.default.stub(mobiusSocket, 'handleImminentShutdown');
          _sinon.default.stub(mobiusSocket, 'emitEvent');
        });
        afterEach(function () {
          mobiusSocket.handleImminentShutdown.restore();
          mobiusSocket.emitEvent.restore();
        });
        it('should trigger _handleImminentShutdown on shutdown message', function () {
          var shutdownEvent = {
            data: {
              type: 'shutdown'
            }
          };
          var result = mobiusSocket.onmessage(shutdownEvent);
          _testHelperChai.assert.calledOnce(mobiusSocket.handleImminentShutdown);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:mobius_shutdown_imminent', shutdownEvent.data);
          _testHelperChai.assert.instanceOf(result, _promise.default);
        });
        it('should handle shutdown message without additional data gracefully', function () {
          var shutdownEvent = {
            data: {
              type: 'shutdown'
            }
          };
          mobiusSocket.onmessage(shutdownEvent);
          _testHelperChai.assert.calledOnce(mobiusSocket.handleImminentShutdown);
        });
        it('should not trigger shutdown handling for non-shutdown messages', function () {
          var regularEvent = {
            data: {
              type: 'regular',
              data: {
                eventType: 'conversation.activity'
              }
            }
          };
          mobiusSocket.onmessage(regularEvent);
          _testHelperChai.assert.notCalled(mobiusSocket.handleImminentShutdown);
        });
      });
      describe('#_onmessage() with missing data or eventType', function () {
        beforeEach(function () {
          _sinon.default.stub(mobiusSocket, 'emitEvent');
        });
        afterEach(function () {
          mobiusSocket.emitEvent.restore();
        });
        it('should not throw when envelope.data is undefined', function () {
          var event = {
            data: {
              type: 'someType'
              // no nested data property
            }
          };
          var result = mobiusSocket.onmessage(event);
          _testHelperChai.assert.instanceOf(result, _promise.default);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event', event.data);
        });
        it('should not throw when data.eventType is undefined', function () {
          var event = {
            data: {
              type: 'someType',
              data: {
                // no eventType property
                someField: 'value'
              }
            }
          };
          var result = mobiusSocket.onmessage(event);
          _testHelperChai.assert.instanceOf(result, _promise.default);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event', event.data);
        });
        it('should emit generic event for messages without eventType (e.g. subscription responses)', function () {
          var event = {
            data: {
              id: 'msg-123',
              sequenceNumber: 5,
              data: {
                statusCode: 200
              }
            }
          };
          var result = mobiusSocket.onmessage(event);
          _testHelperChai.assert.instanceOf(result, _promise.default);
          _testHelperChai.assert.calledOnce(mobiusSocket.emitEvent);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event', event.data);
        });
        it('should still process messages with a valid eventType', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee0() {
          var event;
          return _regenerator.default.wrap(function (_context0) {
            while (1) switch (_context0.prev = _context0.next) {
              case 0:
                event = {
                  data: {
                    data: {
                      eventType: 'conversation.activity'
                    }
                  }
                };
                _context0.next = 1;
                return mobiusSocket.onmessage(event);
              case 1:
                _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:conversation', event.data);
                _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:conversation.activity', event.data);
              case 2:
              case "end":
                return _context0.stop();
            }
          }, _callee0);
        })));
      });
      describe('#_onmessage() async_event deduplication', function () {
        var originalDedupCacheMaxSize;
        beforeEach(function () {
          originalDedupCacheMaxSize = mobiusSocket.config.dedupCacheMaxSize;
        });
        afterEach(function () {
          mobiusSocket.config.dedupCacheMaxSize = originalDedupCacheMaxSize;
        });
        it('suppresses duplicate async_event messages', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee1() {
          var emitSpy;
          return _regenerator.default.wrap(function (_context1) {
            while (1) switch (_context1.prev = _context1.next) {
              case 0:
                emitSpy = _sinon.default.spy(mobiusSocket, 'emitEvent');
                _context1.next = 1;
                return mobiusSocket.onmessage(createAsyncEvent('evt-1'));
              case 1:
                _context1.next = 2;
                return mobiusSocket.onmessage(createAsyncEvent('evt-1'));
              case 2:
                _testHelperChai.assert.equal(countGenericEventEmits(emitSpy), 1);
                emitSpy.restore();
              case 3:
              case "end":
                return _context1.stop();
            }
          }, _callee1);
        })));
        it('suppresses duplicate async_event messages across socket replacement without disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee10() {
          var emitSpy;
          return _regenerator.default.wrap(function (_context10) {
            while (1) switch (_context10.prev = _context10.next) {
              case 0:
                emitSpy = _sinon.default.spy(mobiusSocket, 'emitEvent');
                mobiusSocket.socket = createSessionSocket();
                _context10.next = 1;
                return mobiusSocket.onmessage(createAsyncEvent('evt-2'));
              case 1:
                mobiusSocket.socket = createSessionSocket();
                _context10.next = 2;
                return mobiusSocket.onmessage(createAsyncEvent('evt-2'));
              case 2:
                _testHelperChai.assert.equal(countGenericEventEmits(emitSpy), 1);
                emitSpy.restore();
              case 3:
              case "end":
                return _context10.stop();
            }
          }, _callee10);
        })));
        it('evicts only the oldest eventId when the dedup cache exceeds max size', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee11() {
          var emitSpy;
          return _regenerator.default.wrap(function (_context11) {
            while (1) switch (_context11.prev = _context11.next) {
              case 0:
                emitSpy = _sinon.default.spy(mobiusSocket, 'emitEvent');
                mobiusSocket.config.dedupCacheMaxSize = 3;
                _context11.next = 1;
                return mobiusSocket.onmessage(createAsyncEvent('e1'));
              case 1:
                _context11.next = 2;
                return mobiusSocket.onmessage(createAsyncEvent('e2'));
              case 2:
                _context11.next = 3;
                return mobiusSocket.onmessage(createAsyncEvent('e3'));
              case 3:
                _context11.next = 4;
                return mobiusSocket.onmessage(createAsyncEvent('e4'));
              case 4:
                _context11.next = 5;
                return mobiusSocket.onmessage(createAsyncEvent('e2'));
              case 5:
                _context11.next = 6;
                return mobiusSocket.onmessage(createAsyncEvent('e1'));
              case 6:
                _testHelperChai.assert.equal(countGenericEventEmits(emitSpy), 5);
                emitSpy.restore();
              case 7:
              case "end":
                return _context11.stop();
            }
          }, _callee11);
        })));
        it('clears the dedup cache on disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee12() {
          var emitSpy;
          return _regenerator.default.wrap(function (_context12) {
            while (1) switch (_context12.prev = _context12.next) {
              case 0:
                emitSpy = _sinon.default.spy(mobiusSocket, 'emitEvent');
                _context12.next = 1;
                return mobiusSocket.onmessage(createAsyncEvent('evt-3'));
              case 1:
                mobiusSocket.socket = createSessionSocket();
                _context12.next = 2;
                return mobiusSocket.disconnect();
              case 2:
                _context12.next = 3;
                return mobiusSocket.onmessage(createAsyncEvent('evt-3'));
              case 3:
                _testHelperChai.assert.equal(countGenericEventEmits(emitSpy), 2);
                emitSpy.restore();
              case 4:
              case "end":
                return _context12.stop();
            }
          }, _callee12);
        })));
      });
      describe('#_onclose() with code 4001 (shutdown replacement)', function () {
        var mockSocket;
        var anotherSocket;
        beforeEach(function () {
          mockSocket = {
            url: 'ws://active-socket.com',
            removeAllListeners: _sinon.default.stub()
          };
          anotherSocket = {
            url: 'ws://old-socket.com',
            removeAllListeners: _sinon.default.stub()
          };
          mobiusSocket.socket = mockSocket;
          mobiusSocket.connected = true;
          _sinon.default.stub(mobiusSocket, 'emitEvent');
          _sinon.default.stub(mobiusSocket, 'reconnect');
        });
        afterEach(function () {
          mobiusSocket.emitEvent.restore();
          mobiusSocket.reconnect.restore();
        });
        it('should handle active socket close with 4001 - emits registration.down and tears down state', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };
          mobiusSocket.onclose(closeEvent, mockSocket);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'offline', closeEvent);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:async_event', _constants.MOBIUS_SOCKET_4001_EVENT);
          _testHelperChai.assert.neverCalledWith(mobiusSocket.emitEvent, 'offline.permanent', closeEvent);
          _testHelperChai.assert.notCalled(mobiusSocket.reconnect);
          _testHelperChai.assert.isFalse(mobiusSocket.connected);
        });
        it('should handle non-active socket close with 4001 - emits registration.down without tearing down state', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };
          mobiusSocket.onclose(closeEvent, anotherSocket);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:async_event', _constants.MOBIUS_SOCKET_4001_EVENT);
          _testHelperChai.assert.neverCalledWith(mobiusSocket.emitEvent, 'offline.replaced', closeEvent);
          _testHelperChai.assert.neverCalledWith(mobiusSocket.emitEvent, 'offline', closeEvent);
          _testHelperChai.assert.notCalled(mobiusSocket.reconnect);
          _testHelperChai.assert.isTrue(mobiusSocket.connected);
          _testHelperChai.assert.strictEqual(mobiusSocket.socket, mockSocket);
        });
        it('should emit registration.down for both active and non-active socket closes with 4001', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };

          // Non-active socket: only registration.down is emitted (no 'offline')
          mobiusSocket.onclose(closeEvent, anotherSocket);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:async_event', _constants.MOBIUS_SOCKET_4001_EVENT);
          _testHelperChai.assert.neverCalledWith(mobiusSocket.emitEvent, 'offline', closeEvent);
          mobiusSocket.emitEvent.resetHistory();

          // Active socket: 'offline' is emitted alongside registration.down
          mobiusSocket.onclose(closeEvent, mockSocket);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'offline', closeEvent);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:async_event', _constants.MOBIUS_SOCKET_4001_EVENT);
        });
        it('should handle missing sourceSocket parameter (treats as non-active)', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };
          mobiusSocket.onclose(closeEvent, undefined);
          _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:async_event', _constants.MOBIUS_SOCKET_4001_EVENT);
          _testHelperChai.assert.neverCalledWith(mobiusSocket.emitEvent, 'offline.replaced', closeEvent);
          _testHelperChai.assert.notCalled(mobiusSocket.reconnect);
        });
        it('should clean up event listeners from non-active socket when it closes', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };
          mobiusSocket.onclose(closeEvent, anotherSocket);
          _testHelperChai.assert.calledOnce(anotherSocket.removeAllListeners);
        });
        it('should not clean up listeners from active socket listeners until close handler runs', function () {
          var closeEvent = {
            code: 4001,
            reason: 'replaced during shutdown'
          };
          mobiusSocket.onclose(closeEvent, mockSocket);
          _testHelperChai.assert.calledOnce(mockSocket.removeAllListeners);
        });
      });
      describe('shutdown switchover with retry logic', function () {
        var connectWithBackoffStub;
        beforeEach(function () {
          mobiusSocket.connected = true;
          mobiusSocket.socket = {
            url: 'ws://old-socket.com',
            removeAllListeners: _sinon.default.stub()
          };
          connectWithBackoffStub = _sinon.default.stub(mobiusSocket, 'connectWithBackoff');
          _sinon.default.stub(mobiusSocket, 'emitEvent');
        });
        afterEach(function () {
          connectWithBackoffStub.restore();
          mobiusSocket.emitEvent.restore();
        });
        it('should call _connectWithBackoff with shutdown switchover context', function (done) {
          connectWithBackoffStub.returns(_promise.default.resolve());
          mobiusSocket.handleImminentShutdown();
          process.nextTick(function () {
            _testHelperChai.assert.calledOnce(connectWithBackoffStub);
            var callArgs = connectWithBackoffStub.firstCall.args;
            _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
            _testHelperChai.assert.isObject(callArgs[1]);
            _testHelperChai.assert.isTrue(callArgs[1].isShutdownSwitchover);
            _testHelperChai.assert.isObject(callArgs[1].attemptOptions);
            _testHelperChai.assert.isTrue(callArgs[1].attemptOptions.isShutdownSwitchover);
            done();
          });
        });
        it('should set shutdownSwitchoverBackoffCall during switchover', function () {
          connectWithBackoffStub.callsFake(function () {
            mobiusSocket.shutdownSwitchoverBackoffCall = {
              placeholder: true
            };
            return new _promise.default(function () {}); // Never resolves
          });
          mobiusSocket.handleImminentShutdown();
          _testHelperChai.assert.isOk(mobiusSocket.shutdownSwitchoverBackoffCall);
        });
        it('should emit success event when switchover completes', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee13() {
          var emitCalls, hasCompleteEvent;
          return _regenerator.default.wrap(function (_context13) {
            while (1) switch (_context13.prev = _context13.next) {
              case 0:
                connectWithBackoffStub.callsFake(function (url, context) {
                  if (context && context.attemptOptions && context.attemptOptions.onSuccess) {
                    var newSocket = {
                      url: 'ws://new-socket.com'
                    };
                    context.attemptOptions.onSuccess(newSocket, 'ws://new-socket.com');
                  }
                  return _promise.default.resolve();
                });
                mobiusSocket.handleImminentShutdown();
                _context13.next = 1;
                return (0, _promiseTick.default)(50);
              case 1:
                emitCalls = mobiusSocket.emitEvent.getCalls();
                hasCompleteEvent = emitCalls.some(function (call) {
                  return call.args[0] === 'event:mobius_shutdown_switchover_complete';
                });
                _testHelperChai.assert.isTrue(hasCompleteEvent, 'Should emit switchover complete event');
              case 2:
              case "end":
                return _context13.stop();
            }
          }, _callee13);
        })));
        it('should emit failure event when switchover exhausts retries', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee14() {
          var testError, emitCalls, hasFailureEvent;
          return _regenerator.default.wrap(function (_context14) {
            while (1) switch (_context14.prev = _context14.next) {
              case 0:
                testError = new Error('Connection failed');
                connectWithBackoffStub.returns(_promise.default.reject(testError));
                mobiusSocket.handleImminentShutdown();
                _context14.next = 1;
                return (0, _promiseTick.default)(50);
              case 1:
                emitCalls = mobiusSocket.emitEvent.getCalls();
                hasFailureEvent = emitCalls.some(function (call) {
                  return call.args[0] === 'event:mobius_shutdown_switchover_failed' && call.args[1] && call.args[1].reason === testError;
                });
                _testHelperChai.assert.isTrue(hasFailureEvent, 'Should emit switchover failed event');
              case 2:
              case "end":
                return _context14.stop();
            }
          }, _callee14);
        })));
        it('should allow old socket to be closed by server after switchover failure', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee15() {
          return _regenerator.default.wrap(function (_context15) {
            while (1) switch (_context15.prev = _context15.next) {
              case 0:
                connectWithBackoffStub.returns(_promise.default.reject(new Error('Failed')));
                mobiusSocket.handleImminentShutdown();
                _context15.next = 1;
                return (0, _promiseTick.default)(50);
              case 1:
                _testHelperChai.assert.equal(mobiusSocket.socket.removeAllListeners.callCount, 0);
              case 2:
              case "end":
                return _context15.stop();
            }
          }, _callee15);
        })));
      });
      describe('#_prepareAndOpenSocket()', function () {
        var mockSocket;
        var prepareUrlStub;
        var getUserTokenStub;
        beforeEach(function () {
          mockSocket = {
            open: _sinon.default.stub().returns(_promise.default.resolve())
          };
          prepareUrlStub = _sinon.default.stub(mobiusSocket, 'prepareUrl').returns(_promise.default.resolve('ws://example.com'));
          getUserTokenStub = webex.credentials.getUserToken;
          getUserTokenStub.returns(_promise.default.resolve({
            toString: function toString() {
              return 'mock-token';
            }
          }));
        });
        afterEach(function () {
          prepareUrlStub.restore();
        });
        it('should prepare URL and get user token', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee16() {
          return _regenerator.default.wrap(function (_context16) {
            while (1) switch (_context16.prev = _context16.next) {
              case 0:
                _context16.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, 'ws://test.com', false);
              case 1:
                _testHelperChai.assert.calledOnce(prepareUrlStub);
                _testHelperChai.assert.calledWith(prepareUrlStub, 'ws://test.com');
                _testHelperChai.assert.calledOnce(getUserTokenStub);
              case 2:
              case "end":
                return _context16.stop();
            }
          }, _callee16);
        })));
        it('should open socket with correct options for normal connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee17() {
          var callArgs;
          return _regenerator.default.wrap(function (_context17) {
            while (1) switch (_context17.prev = _context17.next) {
              case 0:
                _context17.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, false);
              case 1:
                _testHelperChai.assert.calledOnce(mockSocket.open);
                callArgs = mockSocket.open.firstCall.args;
                _testHelperChai.assert.equal(callArgs[0], 'ws://example.com');
                _testHelperChai.assert.isObject(callArgs[1]);
                _testHelperChai.assert.equal(callArgs[1].token, 'mock-token');
                _testHelperChai.assert.isDefined(callArgs[1].forceCloseDelay);
                _testHelperChai.assert.isDefined(callArgs[1].wssResponseTimeout);
              case 2:
              case "end":
                return _context17.stop();
            }
          }, _callee17);
        })));
        it('should log with correct prefix for normal connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee18() {
          return _regenerator.default.wrap(function (_context18) {
            while (1) switch (_context18.prev = _context18.next) {
              case 0:
                _context18.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, false);
              case 1:
                // The method should complete successfully - we're testing it runs without error
                // Actual log message verification is complex due to existing stubs in parent scope
                _testHelperChai.assert.calledOnce(mockSocket.open);
              case 2:
              case "end":
                return _context18.stop();
            }
          }, _callee18);
        })));
        it('should log with shutdown prefix for shutdown connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee19() {
          return _regenerator.default.wrap(function (_context19) {
            while (1) switch (_context19.prev = _context19.next) {
              case 0:
                _context19.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, true);
              case 1:
                // The method should complete successfully with shutdown flag
                _testHelperChai.assert.calledOnce(mockSocket.open);
              case 2:
              case "end":
                return _context19.stop();
            }
          }, _callee19);
        })));
        it('should merge custom mobiusSocket options when provided', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee20() {
          var callArgs;
          return _regenerator.default.wrap(function (_context20) {
            while (1) switch (_context20.prev = _context20.next) {
              case 0:
                webex.config.defaultMobiusSocketOptions = {
                  customOption: 'test-value',
                  wssResponseTimeout: 99999
                };
                _context20.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, false);
              case 1:
                callArgs = mockSocket.open.firstCall.args;
                _testHelperChai.assert.equal(callArgs[1].customOption, 'test-value');
                _testHelperChai.assert.equal(callArgs[1].wssResponseTimeout, 99999); // Custom value overrides default
              case 2:
              case "end":
                return _context20.stop();
            }
          }, _callee20);
        })));
        it('should return the webSocketUrl after opening', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee21() {
          var result;
          return _regenerator.default.wrap(function (_context21) {
            while (1) switch (_context21.prev = _context21.next) {
              case 0:
                _context21.next = 1;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, false);
              case 1:
                result = _context21.sent;
                _testHelperChai.assert.equal(result, 'ws://example.com');
              case 2:
              case "end":
                return _context21.stop();
            }
          }, _callee21);
        })));
        it('should handle errors during socket open', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee22() {
          var _t2;
          return _regenerator.default.wrap(function (_context22) {
            while (1) switch (_context22.prev = _context22.next) {
              case 0:
                mockSocket.open.returns(_promise.default.reject(new Error('Open failed')));
                _context22.prev = 1;
                _context22.next = 2;
                return mobiusSocket.prepareAndOpenSocket(mockSocket, undefined, false);
              case 2:
                _testHelperChai.assert.fail('Should have thrown an error');
                _context22.next = 4;
                break;
              case 3:
                _context22.prev = 3;
                _t2 = _context22["catch"](1);
                _testHelperChai.assert.equal(_t2.message, 'Open failed');
              case 4:
              case "end":
                return _context22.stop();
            }
          }, _callee22, null, [[1, 3]]);
        })));
      });
      describe('#_attemptConnection() with shutdown switchover', function () {
        var prepareAndOpenSocketStub;
        var callback;
        beforeEach(function () {
          prepareAndOpenSocketStub = _sinon.default.stub(mobiusSocket, 'prepareAndOpenSocket').returns(_promise.default.resolve('ws://new-socket.com'));
          callback = _sinon.default.stub();
          mobiusSocket.shutdownSwitchoverBackoffCall = {
            abort: _sinon.default.stub()
          };
          mobiusSocket.socket = {
            url: 'ws://test.com'
          };
          mobiusSocket.connected = true;
          _sinon.default.stub(mobiusSocket, 'emitEvent');
          _sinon.default.stub(mobiusSocket, 'attachSocketEventListeners');
        });
        afterEach(function () {
          prepareAndOpenSocketStub.restore();
          mobiusSocket.emitEvent.restore();
          mobiusSocket.attachSocketEventListeners.restore();
          mobiusSocket.shutdownSwitchoverBackoffCall = undefined;
        });
        it('should not set socket reference before opening for shutdown switchover', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee23() {
          var originalSocket;
          return _regenerator.default.wrap(function (_context23) {
            while (1) switch (_context23.prev = _context23.next) {
              case 0:
                originalSocket = mobiusSocket.socket;
                _context23.next = 1;
                return mobiusSocket.attemptConnection('ws://test.com', callback, {
                  isShutdownSwitchover: true,
                  attemptOptions: {
                    onSuccess: function onSuccess(newSocket, url) {
                      _testHelperChai.assert.exists(newSocket);
                      _testHelperChai.assert.equal(url, 'ws://new-socket.com');
                    }
                  }
                });
              case 1:
                // During shutdown switchover, this.socket should remain the old socket
                _testHelperChai.assert.equal(mobiusSocket.socket, originalSocket);
              case 2:
              case "end":
                return _context23.stop();
            }
          }, _callee23);
        })));
        it('should call onSuccess callback with new socket and URL for shutdown', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee24() {
          var onSuccessStub;
          return _regenerator.default.wrap(function (_context24) {
            while (1) switch (_context24.prev = _context24.next) {
              case 0:
                onSuccessStub = _sinon.default.stub();
                _context24.next = 1;
                return mobiusSocket.attemptConnection('ws://test.com', callback, {
                  isShutdownSwitchover: true,
                  attemptOptions: {
                    onSuccess: onSuccessStub
                  }
                });
              case 1:
                _testHelperChai.assert.calledOnce(onSuccessStub);
                _testHelperChai.assert.equal(onSuccessStub.firstCall.args[1], 'ws://new-socket.com');
              case 2:
              case "end":
                return _context24.stop();
            }
          }, _callee24);
        })));
        it('should emit shutdown switchover complete event', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee25() {
          return _regenerator.default.wrap(function (_context25) {
            while (1) switch (_context25.prev = _context25.next) {
              case 0:
                _context25.next = 1;
                return mobiusSocket.attemptConnection('ws://test.com', callback, {
                  isShutdownSwitchover: true,
                  attemptOptions: {
                    onSuccess: function onSuccess(newSocket, url) {
                      mobiusSocket.socket = newSocket;
                      mobiusSocket.connected = true;
                      mobiusSocket.emitEvent('event:mobius_shutdown_switchover_complete', {
                        url: url
                      });
                    }
                  }
                });
              case 1:
                _testHelperChai.assert.calledWith(mobiusSocket.emitEvent, 'event:mobius_shutdown_switchover_complete', _sinon.default.match.has('url', 'ws://new-socket.com'));
              case 2:
              case "end":
                return _context25.stop();
            }
          }, _callee25);
        })));
        it('should use simpler error handling for shutdown switchover failures', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee26() {
          return _regenerator.default.wrap(function (_context26) {
            while (1) switch (_context26.prev = _context26.next) {
              case 0:
                prepareAndOpenSocketStub.returns(_promise.default.reject(new Error('Connection failed')));
                _context26.next = 1;
                return mobiusSocket.attemptConnection('ws://test.com', callback, {
                  isShutdownSwitchover: true,
                  attemptOptions: {}
                }).catch(function () {});
              case 1:
                _testHelperChai.assert.calledOnce(callback);
                _testHelperChai.assert.instanceOf(callback.firstCall.args[0], Error);
              case 2:
              case "end":
                return _context26.stop();
            }
          }, _callee26);
        })));
        it('should check shutdownSwitchoverBackoffCall for shutdown connections', function () {
          mobiusSocket.shutdownSwitchoverBackoffCall = undefined;
          var result = mobiusSocket.attemptConnection('ws://test.com', callback, {
            isShutdownSwitchover: true
          });
          return result.catch(function (err) {
            _testHelperChai.assert.instanceOf(err, Error);
            _testHelperChai.assert.match(err.message, /switchover backoff call/);
          });
        });
      });
      describe('#_connectWithBackoff() with shutdown switchover', function () {
        it('should use shutdown-specific parameters when called', function () {
          var connectWithBackoffStub = _sinon.default.stub(mobiusSocket, 'connectWithBackoff').returns(_promise.default.resolve());
          mobiusSocket.handleImminentShutdown();
          _testHelperChai.assert.calledOnce(connectWithBackoffStub);
          var callArgs = connectWithBackoffStub.firstCall.args;
          _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
          _testHelperChai.assert.isObject(callArgs[1]);
          _testHelperChai.assert.isTrue(callArgs[1].isShutdownSwitchover);
          connectWithBackoffStub.restore();
        });
        it('should pass shutdown switchover options to _attemptConnection', function () {
          var attemptStub = _sinon.default.stub(mobiusSocket, 'attemptConnection');
          attemptStub.callsFake(function (url, cb) {
            return cb();
          });
          var context = {
            isShutdownSwitchover: true,
            attemptOptions: {
              isShutdownSwitchover: true,
              onSuccess: function onSuccess() {}
            }
          };
          var promise = mobiusSocket.connectWithBackoff(undefined, context);
          return promise.then(function () {
            _testHelperChai.assert.calledOnce(attemptStub);
            var callArgs = attemptStub.firstCall.args;
            _testHelperChai.assert.isObject(callArgs[2]);
            _testHelperChai.assert.isTrue(callArgs[2].isShutdownSwitchover);
            attemptStub.restore();
          });
        });
        it('should set and clear state flags appropriately', function () {
          _sinon.default.stub(mobiusSocket, 'attemptConnection').callsFake(function (url, cb) {
            return cb();
          });
          mobiusSocket.shutdownSwitchoverBackoffCall = {
            placeholder: true
          };
          var promise = mobiusSocket.connectWithBackoff(undefined, {
            isShutdownSwitchover: true,
            attemptOptions: {
              isShutdownSwitchover: true,
              onSuccess: function onSuccess() {}
            }
          });
          return promise.then(function () {
            _testHelperChai.assert.isUndefined(mobiusSocket.shutdownSwitchoverBackoffCall);
            mobiusSocket.attemptConnection.restore();
          });
        });
      });
      describe('#disconnect() with shutdown switchover in progress', function () {
        var abortStub;
        beforeEach(function () {
          mobiusSocket.socket = {
            close: _sinon.default.stub().returns(_promise.default.resolve()),
            removeAllListeners: _sinon.default.stub(),
            connecting: false,
            connected: true
          };
          abortStub = _sinon.default.stub();
          mobiusSocket.shutdownSwitchoverBackoffCall = {
            abort: abortStub
          };
        });
        it('should abort shutdown switchover backoff call on disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee27() {
          return _regenerator.default.wrap(function (_context27) {
            while (1) switch (_context27.prev = _context27.next) {
              case 0:
                _context27.next = 1;
                return mobiusSocket.disconnect();
              case 1:
                _testHelperChai.assert.calledOnce(abortStub);
              case 2:
              case "end":
                return _context27.stop();
            }
          }, _callee27);
        })));
        it('should handle disconnect when no switchover is in progress', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee28() {
          return _regenerator.default.wrap(function (_context28) {
            while (1) switch (_context28.prev = _context28.next) {
              case 0:
                mobiusSocket.shutdownSwitchoverBackoffCall = undefined;
                _context28.next = 1;
                return mobiusSocket.disconnect();
              case 1:
                _testHelperChai.assert.calledOnce(mobiusSocket.socket.close);
              case 2:
              case "end":
                return _context28.stop();
            }
          }, _callee28);
        })));
      });
    });
  });
});
//# sourceMappingURL=mobius-socket.test.js.map
