Update checked-in dependencies
This commit is contained in:
Родитель
35f1961385
Коммит
17223bdff7
|
@ -3357,6 +3357,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.set": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||
"integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"dev": true,
|
||||
|
@ -3579,13 +3585,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/nock": {
|
||||
"version": "12.0.3",
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-13.1.1.tgz",
|
||||
"integrity": "sha512-YKTR9MjfK3kS9/l4nuTxyYm30cgOExRHzkLNhL8nhEUyU4f8Za/dRxOqjhVT1vGs0svWo3dDnJTUX1qxYeWy5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"lodash": "^4.17.13",
|
||||
"lodash.set": "^4.3.2",
|
||||
"propagate": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
Copyright jQuery Foundation and other contributors <https://jquery.org/>
|
||||
|
||||
Based on Underscore.js, copyright Jeremy Ashkenas,
|
||||
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
|
||||
|
||||
This software consists of voluntary contributions made by many
|
||||
individuals. For exact contribution history, see the revision history
|
||||
available at https://github.com/lodash/lodash
|
||||
|
||||
The following license applies to all parts of this software except as
|
||||
documented below:
|
||||
|
||||
====
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
====
|
||||
|
||||
Copyright and related rights for sample code are waived via CC0. Sample
|
||||
code is defined as all source code displayed within the prose of the
|
||||
documentation.
|
||||
|
||||
CC0: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
====
|
||||
|
||||
Files located in the node_modules and vendor directories are externally
|
||||
maintained libraries used by this software which have their own
|
||||
licenses; we recommend you read them, as their terms may differ from the
|
||||
terms above.
|
|
@ -0,0 +1,18 @@
|
|||
# lodash.set v4.3.2
|
||||
|
||||
The [lodash](https://lodash.com/) method `_.set` exported as a [Node.js](https://nodejs.org/) module.
|
||||
|
||||
## Installation
|
||||
|
||||
Using npm:
|
||||
```bash
|
||||
$ {sudo -H} npm i -g npm
|
||||
$ npm i --save lodash.set
|
||||
```
|
||||
|
||||
In Node.js:
|
||||
```js
|
||||
var set = require('lodash.set');
|
||||
```
|
||||
|
||||
See the [documentation](https://lodash.com/docs#set) or [package source](https://github.com/lodash/lodash/blob/4.3.2-npm-packages/lodash.set) for more details.
|
|
@ -0,0 +1,990 @@
|
|||
/**
|
||||
* lodash (Custom Build) <https://lodash.com/>
|
||||
* Build: `lodash modularize exports="npm" -o ./`
|
||||
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
||||
/** Used as the `TypeError` message for "Functions" methods. */
|
||||
var FUNC_ERROR_TEXT = 'Expected a function';
|
||||
|
||||
/** Used to stand-in for `undefined` hash values. */
|
||||
var HASH_UNDEFINED = '__lodash_hash_undefined__';
|
||||
|
||||
/** Used as references for various `Number` constants. */
|
||||
var INFINITY = 1 / 0,
|
||||
MAX_SAFE_INTEGER = 9007199254740991;
|
||||
|
||||
/** `Object#toString` result references. */
|
||||
var funcTag = '[object Function]',
|
||||
genTag = '[object GeneratorFunction]',
|
||||
symbolTag = '[object Symbol]';
|
||||
|
||||
/** Used to match property names within property paths. */
|
||||
var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
|
||||
reIsPlainProp = /^\w*$/,
|
||||
reLeadingDot = /^\./,
|
||||
rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
|
||||
|
||||
/**
|
||||
* Used to match `RegExp`
|
||||
* [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
|
||||
*/
|
||||
var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
|
||||
|
||||
/** Used to match backslashes in property paths. */
|
||||
var reEscapeChar = /\\(\\)?/g;
|
||||
|
||||
/** Used to detect host constructors (Safari). */
|
||||
var reIsHostCtor = /^\[object .+?Constructor\]$/;
|
||||
|
||||
/** Used to detect unsigned integer values. */
|
||||
var reIsUint = /^(?:0|[1-9]\d*)$/;
|
||||
|
||||
/** Detect free variable `global` from Node.js. */
|
||||
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
|
||||
|
||||
/** Detect free variable `self`. */
|
||||
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
|
||||
|
||||
/** Used as a reference to the global object. */
|
||||
var root = freeGlobal || freeSelf || Function('return this')();
|
||||
|
||||
/**
|
||||
* Gets the value at `key` of `object`.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} [object] The object to query.
|
||||
* @param {string} key The key of the property to get.
|
||||
* @returns {*} Returns the property value.
|
||||
*/
|
||||
function getValue(object, key) {
|
||||
return object == null ? undefined : object[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is a host object in IE < 9.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
|
||||
*/
|
||||
function isHostObject(value) {
|
||||
// Many host objects are `Object` objects that can coerce to strings
|
||||
// despite having improperly defined `toString` methods.
|
||||
var result = false;
|
||||
if (value != null && typeof value.toString != 'function') {
|
||||
try {
|
||||
result = !!(value + '');
|
||||
} catch (e) {}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Used for built-in method references. */
|
||||
var arrayProto = Array.prototype,
|
||||
funcProto = Function.prototype,
|
||||
objectProto = Object.prototype;
|
||||
|
||||
/** Used to detect overreaching core-js shims. */
|
||||
var coreJsData = root['__core-js_shared__'];
|
||||
|
||||
/** Used to detect methods masquerading as native. */
|
||||
var maskSrcKey = (function() {
|
||||
var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
|
||||
return uid ? ('Symbol(src)_1.' + uid) : '';
|
||||
}());
|
||||
|
||||
/** Used to resolve the decompiled source of functions. */
|
||||
var funcToString = funcProto.toString;
|
||||
|
||||
/** Used to check objects for own properties. */
|
||||
var hasOwnProperty = objectProto.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Used to resolve the
|
||||
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
|
||||
* of values.
|
||||
*/
|
||||
var objectToString = objectProto.toString;
|
||||
|
||||
/** Used to detect if a method is native. */
|
||||
var reIsNative = RegExp('^' +
|
||||
funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
|
||||
.replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
|
||||
);
|
||||
|
||||
/** Built-in value references. */
|
||||
var Symbol = root.Symbol,
|
||||
splice = arrayProto.splice;
|
||||
|
||||
/* Built-in method references that are verified to be native. */
|
||||
var Map = getNative(root, 'Map'),
|
||||
nativeCreate = getNative(Object, 'create');
|
||||
|
||||
/** Used to convert symbols to primitives and strings. */
|
||||
var symbolProto = Symbol ? Symbol.prototype : undefined,
|
||||
symbolToString = symbolProto ? symbolProto.toString : undefined;
|
||||
|
||||
/**
|
||||
* Creates a hash object.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function Hash(entries) {
|
||||
var index = -1,
|
||||
length = entries ? entries.length : 0;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value entries from the hash.
|
||||
*
|
||||
* @private
|
||||
* @name clear
|
||||
* @memberOf Hash
|
||||
*/
|
||||
function hashClear() {
|
||||
this.__data__ = nativeCreate ? nativeCreate(null) : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes `key` and its value from the hash.
|
||||
*
|
||||
* @private
|
||||
* @name delete
|
||||
* @memberOf Hash
|
||||
* @param {Object} hash The hash to modify.
|
||||
* @param {string} key The key of the value to remove.
|
||||
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
||||
*/
|
||||
function hashDelete(key) {
|
||||
return this.has(key) && delete this.__data__[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash value for `key`.
|
||||
*
|
||||
* @private
|
||||
* @name get
|
||||
* @memberOf Hash
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} Returns the entry value.
|
||||
*/
|
||||
function hashGet(key) {
|
||||
var data = this.__data__;
|
||||
if (nativeCreate) {
|
||||
var result = data[key];
|
||||
return result === HASH_UNDEFINED ? undefined : result;
|
||||
}
|
||||
return hasOwnProperty.call(data, key) ? data[key] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a hash value for `key` exists.
|
||||
*
|
||||
* @private
|
||||
* @name has
|
||||
* @memberOf Hash
|
||||
* @param {string} key The key of the entry to check.
|
||||
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
||||
*/
|
||||
function hashHas(key) {
|
||||
var data = this.__data__;
|
||||
return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hash `key` to `value`.
|
||||
*
|
||||
* @private
|
||||
* @name set
|
||||
* @memberOf Hash
|
||||
* @param {string} key The key of the value to set.
|
||||
* @param {*} value The value to set.
|
||||
* @returns {Object} Returns the hash instance.
|
||||
*/
|
||||
function hashSet(key, value) {
|
||||
var data = this.__data__;
|
||||
data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Add methods to `Hash`.
|
||||
Hash.prototype.clear = hashClear;
|
||||
Hash.prototype['delete'] = hashDelete;
|
||||
Hash.prototype.get = hashGet;
|
||||
Hash.prototype.has = hashHas;
|
||||
Hash.prototype.set = hashSet;
|
||||
|
||||
/**
|
||||
* Creates an list cache object.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function ListCache(entries) {
|
||||
var index = -1,
|
||||
length = entries ? entries.length : 0;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value entries from the list cache.
|
||||
*
|
||||
* @private
|
||||
* @name clear
|
||||
* @memberOf ListCache
|
||||
*/
|
||||
function listCacheClear() {
|
||||
this.__data__ = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes `key` and its value from the list cache.
|
||||
*
|
||||
* @private
|
||||
* @name delete
|
||||
* @memberOf ListCache
|
||||
* @param {string} key The key of the value to remove.
|
||||
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
||||
*/
|
||||
function listCacheDelete(key) {
|
||||
var data = this.__data__,
|
||||
index = assocIndexOf(data, key);
|
||||
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
var lastIndex = data.length - 1;
|
||||
if (index == lastIndex) {
|
||||
data.pop();
|
||||
} else {
|
||||
splice.call(data, index, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list cache value for `key`.
|
||||
*
|
||||
* @private
|
||||
* @name get
|
||||
* @memberOf ListCache
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} Returns the entry value.
|
||||
*/
|
||||
function listCacheGet(key) {
|
||||
var data = this.__data__,
|
||||
index = assocIndexOf(data, key);
|
||||
|
||||
return index < 0 ? undefined : data[index][1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list cache value for `key` exists.
|
||||
*
|
||||
* @private
|
||||
* @name has
|
||||
* @memberOf ListCache
|
||||
* @param {string} key The key of the entry to check.
|
||||
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
||||
*/
|
||||
function listCacheHas(key) {
|
||||
return assocIndexOf(this.__data__, key) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list cache `key` to `value`.
|
||||
*
|
||||
* @private
|
||||
* @name set
|
||||
* @memberOf ListCache
|
||||
* @param {string} key The key of the value to set.
|
||||
* @param {*} value The value to set.
|
||||
* @returns {Object} Returns the list cache instance.
|
||||
*/
|
||||
function listCacheSet(key, value) {
|
||||
var data = this.__data__,
|
||||
index = assocIndexOf(data, key);
|
||||
|
||||
if (index < 0) {
|
||||
data.push([key, value]);
|
||||
} else {
|
||||
data[index][1] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Add methods to `ListCache`.
|
||||
ListCache.prototype.clear = listCacheClear;
|
||||
ListCache.prototype['delete'] = listCacheDelete;
|
||||
ListCache.prototype.get = listCacheGet;
|
||||
ListCache.prototype.has = listCacheHas;
|
||||
ListCache.prototype.set = listCacheSet;
|
||||
|
||||
/**
|
||||
* Creates a map cache object to store key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @constructor
|
||||
* @param {Array} [entries] The key-value pairs to cache.
|
||||
*/
|
||||
function MapCache(entries) {
|
||||
var index = -1,
|
||||
length = entries ? entries.length : 0;
|
||||
|
||||
this.clear();
|
||||
while (++index < length) {
|
||||
var entry = entries[index];
|
||||
this.set(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value entries from the map.
|
||||
*
|
||||
* @private
|
||||
* @name clear
|
||||
* @memberOf MapCache
|
||||
*/
|
||||
function mapCacheClear() {
|
||||
this.__data__ = {
|
||||
'hash': new Hash,
|
||||
'map': new (Map || ListCache),
|
||||
'string': new Hash
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes `key` and its value from the map.
|
||||
*
|
||||
* @private
|
||||
* @name delete
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to remove.
|
||||
* @returns {boolean} Returns `true` if the entry was removed, else `false`.
|
||||
*/
|
||||
function mapCacheDelete(key) {
|
||||
return getMapData(this, key)['delete'](key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map value for `key`.
|
||||
*
|
||||
* @private
|
||||
* @name get
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to get.
|
||||
* @returns {*} Returns the entry value.
|
||||
*/
|
||||
function mapCacheGet(key) {
|
||||
return getMapData(this, key).get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a map value for `key` exists.
|
||||
*
|
||||
* @private
|
||||
* @name has
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the entry to check.
|
||||
* @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
|
||||
*/
|
||||
function mapCacheHas(key) {
|
||||
return getMapData(this, key).has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map `key` to `value`.
|
||||
*
|
||||
* @private
|
||||
* @name set
|
||||
* @memberOf MapCache
|
||||
* @param {string} key The key of the value to set.
|
||||
* @param {*} value The value to set.
|
||||
* @returns {Object} Returns the map cache instance.
|
||||
*/
|
||||
function mapCacheSet(key, value) {
|
||||
getMapData(this, key).set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Add methods to `MapCache`.
|
||||
MapCache.prototype.clear = mapCacheClear;
|
||||
MapCache.prototype['delete'] = mapCacheDelete;
|
||||
MapCache.prototype.get = mapCacheGet;
|
||||
MapCache.prototype.has = mapCacheHas;
|
||||
MapCache.prototype.set = mapCacheSet;
|
||||
|
||||
/**
|
||||
* Assigns `value` to `key` of `object` if the existing value is not equivalent
|
||||
* using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
||||
* for equality comparisons.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {string} key The key of the property to assign.
|
||||
* @param {*} value The value to assign.
|
||||
*/
|
||||
function assignValue(object, key, value) {
|
||||
var objValue = object[key];
|
||||
if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
|
||||
(value === undefined && !(key in object))) {
|
||||
object[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index at which the `key` is found in `array` of key-value pairs.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} array The array to inspect.
|
||||
* @param {*} key The key to search for.
|
||||
* @returns {number} Returns the index of the matched value, else `-1`.
|
||||
*/
|
||||
function assocIndexOf(array, key) {
|
||||
var length = array.length;
|
||||
while (length--) {
|
||||
if (eq(array[length][0], key)) {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base implementation of `_.isNative` without bad shim checks.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a native function,
|
||||
* else `false`.
|
||||
*/
|
||||
function baseIsNative(value) {
|
||||
if (!isObject(value) || isMasked(value)) {
|
||||
return false;
|
||||
}
|
||||
var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
|
||||
return pattern.test(toSource(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* The base implementation of `_.set`.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {Array|string} path The path of the property to set.
|
||||
* @param {*} value The value to set.
|
||||
* @param {Function} [customizer] The function to customize path creation.
|
||||
* @returns {Object} Returns `object`.
|
||||
*/
|
||||
function baseSet(object, path, value, customizer) {
|
||||
if (!isObject(object)) {
|
||||
return object;
|
||||
}
|
||||
path = isKey(path, object) ? [path] : castPath(path);
|
||||
|
||||
var index = -1,
|
||||
length = path.length,
|
||||
lastIndex = length - 1,
|
||||
nested = object;
|
||||
|
||||
while (nested != null && ++index < length) {
|
||||
var key = toKey(path[index]),
|
||||
newValue = value;
|
||||
|
||||
if (index != lastIndex) {
|
||||
var objValue = nested[key];
|
||||
newValue = customizer ? customizer(objValue, key, nested) : undefined;
|
||||
if (newValue === undefined) {
|
||||
newValue = isObject(objValue)
|
||||
? objValue
|
||||
: (isIndex(path[index + 1]) ? [] : {});
|
||||
}
|
||||
}
|
||||
assignValue(nested, key, newValue);
|
||||
nested = nested[key];
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base implementation of `_.toString` which doesn't convert nullish
|
||||
* values to empty strings.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to process.
|
||||
* @returns {string} Returns the string.
|
||||
*/
|
||||
function baseToString(value) {
|
||||
// Exit early for strings to avoid a performance hit in some environments.
|
||||
if (typeof value == 'string') {
|
||||
return value;
|
||||
}
|
||||
if (isSymbol(value)) {
|
||||
return symbolToString ? symbolToString.call(value) : '';
|
||||
}
|
||||
var result = (value + '');
|
||||
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts `value` to a path array if it's not one.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to inspect.
|
||||
* @returns {Array} Returns the cast property path array.
|
||||
*/
|
||||
function castPath(value) {
|
||||
return isArray(value) ? value : stringToPath(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data for `map`.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} map The map to query.
|
||||
* @param {string} key The reference key.
|
||||
* @returns {*} Returns the map data.
|
||||
*/
|
||||
function getMapData(map, key) {
|
||||
var data = map.__data__;
|
||||
return isKeyable(key)
|
||||
? data[typeof key == 'string' ? 'string' : 'hash']
|
||||
: data.map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native function at `key` of `object`.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} object The object to query.
|
||||
* @param {string} key The key of the method to get.
|
||||
* @returns {*} Returns the function if it's native, else `undefined`.
|
||||
*/
|
||||
function getNative(object, key) {
|
||||
var value = getValue(object, key);
|
||||
return baseIsNative(value) ? value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is a valid array-like index.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
|
||||
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
|
||||
*/
|
||||
function isIndex(value, length) {
|
||||
length = length == null ? MAX_SAFE_INTEGER : length;
|
||||
return !!length &&
|
||||
(typeof value == 'number' || reIsUint.test(value)) &&
|
||||
(value > -1 && value % 1 == 0 && value < length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is a property name and not a property path.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @param {Object} [object] The object to query keys on.
|
||||
* @returns {boolean} Returns `true` if `value` is a property name, else `false`.
|
||||
*/
|
||||
function isKey(value, object) {
|
||||
if (isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
var type = typeof value;
|
||||
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
|
||||
value == null || isSymbol(value)) {
|
||||
return true;
|
||||
}
|
||||
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
|
||||
(object != null && value in Object(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is suitable for use as unique object key.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is suitable, else `false`.
|
||||
*/
|
||||
function isKeyable(value) {
|
||||
var type = typeof value;
|
||||
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
|
||||
? (value !== '__proto__')
|
||||
: (value === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `func` has its source masked.
|
||||
*
|
||||
* @private
|
||||
* @param {Function} func The function to check.
|
||||
* @returns {boolean} Returns `true` if `func` is masked, else `false`.
|
||||
*/
|
||||
function isMasked(func) {
|
||||
return !!maskSrcKey && (maskSrcKey in func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `string` to a property path array.
|
||||
*
|
||||
* @private
|
||||
* @param {string} string The string to convert.
|
||||
* @returns {Array} Returns the property path array.
|
||||
*/
|
||||
var stringToPath = memoize(function(string) {
|
||||
string = toString(string);
|
||||
|
||||
var result = [];
|
||||
if (reLeadingDot.test(string)) {
|
||||
result.push('');
|
||||
}
|
||||
string.replace(rePropName, function(match, number, quote, string) {
|
||||
result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts `value` to a string key if it's not a string or symbol.
|
||||
*
|
||||
* @private
|
||||
* @param {*} value The value to inspect.
|
||||
* @returns {string|symbol} Returns the key.
|
||||
*/
|
||||
function toKey(value) {
|
||||
if (typeof value == 'string' || isSymbol(value)) {
|
||||
return value;
|
||||
}
|
||||
var result = (value + '');
|
||||
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `func` to its source code.
|
||||
*
|
||||
* @private
|
||||
* @param {Function} func The function to process.
|
||||
* @returns {string} Returns the source code.
|
||||
*/
|
||||
function toSource(func) {
|
||||
if (func != null) {
|
||||
try {
|
||||
return funcToString.call(func);
|
||||
} catch (e) {}
|
||||
try {
|
||||
return (func + '');
|
||||
} catch (e) {}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that memoizes the result of `func`. If `resolver` is
|
||||
* provided, it determines the cache key for storing the result based on the
|
||||
* arguments provided to the memoized function. By default, the first argument
|
||||
* provided to the memoized function is used as the map cache key. The `func`
|
||||
* is invoked with the `this` binding of the memoized function.
|
||||
*
|
||||
* **Note:** The cache is exposed as the `cache` property on the memoized
|
||||
* function. Its creation may be customized by replacing the `_.memoize.Cache`
|
||||
* constructor with one whose instances implement the
|
||||
* [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
|
||||
* method interface of `delete`, `get`, `has`, and `set`.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.1.0
|
||||
* @category Function
|
||||
* @param {Function} func The function to have its output memoized.
|
||||
* @param {Function} [resolver] The function to resolve the cache key.
|
||||
* @returns {Function} Returns the new memoized function.
|
||||
* @example
|
||||
*
|
||||
* var object = { 'a': 1, 'b': 2 };
|
||||
* var other = { 'c': 3, 'd': 4 };
|
||||
*
|
||||
* var values = _.memoize(_.values);
|
||||
* values(object);
|
||||
* // => [1, 2]
|
||||
*
|
||||
* values(other);
|
||||
* // => [3, 4]
|
||||
*
|
||||
* object.a = 2;
|
||||
* values(object);
|
||||
* // => [1, 2]
|
||||
*
|
||||
* // Modify the result cache.
|
||||
* values.cache.set(object, ['a', 'b']);
|
||||
* values(object);
|
||||
* // => ['a', 'b']
|
||||
*
|
||||
* // Replace `_.memoize.Cache`.
|
||||
* _.memoize.Cache = WeakMap;
|
||||
*/
|
||||
function memoize(func, resolver) {
|
||||
if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
|
||||
throw new TypeError(FUNC_ERROR_TEXT);
|
||||
}
|
||||
var memoized = function() {
|
||||
var args = arguments,
|
||||
key = resolver ? resolver.apply(this, args) : args[0],
|
||||
cache = memoized.cache;
|
||||
|
||||
if (cache.has(key)) {
|
||||
return cache.get(key);
|
||||
}
|
||||
var result = func.apply(this, args);
|
||||
memoized.cache = cache.set(key, result);
|
||||
return result;
|
||||
};
|
||||
memoized.cache = new (memoize.Cache || MapCache);
|
||||
return memoized;
|
||||
}
|
||||
|
||||
// Assign cache to `_.memoize`.
|
||||
memoize.Cache = MapCache;
|
||||
|
||||
/**
|
||||
* Performs a
|
||||
* [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
|
||||
* comparison between two values to determine if they are equivalent.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 4.0.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to compare.
|
||||
* @param {*} other The other value to compare.
|
||||
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
|
||||
* @example
|
||||
*
|
||||
* var object = { 'a': 1 };
|
||||
* var other = { 'a': 1 };
|
||||
*
|
||||
* _.eq(object, object);
|
||||
* // => true
|
||||
*
|
||||
* _.eq(object, other);
|
||||
* // => false
|
||||
*
|
||||
* _.eq('a', 'a');
|
||||
* // => true
|
||||
*
|
||||
* _.eq('a', Object('a'));
|
||||
* // => false
|
||||
*
|
||||
* _.eq(NaN, NaN);
|
||||
* // => true
|
||||
*/
|
||||
function eq(value, other) {
|
||||
return value === other || (value !== value && other !== other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is classified as an `Array` object.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.1.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
|
||||
* @example
|
||||
*
|
||||
* _.isArray([1, 2, 3]);
|
||||
* // => true
|
||||
*
|
||||
* _.isArray(document.body.children);
|
||||
* // => false
|
||||
*
|
||||
* _.isArray('abc');
|
||||
* // => false
|
||||
*
|
||||
* _.isArray(_.noop);
|
||||
* // => false
|
||||
*/
|
||||
var isArray = Array.isArray;
|
||||
|
||||
/**
|
||||
* Checks if `value` is classified as a `Function` object.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.1.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
|
||||
* @example
|
||||
*
|
||||
* _.isFunction(_);
|
||||
* // => true
|
||||
*
|
||||
* _.isFunction(/abc/);
|
||||
* // => false
|
||||
*/
|
||||
function isFunction(value) {
|
||||
// The use of `Object#toString` avoids issues with the `typeof` operator
|
||||
// in Safari 8-9 which returns 'object' for typed array and other constructors.
|
||||
var tag = isObject(value) ? objectToString.call(value) : '';
|
||||
return tag == funcTag || tag == genTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is the
|
||||
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
|
||||
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 0.1.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
|
||||
* @example
|
||||
*
|
||||
* _.isObject({});
|
||||
* // => true
|
||||
*
|
||||
* _.isObject([1, 2, 3]);
|
||||
* // => true
|
||||
*
|
||||
* _.isObject(_.noop);
|
||||
* // => true
|
||||
*
|
||||
* _.isObject(null);
|
||||
* // => false
|
||||
*/
|
||||
function isObject(value) {
|
||||
var type = typeof value;
|
||||
return !!value && (type == 'object' || type == 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is object-like. A value is object-like if it's not `null`
|
||||
* and has a `typeof` result of "object".
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 4.0.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
|
||||
* @example
|
||||
*
|
||||
* _.isObjectLike({});
|
||||
* // => true
|
||||
*
|
||||
* _.isObjectLike([1, 2, 3]);
|
||||
* // => true
|
||||
*
|
||||
* _.isObjectLike(_.noop);
|
||||
* // => false
|
||||
*
|
||||
* _.isObjectLike(null);
|
||||
* // => false
|
||||
*/
|
||||
function isObjectLike(value) {
|
||||
return !!value && typeof value == 'object';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is classified as a `Symbol` primitive or object.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 4.0.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to check.
|
||||
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
|
||||
* @example
|
||||
*
|
||||
* _.isSymbol(Symbol.iterator);
|
||||
* // => true
|
||||
*
|
||||
* _.isSymbol('abc');
|
||||
* // => false
|
||||
*/
|
||||
function isSymbol(value) {
|
||||
return typeof value == 'symbol' ||
|
||||
(isObjectLike(value) && objectToString.call(value) == symbolTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `value` to a string. An empty string is returned for `null`
|
||||
* and `undefined` values. The sign of `-0` is preserved.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 4.0.0
|
||||
* @category Lang
|
||||
* @param {*} value The value to process.
|
||||
* @returns {string} Returns the string.
|
||||
* @example
|
||||
*
|
||||
* _.toString(null);
|
||||
* // => ''
|
||||
*
|
||||
* _.toString(-0);
|
||||
* // => '-0'
|
||||
*
|
||||
* _.toString([1, 2, 3]);
|
||||
* // => '1,2,3'
|
||||
*/
|
||||
function toString(value) {
|
||||
return value == null ? '' : baseToString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
|
||||
* it's created. Arrays are created for missing index properties while objects
|
||||
* are created for all other missing properties. Use `_.setWith` to customize
|
||||
* `path` creation.
|
||||
*
|
||||
* **Note:** This method mutates `object`.
|
||||
*
|
||||
* @static
|
||||
* @memberOf _
|
||||
* @since 3.7.0
|
||||
* @category Object
|
||||
* @param {Object} object The object to modify.
|
||||
* @param {Array|string} path The path of the property to set.
|
||||
* @param {*} value The value to set.
|
||||
* @returns {Object} Returns `object`.
|
||||
* @example
|
||||
*
|
||||
* var object = { 'a': [{ 'b': { 'c': 3 } }] };
|
||||
*
|
||||
* _.set(object, 'a[0].b.c', 4);
|
||||
* console.log(object.a[0].b.c);
|
||||
* // => 4
|
||||
*
|
||||
* _.set(object, ['x', '0', 'y', 'z'], 5);
|
||||
* console.log(object.x[0].y.z);
|
||||
* // => 5
|
||||
*/
|
||||
function set(object, path, value) {
|
||||
return object == null ? object : baseSet(object, path, value);
|
||||
}
|
||||
|
||||
module.exports = set;
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "lodash.set",
|
||||
"version": "4.3.2",
|
||||
"description": "The lodash method `_.set` exported as a module.",
|
||||
"homepage": "https://lodash.com/",
|
||||
"icon": "https://lodash.com/icon.svg",
|
||||
"license": "MIT",
|
||||
"keywords": "lodash-modularized, set",
|
||||
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
|
||||
"contributors": [
|
||||
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
|
||||
"Blaine Bublitz <blaine.bublitz@gmail.com> (https://github.com/phated)",
|
||||
"Mathias Bynens <mathias@qiwi.be> (https://mathiasbynens.be/)"
|
||||
],
|
||||
"repository": "lodash/lodash",
|
||||
"scripts": {
|
||||
"test": "echo \"See https://travis-ci.org/lodash/lodash-cli for testing details.\""
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
Nock’s changelog can be found directly in the [GitHub release notes](https://github.com/nock/nock/releases). These are automatically created by [semantic-release](https://github.com/semantic-release/semantic-release) based on their [commit message conventions](https://semantic-release.gitbook.io/semantic-release#commit-message-format).
|
||||
Nock’s changelog can be found directly in the [GitHub release notes](https://github.com/nock/nock/releases).
|
||||
These are automatically created by [semantic-release](https://github.com/semantic-release/semantic-release) based on their [commit message conventions](https://semantic-release.gitbook.io/semantic-release#commit-message-format).
|
||||
|
||||
Migration guides are available for major versions in the [migration guides directory](https://github.com/nock/nock/tree/main/migration_guides).
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
[![npm](https://img.shields.io/npm/v/nock.svg)][npmjs]
|
||||
[![Build Status](https://travis-ci.org/nock/nock.svg)][build]
|
||||
![Coverage Status](http://img.shields.io/badge/coverage-100%25-brightgreen.svg)
|
||||
[![Greenkeeper](https://badges.greenkeeper.io/nock/nock.svg)](https://greenkeeper.io/)
|
||||
[![Backers on Open Collective](https://opencollective.com/nock/backers/badge.svg)](#backers)
|
||||
[![Sponsors on Open Collective](https://opencollective.com/nock/sponsors/badge.svg)](#sponsors)
|
||||
|
||||
|
@ -43,10 +42,11 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
|
|||
- [Support for HTTP and HTTPS](#support-for-http-and-https)
|
||||
- [Non-standard ports](#non-standard-ports)
|
||||
- [Repeat response n times](#repeat-response-n-times)
|
||||
- [Delay the response body](#delay-the-response-body)
|
||||
- [Delay the response](#delay-the-response)
|
||||
- [Delay the connection](#delay-the-connection)
|
||||
- [Socket timeout](#socket-timeout)
|
||||
- [Delay the connection](#delay-the-connection)
|
||||
- [Technical Details](#technical-details)
|
||||
- [Delay the response body](#delay-the-response-body)
|
||||
- [Technical Details](#technical-details-1)
|
||||
- [Chaining](#chaining)
|
||||
- [Scope filtering](#scope-filtering)
|
||||
- [Conditional scope filtering](#conditional-scope-filtering)
|
||||
|
@ -63,7 +63,6 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
|
|||
- [.pendingMocks()](#pendingmocks)
|
||||
- [.activeMocks()](#activemocks)
|
||||
- [.isActive()](#isactive)
|
||||
- [Logging](#logging)
|
||||
- [Restoring](#restoring)
|
||||
- [Activating](#activating)
|
||||
- [Turning Nock Off (experimental!)](#turning-nock-off-experimental)
|
||||
|
@ -86,9 +85,10 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
|
|||
- [Usage](#usage-1)
|
||||
- [Options](#options-1)
|
||||
- [Example](#example)
|
||||
- [Modes](#modes)
|
||||
- [Modes](#modes)
|
||||
- [Common issues](#common-issues)
|
||||
- [Axios](#axios)
|
||||
- [Memory issues with Jest](#memory-issues-with-jest)
|
||||
- [Debugging](#debugging)
|
||||
- [Contributing](#contributing)
|
||||
- [Contributors](#contributors)
|
||||
|
@ -260,9 +260,7 @@ nock('http://www.example.com')
|
|||
Nock understands query strings. Search parameters can be included as part of the path:
|
||||
|
||||
```js
|
||||
nock('http://example.com')
|
||||
.get('/users?foo=bar')
|
||||
.reply(200)
|
||||
nock('http://example.com').get('/users?foo=bar').reply(200)
|
||||
```
|
||||
|
||||
Instead of placing the entire URL, you can specify the query part as an object:
|
||||
|
@ -294,10 +292,7 @@ A `URLSearchParams` instance can be provided.
|
|||
```js
|
||||
const params = new URLSearchParams({ foo: 'bar' })
|
||||
|
||||
nock('http://example.com')
|
||||
.get('/')
|
||||
.query(params)
|
||||
.reply(200)
|
||||
nock('http://example.com').get('/').query(params).reply(200)
|
||||
```
|
||||
|
||||
Nock supports passing a function to query. The function determines if the actual query matches or not.
|
||||
|
@ -338,9 +333,7 @@ nock('http://example.com', { encodedQueryParams: true })
|
|||
You can specify the return status code for a path on the first argument of reply like this:
|
||||
|
||||
```js
|
||||
const scope = nock('http://myapp.iriscouch.com')
|
||||
.get('/users/1')
|
||||
.reply(404)
|
||||
const scope = nock('http://myapp.iriscouch.com').get('/users/1').reply(404)
|
||||
```
|
||||
|
||||
You can also specify the reply body as a string:
|
||||
|
@ -354,13 +347,11 @@ const scope = nock('http://www.google.com')
|
|||
or as a JSON-encoded object:
|
||||
|
||||
```js
|
||||
const scope = nock('http://myapp.iriscouch.com')
|
||||
.get('/')
|
||||
.reply(200, {
|
||||
username: 'pgte',
|
||||
email: 'pedro.teixeira@gmail.com',
|
||||
_id: '4324243fsd',
|
||||
})
|
||||
const scope = nock('http://myapp.iriscouch.com').get('/').reply(200, {
|
||||
username: 'pgte',
|
||||
email: 'pedro.teixeira@gmail.com',
|
||||
_id: '4324243fsd',
|
||||
})
|
||||
```
|
||||
|
||||
or even as a file:
|
||||
|
@ -384,7 +375,7 @@ const scope = nock('http://www.google.com')
|
|||
In Nock 11.x it was possible to invoke `.reply()` with a status code and a
|
||||
function that returns an array containing a status code and body. (The status
|
||||
code from the array would take precedence over the one passed directly to
|
||||
reply.) This is no longer allowed. In 12.x, either call `.reply()` with a
|
||||
reply.) This is no longer allowed. In Nock 12 and later, either call `.reply()` with a
|
||||
status code and a function that returns the body, or call it with a single
|
||||
argument: a function that returns an array containing both the status code and
|
||||
body.
|
||||
|
@ -443,7 +434,7 @@ If you're using the reply callback style, you can access the original client req
|
|||
```js
|
||||
const scope = nock('http://www.google.com')
|
||||
.get('/cat-poems')
|
||||
.reply(function(uri, requestBody) {
|
||||
.reply(function (uri, requestBody) {
|
||||
console.log('path:', this.req.path)
|
||||
console.log('headers:', this.req.headers)
|
||||
// ...
|
||||
|
@ -465,12 +456,10 @@ nock('http://www.google.com')
|
|||
JSON error responses are allowed too:
|
||||
|
||||
```js
|
||||
nock('http://www.google.com')
|
||||
.get('/cat-poems')
|
||||
.replyWithError({
|
||||
message: 'something awful happened',
|
||||
code: 'AWFUL_ERROR',
|
||||
})
|
||||
nock('http://www.google.com').get('/cat-poems').replyWithError({
|
||||
message: 'something awful happened',
|
||||
code: 'AWFUL_ERROR',
|
||||
})
|
||||
```
|
||||
|
||||
> Note: This will emit an `error` event on the `request` object, not the reply.
|
||||
|
@ -584,7 +573,7 @@ const scope = nock('http://www.headdy.com')
|
|||
|
||||
#### Including Content-Length Header Automatically
|
||||
|
||||
When using `scope.reply()` to set a response body manually, you can have the
|
||||
When using `interceptor.reply()` to set a response body manually, you can have the
|
||||
`Content-Length` header calculated automatically.
|
||||
|
||||
```js
|
||||
|
@ -644,61 +633,45 @@ You are able to specify a non-standard port like this:
|
|||
|
||||
```js
|
||||
const scope = nock('http://my.server.com:8081')
|
||||
...
|
||||
```
|
||||
|
||||
### Repeat response n times
|
||||
|
||||
You are able to specify the number of times to repeat the same response.
|
||||
|
||||
**NOTE:** When request times is more than the number you specified, you will get an error before cleaning this interceptor.
|
||||
|
||||
```js
|
||||
nock('http://zombo.com')
|
||||
.get('/')
|
||||
.times(4)
|
||||
.reply(200, 'Ok')
|
||||
nock('http://zombo.com').get('/').times(4).reply(200, 'Ok')
|
||||
|
||||
http.get('http://zombo.com/') // respond body "Ok"
|
||||
http.get('http://zombo.com/') // respond body "Ok"
|
||||
http.get('http://zombo.com/') // respond body "Ok"
|
||||
http.get('http://zombo.com/') // respond body "Ok"
|
||||
http.get('http://zombo.com/') // respond with zombo.com result
|
||||
|
||||
// This code will get an error with message:
|
||||
// Nock: No match for request
|
||||
http.get('http://zombo.com/')
|
||||
|
||||
// clean your interceptor
|
||||
nock.cleanAll()
|
||||
|
||||
http.get('http://zombo.com/') // real respond with zombo.com result
|
||||
```
|
||||
|
||||
Sugar syntax
|
||||
|
||||
```js
|
||||
nock('http://zombo.com')
|
||||
.get('/')
|
||||
.once()
|
||||
.reply(200, 'Ok')
|
||||
nock('http://zombo.com')
|
||||
.get('/')
|
||||
.twice()
|
||||
.reply(200, 'Ok')
|
||||
nock('http://zombo.com')
|
||||
.get('/')
|
||||
.thrice()
|
||||
.reply(200, 'Ok')
|
||||
nock('http://zombo.com').get('/').once().reply(200, 'Ok')
|
||||
nock('http://zombo.com').get('/').twice().reply(200, 'Ok')
|
||||
nock('http://zombo.com').get('/').thrice().reply(200, 'Ok')
|
||||
```
|
||||
|
||||
To repeat this response for as long as nock is active, use [.persist()](#persist).
|
||||
|
||||
### Delay the response body
|
||||
|
||||
You are able to specify the number of milliseconds that the response body should be delayed. Response header will be replied immediately.
|
||||
`delayBody(1000)` is equivalent to `delay({body: 1000})`.
|
||||
|
||||
```js
|
||||
nock('http://my.server.com')
|
||||
.get('/')
|
||||
.delayBody(2000) // 2 seconds
|
||||
.reply(200, '<html></html>')
|
||||
```
|
||||
|
||||
NOTE: the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event will occur immediately, but the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) will not emit its `'end'` event until after the delay.
|
||||
|
||||
### Delay the response
|
||||
|
||||
Nock can simulate response latency to allow you to test timeouts, race conditions, an other timing related scenarios.
|
||||
You are able to specify the number of milliseconds that your reply should be delayed.
|
||||
|
||||
```js
|
||||
|
@ -708,53 +681,54 @@ nock('http://my.server.com')
|
|||
.reply(200, '<html></html>')
|
||||
```
|
||||
|
||||
`delay()` could also be used as
|
||||
`delay(1000)` is an alias for `delayConnection(1000).delayBody(0)`
|
||||
`delay({ head: 1000, body: 2000 })` is an alias for `delayConnection(1000).delayBody(2000)`
|
||||
Both of which are covered in detail below.
|
||||
|
||||
```
|
||||
delay({
|
||||
head: headDelayInMs,
|
||||
body: bodyDelayInMs
|
||||
})
|
||||
```
|
||||
#### Delay the connection
|
||||
|
||||
for example
|
||||
You are able to specify the number of milliseconds that your connection should be idle before it starts to receive the response.
|
||||
|
||||
To simulate a socket timeout, provide a larger value than the timeout setting on the request.
|
||||
|
||||
```js
|
||||
nock('http://my.server.com')
|
||||
.get('/')
|
||||
.delay({
|
||||
head: 2000, // header will be delayed for 2 seconds, i.e. the whole response will be delayed for 2 seconds.
|
||||
body: 3000, // body will be delayed for another 3 seconds after header is sent out.
|
||||
})
|
||||
.delayConnection(2000) // 2 seconds
|
||||
.reply(200, '<html></html>')
|
||||
|
||||
req = http.request('http://my.server.com', { timeout: 1000 })
|
||||
```
|
||||
|
||||
### Delay the connection
|
||||
Nock emits timeout events almost immediately by comparing the requested connection delay to the timeout parameter passed to `http.request()` or `http.ClientRequest#setTimeout()`.
|
||||
This allows you to test timeouts without using fake timers or slowing down your tests.
|
||||
If the client chooses to _not_ take an action (e.g. abort the request), the request and response will continue on as normal, after real clock time has passed.
|
||||
|
||||
`delayConnection(1000)` is equivalent to `delay({ head: 1000 })`.
|
||||
##### Technical Details
|
||||
|
||||
### Socket timeout
|
||||
Following the `'finish'` event being emitted by `ClientRequest`, Nock will wait for the next event loop iteration before checking if the request has been aborted.
|
||||
At this point, any connection delay value is compared against any request timeout setting and a [`'timeout'`](https://nodejs.org/api/http.html#http_event_timeout) is emitted when appropriate from the socket and the request objects.
|
||||
A Node timeout timer is then registered with any connection delay value to delay real time before checking again if the request has been aborted and the [`'response'`](http://nodejs.org/api/http.html#http_event_response) is emitted by the request.
|
||||
|
||||
You are able to specify the number of milliseconds that your connection should be idle, to simulate a socket timeout.
|
||||
A similar method, `.socketDelay()` was removed in version 13. It was thought that having two methods so subtlety similar was confusing.
|
||||
The discussion can be found at https://github.com/nock/nock/pull/1974.
|
||||
|
||||
#### Delay the response body
|
||||
|
||||
You are able to specify the number of milliseconds that the response body should be delayed.
|
||||
This is the time between the headers being received and the body starting to be received.
|
||||
|
||||
```js
|
||||
nock('http://my.server.com')
|
||||
.get('/')
|
||||
.socketDelay(2000) // 2 seconds
|
||||
.delayBody(2000) // 2 seconds
|
||||
.reply(200, '<html></html>')
|
||||
```
|
||||
|
||||
To test a request like the following:
|
||||
##### Technical Details
|
||||
|
||||
```js
|
||||
req = http.request('http://my.server.com', res => {
|
||||
...
|
||||
})
|
||||
req.setTimeout(1000, () => { req.abort() })
|
||||
req.end()
|
||||
```
|
||||
|
||||
NOTE: the timeout will be fired immediately, and will not leave the simulated connection idle for the specified period of time.
|
||||
Following the [`'response'`](http://nodejs.org/api/http.html#http_event_response) being emitted by `ClientRequest`,
|
||||
Nock will register a timeout timer with the body delay value to delay real time before the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) emits its first `'data'` or the `'end'` event.
|
||||
|
||||
### Chaining
|
||||
|
||||
|
@ -914,20 +888,14 @@ example.pendingMocks() // ["GET http://example.com:80/path"]
|
|||
// ...After a request to example.com/pathA:
|
||||
example.pendingMocks() // []
|
||||
|
||||
example
|
||||
.get('/pathB')
|
||||
.optionally()
|
||||
.reply(200)
|
||||
example.get('/pathB').optionally().reply(200)
|
||||
example.pendingMocks() // []
|
||||
|
||||
// You can also pass a boolean argument to `optionally()`. This
|
||||
// is useful if you want to conditionally make a mocked request
|
||||
// optional.
|
||||
const getMock = optional =>
|
||||
example
|
||||
.get('/pathC')
|
||||
.optionally(optional)
|
||||
.reply(200)
|
||||
example.get('/pathC').optionally(optional).reply(200)
|
||||
|
||||
getMock(true)
|
||||
example.pendingMocks() // []
|
||||
|
@ -977,9 +945,7 @@ setTimeout(() => {
|
|||
You can call `isDone()` on a single expectation to determine if the expectation was met:
|
||||
|
||||
```js
|
||||
const scope = nock('http://google.com')
|
||||
.get('/')
|
||||
.reply(200)
|
||||
const scope = nock('http://google.com').get('/').reply(200)
|
||||
|
||||
scope.isDone() // will return false
|
||||
```
|
||||
|
@ -1022,10 +988,7 @@ Note that while a persisted scope will always intercept the requests, it is cons
|
|||
If you want to stop persisting an individual persisted mock you can call `persist(false)`:
|
||||
|
||||
```js
|
||||
const scope = nock('http://example.com')
|
||||
.persist()
|
||||
.get('/')
|
||||
.reply(200, 'ok')
|
||||
const scope = nock('http://example.com').persist().get('/').reply(200, 'ok')
|
||||
|
||||
// Do some tests ...
|
||||
|
||||
|
@ -1081,16 +1044,6 @@ if (!nock.isActive()) {
|
|||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Nock can log matches if you pass in a log function like this:
|
||||
|
||||
```js
|
||||
const scope = nock('http://google.com')
|
||||
.log(console.log)
|
||||
...
|
||||
```
|
||||
|
||||
## Restoring
|
||||
|
||||
You can restore the HTTP interceptor to the normal unmocked behaviour by calling:
|
||||
|
@ -1119,7 +1072,7 @@ You can bypass Nock completely by setting the `NOCK_OFF` environment variable to
|
|||
|
||||
This way you can have your tests hit the real servers just by switching on this environment variable.
|
||||
|
||||
```js
|
||||
```shell script
|
||||
$ NOCK_OFF=true node my_test.js
|
||||
```
|
||||
|
||||
|
@ -1260,7 +1213,7 @@ If you save this as a JSON file, you can load them directly through `nock.load(p
|
|||
|
||||
```js
|
||||
nocks = nock.load(pathToJson)
|
||||
nocks.forEach(function(nock) {
|
||||
nocks.forEach(function (nock) {
|
||||
nock.filteringRequestBody = (body, aRecordedBody) => {
|
||||
if (typeof body !== 'string' || typeof aRecordedBody !== 'string') {
|
||||
return body
|
||||
|
@ -1269,9 +1222,12 @@ nocks.forEach(function(nock) {
|
|||
const recordedBodyResult = /timestamp:([0-9]+)/.exec(aRecordedBody)
|
||||
if (recordedBodyResult) {
|
||||
const recordedTimestamp = recordedBodyResult[1]
|
||||
return body.replace(/(timestamp):([0-9]+)/g, function(match, key, value) {
|
||||
return key + ':' + recordedTimestamp
|
||||
})
|
||||
return body.replace(
|
||||
/(timestamp):([0-9]+)/g,
|
||||
function (match, key, value) {
|
||||
return key + ':' + recordedTimestamp
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return body
|
||||
}
|
||||
|
@ -1355,10 +1311,10 @@ nock.removeInterceptor({
|
|||
|
||||
```js
|
||||
nock.removeInterceptor({
|
||||
hostname : 'localhost',
|
||||
path : '/login'
|
||||
method: 'POST'
|
||||
proto : 'https'
|
||||
hostname: 'localhost',
|
||||
path: '/login',
|
||||
method: 'POST',
|
||||
proto: 'https',
|
||||
})
|
||||
```
|
||||
|
||||
|
@ -1425,7 +1381,7 @@ nockBack('zomboFixture.json', nockDone => {
|
|||
nockDone()
|
||||
|
||||
// usage of the created fixture
|
||||
nockBack('zomboFixture.json', function(nockDone) {
|
||||
nockBack('zomboFixture.json', function (nockDone) {
|
||||
http.get('http://zombo.com/').end() // respond body "Ok"
|
||||
|
||||
this.assertScopesFinished() //throws an exception if all nocks in fixture were not satisfied
|
||||
|
@ -1439,12 +1395,10 @@ nockBack('zomboFixture.json', nockDone => {
|
|||
|
||||
If your tests are using promises then use `nockBack` like this:
|
||||
|
||||
```
|
||||
return nockBack('promisedFixture.json')
|
||||
.then(({ nockDone, context }) => {
|
||||
// do your tests returning a promise and chain it with
|
||||
// `.then(nockDone)`
|
||||
})
|
||||
```js
|
||||
return nockBack('promisedFixture.json').then(({ nockDone, context }) => {
|
||||
// do your tests returning a promise and chain it with
|
||||
// `.then(nockDone)`
|
||||
})
|
||||
```
|
||||
|
||||
|
@ -1462,7 +1416,7 @@ As an optional second parameter you can pass the following options
|
|||
```js
|
||||
function prepareScope(scope) {
|
||||
scope.filteringRequestBody = (body, aRecordedBody) => {
|
||||
if (typeof(body) !== 'string' || typeof(aRecordedBody) !== 'string') {
|
||||
if (typeof body !== 'string' || typeof aRecordedBody !== 'string') {
|
||||
return body
|
||||
}
|
||||
|
||||
|
@ -1479,15 +1433,15 @@ function prepareScope(scope) {
|
|||
}
|
||||
}
|
||||
|
||||
nockBack('zomboFixture.json', { before: prepareScope }, nockDone => {
|
||||
request.get('http://zombo.com', function(err, res, body) {
|
||||
nockBack('exampleFixture.json', { before: prepareScope }, nockDone => {
|
||||
request.get('http://example.com', function (err, res, body) {
|
||||
// do your tests
|
||||
nockDone()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Modes
|
||||
### Modes
|
||||
|
||||
To set the mode call `nockBack.setMode(mode)` or run the tests with the `NOCK_BACK_MODE` environment variable set before loading nock. If the mode needs to be changed programmatically, the following is valid: `nockBack.setMode(nockBack.currentMode)`
|
||||
|
||||
|
@ -1512,14 +1466,14 @@ The same is true for `.replyWithError()`.
|
|||
|
||||
Adding `{ retry: 0 }` to the `got` invocations will disable retrying, e.g.:
|
||||
|
||||
```
|
||||
await got("http://example.test/", { retry: 0 })
|
||||
```js
|
||||
await got('http://example.test/', { retry: 0 })
|
||||
```
|
||||
|
||||
If you need to do this in all your tests, you can create a module
|
||||
`got_client.js` which exports a custom got instance:
|
||||
|
||||
```
|
||||
```js
|
||||
const got = require('got')
|
||||
|
||||
module.exports = got.extend({ retry: 0 })
|
||||
|
@ -1566,12 +1520,37 @@ test('can fetch test response', async t => {
|
|||
|
||||
[axios]: https://github.com/axios/axios
|
||||
|
||||
### Memory issues with Jest
|
||||
|
||||
Memory issues can be avoided by calling [`nock.restore()`](#restoring) after each test suite.
|
||||
One of the core principles of [Jest](https://jestjs.io/) is that it runs tests in isolation.
|
||||
It does this by manipulating the modules cache of Node in a way that conflicts with how Nock monkey patches the builtin `http` and `https` modules.
|
||||
[Related issue with more details](https://github.com/nock/nock/issues/1817).
|
||||
|
||||
## Debugging
|
||||
|
||||
Nock uses [`debug`](https://github.com/visionmedia/debug), so just run with environmental variable `DEBUG` set to `nock.*`.
|
||||
|
||||
```console
|
||||
user@local$ DEBUG=nock.* node my_test.js
|
||||
```
|
||||
|
||||
Each step in the matching process is logged this way and can be useful when determining why a request was not intercepted by Nock.
|
||||
|
||||
For example the following shows that matching failed because the request had an extra search parameter.
|
||||
|
||||
```js
|
||||
$ DEBUG=nock.* node my_test.js
|
||||
nock('http://example.com').get('/').query({ foo: 'bar' }).reply()
|
||||
|
||||
await got('http://example.com/?foo=bar&baz=foz')
|
||||
```
|
||||
|
||||
```console
|
||||
user@local$ DEBUG=nock.scope:example.com node my_test.js
|
||||
...
|
||||
nock.scope:example.com Interceptor queries: {"foo":"bar"} +1ms
|
||||
nock.scope:example.com Request queries: {"foo":"bar","baz":"foz"} +0ms
|
||||
nock.scope:example.com query matching failed +0ms
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
|
|
@ -73,7 +73,7 @@ function Back(fixtureName, options, nockedFn) {
|
|||
const fixture = path.join(Back.fixtures, fixtureName)
|
||||
const context = _mode.start(fixture, options)
|
||||
|
||||
const nockDone = function() {
|
||||
const nockDone = function () {
|
||||
_mode.finish(fixture, options, context)
|
||||
}
|
||||
|
||||
|
@ -92,24 +92,24 @@ function Back(fixtureName, options, nockedFn) {
|
|||
*******************************************************************************/
|
||||
|
||||
const wild = {
|
||||
setup: function() {
|
||||
setup: function () {
|
||||
cleanAll()
|
||||
recorder.restore()
|
||||
activate()
|
||||
enableNetConnect()
|
||||
},
|
||||
|
||||
start: function() {
|
||||
start: function () {
|
||||
return load() // don't load anything but get correct context
|
||||
},
|
||||
|
||||
finish: function() {
|
||||
finish: function () {
|
||||
// nothing to do
|
||||
},
|
||||
}
|
||||
|
||||
const dryrun = {
|
||||
setup: function() {
|
||||
setup: function () {
|
||||
recorder.restore()
|
||||
cleanAll()
|
||||
activate()
|
||||
|
@ -117,20 +117,20 @@ const dryrun = {
|
|||
enableNetConnect()
|
||||
},
|
||||
|
||||
start: function(fixture, options) {
|
||||
start: function (fixture, options) {
|
||||
const contexts = load(fixture, options)
|
||||
|
||||
enableNetConnect()
|
||||
return contexts
|
||||
},
|
||||
|
||||
finish: function() {
|
||||
finish: function () {
|
||||
// nothing to do
|
||||
},
|
||||
}
|
||||
|
||||
const record = {
|
||||
setup: function() {
|
||||
setup: function () {
|
||||
recorder.restore()
|
||||
recorder.clear()
|
||||
cleanAll()
|
||||
|
@ -138,7 +138,7 @@ const record = {
|
|||
disableNetConnect()
|
||||
},
|
||||
|
||||
start: function(fixture, options) {
|
||||
start: function (fixture, options) {
|
||||
if (!fs) {
|
||||
throw new Error('no fs')
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ const record = {
|
|||
return context
|
||||
},
|
||||
|
||||
finish: function(fixture, options, context) {
|
||||
finish: function (fixture, options, context) {
|
||||
if (context.isRecording) {
|
||||
let outputs = recorder.outputs()
|
||||
|
||||
|
@ -176,7 +176,7 @@ const record = {
|
|||
}
|
||||
|
||||
const lockdown = {
|
||||
setup: function() {
|
||||
setup: function () {
|
||||
recorder.restore()
|
||||
recorder.clear()
|
||||
cleanAll()
|
||||
|
@ -184,11 +184,11 @@ const lockdown = {
|
|||
disableNetConnect()
|
||||
},
|
||||
|
||||
start: function(fixture, options) {
|
||||
start: function (fixture, options) {
|
||||
return load(fixture, options)
|
||||
},
|
||||
|
||||
finish: function() {
|
||||
finish: function () {
|
||||
// nothing to do
|
||||
},
|
||||
}
|
||||
|
@ -196,7 +196,7 @@ const lockdown = {
|
|||
function load(fixture, options) {
|
||||
const context = {
|
||||
scopes: [],
|
||||
assertScopesFinished: function() {
|
||||
assertScopesFinished: function () {
|
||||
assertScopes(this.scopes, fixture)
|
||||
},
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ const Modes = {
|
|||
lockdown, // use recorded nocks, disables all http calls even when not nocked, doesnt record
|
||||
}
|
||||
|
||||
Back.setMode = function(mode) {
|
||||
Back.setMode = function (mode) {
|
||||
if (!(mode in Modes)) {
|
||||
throw new Error(`Unknown mode: ${mode}`)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const debug = require('debug')('nock.common')
|
||||
const url = require('url')
|
||||
const set = require('lodash.set')
|
||||
const timers = require('timers')
|
||||
const url = require('url')
|
||||
const util = require('util')
|
||||
|
||||
/**
|
||||
* Normalizes the request options so that it always has `host` property.
|
||||
|
@ -28,7 +29,7 @@ function normalizeRequestOptions(options) {
|
|||
debug('options.host in the end: %j', options.host)
|
||||
|
||||
/// lowercase host names
|
||||
;['hostname', 'host'].forEach(function(attr) {
|
||||
;['hostname', 'host'].forEach(function (attr) {
|
||||
if (options[attr]) {
|
||||
options[attr] = options[attr].toLowerCase()
|
||||
}
|
||||
|
@ -66,7 +67,7 @@ let requestOverrides = {}
|
|||
*/
|
||||
function overrideRequests(newRequest) {
|
||||
debug('overriding requests')
|
||||
;['http', 'https'].forEach(function(proto) {
|
||||
;['http', 'https'].forEach(function (proto) {
|
||||
debug('- overriding request for', proto)
|
||||
|
||||
const moduleName = proto // 1 to 1 match of protocol and module is fortunate :)
|
||||
|
@ -90,7 +91,7 @@ function overrideRequests(newRequest) {
|
|||
get: overriddenGet,
|
||||
}
|
||||
// https://nodejs.org/api/http.html#http_http_request_url_options_callback
|
||||
module.request = function(input, options, callback) {
|
||||
module.request = function (input, options, callback) {
|
||||
return newRequest(proto, overriddenRequest.bind(module), [
|
||||
input,
|
||||
options,
|
||||
|
@ -98,7 +99,7 @@ function overrideRequests(newRequest) {
|
|||
])
|
||||
}
|
||||
// https://nodejs.org/api/http.html#http_http_get_options_callback
|
||||
module.get = function(input, options, callback) {
|
||||
module.get = function (input, options, callback) {
|
||||
const req = newRequest(proto, overriddenGet.bind(module), [
|
||||
input,
|
||||
options,
|
||||
|
@ -179,7 +180,7 @@ function isContentEncoded(headers) {
|
|||
|
||||
function contentEncoding(headers, encoder) {
|
||||
const contentEncoding = headers['content-encoding']
|
||||
return contentEncoding === encoder
|
||||
return contentEncoding !== undefined && contentEncoding.toString() === encoder
|
||||
}
|
||||
|
||||
function isJSONContent(headers) {
|
||||
|
@ -194,7 +195,7 @@ function isJSONContent(headers) {
|
|||
* Duplicates throw an error.
|
||||
*/
|
||||
function headersFieldNamesToLowerCase(headers) {
|
||||
if (!_.isPlainObject(headers)) {
|
||||
if (!isPlainObject(headers)) {
|
||||
throw Error('Headers must be provided as an object')
|
||||
}
|
||||
|
||||
|
@ -243,11 +244,11 @@ function headersInputToRawArray(headers) {
|
|||
}
|
||||
|
||||
// [].concat(...) is used instead of Array.flat until v11 is the minimum Node version
|
||||
if (_.isMap(headers)) {
|
||||
if (util.types.isMap(headers)) {
|
||||
return [].concat(...Array.from(headers, ([k, v]) => [k.toString(), v]))
|
||||
}
|
||||
|
||||
if (_.isPlainObject(headers)) {
|
||||
if (isPlainObject(headers)) {
|
||||
return [].concat(...Object.entries(headers))
|
||||
}
|
||||
|
||||
|
@ -359,7 +360,7 @@ function addHeaderLine(headers, name, value) {
|
|||
* @fieldName {String} field name - string with the case-insensitive field name
|
||||
*/
|
||||
function deleteHeadersField(headers, fieldNameToDelete) {
|
||||
if (!_.isPlainObject(headers)) {
|
||||
if (!isPlainObject(headers)) {
|
||||
throw Error('headers must be an object')
|
||||
}
|
||||
|
||||
|
@ -406,11 +407,8 @@ function percentDecode(str) {
|
|||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
*/
|
||||
function percentEncode(str) {
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
|
||||
return `%${c
|
||||
.charCodeAt(0)
|
||||
.toString(16)
|
||||
.toUpperCase()}`
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
|
||||
return `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -418,9 +416,12 @@ function matchStringOrRegexp(target, pattern) {
|
|||
const targetStr =
|
||||
target === undefined || target === null ? '' : String(target)
|
||||
|
||||
return pattern instanceof RegExp
|
||||
? pattern.test(targetStr)
|
||||
: targetStr === String(pattern)
|
||||
if (pattern instanceof RegExp) {
|
||||
// if the regexp happens to have a global flag, we want to ensure we test the entire target
|
||||
pattern.lastIndex = 0
|
||||
return pattern.test(targetStr)
|
||||
}
|
||||
return targetStr === String(pattern)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -452,13 +453,13 @@ function formatQueryValue(key, value, stringFormattingFn) {
|
|||
case value instanceof RegExp:
|
||||
break
|
||||
case Array.isArray(value): {
|
||||
value = value.map(function(val, idx) {
|
||||
value = value.map(function (val, idx) {
|
||||
return formatQueryValue(idx, val, stringFormattingFn)[1]
|
||||
})
|
||||
break
|
||||
}
|
||||
case typeof value === 'object': {
|
||||
value = Object.entries(value).reduce(function(acc, [subKey, subVal]) {
|
||||
value = Object.entries(value).reduce(function (acc, [subKey, subVal]) {
|
||||
const subPair = formatQueryValue(subKey, subVal, stringFormattingFn)
|
||||
acc[subPair[0]] = subPair[1]
|
||||
|
||||
|
@ -550,8 +551,15 @@ function urlToOptions(url) {
|
|||
* - The expected data can use regexp to compare values
|
||||
* - JSON path notation and nested objects are considered equal
|
||||
*/
|
||||
const dataEqual = (expected, actual) =>
|
||||
deepEqual(expand(expected), expand(actual))
|
||||
const dataEqual = (expected, actual) => {
|
||||
if (isPlainObject(expected)) {
|
||||
expected = expand(expected)
|
||||
}
|
||||
if (isPlainObject(actual)) {
|
||||
actual = expand(actual)
|
||||
}
|
||||
return deepEqual(expected, actual)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts flat objects whose keys use JSON path notation to nested objects.
|
||||
|
@ -562,7 +570,7 @@ const dataEqual = (expected, actual) =>
|
|||
* { 'foo[bar][0]': 'baz' } -> { foo: { bar: [ 'baz' ] } }
|
||||
*/
|
||||
const expand = input =>
|
||||
Object.entries(input).reduce((acc, [k, v]) => _.set(acc, k, v), {})
|
||||
Object.entries(input).reduce((acc, [k, v]) => set(acc, k, v), {})
|
||||
|
||||
/**
|
||||
* Performs a recursive strict comparison between two values.
|
||||
|
@ -575,22 +583,70 @@ function deepEqual(expected, actual) {
|
|||
return expected.test(actual)
|
||||
}
|
||||
|
||||
if (Array.isArray(expected) || _.isPlainObject(expected)) {
|
||||
if (actual === undefined) {
|
||||
if (Array.isArray(expected) && Array.isArray(actual)) {
|
||||
if (expected.length !== actual.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const expKeys = Object.keys(expected)
|
||||
if (expKeys.length !== Object.keys(actual).length) {
|
||||
return false
|
||||
}
|
||||
return expected.every((expVal, idx) => deepEqual(expVal, actual[idx]))
|
||||
}
|
||||
|
||||
return expKeys.every(key => deepEqual(expected[key], actual[key]))
|
||||
if (isPlainObject(expected) && isPlainObject(actual)) {
|
||||
const allKeys = Array.from(
|
||||
new Set(Object.keys(expected).concat(Object.keys(actual)))
|
||||
)
|
||||
|
||||
return allKeys.every(key => deepEqual(expected[key], actual[key]))
|
||||
}
|
||||
|
||||
return expected === actual
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `value` is a plain object, that is, an object created by the
|
||||
* `Object` constructor or one with a `[[Prototype]]` of `null`.
|
||||
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/isPlainObject.js
|
||||
*
|
||||
* @param {*} value The value to check.
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isPlainObject(value) {
|
||||
const isObjectLike = typeof value === 'object' && value !== null
|
||||
const tag = Object.prototype.toString.call(value)
|
||||
if (!isObjectLike || tag !== '[object Object]') {
|
||||
return false
|
||||
}
|
||||
if (Object.getPrototypeOf(value) === null) {
|
||||
return true
|
||||
}
|
||||
let proto = value
|
||||
while (Object.getPrototypeOf(proto) !== null) {
|
||||
proto = Object.getPrototypeOf(proto)
|
||||
}
|
||||
return Object.getPrototypeOf(value) === proto
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object with the same keys as `object` and values generated
|
||||
* by running each own enumerable string keyed property of `object` thru
|
||||
* `iteratee`. (iteration order is not guaranteed)
|
||||
* The iteratee is invoked with three arguments: (value, key, object).
|
||||
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/mapValue.js
|
||||
*
|
||||
* @param {Object} object The object to iterate over.
|
||||
* @param {Function} iteratee The function invoked per iteration.
|
||||
* @returns {Object} Returns the new mapped object.
|
||||
*/
|
||||
function mapValue(object, iteratee) {
|
||||
object = Object(object)
|
||||
const result = {}
|
||||
|
||||
Object.keys(object).forEach(key => {
|
||||
result[key] = iteratee(object[key], key, object)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const timeouts = []
|
||||
const intervals = []
|
||||
const immediates = []
|
||||
|
@ -617,29 +673,62 @@ function removeAllTimers() {
|
|||
clearTimer(clearImmediate, immediates)
|
||||
}
|
||||
|
||||
exports.normalizeClientRequestArgs = normalizeClientRequestArgs
|
||||
exports.normalizeRequestOptions = normalizeRequestOptions
|
||||
exports.normalizeOrigin = normalizeOrigin
|
||||
exports.isUtf8Representable = isUtf8Representable
|
||||
exports.overrideRequests = overrideRequests
|
||||
exports.restoreOverriddenRequests = restoreOverriddenRequests
|
||||
exports.stringifyRequest = stringifyRequest
|
||||
exports.isContentEncoded = isContentEncoded
|
||||
exports.contentEncoding = contentEncoding
|
||||
exports.isJSONContent = isJSONContent
|
||||
exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase
|
||||
exports.headersFieldsArrayToLowerCase = headersFieldsArrayToLowerCase
|
||||
exports.headersArrayToObject = headersArrayToObject
|
||||
exports.headersInputToRawArray = headersInputToRawArray
|
||||
exports.deleteHeadersField = deleteHeadersField
|
||||
exports.forEachHeader = forEachHeader
|
||||
exports.percentEncode = percentEncode
|
||||
exports.percentDecode = percentDecode
|
||||
exports.matchStringOrRegexp = matchStringOrRegexp
|
||||
exports.formatQueryValue = formatQueryValue
|
||||
exports.isStream = isStream
|
||||
exports.dataEqual = dataEqual
|
||||
exports.setTimeout = setTimeout
|
||||
exports.setInterval = setInterval
|
||||
exports.setImmediate = setImmediate
|
||||
exports.removeAllTimers = removeAllTimers
|
||||
/**
|
||||
* Check if the Client Request has been cancelled.
|
||||
*
|
||||
* Until Node 14 is the minimum, we need to look at both flags to see if the request has been cancelled.
|
||||
* The two flags have the same purpose, but the Node maintainers are migrating from `abort(ed)` to
|
||||
* `destroy(ed)` terminology, to be more consistent with `stream.Writable`.
|
||||
* In Node 14.x+, Calling `abort()` will set both `aborted` and `destroyed` to true, however,
|
||||
* calling `destroy()` will only set `destroyed` to true.
|
||||
* Falling back on checking if the socket is destroyed to cover the case of Node <14.x where
|
||||
* `destroy()` is called, but `destroyed` is undefined.
|
||||
*
|
||||
* Node Client Request history:
|
||||
* - `request.abort()`: Added in: v0.3.8, Deprecated since: v14.1.0, v13.14.0
|
||||
* - `request.aborted`: Added in: v0.11.14, Became a boolean instead of a timestamp: v11.0.0, Not deprecated (yet)
|
||||
* - `request.destroy()`: Added in: v0.3.0
|
||||
* - `request.destroyed`: Added in: v14.1.0, v13.14.0
|
||||
*
|
||||
* @param {ClientRequest} req
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isRequestDestroyed(req) {
|
||||
return !!(
|
||||
req.destroyed === true ||
|
||||
req.aborted ||
|
||||
(req.socket && req.socket.destroyed)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
contentEncoding,
|
||||
dataEqual,
|
||||
deleteHeadersField,
|
||||
forEachHeader,
|
||||
formatQueryValue,
|
||||
headersArrayToObject,
|
||||
headersFieldNamesToLowerCase,
|
||||
headersFieldsArrayToLowerCase,
|
||||
headersInputToRawArray,
|
||||
isContentEncoded,
|
||||
isJSONContent,
|
||||
isPlainObject,
|
||||
isRequestDestroyed,
|
||||
isStream,
|
||||
isUtf8Representable,
|
||||
mapValue,
|
||||
matchStringOrRegexp,
|
||||
normalizeClientRequestArgs,
|
||||
normalizeOrigin,
|
||||
normalizeRequestOptions,
|
||||
overrideRequests,
|
||||
percentDecode,
|
||||
percentEncode,
|
||||
removeAllTimers,
|
||||
restoreOverriddenRequests,
|
||||
setImmediate,
|
||||
setInterval,
|
||||
setTimeout,
|
||||
stringifyRequest,
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
/**
|
||||
* Creates a stream which becomes the response body of the interceptor when a
|
||||
* delay is set. The stream outputs the intended body and EOF after the delay.
|
||||
*
|
||||
* @param {String|Buffer|Stream} body - the body to write/pipe out
|
||||
* @param {Integer} ms - The delay in milliseconds
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
const { Transform } = require('stream')
|
||||
const common = require('./common')
|
||||
|
||||
module.exports = class DelayedBody extends Transform {
|
||||
constructor(ms, body) {
|
||||
super()
|
||||
|
||||
const self = this
|
||||
let data = ''
|
||||
let ended = false
|
||||
|
||||
if (common.isStream(body)) {
|
||||
body.on('data', function(chunk) {
|
||||
data += Buffer.isBuffer(chunk) ? chunk.toString() : chunk
|
||||
})
|
||||
|
||||
body.once('end', function() {
|
||||
ended = true
|
||||
})
|
||||
|
||||
body.resume()
|
||||
}
|
||||
|
||||
// TODO: This would be more readable if the stream case were moved into
|
||||
// the `if` statement above.
|
||||
common.setTimeout(function() {
|
||||
if (common.isStream(body) && !ended) {
|
||||
body.once('end', function() {
|
||||
self.end(data)
|
||||
})
|
||||
} else {
|
||||
self.end(data || body)
|
||||
}
|
||||
}, ms)
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, cb) {
|
||||
this.push(chunk)
|
||||
process.nextTick(cb)
|
||||
}
|
||||
}
|
|
@ -121,14 +121,14 @@ function remove(interceptor) {
|
|||
// TODO: There is a clearer way to write that we want to delete the first
|
||||
// matching instance. I'm also not sure why we couldn't delete _all_
|
||||
// matching instances.
|
||||
interceptors.some(function(thisInterceptor, i) {
|
||||
interceptors.some(function (thisInterceptor, i) {
|
||||
return thisInterceptor === interceptor ? interceptors.splice(i, 1) : false
|
||||
})
|
||||
}
|
||||
|
||||
function removeAll() {
|
||||
Object.keys(allInterceptors).forEach(function(key) {
|
||||
allInterceptors[key].interceptors.forEach(function(interceptor) {
|
||||
Object.keys(allInterceptors).forEach(function (key) {
|
||||
allInterceptors[key].interceptors.forEach(function (interceptor) {
|
||||
interceptor.scope.keyedInterceptors = {}
|
||||
})
|
||||
})
|
||||
|
@ -159,7 +159,7 @@ function interceptorsFor(options) {
|
|||
// 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')
|
||||
interceptor.scope.logger('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.
|
||||
|
@ -237,7 +237,7 @@ let originalClientRequest
|
|||
function ErroringClientRequest(error) {
|
||||
http.OutgoingMessage.call(this)
|
||||
process.nextTick(
|
||||
function() {
|
||||
function () {
|
||||
this.emit('error', error)
|
||||
}.bind(this)
|
||||
)
|
||||
|
@ -281,6 +281,8 @@ function overrideClientRequest() {
|
|||
debug('using', interceptors.length, 'interceptors')
|
||||
|
||||
// Use filtered interceptors to intercept requests.
|
||||
// TODO: this shouldn't be a class anymore
|
||||
// the overrider explicitly overrides methods and attrs on the request so the `assign` below should be removed.
|
||||
const overrider = new InterceptedRequestRouter({
|
||||
req: this,
|
||||
options,
|
||||
|
@ -299,7 +301,7 @@ function overrideClientRequest() {
|
|||
originalClientRequest.apply(this, arguments)
|
||||
} else {
|
||||
common.setImmediate(
|
||||
function() {
|
||||
function () {
|
||||
const error = new NetConnectNotAllowedError(
|
||||
options.host,
|
||||
options.path
|
||||
|
@ -368,7 +370,7 @@ function activate() {
|
|||
|
||||
// ----- Overriding http.request and https.request:
|
||||
|
||||
common.overrideRequests(function(proto, overriddenRequest, args) {
|
||||
common.overrideRequests(function (proto, overriddenRequest, args) {
|
||||
// NOTE: overriddenRequest is already bound to its module.
|
||||
|
||||
const { options, callback } = common.normalizeClientRequestArgs(...args)
|
||||
|
|
|
@ -13,6 +13,20 @@ const globalEmitter = require('./global_emitter')
|
|||
const Socket = require('./socket')
|
||||
const { playbackInterceptor } = require('./playback_interceptor')
|
||||
|
||||
function socketOnClose(req) {
|
||||
debug('socket close')
|
||||
|
||||
if (!req.res && !req.socket._hadError) {
|
||||
// If we don't have a response then we know that the socket
|
||||
// ended prematurely and we need to emit an error on the request.
|
||||
req.socket._hadError = true
|
||||
const err = new Error('socket hang up')
|
||||
err.code = 'ECONNRESET'
|
||||
req.emit('error', err)
|
||||
}
|
||||
req.emit('close')
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a group of interceptors, appropriately route an outgoing request.
|
||||
* Identify which interceptor ought to respond, if any, then delegate to
|
||||
|
@ -39,16 +53,24 @@ class InterceptedRequestRouter {
|
|||
}
|
||||
|
||||
this.response = new IncomingMessage(this.socket)
|
||||
this.playbackStarted = false
|
||||
this.requestBodyBuffers = []
|
||||
this.playbackStarted = false
|
||||
|
||||
// For parity with Node, it's important the socket event is emitted before we begin playback.
|
||||
// This flag is set when playback is triggered if we haven't yet gotten the
|
||||
// socket event to indicate that playback should start as soon as it comes in.
|
||||
this.readyToStartPlaybackOnSocketEvent = false
|
||||
|
||||
this.attachToReq()
|
||||
|
||||
// Emit a fake socket event on the next tick to mimic what would happen on a real request.
|
||||
// Some clients listen for a 'socket' event to be emitted before calling end(),
|
||||
// which causes Nock to hang.
|
||||
process.nextTick(() => this.connectSocket())
|
||||
}
|
||||
|
||||
attachToReq() {
|
||||
const { req, response, socket, options } = this
|
||||
|
||||
response.req = req
|
||||
const { req, options } = this
|
||||
|
||||
for (const [name, val] of Object.entries(options.headers)) {
|
||||
req.setHeader(name.toLowerCase(), val)
|
||||
|
@ -65,18 +87,9 @@ class InterceptedRequestRouter {
|
|||
req.path = options.path
|
||||
req.method = options.method
|
||||
|
||||
// ClientRequest.connection is an alias for ClientRequest.socket
|
||||
// https://nodejs.org/api/http.html#http_request_socket
|
||||
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_client.js#L640-L641
|
||||
// The same Socket is shared between the request and response to mimic native behavior.
|
||||
req.socket = req.connection = socket
|
||||
|
||||
propagate(['error', 'timeout'], req.socket, req)
|
||||
|
||||
req.write = (...args) => this.handleWrite(...args)
|
||||
req.end = (...args) => this.handleEnd(...args)
|
||||
req.flushHeaders = (...args) => this.handleFlushHeaders(...args)
|
||||
req.abort = (...args) => this.handleAbort(...args)
|
||||
|
||||
// https://github.com/nock/nock/issues/256
|
||||
if (options.headers.expect === '100-continue') {
|
||||
|
@ -85,53 +98,79 @@ class InterceptedRequestRouter {
|
|||
req.emit('continue')
|
||||
})
|
||||
}
|
||||
|
||||
// Emit a fake socket event on the next tick to mimic what would happen on a real request.
|
||||
// Some clients listen for a 'socket' event to be emitted before calling end(),
|
||||
// which causes nock to hang.
|
||||
process.nextTick(() => {
|
||||
req.emit('socket', socket)
|
||||
|
||||
// https://nodejs.org/api/net.html#net_event_connect
|
||||
socket.emit('connect')
|
||||
|
||||
// https://nodejs.org/api/tls.html#tls_event_secureconnect
|
||||
if (socket.authorized) {
|
||||
socket.emit('secureConnect')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
emitError(error) {
|
||||
const { req } = this
|
||||
process.nextTick(() => {
|
||||
req.emit('error', error)
|
||||
})
|
||||
}
|
||||
connectSocket() {
|
||||
const { req, socket } = this
|
||||
|
||||
handleWrite(buffer, encoding, callback) {
|
||||
debug('write', arguments)
|
||||
const { req } = this
|
||||
|
||||
if (!req.aborted) {
|
||||
if (buffer) {
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
buffer = Buffer.from(buffer, encoding)
|
||||
}
|
||||
this.requestBodyBuffers.push(buffer)
|
||||
}
|
||||
// can't use instanceof Function because some test runners
|
||||
// run tests in vm.runInNewContext where Function is not same
|
||||
// as that in the current context
|
||||
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
|
||||
if (typeof callback === 'function') {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
this.emitError(new Error('Request aborted'))
|
||||
if (common.isRequestDestroyed(req)) {
|
||||
return
|
||||
}
|
||||
|
||||
common.setImmediate(function() {
|
||||
// ClientRequest.connection is an alias for ClientRequest.socket
|
||||
// https://nodejs.org/api/http.html#http_request_socket
|
||||
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_client.js#L640-L641
|
||||
// The same Socket is shared between the request and response to mimic native behavior.
|
||||
req.socket = req.connection = socket
|
||||
|
||||
propagate(['error', 'timeout'], socket, req)
|
||||
socket.on('close', () => socketOnClose(req))
|
||||
|
||||
socket.connecting = false
|
||||
req.emit('socket', socket)
|
||||
|
||||
// https://nodejs.org/api/net.html#net_event_connect
|
||||
socket.emit('connect')
|
||||
|
||||
// https://nodejs.org/api/tls.html#tls_event_secureconnect
|
||||
if (socket.authorized) {
|
||||
socket.emit('secureConnect')
|
||||
}
|
||||
|
||||
if (this.readyToStartPlaybackOnSocketEvent) {
|
||||
this.maybeStartPlayback()
|
||||
}
|
||||
}
|
||||
|
||||
// from docs: When write function is called with empty string or buffer, it does nothing and waits for more input.
|
||||
// However, actually implementation checks the state of finished and aborted before checking if the first arg is empty.
|
||||
handleWrite(buffer, encoding, callback) {
|
||||
debug('request write')
|
||||
const { req } = this
|
||||
|
||||
if (req.finished) {
|
||||
const err = new Error('write after end')
|
||||
err.code = 'ERR_STREAM_WRITE_AFTER_END'
|
||||
process.nextTick(() => req.emit('error', err))
|
||||
|
||||
// It seems odd to return `true` here, not sure why you'd want to have
|
||||
// the stream potentially written to more, but it's what Node does.
|
||||
// https://github.com/nodejs/node/blob/a9270dcbeba4316b1e179b77ecb6c46af5aa8c20/lib/_http_outgoing.js#L662-L665
|
||||
return true
|
||||
}
|
||||
|
||||
if (req.socket && req.socket.destroyed) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!buffer || buffer.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
buffer = Buffer.from(buffer, encoding)
|
||||
}
|
||||
this.requestBodyBuffers.push(buffer)
|
||||
|
||||
// can't use instanceof Function because some test runners
|
||||
// run tests in vm.runInNewContext where Function is not same
|
||||
// as that in the current context
|
||||
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
|
||||
if (typeof callback === 'function') {
|
||||
callback()
|
||||
}
|
||||
|
||||
common.setImmediate(function () {
|
||||
req.emit('drain')
|
||||
})
|
||||
|
||||
|
@ -139,10 +178,10 @@ class InterceptedRequestRouter {
|
|||
}
|
||||
|
||||
handleEnd(chunk, encoding, callback) {
|
||||
debug('req.end')
|
||||
debug('request end')
|
||||
const { req } = this
|
||||
|
||||
// handle the different overloaded param signatures
|
||||
// handle the different overloaded arg signatures
|
||||
if (typeof chunk === 'function') {
|
||||
callback = chunk
|
||||
chunk = null
|
||||
|
@ -155,51 +194,18 @@ class InterceptedRequestRouter {
|
|||
req.once('finish', callback)
|
||||
}
|
||||
|
||||
if (!req.aborted && !this.playbackStarted) {
|
||||
if (chunk) {
|
||||
req.write(chunk, encoding)
|
||||
this.startPlayback()
|
||||
}
|
||||
if (req.aborted) {
|
||||
this.emitError(new Error('Request aborted'))
|
||||
}
|
||||
req.finished = true
|
||||
this.maybeStartPlayback()
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
handleFlushHeaders() {
|
||||
debug('req.flushHeaders')
|
||||
const { req } = this
|
||||
|
||||
if (!req.aborted && !this.playbackStarted) {
|
||||
this.startPlayback()
|
||||
}
|
||||
if (req.aborted) {
|
||||
this.emitError(new Error('Request aborted'))
|
||||
}
|
||||
}
|
||||
|
||||
handleAbort() {
|
||||
debug('req.abort')
|
||||
const { req, response, socket } = this
|
||||
|
||||
if (req.aborted) {
|
||||
return
|
||||
}
|
||||
req.aborted = Date.now()
|
||||
if (!this.playbackStarted) {
|
||||
this.startPlayback()
|
||||
}
|
||||
const err = new Error()
|
||||
err.code = 'aborted'
|
||||
response.emit('close', err)
|
||||
|
||||
socket.destroy()
|
||||
|
||||
req.emit('abort')
|
||||
|
||||
const connResetError = new Error('socket hang up')
|
||||
connResetError.code = 'ECONNRESET'
|
||||
this.emitError(connResetError)
|
||||
debug('request flushHeaders')
|
||||
this.maybeStartPlayback()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,6 +239,21 @@ class InterceptedRequestRouter {
|
|||
}
|
||||
}
|
||||
|
||||
maybeStartPlayback() {
|
||||
const { req, socket, playbackStarted } = this
|
||||
|
||||
// In order to get the events in the right order we need to delay playback
|
||||
// if we get here before the `socket` event is emitted.
|
||||
if (socket.connecting) {
|
||||
this.readyToStartPlaybackOnSocketEvent = true
|
||||
return
|
||||
}
|
||||
|
||||
if (!common.isRequestDestroyed(req) && !playbackStarted) {
|
||||
this.startPlayback()
|
||||
}
|
||||
}
|
||||
|
||||
startPlayback() {
|
||||
debug('ending')
|
||||
this.playbackStarted = true
|
||||
|
@ -270,11 +291,14 @@ class InterceptedRequestRouter {
|
|||
)
|
||||
|
||||
if (matchedInterceptor) {
|
||||
debug('interceptor identified, starting mocking')
|
||||
matchedInterceptor.scope.logger(
|
||||
'interceptor identified, starting mocking'
|
||||
)
|
||||
|
||||
matchedInterceptor.markConsumed()
|
||||
|
||||
// wait to emit the finish event until we know for sure an Interceptor is going to playback.
|
||||
// otherwise an unmocked request might emit finish twice.
|
||||
req.finished = true
|
||||
req.emit('finish')
|
||||
|
||||
playbackInterceptor({
|
||||
|
@ -304,14 +328,11 @@ class InterceptedRequestRouter {
|
|||
// We send the raw buffer as we received it, not as we interpreted it.
|
||||
newReq.end(requestBodyBuffer)
|
||||
} else {
|
||||
const err = new Error(
|
||||
`Nock: No match for request ${common.stringifyRequest(
|
||||
options,
|
||||
requestBodyString
|
||||
)}`
|
||||
)
|
||||
const reqStr = common.stringifyRequest(options, requestBodyString)
|
||||
const err = new Error(`Nock: No match for request ${reqStr}`)
|
||||
err.code = 'ERR_NOCK_NO_MATCH'
|
||||
err.statusCode = err.status = 404
|
||||
this.emitError(err)
|
||||
req.destroy(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
'use strict'
|
||||
|
||||
const debug = require('debug')('nock.interceptor')
|
||||
const stringify = require('json-stringify-safe')
|
||||
const _ = require('lodash')
|
||||
const querystring = require('querystring')
|
||||
const { URL, URLSearchParams } = require('url')
|
||||
|
||||
|
@ -34,8 +32,9 @@ module.exports = class Interceptor {
|
|||
// When enabled filteringScope ignores the passed URL entirely so we skip validation.
|
||||
|
||||
if (
|
||||
!scope.scopeOptions.filteringScope &&
|
||||
uriIsStr &&
|
||||
!scope.scopeOptions.filteringScope &&
|
||||
!scope.basePathname &&
|
||||
!uri.startsWith('/') &&
|
||||
!uri.startsWith('*')
|
||||
) {
|
||||
|
@ -73,7 +72,7 @@ module.exports = class Interceptor {
|
|||
scope.scopeOptions.badheaders || []
|
||||
)
|
||||
|
||||
this.delayInMs = 0
|
||||
this.delayBodyInMs = 0
|
||||
this.delayConnectionInMs = 0
|
||||
|
||||
this.optional = false
|
||||
|
@ -181,8 +180,8 @@ module.exports = class Interceptor {
|
|||
}
|
||||
}
|
||||
|
||||
debug('reply.headers:', this.headers)
|
||||
debug('reply.rawHeaders:', this.rawHeaders)
|
||||
this.scope.logger('reply.headers:', this.headers)
|
||||
this.scope.logger('reply.rawHeaders:', this.rawHeaders)
|
||||
|
||||
this.body = body
|
||||
|
||||
|
@ -228,13 +227,23 @@ module.exports = class Interceptor {
|
|||
}
|
||||
}
|
||||
|
||||
debug("request header field doesn't match:", key, header, reqHeader)
|
||||
this.scope.logger(
|
||||
"request header field doesn't match:",
|
||||
key,
|
||||
header,
|
||||
reqHeader
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
match(req, options, body) {
|
||||
if (debug.enabled) {
|
||||
debug('match %s, body = %s', stringify(options), stringify(body))
|
||||
// check if the logger is enabled because the stringifies can be expensive.
|
||||
if (this.scope.logger.enabled) {
|
||||
this.scope.logger(
|
||||
'attempting match %s, body = %s',
|
||||
stringify(options),
|
||||
stringify(body)
|
||||
)
|
||||
}
|
||||
|
||||
const method = (options.method || 'GET').toUpperCase()
|
||||
|
@ -244,7 +253,7 @@ module.exports = class Interceptor {
|
|||
const { proto } = options
|
||||
|
||||
if (this.method !== method) {
|
||||
debug(
|
||||
this.scope.logger(
|
||||
`Method did not match. Request ${method} Interceptor ${this.method}`
|
||||
)
|
||||
return false
|
||||
|
@ -276,6 +285,7 @@ module.exports = class Interceptor {
|
|||
)
|
||||
|
||||
if (!reqHeadersMatch) {
|
||||
this.scope.logger("headers don't match")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -283,26 +293,32 @@ module.exports = class Interceptor {
|
|||
this.scope.scopeOptions.conditionally &&
|
||||
!this.scope.scopeOptions.conditionally()
|
||||
) {
|
||||
this.scope.logger(
|
||||
'matching failed because Scope.conditionally() did not validate'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const reqContainsBadHeaders = this.badheaders.some(
|
||||
const badHeaders = this.badheaders.filter(
|
||||
header => header in options.headers
|
||||
)
|
||||
|
||||
if (reqContainsBadHeaders) {
|
||||
if (badHeaders.length) {
|
||||
this.scope.logger('request contains bad headers', ...badHeaders)
|
||||
return false
|
||||
}
|
||||
|
||||
// Match query strings when using query()
|
||||
if (this.queries === null) {
|
||||
debug('query matching skipped')
|
||||
this.scope.logger('query matching skipped')
|
||||
} else {
|
||||
// can't rely on pathname or search being in the options, but path has a default
|
||||
const [pathname, search] = path.split('?')
|
||||
const matchQueries = this.matchQuery({ search })
|
||||
|
||||
debug(matchQueries ? 'query matching succeeded' : 'query matching failed')
|
||||
this.scope.logger(
|
||||
matchQueries ? 'query matching succeeded' : 'query matching failed'
|
||||
)
|
||||
|
||||
if (!matchQueries) {
|
||||
return false
|
||||
|
@ -405,8 +421,8 @@ module.exports = class Interceptor {
|
|||
}
|
||||
|
||||
const reqQueries = querystring.parse(options.search)
|
||||
debug('Interceptor queries: %j', this.queries)
|
||||
debug(' Request queries: %j', reqQueries)
|
||||
this.scope.logger('Interceptor queries: %j', this.queries)
|
||||
this.scope.logger(' Request queries: %j', reqQueries)
|
||||
|
||||
if (typeof this.queries === 'function') {
|
||||
return this.queries(reqQueries)
|
||||
|
@ -483,7 +499,7 @@ module.exports = class Interceptor {
|
|||
// Normalize the data into the shape that is matched against.
|
||||
// Duplicate keys are handled by combining the values into an array.
|
||||
queries = querystring.parse(queries.toString())
|
||||
} else if (!_.isPlainObject(queries)) {
|
||||
} else if (!common.isPlainObject(queries)) {
|
||||
throw Error(`Argument Error: ${queries}`)
|
||||
}
|
||||
|
||||
|
@ -583,7 +599,7 @@ module.exports = class Interceptor {
|
|||
* @return {Interceptor} - the current interceptor for chaining
|
||||
*/
|
||||
delayBody(ms) {
|
||||
this.delayInMs += ms
|
||||
this.delayBodyInMs = ms
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -594,26 +610,7 @@ module.exports = class Interceptor {
|
|||
* @return {Interceptor} - the current interceptor for chaining
|
||||
*/
|
||||
delayConnection(ms) {
|
||||
this.delayConnectionInMs += ms
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {number}
|
||||
*/
|
||||
getTotalDelay() {
|
||||
return this.delayInMs + this.delayConnectionInMs
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the socket idle for a certain number of ms (simulated).
|
||||
*
|
||||
* @param {integer} ms - Number of milliseconds to wait
|
||||
* @return {Interceptor} - the current interceptor for chaining
|
||||
*/
|
||||
socketDelay(ms) {
|
||||
this.socketDelayInMs = ms
|
||||
this.delayConnectionInMs = ms
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const querystring = require('querystring')
|
||||
|
||||
const common = require('./common')
|
||||
|
@ -70,8 +69,8 @@ function mapValuesDeep(obj, cb) {
|
|||
if (Array.isArray(obj)) {
|
||||
return obj.map(v => mapValuesDeep(v, cb))
|
||||
}
|
||||
if (_.isPlainObject(obj)) {
|
||||
return _.mapValues(obj, v => mapValuesDeep(v, cb))
|
||||
if (common.isPlainObject(obj)) {
|
||||
return common.mapValue(obj, v => mapValuesDeep(v, cb))
|
||||
}
|
||||
return cb(obj)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use strict'
|
||||
|
||||
const stream = require('stream')
|
||||
const util = require('util')
|
||||
const zlib = require('zlib')
|
||||
const debug = require('debug')('nock.playback_interceptor')
|
||||
const common = require('./common')
|
||||
const DelayedBody = require('./delayed_body')
|
||||
|
||||
function parseJSONRequestBody(req, requestBody) {
|
||||
if (!requestBody || !common.isJSONContent(req.headers)) {
|
||||
|
@ -71,6 +71,44 @@ function selectDefaultHeaders(existingHeaders, defaultHeaders) {
|
|||
return result
|
||||
}
|
||||
|
||||
// Presents a list of Buffers as a Readable
|
||||
class ReadableBuffers extends stream.Readable {
|
||||
constructor(buffers, opts = {}) {
|
||||
super(opts)
|
||||
|
||||
this.buffers = buffers
|
||||
}
|
||||
|
||||
_read(size) {
|
||||
while (this.buffers.length) {
|
||||
if (!this.push(this.buffers.shift())) {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.push(null)
|
||||
}
|
||||
}
|
||||
|
||||
function convertBodyToStream(body) {
|
||||
if (common.isStream(body)) {
|
||||
return body
|
||||
}
|
||||
|
||||
if (body === undefined) {
|
||||
return new ReadableBuffers([])
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(body)) {
|
||||
return new ReadableBuffers([body])
|
||||
}
|
||||
|
||||
if (typeof body !== 'string') {
|
||||
body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
return new ReadableBuffers([Buffer.from(body)])
|
||||
}
|
||||
|
||||
/**
|
||||
* Play back an interceptor using the given request and mock response.
|
||||
*/
|
||||
|
@ -83,28 +121,23 @@ function playbackInterceptor({
|
|||
response,
|
||||
interceptor,
|
||||
}) {
|
||||
function emitError(error) {
|
||||
process.nextTick(() => {
|
||||
req.emit('error', error)
|
||||
})
|
||||
}
|
||||
const { logger } = interceptor.scope
|
||||
|
||||
function start() {
|
||||
interceptor.req = req
|
||||
req.headers = req.getHeaders()
|
||||
|
||||
interceptor.scope.emit('request', req, interceptor, requestBodyString)
|
||||
|
||||
if (typeof interceptor.errorMessage !== 'undefined') {
|
||||
interceptor.markConsumed()
|
||||
|
||||
let error
|
||||
if (typeof interceptor.errorMessage === 'object') {
|
||||
error = interceptor.errorMessage
|
||||
} else {
|
||||
error = new Error(interceptor.errorMessage)
|
||||
}
|
||||
common.setTimeout(() => emitError(error), interceptor.getTotalDelay())
|
||||
|
||||
const delay = interceptor.delayBodyInMs + interceptor.delayConnectionInMs
|
||||
common.setTimeout(() => req.destroy(error), delay)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -114,7 +147,14 @@ function playbackInterceptor({
|
|||
|
||||
// Clone headers/rawHeaders to not override them when evaluating later
|
||||
response.rawHeaders = [...interceptor.rawHeaders]
|
||||
debug('response.rawHeaders:', response.rawHeaders)
|
||||
logger('response.rawHeaders:', response.rawHeaders)
|
||||
|
||||
// TODO: MAJOR: Don't tack the request onto the interceptor.
|
||||
// The only reason we do this is so that it's available inside reply functions.
|
||||
// It would be better to pass the request as an argument to the functions instead.
|
||||
// Not adding the req as a third arg now because it should first be decided if (path, body, req)
|
||||
// is the signature we want to go with going forward.
|
||||
interceptor.req = req
|
||||
|
||||
if (interceptor.replyFunction) {
|
||||
const parsedRequestBody = parseJSONRequestBody(req, requestBodyString)
|
||||
|
@ -128,8 +168,8 @@ function playbackInterceptor({
|
|||
// At this point `fn` is either a synchronous function or a promise-returning function;
|
||||
// wrapping in `Promise.resolve` makes it into a promise either way.
|
||||
Promise.resolve(fn.call(interceptor, options.path, parsedRequestBody))
|
||||
.then(responseBody => continueWithResponseBody({ responseBody }))
|
||||
.catch(err => emitError(err))
|
||||
.then(continueWithResponseBody)
|
||||
.catch(err => req.destroy(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -142,8 +182,8 @@ function playbackInterceptor({
|
|||
}
|
||||
|
||||
Promise.resolve(fn.call(interceptor, options.path, parsedRequestBody))
|
||||
.then(fullReplyResult => continueWithFullResponse({ fullReplyResult }))
|
||||
.catch(err => emitError(err))
|
||||
.then(continueWithFullResponse)
|
||||
.catch(err => req.destroy(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -155,21 +195,12 @@ function playbackInterceptor({
|
|||
// of response buffers which should be mocked one by one.
|
||||
// (otherwise decompressions after the first one fails as unzip expects to receive
|
||||
// buffer by buffer and not one single merged buffer)
|
||||
|
||||
if (interceptor.delayInMs) {
|
||||
emitError(
|
||||
new Error(
|
||||
'Response delay of the body is currently not supported with content-encoded responses.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const bufferData = Array.isArray(interceptor.body)
|
||||
? interceptor.body
|
||||
: [interceptor.body]
|
||||
const responseBuffers = bufferData.map(data => Buffer.from(data, 'hex'))
|
||||
continueWithResponseBody({ responseBuffers })
|
||||
const responseBody = new ReadableBuffers(responseBuffers)
|
||||
continueWithResponseBody(responseBody)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -194,155 +225,103 @@ function playbackInterceptor({
|
|||
}
|
||||
}
|
||||
|
||||
return continueWithResponseBody({ responseBody })
|
||||
return continueWithResponseBody(responseBody)
|
||||
}
|
||||
|
||||
function continueWithFullResponse({ fullReplyResult }) {
|
||||
function continueWithFullResponse(fullReplyResult) {
|
||||
let responseBody
|
||||
try {
|
||||
responseBody = parseFullReplyResult(response, fullReplyResult)
|
||||
} catch (innerErr) {
|
||||
emitError(innerErr)
|
||||
} catch (err) {
|
||||
req.destroy(err)
|
||||
return
|
||||
}
|
||||
|
||||
continueWithResponseBody({ responseBody })
|
||||
continueWithResponseBody(responseBody)
|
||||
}
|
||||
|
||||
function continueWithResponseBody({ responseBuffers, responseBody }) {
|
||||
// Transform the response body if it exists (it may not exist
|
||||
// if we have `responseBuffers` instead)
|
||||
if (responseBody !== undefined) {
|
||||
debug('transform the response body')
|
||||
function prepareResponseHeaders(body) {
|
||||
const defaultHeaders = [...interceptor.scope._defaultReplyHeaders]
|
||||
|
||||
if (interceptor.delayInMs) {
|
||||
debug(
|
||||
'delaying the response for',
|
||||
interceptor.delayInMs,
|
||||
'milliseconds'
|
||||
)
|
||||
// Because setTimeout is called immediately in DelayedBody(), so we
|
||||
// need count in the delayConnectionInMs.
|
||||
responseBody = new DelayedBody(
|
||||
interceptor.getTotalDelay(),
|
||||
responseBody
|
||||
)
|
||||
}
|
||||
// Include a JSON content type when JSON.stringify is called on the body.
|
||||
// This is a convenience added by Nock that has no analog in Node. It's added to the
|
||||
// defaults, so it will be ignored if the caller explicitly provided the header already.
|
||||
const isJSON =
|
||||
body !== undefined &&
|
||||
typeof body !== 'string' &&
|
||||
!Buffer.isBuffer(body) &&
|
||||
!common.isStream(body)
|
||||
|
||||
if (common.isStream(responseBody)) {
|
||||
debug('response body is a stream')
|
||||
responseBody.pause()
|
||||
responseBody.on('data', function(d) {
|
||||
response.push(d)
|
||||
})
|
||||
responseBody.on('end', function() {
|
||||
response.push(null)
|
||||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete
|
||||
response.complete = true
|
||||
})
|
||||
responseBody.on('error', function(err) {
|
||||
response.emit('error', err)
|
||||
})
|
||||
} else if (!Buffer.isBuffer(responseBody)) {
|
||||
if (typeof responseBody === 'string') {
|
||||
responseBody = Buffer.from(responseBody)
|
||||
} else {
|
||||
responseBody = JSON.stringify(responseBody)
|
||||
response.rawHeaders.push('Content-Type', 'application/json')
|
||||
}
|
||||
}
|
||||
// Why are strings converted to a Buffer, but JSON data is left as a string?
|
||||
// Related to https://github.com/nock/nock/issues/1542 ?
|
||||
}
|
||||
|
||||
interceptor.markConsumed()
|
||||
|
||||
if (req.aborted) {
|
||||
return
|
||||
if (isJSON) {
|
||||
defaultHeaders.push('Content-Type', 'application/json')
|
||||
}
|
||||
|
||||
response.rawHeaders.push(
|
||||
...selectDefaultHeaders(
|
||||
response.rawHeaders,
|
||||
interceptor.scope._defaultReplyHeaders
|
||||
)
|
||||
...selectDefaultHeaders(response.rawHeaders, defaultHeaders)
|
||||
)
|
||||
|
||||
// Evaluate functional headers.
|
||||
common.forEachHeader(response.rawHeaders, (value, fieldName, i) => {
|
||||
if (typeof value === 'function') {
|
||||
response.rawHeaders[i + 1] = value(req, response, responseBody)
|
||||
response.rawHeaders[i + 1] = value(req, response, body)
|
||||
}
|
||||
})
|
||||
|
||||
response.headers = common.headersArrayToObject(response.rawHeaders)
|
||||
|
||||
process.nextTick(() =>
|
||||
respondUsingInterceptor({
|
||||
responseBody,
|
||||
responseBuffers,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function respondUsingInterceptor({ responseBody, responseBuffers }) {
|
||||
if (req.aborted) {
|
||||
return
|
||||
}
|
||||
function continueWithResponseBody(rawBody) {
|
||||
prepareResponseHeaders(rawBody)
|
||||
const bodyAsStream = convertBodyToStream(rawBody)
|
||||
bodyAsStream.pause()
|
||||
|
||||
// IncomingMessage extends Readable so we can't simply pipe.
|
||||
bodyAsStream.on('data', function (chunk) {
|
||||
response.push(chunk)
|
||||
})
|
||||
bodyAsStream.on('end', function () {
|
||||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete
|
||||
response.complete = true
|
||||
response.push(null)
|
||||
|
||||
interceptor.scope.emit('replied', req, interceptor)
|
||||
})
|
||||
bodyAsStream.on('error', function (err) {
|
||||
response.emit('error', err)
|
||||
})
|
||||
|
||||
const { delayBodyInMs, delayConnectionInMs } = interceptor
|
||||
|
||||
function respond() {
|
||||
if (req.aborted) {
|
||||
if (common.isRequestDestroyed(req)) {
|
||||
return
|
||||
}
|
||||
|
||||
debug('emitting response')
|
||||
// Even though we've had the response object for awhile at this point,
|
||||
// we only attach it to the request immediately before the `response`
|
||||
// event because, as in Node, it alters the error handling around aborts.
|
||||
req.res = response
|
||||
response.req = req
|
||||
|
||||
logger('emitting response')
|
||||
req.emit('response', response)
|
||||
|
||||
if (common.isStream(responseBody)) {
|
||||
debug('resuming response stream')
|
||||
responseBody.resume()
|
||||
} else {
|
||||
responseBuffers = responseBuffers || []
|
||||
if (typeof responseBody !== 'undefined') {
|
||||
debug('adding body to buffer list')
|
||||
responseBuffers.push(responseBody)
|
||||
}
|
||||
|
||||
// Stream the response chunks one at a time.
|
||||
common.setImmediate(function emitChunk() {
|
||||
const chunk = responseBuffers.shift()
|
||||
|
||||
if (chunk) {
|
||||
debug('emitting response chunk')
|
||||
response.push(chunk)
|
||||
common.setImmediate(emitChunk)
|
||||
} else {
|
||||
debug('ending response stream')
|
||||
response.push(null)
|
||||
// https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_message_complete
|
||||
response.complete = true
|
||||
interceptor.scope.emit('replied', req, interceptor)
|
||||
}
|
||||
})
|
||||
}
|
||||
common.setTimeout(() => bodyAsStream.resume(), delayBodyInMs)
|
||||
}
|
||||
|
||||
if (interceptor.socketDelayInMs && interceptor.socketDelayInMs > 0) {
|
||||
socket.applyDelay(interceptor.socketDelayInMs)
|
||||
}
|
||||
|
||||
if (
|
||||
interceptor.delayConnectionInMs &&
|
||||
interceptor.delayConnectionInMs > 0
|
||||
) {
|
||||
socket.applyDelay(interceptor.delayConnectionInMs)
|
||||
common.setTimeout(respond, interceptor.delayConnectionInMs)
|
||||
} else {
|
||||
respond()
|
||||
}
|
||||
socket.applyDelay(delayConnectionInMs)
|
||||
common.setTimeout(respond, delayConnectionInMs)
|
||||
}
|
||||
|
||||
start()
|
||||
// Calling `start` immediately could take the request all the way to the connection delay
|
||||
// during a single microtask execution. This setImmediate stalls the playback to ensure the
|
||||
// correct events are emitted first ('socket', 'finish') and any aborts in the in the queue or
|
||||
// called during a 'finish' listener can be called.
|
||||
common.setImmediate(() => {
|
||||
if (!common.isRequestDestroyed(req)) {
|
||||
start()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { playbackInterceptor }
|
||||
|
|
|
@ -110,6 +110,10 @@ function generateRequestAndResponse({
|
|||
const queryStr = req.path.slice(queryIndex + 1)
|
||||
queryObj = querystring.parse(queryStr)
|
||||
}
|
||||
|
||||
// Escape any single quotes in the path as the output uses them
|
||||
path = path.replace(/'/g, `\\'`)
|
||||
|
||||
// Always encode the query parameters when recording.
|
||||
const encodedQueryObj = {}
|
||||
for (const key in queryObj) {
|
||||
|
@ -211,7 +215,7 @@ function record(recOptions) {
|
|||
restoreOverriddenClientRequest()
|
||||
|
||||
// We override the requests so that we can save information on them before executing.
|
||||
common.overrideRequests(function(proto, overriddenRequest, rawArgs) {
|
||||
common.overrideRequests(function (proto, overriddenRequest, rawArgs) {
|
||||
const { options, callback } = common.normalizeClientRequestArgs(...rawArgs)
|
||||
const bodyChunks = []
|
||||
|
||||
|
@ -223,11 +227,11 @@ function record(recOptions) {
|
|||
}
|
||||
options._recording = true
|
||||
|
||||
const req = overriddenRequest(options, function(res) {
|
||||
const req = overriddenRequest(options, function (res) {
|
||||
debug(thisRecordingId, 'intercepting', proto, 'request to record')
|
||||
|
||||
// We put our 'end' listener to the front of the listener array.
|
||||
res.once('end', function() {
|
||||
res.once('end', function () {
|
||||
debug(thisRecordingId, proto, 'intercepted request ended')
|
||||
|
||||
let reqheaders
|
||||
|
@ -284,7 +288,7 @@ function record(recOptions) {
|
|||
// We need to be aware of changes to the stream's encoding so that we
|
||||
// don't accidentally mangle the data.
|
||||
const { setEncoding } = res
|
||||
res.setEncoding = function(newEncoding) {
|
||||
res.setEncoding = function (newEncoding) {
|
||||
encoding = newEncoding
|
||||
return setEncoding.apply(this, arguments)
|
||||
}
|
||||
|
@ -292,7 +296,7 @@ function record(recOptions) {
|
|||
const dataChunks = []
|
||||
// Replace res.push with our own implementation that stores chunks
|
||||
const origResPush = res.push
|
||||
res.push = function(data) {
|
||||
res.push = function (data) {
|
||||
if (data) {
|
||||
if (encoding) {
|
||||
data = Buffer.from(data, encoding)
|
||||
|
@ -305,8 +309,6 @@ function record(recOptions) {
|
|||
|
||||
if (callback) {
|
||||
callback(res, options, callback)
|
||||
} else {
|
||||
res.resume()
|
||||
}
|
||||
|
||||
debug('finished setting up intercepting')
|
||||
|
@ -329,7 +331,7 @@ function record(recOptions) {
|
|||
}
|
||||
|
||||
const oldWrite = req.write
|
||||
req.write = function(chunk, encoding) {
|
||||
req.write = function (chunk, encoding) {
|
||||
if (typeof chunk !== 'undefined') {
|
||||
recordChunk(chunk, encoding)
|
||||
oldWrite.apply(req, arguments)
|
||||
|
@ -342,7 +344,7 @@ function record(recOptions) {
|
|||
// `write_` function instead of proxying to the public
|
||||
// `OutgoingMessage.write()` method, so we have to wrap `end` too.
|
||||
const oldEnd = req.end
|
||||
req.end = function(chunk, encoding, callback) {
|
||||
req.end = function (chunk, encoding, callback) {
|
||||
debug('req.end')
|
||||
if (typeof chunk === 'function') {
|
||||
callback = chunk
|
||||
|
|
|
@ -9,7 +9,6 @@ const assert = require('assert')
|
|||
const url = require('url')
|
||||
const debug = require('debug')('nock.scope')
|
||||
const { EventEmitter } = require('events')
|
||||
const util = require('util')
|
||||
const Interceptor = require('./interceptor')
|
||||
|
||||
let fs
|
||||
|
@ -40,7 +39,6 @@ class Scope extends EventEmitter {
|
|||
this.transformPathFunction = null
|
||||
this.transformRequestBodyFunction = null
|
||||
this.matchHeaders = []
|
||||
this.logger = debug
|
||||
this.scopeOptions = options || {}
|
||||
this.urlParts = {}
|
||||
this._persist = false
|
||||
|
@ -51,13 +49,18 @@ class Scope extends EventEmitter {
|
|||
this.port = null
|
||||
this._defaultReplyHeaders = []
|
||||
|
||||
let logNamespace = String(basePath)
|
||||
|
||||
if (!(basePath instanceof RegExp)) {
|
||||
this.urlParts = url.parse(basePath)
|
||||
this.port =
|
||||
this.urlParts.port || (this.urlParts.protocol === 'http:' ? 80 : 443)
|
||||
this.basePathname = this.urlParts.pathname.replace(/\/$/, '')
|
||||
this.basePath = `${this.urlParts.protocol}//${this.urlParts.hostname}:${this.port}`
|
||||
logNamespace = this.urlParts.host
|
||||
}
|
||||
|
||||
this.logger = debug.extend(logNamespace)
|
||||
}
|
||||
|
||||
add(key, interceptor) {
|
||||
|
@ -170,7 +173,7 @@ class Scope extends EventEmitter {
|
|||
const filteringArguments = arguments
|
||||
|
||||
if (arguments[0] instanceof RegExp) {
|
||||
return function(candidate) {
|
||||
return function (candidate) {
|
||||
/* istanbul ignore if */
|
||||
if (typeof candidate !== 'string') {
|
||||
// Given the way nock is written, it seems like `candidate` will always
|
||||
|
@ -219,11 +222,6 @@ class Scope extends EventEmitter {
|
|||
return this
|
||||
}
|
||||
|
||||
log(newLogger) {
|
||||
this.logger = newLogger
|
||||
return this
|
||||
}
|
||||
|
||||
persist(flag = true) {
|
||||
if (typeof flag !== 'boolean') {
|
||||
throw new Error('Invalid arguments: argument should be a boolean')
|
||||
|
@ -306,17 +304,10 @@ function tryJsonParse(string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Use a noop deprecate util instead calling emitWarning directly so we get --no-deprecation and single warning behavior for free.
|
||||
const emitAsteriskDeprecation = util.deprecate(
|
||||
() => {},
|
||||
'Skipping body matching using "*" is deprecated. Set the definition body to undefined instead.',
|
||||
'NOCK1579'
|
||||
)
|
||||
|
||||
function define(nockDefs) {
|
||||
const scopes = []
|
||||
|
||||
nockDefs.forEach(function(nockDef) {
|
||||
nockDefs.forEach(function (nockDef) {
|
||||
const nscope = getScopeFromDefinition(nockDef)
|
||||
const npath = nockDef.path
|
||||
if (!nockDef.method) {
|
||||
|
@ -335,23 +326,12 @@ function define(nockDefs) {
|
|||
options.reqheaders = reqheaders
|
||||
options.badheaders = badheaders
|
||||
|
||||
let { body } = nockDef
|
||||
|
||||
if (body === '*') {
|
||||
// In previous versions, it was impossible to NOT filter on request bodies. This special value
|
||||
// is sniffed out for users manipulating the definitions and not wanting to match on the
|
||||
// request body. For newer versions, users should remove the `body` key or set to `undefined`
|
||||
// to achieve the same affect. Maintaining legacy behavior for now.
|
||||
emitAsteriskDeprecation()
|
||||
body = undefined
|
||||
}
|
||||
|
||||
// Response is not always JSON as it could be a string or binary data or
|
||||
// even an array of binary buffers (e.g. when content is encoded).
|
||||
let response
|
||||
if (!nockDef.response) {
|
||||
response = ''
|
||||
// TODO: Rename `responseIsBinary` to `reponseIsUtf8Representable`.
|
||||
// TODO: Rename `responseIsBinary` to `responseIsUtf8Representable`.
|
||||
} else if (nockDef.responseIsBinary) {
|
||||
response = Buffer.from(nockDef.response, 'hex')
|
||||
} else {
|
||||
|
@ -375,7 +355,9 @@ function define(nockDefs) {
|
|||
}
|
||||
})
|
||||
|
||||
scope.intercept(npath, method, body).reply(status, response, rawHeaders)
|
||||
scope
|
||||
.intercept(npath, method, nockDef.body)
|
||||
.reply(status, response, rawHeaders)
|
||||
|
||||
scopes.push(scope)
|
||||
})
|
||||
|
|
|
@ -7,9 +7,12 @@ module.exports = class Socket extends EventEmitter {
|
|||
constructor(options) {
|
||||
super()
|
||||
|
||||
// Pretend this is a TLSSocket
|
||||
if (options.proto === 'https') {
|
||||
// https://github.com/nock/nock/issues/158
|
||||
this.authorized = true
|
||||
// https://github.com/nock/nock/issues/2147
|
||||
this.encrypted = true
|
||||
}
|
||||
|
||||
this.bufferSize = 0
|
||||
|
@ -18,14 +21,13 @@ module.exports = class Socket extends EventEmitter {
|
|||
this.readable = true
|
||||
this.pending = false
|
||||
this.destroyed = false
|
||||
this.connecting = false
|
||||
this.connecting = true
|
||||
|
||||
// totalDelay that has already been applied to the current
|
||||
// request/connection, timeout error will be generated if
|
||||
// it is timed-out.
|
||||
this.totalDelayMs = 0
|
||||
// Maximum allowed delay. Null means unlimited.
|
||||
this.timeoutMs = null
|
||||
// Undocumented flag used by ClientRequest to ensure errors aren't double-fired
|
||||
this._hadError = false
|
||||
|
||||
// Maximum allowed delay. 0 means unlimited.
|
||||
this.timeout = 0
|
||||
|
||||
const ipv6 = options.family === 6
|
||||
this.remoteFamily = ipv6 ? 'IPv6' : 'IPv4'
|
||||
|
@ -48,16 +50,22 @@ module.exports = class Socket extends EventEmitter {
|
|||
}
|
||||
|
||||
setTimeout(timeoutMs, fn) {
|
||||
this.timeoutMs = timeoutMs
|
||||
this.timeout = timeoutMs
|
||||
if (fn) {
|
||||
this.once('timeout', fn)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Artificial delay that will trip socket timeouts when appropriate.
|
||||
*
|
||||
* Doesn't actually wait for time to pass.
|
||||
* Timeout events don't necessarily end the request.
|
||||
* While many clients choose to abort the request upon a timeout, Node itself does not.
|
||||
*/
|
||||
applyDelay(delayMs) {
|
||||
this.totalDelayMs += delayMs
|
||||
|
||||
if (this.timeoutMs && this.totalDelayMs > this.timeoutMs) {
|
||||
if (this.timeout && delayMs > this.timeout) {
|
||||
debug('socket timeout')
|
||||
this.emit('timeout')
|
||||
}
|
||||
|
@ -69,12 +77,31 @@ module.exports = class Socket extends EventEmitter {
|
|||
).toString('base64')
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes that no more I/O activity should happen on this socket.
|
||||
*
|
||||
* The implementation in Node if far more complex as it juggles underlying async streams.
|
||||
* For the purposes of Nock, we just need it to set some flags and on the first call
|
||||
* emit a 'close' and optional 'error' event. Both events propagate through the request object.
|
||||
*/
|
||||
destroy(err) {
|
||||
if (this.destroyed) {
|
||||
return this
|
||||
}
|
||||
|
||||
debug('socket destroy')
|
||||
this.destroyed = true
|
||||
this.readable = this.writable = false
|
||||
if (err) {
|
||||
this.emit('error', err)
|
||||
}
|
||||
this.readableEnded = this.writableFinished = true
|
||||
|
||||
process.nextTick(() => {
|
||||
if (err) {
|
||||
this._hadError = true
|
||||
this.emit('error', err)
|
||||
}
|
||||
this.emit('close')
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"testing",
|
||||
"isolation"
|
||||
],
|
||||
"version": "12.0.3",
|
||||
"version": "13.1.1",
|
||||
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -24,36 +24,35 @@
|
|||
"dependencies": {
|
||||
"debug": "^4.1.0",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"lodash": "^4.17.13",
|
||||
"lodash.set": "^4.3.2",
|
||||
"propagate": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sinonjs/fake-timers": "^7.0.2",
|
||||
"assert-rejects": "^1.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"dtslint": "^3.0.0",
|
||||
"eslint": "^6.0.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-config-standard": "^14.0.0",
|
||||
"dtslint": "^4.0.4",
|
||||
"eslint": "^7.3.1",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-mocha": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^8.0.0",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"got": "^10.5.2",
|
||||
"@sinonjs/fake-timers": "^6.0.0",
|
||||
"mocha": "^7.0.1",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.3.0",
|
||||
"mocha": "^8.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "1.19.0",
|
||||
"prettier": "2.2.1",
|
||||
"proxyquire": "^2.1.0",
|
||||
"request": "^2.83.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"semantic-release": "^17.0.2",
|
||||
"sinon": "^9.0.0",
|
||||
"sinon": "^10.0.0",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"superagent": "^5.0.2",
|
||||
"tap": "14.6.1"
|
||||
"typescript": "^4.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"format:fix": "prettier --write '**/*.{js,json,md,ts,yml,yaml}'",
|
||||
|
@ -62,25 +61,29 @@
|
|||
"lint:js": "eslint --cache --cache-location './.cache/eslint' '**/*.js'",
|
||||
"lint:js:fix": "eslint --cache --cache-location './.cache/eslint' --fix '**/*.js'",
|
||||
"lint:ts": "dtslint types",
|
||||
"semantic-release": "semantic-release",
|
||||
"test": "run-s test:mocha test:tap",
|
||||
"test:coverage": "tap --coverage-report=html && open coverage/lcov-report/index.html",
|
||||
"test:mocha": "nyc mocha $(grep -lr '^\\s*it(' tests)",
|
||||
"test:tap": "tap --100 --coverage --coverage-report=text ./tests/test_*.js"
|
||||
},
|
||||
"nyc": {
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
],
|
||||
"exclude": [
|
||||
"tests/"
|
||||
]
|
||||
"test": "nyc mocha tests",
|
||||
"test:coverage": "open coverage/lcov-report/index.html"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"types/index.d.ts"
|
||||
]
|
||||
],
|
||||
"release": {
|
||||
"branches": [
|
||||
"+([0-9])?(.{+([0-9]),x}).x",
|
||||
"main",
|
||||
"next",
|
||||
"next-major",
|
||||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"prerelease": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ declare namespace nock {
|
|||
| RegExp
|
||||
| DataMatcherArray
|
||||
| DataMatcherMap
|
||||
interface DataMatcherArray extends Array<DataMatcher> {}
|
||||
interface DataMatcherArray extends ReadonlyArray<DataMatcher> {}
|
||||
interface DataMatcherMap {
|
||||
[key: string]: DataMatcher
|
||||
}
|
||||
|
@ -120,7 +120,6 @@ declare namespace nock {
|
|||
filteringRequestBody(regex: RegExp, replace: string): this
|
||||
filteringRequestBody(fn: (body: string) => string): this
|
||||
|
||||
log(out: (message: any, ...optionalParams: any[]) => void): this
|
||||
persist(flag?: boolean): this
|
||||
replyContentLength(): this
|
||||
replyDate(d?: Date): this
|
||||
|
@ -204,7 +203,6 @@ declare namespace nock {
|
|||
delay(opts: number | { head?: number; body?: number }): this
|
||||
delayBody(timeMs: number): this
|
||||
delayConnection(timeMs: number): this
|
||||
socketDelay(timeMs: number): this
|
||||
}
|
||||
|
||||
interface Options {
|
||||
|
|
Загрузка…
Ссылка в новой задаче