UNPKG

twreporter-react

Version:

React-Redux site for The Reporter Foundation in Taiwan

451 lines (357 loc) 12.9 kB
'use strict'; /** * @module nock/intercepts */ var RequestOverrider = require('./request_overrider'), common = require('./common'), url = require('url'), inherits = require('util').inherits, http = require('http'), parse = require('url').parse, _ = require('lodash'), debug = require('debug')('nock.intercept'), timers = require('timers'), EventEmitter = require('events').EventEmitter; /** * @name NetConnectNotAllowedError * @private * @desc Error trying to make a connection when disabled external access. * @class * @example * nock.disableNetConnect(); * http.get('http://zombo.com'); * // throw NetConnectNotAllowedError */ function NetConnectNotAllowedError(host, path) { Error.call(this); this.name = 'NetConnectNotAllowedError'; this.message = 'Nock: Not allow net connect for "' + host + path + '"'; Error.captureStackTrace(this, this.constructor); } inherits(NetConnectNotAllowedError, Error); var allInterceptors = {}, allowNetConnect; /** * Enabled real request. * @public * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match * @example * // Enables all real requests * nock.enableNetConnect(); * @example * // Enables real requests for url that matches google * nock.enableNetConnect('google'); * @example * // Enables real requests for url that matches google and amazon * nock.enableNetConnect(/(google|amazon)/); */ function enableNetConnect(matcher) { if (_.isString(matcher)) { allowNetConnect = new RegExp(matcher); } else if (_.isObject(matcher) && _.isFunction(matcher.test)) { allowNetConnect = matcher; } else { allowNetConnect = /.*/; } } function isEnabledForNetConnect(options) { common.normalizeRequestOptions(options); var enabled = allowNetConnect && allowNetConnect.test(options.host); debug('Net connect', enabled ? '' : 'not', 'enabled for', options.host); return enabled; } /** * Disable all real requests. * @public * @param {String|RegExp} matcher=RegExp.new('.*') Expression to match * @example * nock.disableNetConnect(); */ function disableNetConnect() { allowNetConnect = undefined; } function isOn() { return !isOff(); } function isOff() { return process.env.NOCK_OFF === 'true'; } function add(key, interceptor, scope, scopeOptions, host) { if (! allInterceptors.hasOwnProperty(key)) { allInterceptors[key] = []; } interceptor.__nock_scope = scope; // We need scope's key and scope options for scope filtering function (if defined) interceptor.__nock_scopeKey = key; interceptor.__nock_scopeOptions = scopeOptions; // We need scope's host for setting correct request headers for filtered scopes. interceptor.__nock_scopeHost = host; interceptor.interceptionCounter = 0; allInterceptors[key].push(interceptor); } function remove(interceptor) { if (interceptor.__nock_scope.shouldPersist()) { return; } interceptor.counter -= 1; if (interceptor.counter > 0) { return; } var key = interceptor._key.split(' '), u = url.parse(key[1]), hostKey = u.protocol + '//' + u.host, interceptors = allInterceptors[hostKey], thisInterceptor; if (interceptors) { for(var i = 0; i < interceptors.length; i++) { thisInterceptor = interceptors[i]; if (thisInterceptor === interceptor) { interceptors.splice(i, 1); break; } } } } function removeAll() { allInterceptors = {}; } function hasHadInterceptors(options) { var basePath, isBasePathMatched, isScopedMatched; debug('hasHadInterceptors', options.host, options.hostname); common.normalizeRequestOptions(options); basePath = options.proto + '://' + options.host; debug('looking for interceptors for basepath %j', basePath); _.each(allInterceptors, function(interceptor, key) { if (key === basePath) { isBasePathMatched = true; // false to short circuit the .each return false; } _.each(interceptor, function(scope) { var filteringScope = scope.__nock_scopeOptions.filteringScope; // If scope filtering function is defined and returns a truthy value // then we have to treat this as a match. if (filteringScope && filteringScope(basePath)) { debug('found matching scope interceptor'); // Keep the filtered scope (its key) to signal the rest of the module // that this wasn't an exact but filtered match. scope.__nock_filteredScope = scope.__nock_scopeKey; isScopedMatched = true; // Break out of _.each for scopes. return false; } }); // Returning falsy value here (which will happen if we have found our matching interceptor) // will break out of _.each for all interceptors. return !isScopedMatched; }); return (isScopedMatched || isBasePathMatched); } function interceptorsFor(options) { var basePath; common.normalizeRequestOptions(options); debug('interceptors for %j', options.host); basePath = options.proto + '://' + options.host; debug('filtering interceptors for basepath', basePath); // First try to use filteringScope if any of the interceptors has it defined. var matchingInterceptor; _.each(allInterceptors, function(interceptor, key) { _.each(interceptor, function(scope) { var filteringScope = scope.__nock_scopeOptions.filteringScope; // If scope filtering function is defined and returns a truthy value // then we have to treat this as a match. if(filteringScope && filteringScope(basePath)) { debug('found matching scope interceptor'); // Keep the filtered scope (its key) to signal the rest of the module // that this wasn't an exact but filtered match. scope.__nock_filteredScope = scope.__nock_scopeKey; matchingInterceptor = interceptor; // Break out of _.each for scopes. return false; } }); // Returning falsy value here (which will happen if we have found our matching interceptor) // will break out of _.each for all interceptors. return !matchingInterceptor; }); if(matchingInterceptor) { return matchingInterceptor; } return allInterceptors[basePath] || []; } function removeInterceptor(options) { var baseUrl, key, method, proto; proto = options.proto ? options.proto : 'http'; common.normalizeRequestOptions(options); baseUrl = proto + '://' + options.host; if (allInterceptors[baseUrl] && allInterceptors[baseUrl].length > 0) { if (options.path) { method = options.method && options.method.toUpperCase() || 'GET'; key = method + ' ' + baseUrl + (options.path || '/'); for (var i = 0; i < allInterceptors[baseUrl].length; i++) { if (allInterceptors[baseUrl][i]._key === key) { allInterceptors[baseUrl].splice(i, 1); break; } } } else { allInterceptors[baseUrl].length = 0; } return true; } return false; } // Variable where we keep the ClientRequest we have overridden // (which might or might not be node's original http.ClientRequest) var originalClientRequest; function ErroringClientRequest(error) { if (http.OutgoingMessage) http.OutgoingMessage.call(this); process.nextTick(function() { this.emit('error', error); }.bind(this)); } if (http.ClientRequest) { inherits(ErroringClientRequest, http.ClientRequest); } function overrideClientRequest() { debug('Overriding ClientRequest'); if(originalClientRequest) { throw new Error('Nock already overrode http.ClientRequest'); } // ----- Extending http.ClientRequest // Define the overriding client request that nock uses internally. function OverriddenClientRequest(options, cb) { if (http.OutgoingMessage) http.OutgoingMessage.call(this); if (isOn() && hasHadInterceptors(options)) { // Filter the interceptors per request options. var interceptors = interceptorsFor(options); debug('using', interceptors.length, 'interceptors'); // Use filtered interceptors to intercept requests. var overrider = RequestOverrider(this, options, interceptors, remove, cb); for(var propName in overrider) { if (overrider.hasOwnProperty(propName)) { this[propName] = overrider[propName]; } } } else { debug('falling back to original ClientRequest'); // Fallback to original ClientRequest if nock is off or the net connection is enabled. if(isOff() || isEnabledForNetConnect(options)) { http.request.apply(this, arguments); } else { timers.setImmediate(function () { var error = new NetConnectNotAllowedError(options.host, options.path); this.emit('error', error); }.bind(this)); } } } if (http.ClientRequest) { inherits(OverriddenClientRequest, http.ClientRequest); } else { inherits(OverriddenClientRequest, EventEmitter); } // Override the http module's request but keep the original so that we can use it and later restore it. // NOTE: We only override http.ClientRequest as https module also uses it. originalClientRequest = http.ClientRequest; http.ClientRequest = OverriddenClientRequest; debug('ClientRequest overridden'); } function restoreOverriddenClientRequest() { debug('restoring overriden ClientRequest'); // Restore the ClientRequest we have overridden. if(!originalClientRequest) { debug('- ClientRequest was not overridden'); } else { http.ClientRequest = originalClientRequest; originalClientRequest = undefined; debug('- ClientRequest restored'); } } function isActive() { // If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined. // This means that Nock has been activated. return !_.isUndefined(originalClientRequest); } function isDone() { return _.every(allInterceptors, function(interceptors) { return _.every(interceptors, function(interceptor) { return interceptor.__nock_scope.isDone(); }); }); } function pendingMocks() { return _.reduce(allInterceptors, function(result, interceptors) { for (var interceptor in interceptors) { result = result.concat(interceptors[interceptor].__nock_scope.pendingMocks()); } return result; }, []); } function activate() { if(originalClientRequest) { throw new Error('Nock already active'); } overrideClientRequest(); // ----- Overriding http.request and https.request: common.overrideRequests(function(proto, overriddenRequest, options, callback) { // NOTE: overriddenRequest is already bound to its module. var req, res; if (typeof options === 'string') { options = parse(options); } options.proto = proto; if (isOn() && hasHadInterceptors(options)) { var interceptors, matches = false, allowUnmocked = false; interceptors = interceptorsFor(options); interceptors.forEach(function(interceptor) { if (! allowUnmocked && interceptor.options.allowUnmocked) { allowUnmocked = true; } if (interceptor.matchIndependentOfBody(options)) { matches = true; } }); if (! matches && allowUnmocked) { if (proto === 'https') { var ClientRequest = http.ClientRequest; http.ClientRequest = originalClientRequest; req = overriddenRequest(options, callback); http.ClientRequest = ClientRequest; } else { req = overriddenRequest(options, callback); } return req; } // NOTE: Since we already overrode the http.ClientRequest we are in fact constructing // our own OverriddenClientRequest. req = new http.ClientRequest(options); res = RequestOverrider(req, options, interceptors, remove); if (callback) { res.on('response', callback); } return req; } else { if (isOff() || isEnabledForNetConnect(options)) { return overriddenRequest(options, callback); } else { var error = new NetConnectNotAllowedError(options.host, options.path); return new ErroringClientRequest(error); } } }); } activate(); module.exports = add; module.exports.removeAll = removeAll; module.exports.removeInterceptor = removeInterceptor; module.exports.isOn = isOn; module.exports.activate = activate; module.exports.isActive = isActive; module.exports.isDone = isDone; module.exports.pendingMocks = pendingMocks; module.exports.enableNetConnect = enableNetConnect; module.exports.disableNetConnect = disableNetConnect; module.exports.overrideClientRequest = overrideClientRequest; module.exports.restoreOverriddenClientRequest = restoreOverriddenClientRequest;