<commit_before>'use strict';

const CServer = require('../core').Server;
const Cursor = require('../cursor');
const TopologyBase = require('./topology_base').TopologyBase;
const Store = require('./topology_base').Store;
const MongoError = require('../core').MongoError;
const MAX_JS_INT = require('../utils').MAX_JS_INT;
const translateOptions = require('../utils').translateOptions;
const filterOptions = require('../utils').filterOptions;
const mergeOptions = require('../utils').mergeOptions;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 */

// Allowed parameters
var legalOptionNames = [
  'ha',
  'haInterval',
  'acceptableLatencyMS',
  'poolSize',
  'ssl',
  'checkServerIdentity',
  'sslValidate',
  'sslCA',
  'sslCRL',
  'sslCert',
  'ciphers',
  'ecdhCurve',
  'sslKey',
  'sslPass',
  'socketOptions',
  'bufferMaxEntries',
  'store',
  'auto_reconnect',
  'autoReconnect',
  'emitError',
  'keepAlive',
  'keepAliveInitialDelay',
  'noDelay',
  'connectTimeoutMS',
  'socketTimeoutMS',
  'family',
  'loggerLevel',
  'logger',
  'reconnectTries',
  'reconnectInterval',
  'monitoring',
  'appname',
  'domainsEnabled',
  'servername',
  'promoteLongs',
  'promoteValues',
  'promoteBuffers',
  'compression',
  'promiseLibrary',
  'monitorCommands'
];

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {boolean} [options.sslValidate=false] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {array} [options.sslCRL] Array of revocation certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {string} [options.ciphers] Passed directly through to tls.createSecureContext. See https://nodejs.org/dist/latest-v9.x/docs/api/tls.html#tls_tls_createsecurecontext_options for more info.
 * @param {string} [options.ecdhCurve] Passed directly through to tls.createSecureContext. See https://nodejs.org/dist/latest-v9.x/docs/api/tls.html#tls_tls_createsecurecontext_options for more info.
 * @param {(Buffer|string)} [options.sslKey] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {string} [options.servername] String containing the server name requested via TLS SNI.
 * @param {object} [options.socketOptions] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=true] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {boolean} [options.socketOptions.keepAlive=true] TCP Connection keep alive enabled
 * @param {number} [options.socketOptions.keepAliveInitialDelay=30000] The number of milliseconds to wait before initiating keepAlive on the TCP socket
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @param {number} [options.monitoring=true] Triggers the server instance to call ismaster
 * @param {number} [options.haInterval=10000] The interval of calling ismaster when monitoring is enabled.
 * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
 * @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @fires Server#commandStarted
 * @fires Server#commandSucceeded
 * @fires Server#commandFailed
 * @property {string} parserType the parser type used (c++ or js).
 * @return {Server} a Server instance.
 */
class Server extends TopologyBase {
  constructor(host, port, options) {
    super();
    var self = this;

    // Filter the options
    options = filterOptions(options, legalOptionNames);

    // Promise library
    const promiseLibrary = options.promiseLibrary;

    // Stored options
    var storeOptions = {
      force: false,
      bufferMaxEntries:
        typeof options.bufferMaxEntries === 'number' ? options.bufferMaxEntries : MAX_JS_INT
    };

    // Shared global store
    var store = options.store || new Store(self, storeOptions);

    // Detect if we have a socket connection
    if (host.indexOf('/') !== -1) {
      if (port != null && typeof port === 'object') {
        options = port;
        port = null;
      }
    } else if (port == null) {
      throw MongoError.create({ message: 'port must be specified', driver: true });
    }

    // Get the reconnect option
    var reconnect = typeof options.auto_reconnect === 'boolean' ? options.auto_reconnect : true;
    reconnect = typeof options.autoReconnect === 'boolean' ? options.autoReconnect : reconnect;

    // Clone options
    var clonedOptions = mergeOptions(
      {},
      {
        host: host,
        port: port,
        disconnectHandler: store,
        cursorFactory: Cursor,
        reconnect: reconnect,
        emitError: typeof options.emitError === 'boolean' ? options.emitError : true,
        size: typeof options.poolSize === 'number' ? options.poolSize : 5,
        monitorCommands:
          typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false
      }
    );

    // Translate any SSL options and other connectivity options
    clonedOptions = translateOptions(clonedOptions, options);

    // Socket options
    var socketOptions =
      options.socketOptions && Object.keys(options.socketOptions).length > 0
        ? options.socketOptions
        : options;

    // Translate all the options to the core types
    clonedOptions = translateOptions(clonedOptions, socketOptions);

    // Build default client information
    clonedOptions.clientInfo = this.clientInfo;
    // Do we have an application specific string
    if (options.appname) {
      clonedOptions.clientInfo.application = { name: options.appname };
    }

    // Define the internal properties
    this.s = {
      // Create an instance of a server instance from core module
      coreTopology: new CServer(clonedOptions),
      // Server capabilities
      sCapabilities: null,
      // Cloned options
      clonedOptions: clonedOptions,
      // Reconnect
      reconnect: clonedOptions.reconnect,
      // Emit error
      emitError: clonedOptions.emitError,
      // Pool size
      poolSize: clonedOptions.size,
      // Store Options
      storeOptions: storeOptions,
      // Store
      store: store,
      // Host
      host: host,
      // Port
      port: port,
      // Options
      options: options,
      // Server Session Pool
      sessionPool: null,
      // Active client sessions
      sessions: [],
      // Promise library
      promiseLibrary: promiseLibrary || Promise
    };
  }

  // Connect
  connect(_options, callback) {
    var self = this;
    if ('function' === typeof _options) (callback = _options), (_options = {});
    if (_options == null) _options = this.s.clonedOptions;
    if (!('function' === typeof callback)) callback = null;
    _options = Object.assign({}, this.s.clonedOptions, _options);
    self.s.options = _options;

    // Update bufferMaxEntries
    self.s.storeOptions.bufferMaxEntries =
      typeof _options.bufferMaxEntries === 'number' ? _options.bufferMaxEntries : -1;

    // Error handler
    var connectErrorHandler = function() {
      return function(err) {
        // Remove all event handlers
        var events = ['timeout', 'error', 'close'];
        events.forEach(function(e) {
          self.s.coreTopology.removeListener(e, connectHandlers[e]);
        });

        self.s.coreTopology.removeListener('connect', connectErrorHandler);

        // Try to callback
        try {
          callback(err);
        } catch (err) {
          process.nextTick(function() {
            throw err;
          });
        }
      };
    };

    // Actual handler
    var errorHandler = function(event) {
      return function(err) {
        if (event !== 'error') {
          self.emit(event, err);
        }
      };
    };

    // Error handler
    var reconnectHandler = function() {
      self.emit('reconnect', self);
      self.s.store.execute();
    };

    // Reconnect failed
    var reconnectFailedHandler = function(err) {
      self.emit('reconnectFailed', err);
      self.s.store.flush(err);
    };

    // Destroy called on topology, perform cleanup
    var destroyHandler = function() {
      self.s.store.flush();
    };

    // relay the event
    var relay = function(event) {
      return function(t, server) {
        self.emit(event, t, server);
      };
    };

    // Connect handler
    var connectHandler = function() {
      // Clear out all the current handlers left over
      ['timeout', 'error', 'close', 'destroy'].forEach(function(e) {
        self.s.coreTopology.removeAllListeners(e);
      });

      // Set up listeners
      self.s.coreTopology.on('timeout', errorHandler('timeout'));
      self.s.coreTopology.once('error', errorHandler('error'));
      self.s.coreTopology.on('close', errorHandler('close'));
      // Only called on destroy
      self.s.coreTopology.on('destroy', destroyHandler);

      // Emit open event
      self.emit('open', null, self);

      // Return correctly
      try {
        callback(null, self);
      } catch (err) {
        process.nextTick(function() {
          throw err;
        });
      }
    };

    // Set up listeners
    var connectHandlers = {
      timeout: connectErrorHandler('timeout'),
      error: connectErrorHandler('error'),
      close: connectErrorHandler('close')
    };

    // Clear out all the current handlers left over
    [
      'timeout',
      'error',
      'close',
      'serverOpening',
      'serverDescriptionChanged',
      'serverHeartbeatStarted',
      'serverHeartbeatSucceeded',
      'serverHeartbeatFailed',
      'serverClosed',
      'topologyOpening',
      'topologyClosed',
      'topologyDescriptionChanged',
      'commandStarted',
      'commandSucceeded',
      'commandFailed'
    ].forEach(function(e) {
      self.s.coreTopology.removeAllListeners(e);
    });

    // Add the event handlers
    self.s.coreTopology.once('timeout', connectHandlers.timeout);
    self.s.coreTopology.once('error', connectHandlers.error);
    self.s.coreTopology.once('close', connectHandlers.close);
    self.s.coreTopology.once('connect', connectHandler);
    // Reconnect server
    self.s.coreTopology.on('reconnect', reconnectHandler);
    self.s.coreTopology.on('reconnectFailed', reconnectFailedHandler);

    // Set up SDAM listeners
    self.s.coreTopology.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
    self.s.coreTopology.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
    self.s.coreTopology.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
    self.s.coreTopology.on('serverHeartbeatFailed', relay('serverHeartbeatFailed'));
    self.s.coreTopology.on('serverOpening', relay('serverOpening'));
    self.s.coreTopology.on('serverClosed', relay('serverClosed'));
    self.s.coreTopology.on('topologyOpening', relay('topologyOpening'));
    self.s.coreTopology.on('topologyClosed', relay('topologyClosed'));
    self.s.coreTopology.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));
    self.s.coreTopology.on('commandStarted', relay('commandStarted'));
    self.s.coreTopology.on('commandSucceeded', relay('commandSucceeded'));
    self.s.coreTopology.on('commandFailed', relay('commandFailed'));
    self.s.coreTopology.on('attemptReconnect', relay('attemptReconnect'));
    self.s.coreTopology.on('monitoring', relay('monitoring'));

    // Start connection
    self.s.coreTopology.connect(_options);
  }
}

Object.defineProperty(Server.prototype, 'poolSize', {
  enumerable: true,
  get: function() {
    return this.s.coreTopology.connections().length;
  }
});

Object.defineProperty(Server.prototype, 'autoReconnect', {
  enumerable: true,
  get: function() {
    return this.s.reconnect;
  }
});

Object.defineProperty(Server.prototype, 'host', {
  enumerable: true,
  get: function() {
    return this.s.host;
  }
});

Object.defineProperty(Server.prototype, 'port', {
  enumerable: true,
  get: function() {
    return this.s.port;
  }
});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

/**
 * An event emitted indicating a command was started, if command monitoring is enabled
 *
 * @event Server#commandStarted
 * @type {object}
 */

/**
 * An event emitted indicating a command succeeded, if command monitoring is enabled
 *
 * @event Server#commandSucceeded
 * @type {object}
 */

/**
 * An event emitted indicating a command failed, if command monitoring is enabled
 *
 * @event Server#commandFailed
 * @type {object}
 */

module.exports = Server;
<commit_msg>docs(Server): fix erroneous 'number'<commit_after>'use strict';

const CServer = require('../core').Server;
const Cursor = require('../cursor');
const TopologyBase = require('./topology_base').TopologyBase;
const Store = require('./topology_base').Store;
const MongoError = require('../core').MongoError;
const MAX_JS_INT = require('../utils').MAX_JS_INT;
const translateOptions = require('../utils').translateOptions;
const filterOptions = require('../utils').filterOptions;
const mergeOptions = require('../utils').mergeOptions;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 */

// Allowed parameters
var legalOptionNames = [
  'ha',
  'haInterval',
  'acceptableLatencyMS',
  'poolSize',
  'ssl',
  'checkServerIdentity',
  'sslValidate',
  'sslCA',
  'sslCRL',
  'sslCert',
  'ciphers',
  'ecdhCurve',
  'sslKey',
  'sslPass',
  'socketOptions',
  'bufferMaxEntries',
  'store',
  'auto_reconnect',
  'autoReconnect',
  'emitError',
  'keepAlive',
  'keepAliveInitialDelay',
  'noDelay',
  'connectTimeoutMS',
  'socketTimeoutMS',
  'family',
  'loggerLevel',
  'logger',
  'reconnectTries',
  'reconnectInterval',
  'monitoring',
  'appname',
  'domainsEnabled',
  'servername',
  'promoteLongs',
  'promoteValues',
  'promoteBuffers',
  'compression',
  'promiseLibrary',
  'monitorCommands'
];

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {boolean} [options.sslValidate=false] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {array} [options.sslCRL] Array of revocation certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {string} [options.ciphers] Passed directly through to tls.createSecureContext. See https://nodejs.org/dist/latest-v9.x/docs/api/tls.html#tls_tls_createsecurecontext_options for more info.
 * @param {string} [options.ecdhCurve] Passed directly through to tls.createSecureContext. See https://nodejs.org/dist/latest-v9.x/docs/api/tls.html#tls_tls_createsecurecontext_options for more info.
 * @param {(Buffer|string)} [options.sslKey] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {string} [options.servername] String containing the server name requested via TLS SNI.
 * @param {object} [options.socketOptions] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=true] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {boolean} [options.socketOptions.keepAlive=true] TCP Connection keep alive enabled
 * @param {number} [options.socketOptions.keepAliveInitialDelay=30000] The number of milliseconds to wait before initiating keepAlive on the TCP socket
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @param {boolean} [options.monitoring=true] Triggers the server instance to call ismaster
 * @param {number} [options.haInterval=10000] The interval of calling ismaster when monitoring is enabled.
 * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
 * @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @fires Server#commandStarted
 * @fires Server#commandSucceeded
 * @fires Server#commandFailed
 * @property {string} parserType the parser type used (c++ or js).
 * @return {Server} a Server instance.
 */
class Server extends TopologyBase {
  constructor(host, port, options) {
    super();
    var self = this;

    // Filter the options
    options = filterOptions(options, legalOptionNames);

    // Promise library
    const promiseLibrary = options.promiseLibrary;

    // Stored options
    var storeOptions = {
      force: false,
      bufferMaxEntries:
        typeof options.bufferMaxEntries === 'number' ? options.bufferMaxEntries : MAX_JS_INT
    };

    // Shared global store
    var store = options.store || new Store(self, storeOptions);

    // Detect if we have a socket connection
    if (host.indexOf('/') !== -1) {
      if (port != null && typeof port === 'object') {
        options = port;
        port = null;
      }
    } else if (port == null) {
      throw MongoError.create({ message: 'port must be specified', driver: true });
    }

    // Get the reconnect option
    var reconnect = typeof options.auto_reconnect === 'boolean' ? options.auto_reconnect : true;
    reconnect = typeof options.autoReconnect === 'boolean' ? options.autoReconnect : reconnect;

    // Clone options
    var clonedOptions = mergeOptions(
      {},
      {
        host: host,
        port: port,
        disconnectHandler: store,
        cursorFactory: Cursor,
        reconnect: reconnect,
        emitError: typeof options.emitError === 'boolean' ? options.emitError : true,
        size: typeof options.poolSize === 'number' ? options.poolSize : 5,
        monitorCommands:
          typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false
      }
    );

    // Translate any SSL options and other connectivity options
    clonedOptions = translateOptions(clonedOptions, options);

    // Socket options
    var socketOptions =
      options.socketOptions && Object.keys(options.socketOptions).length > 0
        ? options.socketOptions
        : options;

    // Translate all the options to the core types
    clonedOptions = translateOptions(clonedOptions, socketOptions);

    // Build default client information
    clonedOptions.clientInfo = this.clientInfo;
    // Do we have an application specific string
    if (options.appname) {
      clonedOptions.clientInfo.application = { name: options.appname };
    }

    // Define the internal properties
    this.s = {
      // Create an instance of a server instance from core module
      coreTopology: new CServer(clonedOptions),
      // Server capabilities
      sCapabilities: null,
      // Cloned options
      clonedOptions: clonedOptions,
      // Reconnect
      reconnect: clonedOptions.reconnect,
      // Emit error
      emitError: clonedOptions.emitError,
      // Pool size
      poolSize: clonedOptions.size,
      // Store Options
      storeOptions: storeOptions,
      // Store
      store: store,
      // Host
      host: host,
      // Port
      port: port,
      // Options
      options: options,
      // Server Session Pool
      sessionPool: null,
      // Active client sessions
      sessions: [],
      // Promise library
      promiseLibrary: promiseLibrary || Promise
    };
  }

  // Connect
  connect(_options, callback) {
    var self = this;
    if ('function' === typeof _options) (callback = _options), (_options = {});
    if (_options == null) _options = this.s.clonedOptions;
    if (!('function' === typeof callback)) callback = null;
    _options = Object.assign({}, this.s.clonedOptions, _options);
    self.s.options = _options;

    // Update bufferMaxEntries
    self.s.storeOptions.bufferMaxEntries =
      typeof _options.bufferMaxEntries === 'number' ? _options.bufferMaxEntries : -1;

    // Error handler
    var connectErrorHandler = function() {
      return function(err) {
        // Remove all event handlers
        var events = ['timeout', 'error', 'close'];
        events.forEach(function(e) {
          self.s.coreTopology.removeListener(e, connectHandlers[e]);
        });

        self.s.coreTopology.removeListener('connect', connectErrorHandler);

        // Try to callback
        try {
          callback(err);
        } catch (err) {
          process.nextTick(function() {
            throw err;
          });
        }
      };
    };

    // Actual handler
    var errorHandler = function(event) {
      return function(err) {
        if (event !== 'error') {
          self.emit(event, err);
        }
      };
    };

    // Error handler
    var reconnectHandler = function() {
      self.emit('reconnect', self);
      self.s.store.execute();
    };

    // Reconnect failed
    var reconnectFailedHandler = function(err) {
      self.emit('reconnectFailed', err);
      self.s.store.flush(err);
    };

    // Destroy called on topology, perform cleanup
    var destroyHandler = function() {
      self.s.store.flush();
    };

    // relay the event
    var relay = function(event) {
      return function(t, server) {
        self.emit(event, t, server);
      };
    };

    // Connect handler
    var connectHandler = function() {
      // Clear out all the current handlers left over
      ['timeout', 'error', 'close', 'destroy'].forEach(function(e) {
        self.s.coreTopology.removeAllListeners(e);
      });

      // Set up listeners
      self.s.coreTopology.on('timeout', errorHandler('timeout'));
      self.s.coreTopology.once('error', errorHandler('error'));
      self.s.coreTopology.on('close', errorHandler('close'));
      // Only called on destroy
      self.s.coreTopology.on('destroy', destroyHandler);

      // Emit open event
      self.emit('open', null, self);

      // Return correctly
      try {
        callback(null, self);
      } catch (err) {
        process.nextTick(function() {
          throw err;
        });
      }
    };

    // Set up listeners
    var connectHandlers = {
      timeout: connectErrorHandler('timeout'),
      error: connectErrorHandler('error'),
      close: connectErrorHandler('close')
    };

    // Clear out all the current handlers left over
    [
      'timeout',
      'error',
      'close',
      'serverOpening',
      'serverDescriptionChanged',
      'serverHeartbeatStarted',
      'serverHeartbeatSucceeded',
      'serverHeartbeatFailed',
      'serverClosed',
      'topologyOpening',
      'topologyClosed',
      'topologyDescriptionChanged',
      'commandStarted',
      'commandSucceeded',
      'commandFailed'
    ].forEach(function(e) {
      self.s.coreTopology.removeAllListeners(e);
    });

    // Add the event handlers
    self.s.coreTopology.once('timeout', connectHandlers.timeout);
    self.s.coreTopology.once('error', connectHandlers.error);
    self.s.coreTopology.once('close', connectHandlers.close);
    self.s.coreTopology.once('connect', connectHandler);
    // Reconnect server
    self.s.coreTopology.on('reconnect', reconnectHandler);
    self.s.coreTopology.on('reconnectFailed', reconnectFailedHandler);

    // Set up SDAM listeners
    self.s.coreTopology.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
    self.s.coreTopology.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
    self.s.coreTopology.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
    self.s.coreTopology.on('serverHeartbeatFailed', relay('serverHeartbeatFailed'));
    self.s.coreTopology.on('serverOpening', relay('serverOpening'));
    self.s.coreTopology.on('serverClosed', relay('serverClosed'));
    self.s.coreTopology.on('topologyOpening', relay('topologyOpening'));
    self.s.coreTopology.on('topologyClosed', relay('topologyClosed'));
    self.s.coreTopology.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));
    self.s.coreTopology.on('commandStarted', relay('commandStarted'));
    self.s.coreTopology.on('commandSucceeded', relay('commandSucceeded'));
    self.s.coreTopology.on('commandFailed', relay('commandFailed'));
    self.s.coreTopology.on('attemptReconnect', relay('attemptReconnect'));
    self.s.coreTopology.on('monitoring', relay('monitoring'));

    // Start connection
    self.s.coreTopology.connect(_options);
  }
}

Object.defineProperty(Server.prototype, 'poolSize', {
  enumerable: true,
  get: function() {
    return this.s.coreTopology.connections().length;
  }
});

Object.defineProperty(Server.prototype, 'autoReconnect', {
  enumerable: true,
  get: function() {
    return this.s.reconnect;
  }
});

Object.defineProperty(Server.prototype, 'host', {
  enumerable: true,
  get: function() {
    return this.s.host;
  }
});

Object.defineProperty(Server.prototype, 'port', {
  enumerable: true,
  get: function() {
    return this.s.port;
  }
});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

/**
 * An event emitted indicating a command was started, if command monitoring is enabled
 *
 * @event Server#commandStarted
 * @type {object}
 */

/**
 * An event emitted indicating a command succeeded, if command monitoring is enabled
 *
 * @event Server#commandSucceeded
 * @type {object}
 */

/**
 * An event emitted indicating a command failed, if command monitoring is enabled
 *
 * @event Server#commandFailed
 * @type {object}
 */

module.exports = Server;
<|endoftext|>


<commit_before>/* global rdf:true */
'use strict';

var isNode = (typeof process !== 'undefined' && process.versions && process.versions.node);

if (isNode) {
  global.N3 = require('n3');
}

var TurtleSerializer = function (rdf, options) {
  if (options == null) {
    options = {};
  }

  if (!('usePrefixMap' in options)) {
    options.usePrefiMap = true;
  }

  this.serialize = function (graph, callback) {
    var
      writerOptions = {},
      writer;

    if (options.usePrefixMap) {
      writerOptions.prefixes = {};

      Object.keys(rdf.prefixes).forEach(function (prefix) {
        if (typeof rdf.prefixes[prefix] !== 'string') {
          return;
        }

        writerOptions.prefixes[prefix] = rdf.prefixes[prefix];
      });
    }

    writer = N3.Writer(writerOptions);

    if (callback == null) {
      callback = function () {};
    }

    var createN3Node = function (node) {
      if (node.interfaceName.toString() === 'NamedNode') {
        return node.nominalValue;
      } else if (node.interfaceName.toString() === 'BlankNode') {
        return '_:' + node.nominalValue;
      } else {
        if (node.datatype != null) {
          return '"' + node.nominalValue + '"^^' + node.datatype.nominalValue;
        } else if (node.language != null) {
          return '"' + node.nominalValue + '"@' + node.language;
        } else {
          return '"' + node.nominalValue + '"';
        }
      }
    };

    graph.forEach(function (triple) {
      writer.addTriple(
        createN3Node(triple.subject),
        createN3Node(triple.predicate),
        createN3Node(triple.object));
    });

    writer.end(function (error, result) {
      if (error != null) {
        callback(null, error);
      } else {
        callback(result);
      }
    });

    return true;
  };
};

if (isNode) {
  module.exports = function (rdf) {
    rdf.TurtleSerializer = TurtleSerializer.bind(null, rdf);

    var serializer = new TurtleSerializer(rdf);
    rdf.serializeTurtle = serializer.serialize.bind(serializer);
  };

  module.exports.serializer = TurtleSerializer;
} else {
  if (typeof rdf === 'undefined') {
    rdf = {};
  }

  if (typeof N3 !== 'undefined') {
    rdf.TurtleSerializer = TurtleSerializer.bind(null, rdf);

    var serializer = new TurtleSerializer(rdf);
    rdf.serializeTurtle = serializer.serialize.bind(serializer);
  }
}<commit_msg>fixed typo<commit_after>/* global rdf:true */
'use strict';

var isNode = (typeof process !== 'undefined' && process.versions && process.versions.node);

if (isNode) {
  global.N3 = require('n3');
}

var TurtleSerializer = function (rdf, options) {
  if (options == null) {
    options = {};
  }

  if (!('usePrefixMap' in options)) {
    options.usePrefixMap = true;
  }

  this.serialize = function (graph, callback) {
    var
      writerOptions = {},
      writer;

    if (options.usePrefixMap) {
      writerOptions.prefixes = {};

      Object.keys(rdf.prefixes).forEach(function (prefix) {
        if (typeof rdf.prefixes[prefix] !== 'string') {
          return;
        }

        writerOptions.prefixes[prefix] = rdf.prefixes[prefix];
      });
    }

    writer = N3.Writer(writerOptions);

    if (callback == null) {
      callback = function () {};
    }

    var createN3Node = function (node) {
      if (node.interfaceName.toString() === 'NamedNode') {
        return node.nominalValue;
      } else if (node.interfaceName.toString() === 'BlankNode') {
        return '_:' + node.nominalValue;
      } else {
        if (node.datatype != null) {
          return '"' + node.nominalValue + '"^^' + node.datatype.nominalValue;
        } else if (node.language != null) {
          return '"' + node.nominalValue + '"@' + node.language;
        } else {
          return '"' + node.nominalValue + '"';
        }
      }
    };

    graph.forEach(function (triple) {
      writer.addTriple(
        createN3Node(triple.subject),
        createN3Node(triple.predicate),
        createN3Node(triple.object));
    });

    writer.end(function (error, result) {
      if (error != null) {
        callback(null, error);
      } else {
        callback(result);
      }
    });

    return true;
  };
};

if (isNode) {
  module.exports = function (rdf) {
    rdf.TurtleSerializer = TurtleSerializer.bind(null, rdf);

    var serializer = new TurtleSerializer(rdf);
    rdf.serializeTurtle = serializer.serialize.bind(serializer);
  };

  module.exports.serializer = TurtleSerializer;
} else {
  if (typeof rdf === 'undefined') {
    rdf = {};
  }

  if (typeof N3 !== 'undefined') {
    rdf.TurtleSerializer = TurtleSerializer.bind(null, rdf);

    var serializer = new TurtleSerializer(rdf);
    rdf.serializeTurtle = serializer.serialize.bind(serializer);
  }
}<|endoftext|>


<commit_before>var BufferedStream = require('bufferedstream'),
    http = require('http'),
    _ = require('underscore');

// Convert a header name to its canonical form, eg. "content-length" => "Content-Length".
var headerNameSpecialCases = {
    'content-md5': 'Content-MD5',
    dnt: 'DNT',
    etag: 'ETag',
    p3p: 'P3P',
    te: 'TE',
    'www-authenticate': 'WWW-Authenticate',
    'x-att-deviceid': 'X-ATT-DeviceId',
    'x-cdn': 'X-CDN',
    'x-ua-compatible': 'X-UA-Compatible',
    'x-xss-protection': 'X-XSS-Protection'
};

function formatHeaderName(headerName) {
    var lowerCasedHeaderName = headerName.toLowerCase();
    if (headerNameSpecialCases.hasOwnProperty(lowerCasedHeaderName)) {
        return headerNameSpecialCases[lowerCasedHeaderName];
    } else {
        // Make sure that the first char and all chars following a dash are upper-case:
        return lowerCasedHeaderName.replace(/(^|-)([a-z])/g, function ($0, optionalLeadingDash, ch) {
            return optionalLeadingDash + ch.toUpperCase();
        });
    }
}

function formatHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[formatHeaderName(headerName)] = obj[headerName];
    });
    return resultObj;
}

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

module.exports = function (expect) {
    return expect.addAssertion('to be middleware that processes', function (expect, subject, value, done) {
        expect(done, 'to be a function');
        var requestProperties = _.extend({}, value.request),
            expectedResponseProperties = typeof value.response === 'number' ? {statusCode: value.response} : _.extend({}, value.response);

        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers || {});

        var responseProperties = {},
            requestStream,
            req;

        if (requestProperties.body) {
            if (requestProperties.body.pipe) {
                requestStream = requestProperties.body;
                delete requestProperties.body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                    requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                }
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof requestProperties.body === 'object') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                }
            }
        } else {
            // FIXME: Empty BufferedStream?
            requestStream = {
                destroy: function () {
                    responseProperties.requestDestroyed = true;
                }
            };
        }

        req = new http.IncomingMessage(requestStream);
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                res.statusCode = err && (err.statusCode || err.status) || 404;
                done();
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        function doTheAssertions() {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            try {
                var expectedResponseHeaders,
                    responseProperties = {
                        strictAsync: isAsync,
                        statusCode: res.statusCode,
                        body: Buffer.concat(responseBodyChunks),
                        isDestroyed: isDestroyed,
                        nextCalled: nextCallCount > 0
                    };

                if (typeof expectedResponseProperties.headers === 'object') {
                    expectedResponseHeaders = expectedResponseProperties.headers;
                    delete expectedResponseProperties.headers;
                }

                if (typeof expectedResponseProperties.body === 'string') {
                    responseProperties.body = responseProperties.body.toString('utf-8');
                } else if (expectedResponseProperties.body && !Buffer.isBuffer(expectedResponseProperties.body) && typeof expectedResponseProperties.body === 'object' && /^application\/json\b/.test(res._headers['content-type'])) {
                    responseProperties.body = JSON.parse(responseProperties.body.toString('utf-8'));
                }
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (expectedResponseHeaders) {
                    expect(formatHeaderNames(res._headers), 'to have properties', formatHeaderNames(expectedResponseHeaders));
                }
            } catch (err) {
                return done(err);
            }
            done();
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding) {
                        throw new Error('encoding parameter not supported');
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<commit_msg>FIX: Make sure assertions are also run when 'next' is called<commit_after>var BufferedStream = require('bufferedstream'),
    http = require('http'),
    _ = require('underscore');

// Convert a header name to its canonical form, eg. "content-length" => "Content-Length".
var headerNameSpecialCases = {
    'content-md5': 'Content-MD5',
    dnt: 'DNT',
    etag: 'ETag',
    p3p: 'P3P',
    te: 'TE',
    'www-authenticate': 'WWW-Authenticate',
    'x-att-deviceid': 'X-ATT-DeviceId',
    'x-cdn': 'X-CDN',
    'x-ua-compatible': 'X-UA-Compatible',
    'x-xss-protection': 'X-XSS-Protection'
};

function formatHeaderName(headerName) {
    var lowerCasedHeaderName = headerName.toLowerCase();
    if (headerNameSpecialCases.hasOwnProperty(lowerCasedHeaderName)) {
        return headerNameSpecialCases[lowerCasedHeaderName];
    } else {
        // Make sure that the first char and all chars following a dash are upper-case:
        return lowerCasedHeaderName.replace(/(^|-)([a-z])/g, function ($0, optionalLeadingDash, ch) {
            return optionalLeadingDash + ch.toUpperCase();
        });
    }
}

function formatHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[formatHeaderName(headerName)] = obj[headerName];
    });
    return resultObj;
}

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

module.exports = function (expect) {
    return expect.addAssertion('to be middleware that processes', function (expect, subject, value, done) {
        expect(done, 'to be a function');
        var requestProperties = _.extend({}, value.request),
            expectedResponseProperties = typeof value.response === 'number' ? {statusCode: value.response} : _.extend({}, value.response);

        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers || {});

        var responseProperties = {},
            requestStream,
            req;

        if (requestProperties.body) {
            if (requestProperties.body.pipe) {
                requestStream = requestProperties.body;
                delete requestProperties.body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                    requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                }
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof requestProperties.body === 'object') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                }
            }
        } else {
            // FIXME: Empty BufferedStream?
            requestStream = {
                destroy: function () {
                    responseProperties.requestDestroyed = true;
                }
            };
        }

        req = new http.IncomingMessage(requestStream);
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                res.statusCode = err && (err.statusCode || err.status) || 404;
                doTheAssertions();
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        function doTheAssertions() {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            try {
                var expectedResponseHeaders,
                    responseProperties = {
                        strictAsync: isAsync,
                        statusCode: res.statusCode,
                        body: Buffer.concat(responseBodyChunks),
                        isDestroyed: isDestroyed,
                        nextCalled: nextCallCount > 0
                    };

                if (typeof expectedResponseProperties.headers === 'object') {
                    expectedResponseHeaders = expectedResponseProperties.headers;
                    delete expectedResponseProperties.headers;
                }

                if (typeof expectedResponseProperties.body === 'string') {
                    responseProperties.body = responseProperties.body.toString('utf-8');
                } else if (expectedResponseProperties.body && !Buffer.isBuffer(expectedResponseProperties.body) && typeof expectedResponseProperties.body === 'object' && /^application\/json\b/.test(res._headers['content-type'])) {
                    responseProperties.body = JSON.parse(responseProperties.body.toString('utf-8'));
                }
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (expectedResponseHeaders) {
                    expect(formatHeaderNames(res._headers), 'to have properties', formatHeaderNames(expectedResponseHeaders));
                }
            } catch (err) {
                return done(err);
            }
            done();
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding) {
                        throw new Error('encoding parameter not supported');
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<|endoftext|>


<commit_before>/*global setImmediate:true, setTimeout*/

if (typeof setImmediate === 'undefined') {
    setImmediate = process.nextTick;
}

var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    stream = require('stream'),
    _ = require('underscore'),
    FormData = require('form-data'),
    messy = require('messy'),
    metadataPropertyNames = ['strictAsync', 'errorPassedToNext', 'isDestroyed', 'nextCalled'];

module.exports = {
    name: 'unexpected-express',
    installInto: function (expect) {
        expect.installPlugin(require('unexpected-messy'));

        expect.addType({
            name: 'IncomingMessage',
            base: 'object',
            identify: function (obj) {
                return obj && obj.constructor && obj.constructor.name === 'IncomingMessage';
            },
            inspect: function (obj, depth, output) {
                output.text(obj.constructor.name, 'jsFunctionName');
            }
        });

        expect.addAssertion('IncomingMessage', 'to have url satisfying', function (expect, subject, value) {
            this.errorMode = 'nested';
            expect(subject.url, 'to satisfy', value);
        });

        expect.addAssertion('function', 'to yield exchange', function (expect, subject, value, unsupportedDone) {
            var that = this;

            if (!subject.handle || !subject.set) {
                // This check is from the lib/application file in express @ 4.10.2.
                // If we get inside here, we have something that is not an express app
                // https://github.com/strongloop/express/blob/661435256384165bb656cb7b6046b4138ca24c9e/lib/application.js#L186
                subject = require('express')().use(subject);
                this.subject = expect.output.clone().text('express middleware');
            } else {
                this.subject = expect.output.clone().text('express app');
            }

            if (unsupportedDone) {
                throw new Error('unexpected-express 6 no longer supports a "done" callback, but returns a promise');
            }

            var context = {},
                nextCalls = [];

            value = _.extend({}, value);
            var requestProperties = typeof value.request === 'string' ? {url: value.request} : _.extend({}, value.request),
                httpRequest = new messy.HttpRequest({
                    method: requestProperties.method,
                    url: requestProperties.url || '/',
                    protocolName: 'HTTP',
                    protocolVersion: requestProperties.httpVersion || '1.1',
                    headers: requestProperties.headers
                });
            httpRequest._body = requestProperties.body;
            delete value.request;
            delete requestProperties.method;
            delete requestProperties.url;
            delete requestProperties.httpVersion;
            delete requestProperties.headers;
            delete requestProperties.body;
            if (typeof httpRequest.url === 'string') {
                var matchMethod = httpRequest.url.match(/^([A-Z]+) ([\s\S]*)$/);
                if (matchMethod) {
                    httpRequest.method = httpRequest.method || matchMethod[1];
                    httpRequest.url = matchMethod[2];
                }
            }
            httpRequest.method = httpRequest.method || 'GET';
            if (/^https?:\/\//.test(httpRequest.url)) {
                var urlObj = URL.parse(httpRequest.url);
                if (!httpRequest.headers.has('Host')) {
                    httpRequest.headers.set('Host', urlObj.host);
                }
                if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                    requestProperties.https = true;
                }
                httpRequest.url = urlObj.path;
            }
            if (requestProperties.formData) {
                if (httpRequest._body) {
                    throw new Error('unexpected-express: The "body" and "formData" options are not supported together');
                }
                httpRequest._body = new FormData();
                Object.keys(requestProperties.formData).forEach(function (name) {
                    var value = requestProperties.formData[name],
                        options;
                    if (typeof value === 'object') {
                        options = _.extend({}, value);
                        value = options.value;
                        delete options.value;
                        if (options.fileName) {
                            options.filename = options.fileName;
                            delete options.fileName;
                        }
                    }
                    httpRequest._body.append(name, value, options);
                });

                delete requestProperties.formData;
            }

            if (typeof requestProperties.query !== 'undefined') {
                if (typeof requestProperties.query === 'object' && requestProperties.query) {
                    Object.keys(requestProperties.query).forEach(function (parameterName) {
                        var valueOrValues = requestProperties.query[parameterName];
                        (Array.isArray(valueOrValues) ? valueOrValues : [String(valueOrValues)]).forEach(function (value) {
                            httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') +
                                encodeURIComponent(parameterName) + '=' + encodeURIComponent(value);
                        });
                    });
                } else {
                    httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') + String(requestProperties.query);
                }
                delete requestProperties.query;
            }

            var expectedResponseProperties;

            if (typeof value.response === 'number') {
                expectedResponseProperties = {statusCode: value.response};
            } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
                expectedResponseProperties = {body: value.response};
            } else {
                expectedResponseProperties = _.extend({}, value.response);
            }
            delete value.response;

            var expectedMetadata = _.extend(
                    {},
                    _.pick(expectedResponseProperties, metadataPropertyNames),
                    _.pick(value, metadataPropertyNames)
                );
            expectedResponseProperties = _.omit(expectedResponseProperties, metadataPropertyNames);

            var missingResponseProperties = Object.keys(expectedResponseProperties).filter(function (key) {
                return messy.HttpResponse.propertyNames.indexOf(key) === -1 && metadataPropertyNames.indexOf(key) === -1 && key !== 'url' && key !== 'locals';
            });
            if (missingResponseProperties.length > 0) {
                throw new Error('Property "' + missingResponseProperties[0] + '" does not exist on the response object.');
            }

            var responseProperties = {},
                requestStream,
                req;

            if (typeof httpRequest._body !== 'undefined') {
                httpRequest.headers.set('Transfer-Encoding', 'chunked');
                if (httpRequest._body.pipe) {
                    requestStream = httpRequest._body;
                    if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                        if (!httpRequest.headers.has('Content-Type')) {
                            httpRequest.headers.set('Content-Type', 'multipart/form-data; boundary=' + requestStream.getBoundary());
                            // form-data pauses its streams by default for some reason:
                            setImmediate(function () {
                                requestStream.resume();
                            });
                        }
                    }
                    var requestBodyChunks = [];
                    requestStream.on('data', function (chunk) {
                        if (!Buffer.isBuffer(chunk)) {
                            chunk = new Buffer(chunk, 'utf-8');
                        }
                        requestBodyChunks.push(chunk);
                        req.emit('data', chunk);
                    }).on('end', function () {
                        httpRequest._body = Buffer.concat(requestBodyChunks);
                        req.emit('end');
                    }).on('error', function (err) {
                        req.emit('error', err);
                    });
                } else {
                    if (typeof httpRequest._body === 'object' && !Buffer.isBuffer(httpRequest._body)) {
                        if (!httpRequest.headers.has('Content-Type')) {
                            httpRequest.headers.set('Content-Type', 'application/json');
                        }
                        httpRequest._body = JSON.stringify(httpRequest._body);
                    }

                    if (!Buffer.isBuffer(httpRequest._body)) {
                        httpRequest._body = new Buffer(String(httpRequest._body), 'utf-8');
                    }
                    if (!httpRequest.headers.has('Content-Length') && !httpRequest.headers.has('Transfer-Encoding')) {
                        httpRequest.headers.set('Content-Length', String(httpRequest._body.length));
                    }
                    requestStream = new BufferedStream();
                    setImmediate(function emitRequestBodyOnceThereIsAListener() {
                        if (req.listeners('data').length > 0 || req.listeners('end').length > 0) {
                            req.emit('data', httpRequest._body);
                            req.emit('end');
                        } else {
                            setTimeout(emitRequestBodyOnceThereIsAListener, 10);
                        }
                    });
                }
            } else {
                requestStream = new BufferedStream();
                requestStream.destroy = function () {
                    responseProperties.requestDestroyed = true;
                };
                setImmediate(function () {
                    requestStream.emit('end');
                    req.emit('end');
                });
            }

            req = new http.IncomingMessage(requestStream);
            req.httpVersion = httpRequest.protocolVersion;
            if (requestProperties.flowMode) {
                delete requestProperties.flowMode;
                req.resume();
            }
            var matchProtocolVersion = String(httpRequest.protocolVersion).match(/^(\d+)(?:\.(\d+))$/);
            if (matchProtocolVersion) {
                req.httpVersionMajor = parseInt(matchProtocolVersion[1], 10);
                req.httpVersionMinor = matchProtocolVersion[2] ? parseInt(matchProtocolVersion[2], 10) : 0;
            }
            req.connection = req.connection || {};
            req.connection.encrypted = !!requestProperties.https;
            delete requestProperties.https;
            req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
            delete requestProperties.ip;
            delete requestProperties.remoteAddress;
            req.headers = {};
            httpRequest.headers.getNames().forEach(function (headerName) {
                if (headerName === 'set-cookie') {
                    req.headers[headerName] = [].concat(httpRequest.headers.valuesByName[headerName]);
                } else {
                    req.headers[headerName] = httpRequest.headers.valuesByName[headerName].join(', ');
                }
            });
            req.method = httpRequest.method;
            req.url = httpRequest.url;
            _.extend(req, requestProperties);

            var res = new http.ServerResponse(req);
            _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
            delete requestProperties.res;
            res.locals = res.locals || {};

            var rawResponseChunks = [];
            res.assignSocket(new stream.Writable());
            res.connection._write = function (chunk, encoding, cb) {
                rawResponseChunks.push(chunk);
                cb();
            };

            var isAsync = false,
                isDestroyed = false,
                done = false,
                errorPassedToNext;

            setImmediate(function () {
                isAsync = true;
            });

            return expect.promise(function (resolve, reject) {
                ['write', 'end', 'destroy'].forEach(function (methodName) {
                    var orig = res[methodName];
                    res[methodName] = function (chunk, encoding) {
                        var returnValue = orig.apply(this, arguments);
                        isDestroyed = isDestroyed || methodName === 'destroy';
                        if (methodName === 'end' || methodName === 'destroy') {
                            resolve();
                        }
                        // Don't attempt to implement backpressure, since we're buffering the entire response anyway.
                        if (methodName !== 'write') {
                            return returnValue;
                        }
                    };
                });
                subject(req, res, function (err, _req, _res, _next) {
                    nextCalls.push(arguments);
                    if (nextCalls.length > 1) {
                        if (done) {
                            if (err) {
                                throw err;
                            } else {
                                throw new Error('next called more than once');
                            }
                        } else {
                            // Will be reported as a failure later
                            return;
                        }
                    }
                    errorPassedToNext = err;
                    if (typeof err === 'number') {
                        var statusCode = err;
                        err = new Error('' + statusCode);
                        err.statusCode = statusCode;
                    }
                    res.statusCode = err && (err.statusCode || err.status) || 404;
                    resolve();
                });
            }).then(function () {
                _.extend(context, {
                    req: req,
                    res: res,
                    httpRequest: httpRequest,
                    metadata: {
                        strictAsync: isAsync,
                        errorPassedToNext: false,
                        isDestroyed: isDestroyed,
                        nextCalled: nextCalls.length > 0
                    }
                });

                if (errorPassedToNext && errorPassedToNext.statusCode) {
                    res.writeHead(errorPassedToNext.statusCode);
                }

                var httpResponse = context.httpResponse = new messy.HttpResponse(
                    rawResponseChunks.length > 0 ? Buffer.concat(rawResponseChunks) : res._header || undefined
                );
                httpResponse.statusCode = httpResponse.statusCode || res.statusCode;

                if (errorPassedToNext) {
                    context.metadata.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedMetadata.errorPassedToNext !== 'undefined') {
                        if (expectedMetadata.errorPassedToNext === true) {
                            context.metadata.errorPassedToNext = true;
                        } else if (typeof expectedMetadata.errorPassedToNext === 'string') {
                            context.metadata.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            context.metadata.errorPassedToNext = errorPassedToNext;
                        }
                    } else if (typeof errorPassedToNext.statusCode === 'number') {
                        // FIXME
                        if (!httpResponse.headers.get('Content-Type')) {
                            httpResponse.headers.set('Content-Type', 'text/plain');
                            httpResponse.body = errorPassedToNext.stack;
                        }
                    } else {
                        throw errorPassedToNext;
                    }
                }
                return expect(context.metadata, 'to satisfy', expectedMetadata);
            }).then(function () {
                if (expectedResponseProperties.locals) {
                    var expectedLocals = expectedResponseProperties.locals;
                    delete expectedResponseProperties.locals;
                    return expect({ res: { locals: res.locals } }, 'to satisfy', { res: { locals: expectedLocals } });
                }
            }).then(function () {
                if (expectedResponseProperties.url) {
                    var expectedUrl = expectedResponseProperties.url;
                    delete expectedResponseProperties.url;
                    that.errorMode = 'diff';
                    return expect(req, 'to have url satisfying', expectedUrl);
                }
            }).then(function () {
                that.errorMode = 'default';
                return expect(new messy.HttpExchange({
                    request: context.httpRequest,
                    response: context.httpResponse
                }), 'to satisfy', {response: expectedResponseProperties});
            }).then(function () {
                if (nextCalls.length > 1) {
                    throw new Error('next called more than once');
                }
                done = true; // Tell the next function that subsequent calls should cause an exception to be thrown
            });
        });
    }
};

module.exports.messy = messy;
<commit_msg>Whoops, remember to provide the context as the completion value.<commit_after>/*global setImmediate:true, setTimeout*/

if (typeof setImmediate === 'undefined') {
    setImmediate = process.nextTick;
}

var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    stream = require('stream'),
    _ = require('underscore'),
    FormData = require('form-data'),
    messy = require('messy'),
    metadataPropertyNames = ['strictAsync', 'errorPassedToNext', 'isDestroyed', 'nextCalled'];

module.exports = {
    name: 'unexpected-express',
    installInto: function (expect) {
        expect.installPlugin(require('unexpected-messy'));

        expect.addType({
            name: 'IncomingMessage',
            base: 'object',
            identify: function (obj) {
                return obj && obj.constructor && obj.constructor.name === 'IncomingMessage';
            },
            inspect: function (obj, depth, output) {
                output.text(obj.constructor.name, 'jsFunctionName');
            }
        });

        expect.addAssertion('IncomingMessage', 'to have url satisfying', function (expect, subject, value) {
            this.errorMode = 'nested';
            expect(subject.url, 'to satisfy', value);
        });

        expect.addAssertion('function', 'to yield exchange', function (expect, subject, value, unsupportedDone) {
            var that = this;

            if (!subject.handle || !subject.set) {
                // This check is from the lib/application file in express @ 4.10.2.
                // If we get inside here, we have something that is not an express app
                // https://github.com/strongloop/express/blob/661435256384165bb656cb7b6046b4138ca24c9e/lib/application.js#L186
                subject = require('express')().use(subject);
                this.subject = expect.output.clone().text('express middleware');
            } else {
                this.subject = expect.output.clone().text('express app');
            }

            if (unsupportedDone) {
                throw new Error('unexpected-express 6 no longer supports a "done" callback, but returns a promise');
            }

            var context = {},
                nextCalls = [];

            value = _.extend({}, value);
            var requestProperties = typeof value.request === 'string' ? {url: value.request} : _.extend({}, value.request),
                httpRequest = new messy.HttpRequest({
                    method: requestProperties.method,
                    url: requestProperties.url || '/',
                    protocolName: 'HTTP',
                    protocolVersion: requestProperties.httpVersion || '1.1',
                    headers: requestProperties.headers
                });
            httpRequest._body = requestProperties.body;
            delete value.request;
            delete requestProperties.method;
            delete requestProperties.url;
            delete requestProperties.httpVersion;
            delete requestProperties.headers;
            delete requestProperties.body;
            if (typeof httpRequest.url === 'string') {
                var matchMethod = httpRequest.url.match(/^([A-Z]+) ([\s\S]*)$/);
                if (matchMethod) {
                    httpRequest.method = httpRequest.method || matchMethod[1];
                    httpRequest.url = matchMethod[2];
                }
            }
            httpRequest.method = httpRequest.method || 'GET';
            if (/^https?:\/\//.test(httpRequest.url)) {
                var urlObj = URL.parse(httpRequest.url);
                if (!httpRequest.headers.has('Host')) {
                    httpRequest.headers.set('Host', urlObj.host);
                }
                if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                    requestProperties.https = true;
                }
                httpRequest.url = urlObj.path;
            }
            if (requestProperties.formData) {
                if (httpRequest._body) {
                    throw new Error('unexpected-express: The "body" and "formData" options are not supported together');
                }
                httpRequest._body = new FormData();
                Object.keys(requestProperties.formData).forEach(function (name) {
                    var value = requestProperties.formData[name],
                        options;
                    if (typeof value === 'object') {
                        options = _.extend({}, value);
                        value = options.value;
                        delete options.value;
                        if (options.fileName) {
                            options.filename = options.fileName;
                            delete options.fileName;
                        }
                    }
                    httpRequest._body.append(name, value, options);
                });

                delete requestProperties.formData;
            }

            if (typeof requestProperties.query !== 'undefined') {
                if (typeof requestProperties.query === 'object' && requestProperties.query) {
                    Object.keys(requestProperties.query).forEach(function (parameterName) {
                        var valueOrValues = requestProperties.query[parameterName];
                        (Array.isArray(valueOrValues) ? valueOrValues : [String(valueOrValues)]).forEach(function (value) {
                            httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') +
                                encodeURIComponent(parameterName) + '=' + encodeURIComponent(value);
                        });
                    });
                } else {
                    httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') + String(requestProperties.query);
                }
                delete requestProperties.query;
            }

            var expectedResponseProperties;

            if (typeof value.response === 'number') {
                expectedResponseProperties = {statusCode: value.response};
            } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
                expectedResponseProperties = {body: value.response};
            } else {
                expectedResponseProperties = _.extend({}, value.response);
            }
            delete value.response;

            var expectedMetadata = _.extend(
                    {},
                    _.pick(expectedResponseProperties, metadataPropertyNames),
                    _.pick(value, metadataPropertyNames)
                );
            expectedResponseProperties = _.omit(expectedResponseProperties, metadataPropertyNames);

            var missingResponseProperties = Object.keys(expectedResponseProperties).filter(function (key) {
                return messy.HttpResponse.propertyNames.indexOf(key) === -1 && metadataPropertyNames.indexOf(key) === -1 && key !== 'url' && key !== 'locals';
            });
            if (missingResponseProperties.length > 0) {
                throw new Error('Property "' + missingResponseProperties[0] + '" does not exist on the response object.');
            }

            var responseProperties = {},
                requestStream,
                req;

            if (typeof httpRequest._body !== 'undefined') {
                httpRequest.headers.set('Transfer-Encoding', 'chunked');
                if (httpRequest._body.pipe) {
                    requestStream = httpRequest._body;
                    if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                        if (!httpRequest.headers.has('Content-Type')) {
                            httpRequest.headers.set('Content-Type', 'multipart/form-data; boundary=' + requestStream.getBoundary());
                            // form-data pauses its streams by default for some reason:
                            setImmediate(function () {
                                requestStream.resume();
                            });
                        }
                    }
                    var requestBodyChunks = [];
                    requestStream.on('data', function (chunk) {
                        if (!Buffer.isBuffer(chunk)) {
                            chunk = new Buffer(chunk, 'utf-8');
                        }
                        requestBodyChunks.push(chunk);
                        req.emit('data', chunk);
                    }).on('end', function () {
                        httpRequest._body = Buffer.concat(requestBodyChunks);
                        req.emit('end');
                    }).on('error', function (err) {
                        req.emit('error', err);
                    });
                } else {
                    if (typeof httpRequest._body === 'object' && !Buffer.isBuffer(httpRequest._body)) {
                        if (!httpRequest.headers.has('Content-Type')) {
                            httpRequest.headers.set('Content-Type', 'application/json');
                        }
                        httpRequest._body = JSON.stringify(httpRequest._body);
                    }

                    if (!Buffer.isBuffer(httpRequest._body)) {
                        httpRequest._body = new Buffer(String(httpRequest._body), 'utf-8');
                    }
                    if (!httpRequest.headers.has('Content-Length') && !httpRequest.headers.has('Transfer-Encoding')) {
                        httpRequest.headers.set('Content-Length', String(httpRequest._body.length));
                    }
                    requestStream = new BufferedStream();
                    setImmediate(function emitRequestBodyOnceThereIsAListener() {
                        if (req.listeners('data').length > 0 || req.listeners('end').length > 0) {
                            req.emit('data', httpRequest._body);
                            req.emit('end');
                        } else {
                            setTimeout(emitRequestBodyOnceThereIsAListener, 10);
                        }
                    });
                }
            } else {
                requestStream = new BufferedStream();
                requestStream.destroy = function () {
                    responseProperties.requestDestroyed = true;
                };
                setImmediate(function () {
                    requestStream.emit('end');
                    req.emit('end');
                });
            }

            req = new http.IncomingMessage(requestStream);
            req.httpVersion = httpRequest.protocolVersion;
            if (requestProperties.flowMode) {
                delete requestProperties.flowMode;
                req.resume();
            }
            var matchProtocolVersion = String(httpRequest.protocolVersion).match(/^(\d+)(?:\.(\d+))$/);
            if (matchProtocolVersion) {
                req.httpVersionMajor = parseInt(matchProtocolVersion[1], 10);
                req.httpVersionMinor = matchProtocolVersion[2] ? parseInt(matchProtocolVersion[2], 10) : 0;
            }
            req.connection = req.connection || {};
            req.connection.encrypted = !!requestProperties.https;
            delete requestProperties.https;
            req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
            delete requestProperties.ip;
            delete requestProperties.remoteAddress;
            req.headers = {};
            httpRequest.headers.getNames().forEach(function (headerName) {
                if (headerName === 'set-cookie') {
                    req.headers[headerName] = [].concat(httpRequest.headers.valuesByName[headerName]);
                } else {
                    req.headers[headerName] = httpRequest.headers.valuesByName[headerName].join(', ');
                }
            });
            req.method = httpRequest.method;
            req.url = httpRequest.url;
            _.extend(req, requestProperties);

            var res = new http.ServerResponse(req);
            _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
            delete requestProperties.res;
            res.locals = res.locals || {};

            var rawResponseChunks = [];
            res.assignSocket(new stream.Writable());
            res.connection._write = function (chunk, encoding, cb) {
                rawResponseChunks.push(chunk);
                cb();
            };

            var isAsync = false,
                isDestroyed = false,
                done = false,
                errorPassedToNext;

            setImmediate(function () {
                isAsync = true;
            });

            return expect.promise(function (resolve, reject) {
                ['write', 'end', 'destroy'].forEach(function (methodName) {
                    var orig = res[methodName];
                    res[methodName] = function (chunk, encoding) {
                        var returnValue = orig.apply(this, arguments);
                        isDestroyed = isDestroyed || methodName === 'destroy';
                        if (methodName === 'end' || methodName === 'destroy') {
                            resolve();
                        }
                        // Don't attempt to implement backpressure, since we're buffering the entire response anyway.
                        if (methodName !== 'write') {
                            return returnValue;
                        }
                    };
                });
                subject(req, res, function (err, _req, _res, _next) {
                    nextCalls.push(arguments);
                    if (nextCalls.length > 1) {
                        if (done) {
                            if (err) {
                                throw err;
                            } else {
                                throw new Error('next called more than once');
                            }
                        } else {
                            // Will be reported as a failure later
                            return;
                        }
                    }
                    errorPassedToNext = err;
                    if (typeof err === 'number') {
                        var statusCode = err;
                        err = new Error('' + statusCode);
                        err.statusCode = statusCode;
                    }
                    res.statusCode = err && (err.statusCode || err.status) || 404;
                    resolve();
                });
            }).then(function () {
                _.extend(context, {
                    req: req,
                    res: res,
                    httpRequest: httpRequest,
                    metadata: {
                        strictAsync: isAsync,
                        errorPassedToNext: false,
                        isDestroyed: isDestroyed,
                        nextCalled: nextCalls.length > 0
                    }
                });

                if (errorPassedToNext && errorPassedToNext.statusCode) {
                    res.writeHead(errorPassedToNext.statusCode);
                }

                var httpResponse = context.httpResponse = new messy.HttpResponse(
                    rawResponseChunks.length > 0 ? Buffer.concat(rawResponseChunks) : res._header || undefined
                );
                httpResponse.statusCode = httpResponse.statusCode || res.statusCode;

                if (errorPassedToNext) {
                    context.metadata.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedMetadata.errorPassedToNext !== 'undefined') {
                        if (expectedMetadata.errorPassedToNext === true) {
                            context.metadata.errorPassedToNext = true;
                        } else if (typeof expectedMetadata.errorPassedToNext === 'string') {
                            context.metadata.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            context.metadata.errorPassedToNext = errorPassedToNext;
                        }
                    } else if (typeof errorPassedToNext.statusCode === 'number') {
                        // FIXME
                        if (!httpResponse.headers.get('Content-Type')) {
                            httpResponse.headers.set('Content-Type', 'text/plain');
                            httpResponse.body = errorPassedToNext.stack;
                        }
                    } else {
                        throw errorPassedToNext;
                    }
                }
                return expect(context.metadata, 'to satisfy', expectedMetadata);
            }).then(function () {
                if (expectedResponseProperties.locals) {
                    var expectedLocals = expectedResponseProperties.locals;
                    delete expectedResponseProperties.locals;
                    return expect({ res: { locals: res.locals } }, 'to satisfy', { res: { locals: expectedLocals } });
                }
            }).then(function () {
                if (expectedResponseProperties.url) {
                    var expectedUrl = expectedResponseProperties.url;
                    delete expectedResponseProperties.url;
                    that.errorMode = 'diff';
                    return expect(req, 'to have url satisfying', expectedUrl);
                }
            }).then(function () {
                that.errorMode = 'default';
                return expect(new messy.HttpExchange({
                    request: context.httpRequest,
                    response: context.httpResponse
                }), 'to satisfy', {response: expectedResponseProperties});
            }).then(function () {
                if (nextCalls.length > 1) {
                    throw new Error('next called more than once');
                }
                done = true; // Tell the next function that subsequent calls should cause an exception to be thrown
                return context;
            });
        });
    }
};

module.exports.messy = messy;
<|endoftext|>


<commit_before>/**
 *
 * Class to do ajax requests with Butterfly
 *
 */

Butterfly.Ajax = {

    /**
     *
     * behaviors : functions for what to do on each status ans readyState
     *
     */
    request: function(url, behaviorsStatus, behaviorsReadyState, method, params, options)
    {

        if (behaviorsStatus == null) {
            behaviorsStatus = {};
        }

        if (behaviorsReadyState == null) {
            behaviorsReadyState = {};
        }

        if (options == null) {
            options = {};
        }

        var xhr;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        }
        else if (window.ActiveXObject) {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        }

        if (xhr) {
            xhr.onreadystatechange = function()
            {
                if(xhr.readyState  == 4) {
                    if(behaviorsStatus[xhr.status]) {
                        var func = behaviorsStatus[xhr.status];
                        func(xhr);
                    }
                }
                else {
                    if (behaviorsReadyState[xhr.readyState]) {
                        var func = behaviorsReadyState[xhr.readyState];
                        func(xhr);
                    }
                }
            };

            xhr.open( method == 'POST' ? method : 'GET', url,  (Butterfly.exists(options.async) ? options.async : true));

            if (method == 'POST') {
                xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhr.setRequestHeader("Content-length", params.length);
                xhr.setRequestHeader("Connection", "close");
            }

            xhr.send( method == 'POST' ? params : null );

            return xhr;
        }
    },

    update: function(nodeToUpdate, url, evalScripts, method, params, options)
    {
        Butterfly.Ajax.request(url, {200: function(xhr)
        {
            $id(nodeToUpdate).innerHTML = xhr.responseText;

            setTimeout(
                function()
                {
                    if (evalScripts) {
                        var scripts = Butterfly.Ajax._getScriptsFromString(xhr.responseText);
                        var nbScripts = scripts.length;
                        for (var i = 0 ; i < nbScripts ; i ++) {
                            window.eval(scripts[i]);
                        }
                    }
                },
                700
            );
        }}, {}, method, params, options);
    },

    /**
     *
     * extract all js code in <scripts> tags and return an array
     * containing the codes
     *
     * For now, htmls is useless... maybe for another use ?
     * str is [html part][script part][html part][script part]...
     *
     * [html part]s go in htmls var
     * [script part]s go in scripts var
     *
     */
    _getScriptsFromString: function(str)
    {
        var scriptBeginRegexp = /<script[\stype="'x\/javascri]*>/gi;
        var scriptEndRegexp = /<\/[\s]*script>/gi;

        var posFirst, valueFirst;
        var posSecond, valueSecond;
        var pos1, pos2, val2;
        var htmls = [];
        var scripts = [];

        while (str != '') {
            //return the position of the first script open tag (<script>)
            posFirst = str.search(scriptBeginRegexp);
            valueFirst = str.match(scriptBeginRegexp);

            //if a script is found
            if (posFirst != -1) {
                pos1 = posFirst;
            }
            else {
                pos1 = str.length - 1;
            }

            //put the current html part
            htmls.push(str.substring(0, pos1));

            if (posFirst != -1) {
                posSecond = str.search(scriptEndRegexp);
                valueSecond = str.match(scriptEndRegexp);

                if (posSecond != -1) {
                    pos2 = posSecond;
                }
                else {
                    pos2 = str.length - 1;
                }
                scripts.push(str.substring(pos1 + valueFirst[0].length, pos2));
                val2 = valueSecond[0].length;
            }
            else {
                //stop the loop
                pos2 = str.length - 1;
                val2 = 0;
            }

            str = str.substr(pos2 + val2, str.length - 1);
        }

        return scripts;
    }

};
<commit_msg>first raw changes on Ajax<commit_after>(function(){
	if (!window.B) throw 'Butterfly base library is needed';
	var B.Ajax={};

	/**
	 *
	 * extract all js code in <scripts> tags and return an array
	 * containing the codes
	 *
	 * str is [html part][script part][html part][script part]...
	 *
	 * [script part]s go in scripts var
	 *
	 */
	var getScripts=function(s){
		var scriptBeginRegexp = /<script[\stype="'x\/javascri]*>/gi;
		var scriptEndRegexp = /<\/[\s]*script>/gi;
		var posStart,valueStart,posEnd,valueEnd,pos1,pos2,val2;
		var scripts=[];
		while(s){
			//return the position of the first script open tag (<script>)
			posStart = s.search(scriptBeginRegexp);
			valueStart = s.match(scriptBeginRegexp);
			//if a script is found
			if(~posStart)
				pos1=posStart;
			else
				pos1=s.length-1;
			if(!~posStart){
				//stop the loop
				pos2=s.length-1;
				val2=0;
			}else{
				posEnd = s.search(scriptEndRegexp);
				valueEnd = s.match(scriptEndRegexp);
				if(~posEnd)
					pos2=posEnd;
				else
					pos2=s.length - 1;
				scripts.push(s.subsing(pos1+valueStart[0].length,pos2));
				val2=valueEnd[0].length;
			}
			s=s.subs(pos2+val2,s.length-1);
		}
		return scripts;
	}

		/**
	 *
	 * behaviors : functions for what to do on each status ans readyState
	 *
	 */
	B.Ajax.request=function(url,behaviorsStatus,behaviorsReadyState,method,params,options){
		!behaviorsStatus && behaviorsStatus={};
		!behaviorsReadyState && behaviorsReadyState={};
		!options && options={};

		var xhr;
		if(window.XMLHttpRequest)
			xhr = new XMLHttpRequest();
		else if(window.ActiveXObject)
			xhr = new ActiveXObject("Microsoft.XMLHTTP");

		if(xhr){
			xhr.onreadystatechange = function()
			{
				if(xhr.readyState  == 4) {
					if(behaviorsStatus[xhr.status]) {
						var func = behaviorsStatus[xhr.status];
						func(xhr);
					}
				}
				else {
					if (behaviorsReadyState[xhr.readyState]) {
						var func = behaviorsReadyState[xhr.readyState];
						func(xhr);
					}
				}
			};

			xhr.open( method == 'POST' ? method : 'GET', url,  (Butterfly.exists(options.async) ? options.async : true));

			if (method == 'POST') {
				xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
				xhr.setRequestHeader("Content-length", params.length);
				xhr.setRequestHeader("Connection", "close");
			}

			xhr.send( method == 'POST' ? params : null );

			return xhr;
		}
	}
})();
/**
 *
 * Class to do ajax requests with Butterfly
 *
 */

Butterfly.Ajax = {

	/**
	 *
	 * behaviors : functions for what to do on each status ans readyState
	 *
	 */
	request: function(url, behaviorsStatus, behaviorsReadyState, method, params, options)
	{

		if (behaviorsStatus == null) {
			behaviorsStatus = {};
		}

		if (behaviorsReadyState == null) {
			behaviorsReadyState = {};
		}

		if (options == null) {
			options = {};
		}

		var xhr;
		if (window.XMLHttpRequest) {
			xhr = new XMLHttpRequest();
		}
		else if (window.ActiveXObject) {
			xhr = new ActiveXObject("Microsoft.XMLHTTP");
		}

		if (xhr) {
			xhr.onreadystatechange = function()
			{
				if(xhr.readyState  == 4) {
					if(behaviorsStatus[xhr.status]) {
						var func = behaviorsStatus[xhr.status];
						func(xhr);
					}
				}
				else {
					if (behaviorsReadyState[xhr.readyState]) {
						var func = behaviorsReadyState[xhr.readyState];
						func(xhr);
					}
				}
			};

			xhr.open( method == 'POST' ? method : 'GET', url,  (Butterfly.exists(options.async) ? options.async : true));

			if (method == 'POST') {
				xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
				xhr.setRequestHeader("Content-length", params.length);
				xhr.setRequestHeader("Connection", "close");
			}

			xhr.send( method == 'POST' ? params : null );

			return xhr;
		}
	},

	update: function(nodeToUpdate, url, evalScripts, method, params, options)
	{
		Butterfly.Ajax.request(url, {200: function(xhr)
		{
			$id(nodeToUpdate).innerHTML = xhr.responseText;

			setTimeout(
				function()
				{
					if (evalScripts) {
						var scripts = Butterfly.Ajax._getScriptsFromString(xhr.responseText);
						var nbScripts = scripts.length;
						for (var i = 0 ; i < nbScripts ; i ++) {
							window.eval(scripts[i]);
						}
					}
				},
				700
			);
		}}, {}, method, params, options);
	},

	/**
	 *
	 * extract all js code in <scripts> tags and return an array
	 * containing the codes
	 *
	 * For now, htmls is useless... maybe for another use ?
	 * str is [html part][script part][html part][script part]...
	 *
	 * [html part]s go in htmls var
	 * [script part]s go in scripts var
	 *
	 */
	_getScriptsFromString: function(str)
	{
		var scriptBeginRegexp = /<script[\stype="'x\/javascri]*>/gi;
		var scriptEndRegexp = /<\/[\s]*script>/gi;

		var posFirst, valueFirst;
		var posSecond, valueSecond;
		var pos1, pos2, val2;
		var htmls = [];
		var scripts = [];

		while (str != '') {
			//return the position of the first script open tag (<script>)
			posFirst = str.search(scriptBeginRegexp);
			valueFirst = str.match(scriptBeginRegexp);

			//if a script is found
			if (posFirst != -1) {
				pos1 = posFirst;
			}
			else {
				pos1 = str.length - 1;
			}

			//put the current html part
			htmls.push(str.substring(0, pos1));

			if (posFirst != -1) {
				posSecond = str.search(scriptEndRegexp);
				valueSecond = str.match(scriptEndRegexp);

				if (posSecond != -1) {
					pos2 = posSecond;
				}
				else {
					pos2 = str.length - 1;
				}
				scripts.push(str.substring(pos1 + valueFirst[0].length, pos2));
				val2 = valueSecond[0].length;
			}
			else {
				//stop the loop
				pos2 = str.length - 1;
				val2 = 0;
			}

			str = str.substr(pos2 + val2, str.length - 1);
		}

		return scripts;
	}

};
<|endoftext|>


<commit_before>module.exports = {
    name: 'unexpected-promise',
    installInto: function (expect) {
        expect.addType({
            name: 'Promise',
            base: 'object',
            inspect: function (promise, depth, output, inspect) {
                return output.text('Promise', 'yellow');
            },
            identify: function (obj) {
                return this.baseType.identify(obj) && typeof obj.then === 'function';
            }
        }).addAssertion('Promise', 'to be rejected [with]', function (expect, subject, value) {
            var flags = this.flags;
            return subject.then(function () {
                throw new Error('Promise unexpectedly fulfilled');
            }).caught(function (reason) {
                if (flags['with'] || typeof value !== 'undefined') {
                    return expect(reason, 'to satisfy', value);
                }
            });
        }).addAssertion('Promise', 'to be resolved [with]', function (expect, subject, value) {
            var flags = this.flags;
            return subject.then(function (obj) {
                if (flags['with'] || typeof value !== 'undefined') {
                    return expect(obj, 'to satisfy', value);
                }
            });
        }).addAssertion('Promise', 'when rejected', function (expect, subject) {
            var that = this;
            return subject.then(function () {
                throw new Error('Promise unexpectedly fulfilled');
            }, function (reason) {
                return that.shift(reason, 0);
            });
        }).addAssertion('Promise', 'when resolved', function (expect, subject) {
            var that = this;
            return subject.then(function (value) {
                return that.shift(value, 0);
            });
        });
    }
};
<commit_msg>Changed Promise inspection style to use jsFunctionName.<commit_after>module.exports = {
    name: 'unexpected-promise',
    installInto: function (expect) {
        expect.addType({
            name: 'Promise',
            base: 'object',
            inspect: function (promise, depth, output, inspect) {
                return output.text('Promise', 'jsFunctionName');
            },
            identify: function (obj) {
                return this.baseType.identify(obj) && typeof obj.then === 'function';
            }
        }).addAssertion('Promise', 'to be rejected [with]', function (expect, subject, value) {
            var flags = this.flags;
            return subject.then(function () {
                throw new Error('Promise unexpectedly fulfilled');
            }).caught(function (reason) {
                if (flags['with'] || typeof value !== 'undefined') {
                    return expect(reason, 'to satisfy', value);
                }
            });
        }).addAssertion('Promise', 'to be resolved [with]', function (expect, subject, value) {
            var flags = this.flags;
            return subject.then(function (obj) {
                if (flags['with'] || typeof value !== 'undefined') {
                    return expect(obj, 'to satisfy', value);
                }
            });
        }).addAssertion('Promise', 'when rejected', function (expect, subject) {
            var that = this;
            return subject.then(function () {
                throw new Error('Promise unexpectedly fulfilled');
            }, function (reason) {
                return that.shift(reason, 0);
            });
        }).addAssertion('Promise', 'when resolved', function (expect, subject) {
            var that = this;
            return subject.then(function (value) {
                return that.shift(value, 0);
            });
        });
    }
};
<|endoftext|>