update @actions/tool-cache, install semver, nock

This commit is contained in:
Alex Kalyvitis 2020-06-18 16:31:13 +02:00
parent 74d434c5ca
commit 4c6749115a
678 changed files with 39259 additions and 14619 deletions

279
node_modules/nock/lib/back.js generated vendored Normal file
View file

@ -0,0 +1,279 @@
'use strict'
const assert = require('assert')
const recorder = require('./recorder')
const {
activate,
disableNetConnect,
enableNetConnect,
removeAll: cleanAll,
} = require('./intercept')
const { loadDefs, define } = require('./scope')
const { format } = require('util')
const path = require('path')
const debug = require('debug')('nock.back')
let _mode = null
let fs
try {
fs = require('fs')
} catch (err) {
// do nothing, probably in browser
}
/**
* nock the current function with the fixture given
*
* @param {string} fixtureName - the name of the fixture, e.x. 'foo.json'
* @param {object} options - [optional] extra options for nock with, e.x. `{ assert: true }`
* @param {function} nockedFn - [optional] callback function to be executed with the given fixture being loaded;
* if defined the function will be called with context `{ scopes: loaded_nocks || [] }`
* set as `this` and `nockDone` callback function as first and only parameter;
* if not defined a promise resolving to `{nockDone, context}` where `context` is
* aforementioned `{ scopes: loaded_nocks || [] }`
*
* List of options:
*
* @param {function} before - a preprocessing function, gets called before nock.define
* @param {function} after - a postprocessing function, gets called after nock.define
* @param {function} afterRecord - a postprocessing function, gets called after recording. Is passed the array
* of scopes recorded and should return the array scopes to save to the fixture
* @param {function} recorder - custom options to pass to the recorder
*
*/
function Back(fixtureName, options, nockedFn) {
if (!Back.fixtures) {
throw new Error(
'Back requires nock.back.fixtures to be set\n' +
'Ex:\n' +
"\trequire(nock).back.fixtures = '/path/to/fixtures/'"
)
}
if (typeof fixtureName !== 'string') {
throw new Error('Parameter fixtureName must be a string')
}
if (arguments.length === 1) {
options = {}
} else if (arguments.length === 2) {
// If 2nd parameter is a function then `options` has been omitted
// otherwise `options` haven't been omitted but `nockedFn` was.
if (typeof options === 'function') {
nockedFn = options
options = {}
}
}
_mode.setup()
const fixture = path.join(Back.fixtures, fixtureName)
const context = _mode.start(fixture, options)
const nockDone = function() {
_mode.finish(fixture, options, context)
}
debug('context:', context)
// If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
if (typeof nockedFn === 'function') {
nockedFn.call(context, nockDone)
} else {
return Promise.resolve({ nockDone, context })
}
}
/*******************************************************************************
* Modes *
*******************************************************************************/
const wild = {
setup: function() {
cleanAll()
recorder.restore()
activate()
enableNetConnect()
},
start: function() {
return load() // don't load anything but get correct context
},
finish: function() {
// nothing to do
},
}
const dryrun = {
setup: function() {
recorder.restore()
cleanAll()
activate()
// We have to explicitly enable net connectivity as by default it's off.
enableNetConnect()
},
start: function(fixture, options) {
const contexts = load(fixture, options)
enableNetConnect()
return contexts
},
finish: function() {
// nothing to do
},
}
const record = {
setup: function() {
recorder.restore()
recorder.clear()
cleanAll()
activate()
disableNetConnect()
},
start: function(fixture, options) {
if (!fs) {
throw new Error('no fs')
}
const context = load(fixture, options)
if (!context.isLoaded) {
recorder.record({
dont_print: true,
output_objects: true,
...options.recorder,
})
context.isRecording = true
}
return context
},
finish: function(fixture, options, context) {
if (context.isRecording) {
let outputs = recorder.outputs()
if (typeof options.afterRecord === 'function') {
outputs = options.afterRecord(outputs)
}
outputs =
typeof outputs === 'string' ? outputs : JSON.stringify(outputs, null, 4)
debug('recorder outputs:', outputs)
fs.mkdirSync(path.dirname(fixture), { recursive: true })
fs.writeFileSync(fixture, outputs)
}
},
}
const lockdown = {
setup: function() {
recorder.restore()
recorder.clear()
cleanAll()
activate()
disableNetConnect()
},
start: function(fixture, options) {
return load(fixture, options)
},
finish: function() {
// nothing to do
},
}
function load(fixture, options) {
const context = {
scopes: [],
assertScopesFinished: function() {
assertScopes(this.scopes, fixture)
},
}
if (fixture && fixtureExists(fixture)) {
let scopes = loadDefs(fixture)
applyHook(scopes, options.before)
scopes = define(scopes)
applyHook(scopes, options.after)
context.scopes = scopes
context.isLoaded = true
}
return context
}
function applyHook(scopes, fn) {
if (!fn) {
return
}
if (typeof fn !== 'function') {
throw new Error('processing hooks must be a function')
}
scopes.forEach(fn)
}
function fixtureExists(fixture) {
if (!fs) {
throw new Error('no fs')
}
return fs.existsSync(fixture)
}
function assertScopes(scopes, fixture) {
const pending = scopes
.filter(scope => !scope.isDone())
.map(scope => scope.pendingMocks())
if (pending.length) {
assert.fail(
format(
'%j was not used, consider removing %s to rerecord fixture',
[].concat(...pending),
fixture
)
)
}
}
const Modes = {
wild, // all requests go out to the internet, dont replay anything, doesnt record anything
dryrun, // use recorded nocks, allow http calls, doesnt record anything, useful for writing new tests (default)
record, // use recorded nocks, record new nocks
lockdown, // use recorded nocks, disables all http calls even when not nocked, doesnt record
}
Back.setMode = function(mode) {
if (!(mode in Modes)) {
throw new Error(`Unknown mode: ${mode}`)
}
Back.currentMode = mode
debug('New nock back mode:', Back.currentMode)
_mode = Modes[mode]
_mode.setup()
}
Back.fixtures = null
Back.currentMode = null
module.exports = Back

645
node_modules/nock/lib/common.js generated vendored Normal file
View file

@ -0,0 +1,645 @@
'use strict'
const _ = require('lodash')
const debug = require('debug')('nock.common')
const url = require('url')
const timers = require('timers')
/**
* Normalizes the request options so that it always has `host` property.
*
* @param {Object} options - a parsed options object of the request
*/
function normalizeRequestOptions(options) {
options.proto = options.proto || 'http'
options.port = options.port || (options.proto === 'http' ? 80 : 443)
if (options.host) {
debug('options.host:', options.host)
if (!options.hostname) {
if (options.host.split(':').length === 2) {
options.hostname = options.host.split(':')[0]
} else {
options.hostname = options.host
}
}
}
debug('options.hostname in the end: %j', options.hostname)
options.host = `${options.hostname || 'localhost'}:${options.port}`
debug('options.host in the end: %j', options.host)
/// lowercase host names
;['hostname', 'host'].forEach(function(attr) {
if (options[attr]) {
options[attr] = options[attr].toLowerCase()
}
})
return options
}
/**
* Returns true if the data contained in buffer can be reconstructed
* from its utf8 representation.
*
* @param {Object} buffer - a Buffer object
* @returns {boolean}
*/
function isUtf8Representable(buffer) {
const utfEncodedBuffer = buffer.toString('utf8')
const reconstructedBuffer = Buffer.from(utfEncodedBuffer, 'utf8')
return reconstructedBuffer.equals(buffer)
}
// Array where all information about all the overridden requests are held.
let requestOverrides = {}
/**
* Overrides the current `request` function of `http` and `https` modules with
* our own version which intercepts issues HTTP/HTTPS requests and forwards them
* to the given `newRequest` function.
*
* @param {Function} newRequest - a function handling requests; it accepts four arguments:
* - proto - a string with the overridden module's protocol name (either `http` or `https`)
* - overriddenRequest - the overridden module's request function already bound to module's object
* - options - the options of the issued request
* - callback - the callback of the issued request
*/
function overrideRequests(newRequest) {
debug('overriding requests')
;['http', 'https'].forEach(function(proto) {
debug('- overriding request for', proto)
const moduleName = proto // 1 to 1 match of protocol and module is fortunate :)
const module = {
http: require('http'),
https: require('https'),
}[moduleName]
const overriddenRequest = module.request
const overriddenGet = module.get
if (requestOverrides[moduleName]) {
throw new Error(
`Module's request already overridden for ${moduleName} protocol.`
)
}
// Store the properties of the overridden request so that it can be restored later on.
requestOverrides[moduleName] = {
module,
request: overriddenRequest,
get: overriddenGet,
}
// https://nodejs.org/api/http.html#http_http_request_url_options_callback
module.request = function(input, options, callback) {
return newRequest(proto, overriddenRequest.bind(module), [
input,
options,
callback,
])
}
// https://nodejs.org/api/http.html#http_http_get_options_callback
module.get = function(input, options, callback) {
const req = newRequest(proto, overriddenGet.bind(module), [
input,
options,
callback,
])
req.end()
return req
}
debug('- overridden request for', proto)
})
}
/**
* Restores `request` function of `http` and `https` modules to values they
* held before they were overridden by us.
*/
function restoreOverriddenRequests() {
debug('restoring requests')
Object.entries(requestOverrides).forEach(
([proto, { module, request, get }]) => {
debug('- restoring request for', proto)
module.request = request
module.get = get
debug('- restored request for', proto)
}
)
requestOverrides = {}
}
/**
* In WHATWG URL vernacular, this returns the origin portion of a URL.
* However, the port is not included if it's standard and not already present on the host.
*/
function normalizeOrigin(proto, host, port) {
const hostHasPort = host.includes(':')
const portIsStandard =
(proto === 'http' && (port === 80 || port === '80')) ||
(proto === 'https' && (port === 443 || port === '443'))
const portStr = hostHasPort || portIsStandard ? '' : `:${port}`
return `${proto}://${host}${portStr}`
}
/**
* Get high level information about request as string
* @param {Object} options
* @param {string} options.method
* @param {number|string} options.port
* @param {string} options.proto Set internally. always http or https
* @param {string} options.hostname
* @param {string} options.path
* @param {Object} options.headers
* @param {string} body
* @return {string}
*/
function stringifyRequest(options, body) {
const { method = 'GET', path = '', port } = options
const origin = normalizeOrigin(options.proto, options.hostname, port)
const log = {
method,
url: `${origin}${path}`,
headers: options.headers,
}
if (body) {
log.body = body
}
return JSON.stringify(log, null, 2)
}
function isContentEncoded(headers) {
const contentEncoding = headers['content-encoding']
return typeof contentEncoding === 'string' && contentEncoding !== ''
}
function contentEncoding(headers, encoder) {
const contentEncoding = headers['content-encoding']
return contentEncoding === encoder
}
function isJSONContent(headers) {
// https://tools.ietf.org/html/rfc8259
const contentType = String(headers['content-type'] || '').toLowerCase()
return contentType.startsWith('application/json')
}
/**
* Return a new object with all field names of the headers lower-cased.
*
* Duplicates throw an error.
*/
function headersFieldNamesToLowerCase(headers) {
if (!_.isPlainObject(headers)) {
throw Error('Headers must be provided as an object')
}
const lowerCaseHeaders = {}
Object.entries(headers).forEach(([fieldName, fieldValue]) => {
const key = fieldName.toLowerCase()
if (lowerCaseHeaders[key] !== undefined) {
throw Error(
`Failed to convert header keys to lower case due to field name conflict: ${key}`
)
}
lowerCaseHeaders[key] = fieldValue
})
return lowerCaseHeaders
}
const headersFieldsArrayToLowerCase = headers => [
...new Set(headers.map(fieldName => fieldName.toLowerCase())),
]
/**
* Converts the various accepted formats of headers into a flat array representing "raw headers".
*
* Nock allows headers to be provided as a raw array, a plain object, or a Map.
*
* While all the header names are expected to be strings, the values are left intact as they can
* be functions, strings, or arrays of strings.
*
* https://nodejs.org/api/http.html#http_message_rawheaders
*/
function headersInputToRawArray(headers) {
if (headers === undefined) {
return []
}
if (Array.isArray(headers)) {
// If the input is an array, assume it's already in the raw format and simply return a copy
// but throw an error if there aren't an even number of items in the array
if (headers.length % 2) {
throw new Error(
`Raw headers must be provided as an array with an even number of items. [fieldName, value, ...]`
)
}
return [...headers]
}
// [].concat(...) is used instead of Array.flat until v11 is the minimum Node version
if (_.isMap(headers)) {
return [].concat(...Array.from(headers, ([k, v]) => [k.toString(), v]))
}
if (_.isPlainObject(headers)) {
return [].concat(...Object.entries(headers))
}
throw new Error(
`Headers must be provided as an array of raw values, a Map, or a plain Object. ${headers}`
)
}
/**
* Converts an array of raw headers to an object, using the same rules as Nodes `http.IncomingMessage.headers`.
*
* Header names/keys are lower-cased.
*/
function headersArrayToObject(rawHeaders) {
if (!Array.isArray(rawHeaders)) {
throw Error('Expected a header array')
}
const accumulator = {}
forEachHeader(rawHeaders, (value, fieldName) => {
addHeaderLine(accumulator, fieldName, value)
})
return accumulator
}
const noDuplicatesHeaders = new Set([
'age',
'authorization',
'content-length',
'content-type',
'etag',
'expires',
'from',
'host',
'if-modified-since',
'if-unmodified-since',
'last-modified',
'location',
'max-forwards',
'proxy-authorization',
'referer',
'retry-after',
'user-agent',
])
/**
* Set key/value data in accordance with Node's logic for folding duplicate headers.
*
* The `value` param should be a function, string, or array of strings.
*
* Node's docs and source:
* https://nodejs.org/api/http.html#http_message_headers
* https://github.com/nodejs/node/blob/908292cf1f551c614a733d858528ffb13fb3a524/lib/_http_incoming.js#L245
*
* Header names are lower-cased.
* Duplicates in raw headers are handled in the following ways, depending on the header name:
* - Duplicates of field names listed in `noDuplicatesHeaders` (above) are discarded.
* - `set-cookie` is always an array. Duplicates are added to the array.
* - For duplicate `cookie` headers, the values are joined together with '; '.
* - For all other headers, the values are joined together with ', '.
*
* Node's implementation is larger because it highly optimizes for not having to call `toLowerCase()`.
* We've opted to always call `toLowerCase` in exchange for a more concise function.
*
* While Node has the luxury of knowing `value` is always a string, we do an extra step of coercion at the top.
*/
function addHeaderLine(headers, name, value) {
let values // code below expects `values` to be an array of strings
if (typeof value === 'function') {
// Function values are evaluated towards the end of the response, before that we use a placeholder
// string just to designate that the header exists. Useful when `Content-Type` is set with a function.
values = [value.name]
} else if (Array.isArray(value)) {
values = value.map(String)
} else {
values = [String(value)]
}
const key = name.toLowerCase()
if (key === 'set-cookie') {
// Array header -- only Set-Cookie at the moment
if (headers['set-cookie'] === undefined) {
headers['set-cookie'] = values
} else {
headers['set-cookie'].push(...values)
}
} else if (noDuplicatesHeaders.has(key)) {
if (headers[key] === undefined) {
// Drop duplicates
headers[key] = values[0]
}
} else {
if (headers[key] !== undefined) {
values = [headers[key], ...values]
}
const separator = key === 'cookie' ? '; ' : ', '
headers[key] = values.join(separator)
}
}
/**
* Deletes the given `fieldName` property from `headers` object by performing
* case-insensitive search through keys.
*
* @headers {Object} headers - object of header field names and values
* @fieldName {String} field name - string with the case-insensitive field name
*/
function deleteHeadersField(headers, fieldNameToDelete) {
if (!_.isPlainObject(headers)) {
throw Error('headers must be an object')
}
if (typeof fieldNameToDelete !== 'string') {
throw Error('field name must be a string')
}
const lowerCaseFieldNameToDelete = fieldNameToDelete.toLowerCase()
// Search through the headers and delete all values whose field name matches the given field name.
Object.keys(headers)
.filter(fieldName => fieldName.toLowerCase() === lowerCaseFieldNameToDelete)
.forEach(fieldName => delete headers[fieldName])
}
/**
* Utility for iterating over a raw headers array.
*
* The callback is called with:
* - The header value. string, array of strings, or a function
* - The header field name. string
* - Index of the header field in the raw header array.
*/
function forEachHeader(rawHeaders, callback) {
for (let i = 0; i < rawHeaders.length; i += 2) {
callback(rawHeaders[i + 1], rawHeaders[i], i)
}
}
function percentDecode(str) {
try {
return decodeURIComponent(str.replace(/\+/g, ' '))
} catch (e) {
return str
}
}
/**
* URI encode the provided string, stringently adhering to RFC 3986.
*
* RFC 3986 reserves !, ', (, ), and * but encodeURIComponent does not encode them so we do it manually.
*
* https://tools.ietf.org/html/rfc3986
* 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()}`
})
}
function matchStringOrRegexp(target, pattern) {
const targetStr =
target === undefined || target === null ? '' : String(target)
return pattern instanceof RegExp
? pattern.test(targetStr)
: targetStr === String(pattern)
}
/**
* Formats a query parameter.
*
* @param key The key of the query parameter to format.
* @param value The value of the query parameter to format.
* @param stringFormattingFn The function used to format string values. Can
* be used to encode or decode the query value.
*
* @returns *[] the formatted [key, value] pair.
*/
function formatQueryValue(key, value, stringFormattingFn) {
// TODO: Probably refactor code to replace `switch(true)` with `if`/`else`.
switch (true) {
case typeof value === 'number': // fall-through
case typeof value === 'boolean':
value = value.toString()
break
case value === null:
case value === undefined:
value = ''
break
case typeof value === 'string':
if (stringFormattingFn) {
value = stringFormattingFn(value)
}
break
case value instanceof RegExp:
break
case Array.isArray(value): {
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]) {
const subPair = formatQueryValue(subKey, subVal, stringFormattingFn)
acc[subPair[0]] = subPair[1]
return acc
}, {})
break
}
}
if (stringFormattingFn) key = stringFormattingFn(key)
return [key, value]
}
function isStream(obj) {
return (
obj &&
typeof obj !== 'string' &&
!Buffer.isBuffer(obj) &&
typeof obj.setEncoding === 'function'
)
}
/**
* Converts the arguments from the various signatures of http[s].request into a standard
* options object and an optional callback function.
*
* https://nodejs.org/api/http.html#http_http_request_url_options_callback
*
* Taken from the beginning of the native `ClientRequest`.
* https://github.com/nodejs/node/blob/908292cf1f551c614a733d858528ffb13fb3a524/lib/_http_client.js#L68
*/
function normalizeClientRequestArgs(input, options, cb) {
if (typeof input === 'string') {
input = urlToOptions(new url.URL(input))
} else if (input instanceof url.URL) {
input = urlToOptions(input)
} else {
cb = options
options = input
input = null
}
if (typeof options === 'function') {
cb = options
options = input || {}
} else {
options = Object.assign(input || {}, options)
}
return { options, callback: cb }
}
/**
* Utility function that converts a URL object into an ordinary
* options object as expected by the http.request and https.request APIs.
*
* This was copied from Node's source
* https://github.com/nodejs/node/blob/908292cf1f551c614a733d858528ffb13fb3a524/lib/internal/url.js#L1257
*/
function urlToOptions(url) {
const options = {
protocol: url.protocol,
hostname:
typeof url.hostname === 'string' && url.hostname.startsWith('[')
? url.hostname.slice(1, -1)
: url.hostname,
hash: url.hash,
search: url.search,
pathname: url.pathname,
path: `${url.pathname}${url.search || ''}`,
href: url.href,
}
if (url.port !== '') {
options.port = Number(url.port)
}
if (url.username || url.password) {
options.auth = `${url.username}:${url.password}`
}
return options
}
/**
* Determines if request data matches the expected schema.
*
* Used for comparing decoded search parameters, request body JSON objects,
* and URL decoded request form bodies.
*
* Performs a general recursive strict comparision with two caveats:
* - 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))
/**
* Converts flat objects whose keys use JSON path notation to nested objects.
*
* The input object is not mutated.
*
* @example
* { 'foo[bar][0]': 'baz' } -> { foo: { bar: [ 'baz' ] } }
*/
const expand = input =>
Object.entries(input).reduce((acc, [k, v]) => _.set(acc, k, v), {})
/**
* Performs a recursive strict comparison between two values.
*
* Expected values or leaf nodes of expected object values that are RegExp use test() for comparison.
*/
function deepEqual(expected, actual) {
debug('deepEqual comparing', typeof expected, expected, typeof actual, actual)
if (expected instanceof RegExp) {
return expected.test(actual)
}
if (Array.isArray(expected) || _.isPlainObject(expected)) {
if (actual === undefined) {
return false
}
const expKeys = Object.keys(expected)
if (expKeys.length !== Object.keys(actual).length) {
return false
}
return expKeys.every(key => deepEqual(expected[key], actual[key]))
}
return expected === actual
}
const timeouts = []
const intervals = []
const immediates = []
const wrapTimer = (timer, ids) => (...args) => {
const id = timer(...args)
ids.push(id)
return id
}
const setTimeout = wrapTimer(timers.setTimeout, timeouts)
const setInterval = wrapTimer(timers.setInterval, intervals)
const setImmediate = wrapTimer(timers.setImmediate, immediates)
function clearTimer(clear, ids) {
while (ids.length) {
clear(ids.shift())
}
}
function removeAllTimers() {
clearTimer(clearTimeout, timeouts)
clearTimer(clearInterval, intervals)
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

52
node_modules/nock/lib/delayed_body.js generated vendored Normal file
View file

@ -0,0 +1,52 @@
'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)
}
}

5
node_modules/nock/lib/global_emitter.js generated vendored Normal file
View file

@ -0,0 +1,5 @@
'use strict'
const { EventEmitter } = require('events')
module.exports = new EventEmitter()

449
node_modules/nock/lib/intercept.js generated vendored Normal file
View file

@ -0,0 +1,449 @@
'use strict'
/**
* @module nock/intercept
*/
const { InterceptedRequestRouter } = require('./intercepted_request_router')
const common = require('./common')
const { inherits } = require('util')
const http = require('http')
const debug = require('debug')('nock.intercept')
const globalEmitter = require('./global_emitter')
/**
* @name NetConnectNotAllowedError
* @private
* @desc Error trying to make a connection when disabled external access.
* @class
* @example
* nock.disableNetConnect();
* http.get('http://zombo.com');
* // throw NetConnectNotAllowedError
*/
function NetConnectNotAllowedError(host, path) {
Error.call(this)
this.name = 'NetConnectNotAllowedError'
this.code = 'ENETUNREACH'
this.message = `Nock: Disallowed net connect for "${host}${path}"`
Error.captureStackTrace(this, this.constructor)
}
inherits(NetConnectNotAllowedError, Error)
let allInterceptors = {}
let allowNetConnect
/**
* Enabled real request.
* @public
* @param {String|RegExp} matcher=RegExp.new('.*') Expression to match
* @example
* // Enables all real requests
* nock.enableNetConnect();
* @example
* // Enables real requests for url that matches google
* nock.enableNetConnect('google');
* @example
* // Enables real requests for url that matches google and amazon
* nock.enableNetConnect(/(google|amazon)/);
* @example
* // Enables real requests for url that includes google
* nock.enableNetConnect(host => host.includes('google'));
*/
function enableNetConnect(matcher) {
if (typeof matcher === 'string') {
allowNetConnect = new RegExp(matcher)
} else if (matcher instanceof RegExp) {
allowNetConnect = matcher
} else if (typeof matcher === 'function') {
allowNetConnect = { test: matcher }
} else {
allowNetConnect = /.*/
}
}
function isEnabledForNetConnect(options) {
common.normalizeRequestOptions(options)
const enabled = allowNetConnect && allowNetConnect.test(options.host)
debug('Net connect', enabled ? '' : 'not', 'enabled for', options.host)
return enabled
}
/**
* Disable all real requests.
* @public
* @example
* nock.disableNetConnect();
*/
function disableNetConnect() {
allowNetConnect = undefined
}
function isOn() {
return !isOff()
}
function isOff() {
return process.env.NOCK_OFF === 'true'
}
function addInterceptor(key, interceptor, scope, scopeOptions, host) {
if (!(key in allInterceptors)) {
allInterceptors[key] = { key, interceptors: [] }
}
interceptor.__nock_scope = scope
// We need scope's key and scope options for scope filtering function (if defined)
interceptor.__nock_scopeKey = key
interceptor.__nock_scopeOptions = scopeOptions
// We need scope's host for setting correct request headers for filtered scopes.
interceptor.__nock_scopeHost = host
interceptor.interceptionCounter = 0
if (scopeOptions.allowUnmocked) allInterceptors[key].allowUnmocked = true
allInterceptors[key].interceptors.push(interceptor)
}
function remove(interceptor) {
if (interceptor.__nock_scope.shouldPersist() || --interceptor.counter > 0) {
return
}
const { basePath } = interceptor
const interceptors =
(allInterceptors[basePath] && allInterceptors[basePath].interceptors) || []
// 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) {
return thisInterceptor === interceptor ? interceptors.splice(i, 1) : false
})
}
function removeAll() {
Object.keys(allInterceptors).forEach(function(key) {
allInterceptors[key].interceptors.forEach(function(interceptor) {
interceptor.scope.keyedInterceptors = {}
})
})
allInterceptors = {}
}
/**
* Return all the Interceptors whose Scopes match against the base path of the provided options.
*
* @returns {Interceptor[]}
*/
function interceptorsFor(options) {
common.normalizeRequestOptions(options)
debug('interceptors for %j', options.host)
const basePath = `${options.proto}://${options.host}`
debug('filtering interceptors for basepath', basePath)
// First try to use filteringScope if any of the interceptors has it defined.
for (const { key, interceptors, allowUnmocked } of Object.values(
allInterceptors
)) {
for (const interceptor of interceptors) {
const { filteringScope } = interceptor.__nock_scopeOptions
// If scope filtering function is defined and returns a truthy value then
// we have to treat this as a match.
if (filteringScope && filteringScope(basePath)) {
debug('found matching scope interceptor')
// Keep the filtered scope (its key) to signal the rest of the module
// that this wasn't an exact but filtered match.
interceptors.forEach(ic => {
ic.__nock_filteredScope = ic.__nock_scopeKey
})
return interceptors
}
}
if (common.matchStringOrRegexp(basePath, key)) {
if (allowUnmocked && interceptors.length === 0) {
debug('matched base path with allowUnmocked (no matching interceptors)')
return [
{
options: { allowUnmocked: true },
matchOrigin() {
return false
},
},
]
} else {
debug(
`matched base path (${interceptors.length} interceptor${
interceptors.length > 1 ? 's' : ''
})`
)
return interceptors
}
}
}
return undefined
}
function removeInterceptor(options) {
// Lazily import to avoid circular imports.
const Interceptor = require('./interceptor')
let baseUrl, key, method, proto
if (options instanceof Interceptor) {
baseUrl = options.basePath
key = options._key
} else {
proto = options.proto ? options.proto : 'http'
common.normalizeRequestOptions(options)
baseUrl = `${proto}://${options.host}`
method = (options.method && options.method.toUpperCase()) || 'GET'
key = `${method} ${baseUrl}${options.path || '/'}`
}
if (
allInterceptors[baseUrl] &&
allInterceptors[baseUrl].interceptors.length > 0
) {
for (let i = 0; i < allInterceptors[baseUrl].interceptors.length; i++) {
const interceptor = allInterceptors[baseUrl].interceptors[i]
if (interceptor._key === key) {
allInterceptors[baseUrl].interceptors.splice(i, 1)
interceptor.scope.remove(key, interceptor)
break
}
}
return true
}
return false
}
// Variable where we keep the ClientRequest we have overridden
// (which might or might not be node's original http.ClientRequest)
let originalClientRequest
function ErroringClientRequest(error) {
http.OutgoingMessage.call(this)
process.nextTick(
function() {
this.emit('error', error)
}.bind(this)
)
}
inherits(ErroringClientRequest, http.ClientRequest)
function overrideClientRequest() {
// Here's some background discussion about overriding ClientRequest:
// - https://github.com/nodejitsu/mock-request/issues/4
// - https://github.com/nock/nock/issues/26
// It would be good to add a comment that explains this more clearly.
debug('Overriding ClientRequest')
// ----- Extending http.ClientRequest
// Define the overriding client request that nock uses internally.
function OverriddenClientRequest(...args) {
const { options, callback } = common.normalizeClientRequestArgs(...args)
if (Object.keys(options).length === 0) {
// As weird as it is, it's possible to call `http.request` without
// options, and it makes a request to localhost or somesuch. We should
// support it too, for parity. However it doesn't work today, and fixing
// it seems low priority. Giving an explicit error is nicer than
// crashing with a weird stack trace. `http[s].request()`, nock's other
// client-facing entry point, makes a similar check.
// https://github.com/nock/nock/pull/1386
// https://github.com/nock/nock/pull/1440
throw Error(
'Creating a ClientRequest with empty `options` is not supported in Nock'
)
}
http.OutgoingMessage.call(this)
// Filter the interceptors per request options.
const interceptors = interceptorsFor(options)
if (isOn() && interceptors) {
debug('using', interceptors.length, 'interceptors')
// Use filtered interceptors to intercept requests.
const overrider = new InterceptedRequestRouter({
req: this,
options,
interceptors,
})
Object.assign(this, overrider)
if (callback) {
this.once('response', callback)
}
} else {
debug('falling back to original ClientRequest')
// Fallback to original ClientRequest if nock is off or the net connection is enabled.
if (isOff() || isEnabledForNetConnect(options)) {
originalClientRequest.apply(this, arguments)
} else {
common.setImmediate(
function() {
const error = new NetConnectNotAllowedError(
options.host,
options.path
)
this.emit('error', error)
}.bind(this)
)
}
}
}
inherits(OverriddenClientRequest, http.ClientRequest)
// Override the http module's request but keep the original so that we can use it and later restore it.
// NOTE: We only override http.ClientRequest as https module also uses it.
originalClientRequest = http.ClientRequest
http.ClientRequest = OverriddenClientRequest
debug('ClientRequest overridden')
}
function restoreOverriddenClientRequest() {
debug('restoring overridden ClientRequest')
// Restore the ClientRequest we have overridden.
if (!originalClientRequest) {
debug('- ClientRequest was not overridden')
} else {
http.ClientRequest = originalClientRequest
originalClientRequest = undefined
debug('- ClientRequest restored')
}
}
function isActive() {
// If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined.
// This means that Nock has been activated.
return originalClientRequest !== undefined
}
function interceptorScopes() {
const nestedInterceptors = Object.values(allInterceptors).map(
i => i.interceptors
)
return [].concat(...nestedInterceptors).map(i => i.scope)
}
function isDone() {
return interceptorScopes().every(scope => scope.isDone())
}
function pendingMocks() {
return [].concat(...interceptorScopes().map(scope => scope.pendingMocks()))
}
function activeMocks() {
return [].concat(...interceptorScopes().map(scope => scope.activeMocks()))
}
function activate() {
if (originalClientRequest) {
throw new Error('Nock already active')
}
overrideClientRequest()
// ----- Overriding http.request and https.request:
common.overrideRequests(function(proto, overriddenRequest, args) {
// NOTE: overriddenRequest is already bound to its module.
const { options, callback } = common.normalizeClientRequestArgs(...args)
if (Object.keys(options).length === 0) {
// As weird as it is, it's possible to call `http.request` without
// options, and it makes a request to localhost or somesuch. We should
// support it too, for parity. However it doesn't work today, and fixing
// it seems low priority. Giving an explicit error is nicer than
// crashing with a weird stack trace. `new ClientRequest()`, nock's
// other client-facing entry point, makes a similar check.
// https://github.com/nock/nock/pull/1386
// https://github.com/nock/nock/pull/1440
throw Error(
'Making a request with empty `options` is not supported in Nock'
)
}
// The option per the docs is `protocol`. Its unclear if this line is meant to override that and is misspelled or if
// the intend is to explicitly keep track of which module was called using a separate name.
// Either way, `proto` is used as the source of truth from here on out.
options.proto = proto
const interceptors = interceptorsFor(options)
if (isOn() && interceptors) {
const matches = interceptors.some(interceptor =>
interceptor.matchOrigin(options)
)
const allowUnmocked = interceptors.some(
interceptor => interceptor.options.allowUnmocked
)
if (!matches && allowUnmocked) {
let req
if (proto === 'https') {
const { ClientRequest } = http
http.ClientRequest = originalClientRequest
req = overriddenRequest(options, callback)
http.ClientRequest = ClientRequest
} else {
req = overriddenRequest(options, callback)
}
globalEmitter.emit('no match', req)
return req
}
// NOTE: Since we already overrode the http.ClientRequest we are in fact constructing
// our own OverriddenClientRequest.
return new http.ClientRequest(options, callback)
} else {
globalEmitter.emit('no match', options)
if (isOff() || isEnabledForNetConnect(options)) {
return overriddenRequest(options, callback)
} else {
const error = new NetConnectNotAllowedError(options.host, options.path)
return new ErroringClientRequest(error)
}
}
})
}
module.exports = {
addInterceptor,
remove,
removeAll,
removeInterceptor,
isOn,
activate,
isActive,
isDone,
pendingMocks,
activeMocks,
enableNetConnect,
disableNetConnect,
restoreOverriddenClientRequest,
abortPendingRequests: common.removeAllTimers,
}

320
node_modules/nock/lib/intercepted_request_router.js generated vendored Normal file
View file

@ -0,0 +1,320 @@
'use strict'
const debug = require('debug')('nock.request_overrider')
const {
IncomingMessage,
ClientRequest,
request: originalHttpRequest,
} = require('http')
const { request: originalHttpsRequest } = require('https')
const propagate = require('propagate')
const common = require('./common')
const globalEmitter = require('./global_emitter')
const Socket = require('./socket')
const { playbackInterceptor } = require('./playback_interceptor')
/**
* Given a group of interceptors, appropriately route an outgoing request.
* Identify which interceptor ought to respond, if any, then delegate to
* `playbackInterceptor()` to consume the request itself.
*/
class InterceptedRequestRouter {
constructor({ req, options, interceptors }) {
this.req = req
this.options = {
// We may be changing the options object and we don't want those changes
// affecting the user so we use a clone of the object.
...options,
// We use lower-case header field names throughout Nock.
headers: common.headersFieldNamesToLowerCase(options.headers || {}),
}
this.interceptors = interceptors
this.socket = new Socket(options)
// support setting `timeout` using request `options`
// https://nodejs.org/docs/latest-v12.x/api/http.html#http_http_request_url_options_callback
if (options.timeout) {
this.socket.setTimeout(options.timeout)
}
this.response = new IncomingMessage(this.socket)
this.playbackStarted = false
this.requestBodyBuffers = []
this.attachToReq()
}
attachToReq() {
const { req, response, socket, options } = this
response.req = req
for (const [name, val] of Object.entries(options.headers)) {
req.setHeader(name.toLowerCase(), val)
}
if (options.auth && !options.headers.authorization) {
req.setHeader(
// We use lower-case header field names throughout Nock.
'authorization',
`Basic ${Buffer.from(options.auth).toString('base64')}`
)
}
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') {
common.setImmediate(() => {
debug('continue')
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)
})
}
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'))
}
common.setImmediate(function() {
req.emit('drain')
})
return false
}
handleEnd(chunk, encoding, callback) {
debug('req.end')
const { req } = this
// handle the different overloaded param signatures
if (typeof chunk === 'function') {
callback = chunk
chunk = null
} else if (typeof encoding === 'function') {
callback = encoding
encoding = null
}
if (typeof callback === 'function') {
req.once('finish', callback)
}
if (!req.aborted && !this.playbackStarted) {
req.write(chunk, encoding)
this.startPlayback()
}
if (req.aborted) {
this.emitError(new Error('Request aborted'))
}
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)
}
/**
* Set request headers of the given request. This is needed both during the
* routing phase, in case header filters were specified, and during the
* interceptor-playback phase, to correctly pass mocked request headers.
* TODO There are some problems with this; see https://github.com/nock/nock/issues/1718
*/
setHostHeaderUsingInterceptor(interceptor) {
const { req, options } = this
// If a filtered scope is being used we have to use scope's host in the
// header, otherwise 'host' header won't match.
// NOTE: We use lower-case header field names throughout Nock.
const HOST_HEADER = 'host'
if (interceptor.__nock_filteredScope && interceptor.__nock_scopeHost) {
options.headers[HOST_HEADER] = interceptor.__nock_scopeHost
req.setHeader(HOST_HEADER, interceptor.__nock_scopeHost)
} else {
// For all other cases, we always add host header equal to the requested
// host unless it was already defined.
if (options.host && !req.getHeader(HOST_HEADER)) {
let hostHeader = options.host
if (options.port === 80 || options.port === 443) {
hostHeader = hostHeader.split(':')[0]
}
req.setHeader(HOST_HEADER, hostHeader)
}
}
}
startPlayback() {
debug('ending')
this.playbackStarted = true
const { req, response, socket, options, interceptors } = this
Object.assign(options, {
// Re-update `options` with the current value of `req.path` because badly
// behaving agents like superagent like to change `req.path` mid-flight.
path: req.path,
// Similarly, node-http-proxy will modify headers in flight, so we have
// to put the headers back into options.
// https://github.com/nock/nock/pull/1484
headers: req.getHeaders(),
// Fixes https://github.com/nock/nock/issues/976
protocol: `${options.proto}:`,
})
interceptors.forEach(interceptor => {
this.setHostHeaderUsingInterceptor(interceptor)
})
const requestBodyBuffer = Buffer.concat(this.requestBodyBuffers)
// When request body is a binary buffer we internally use in its hexadecimal
// representation.
const requestBodyIsUtf8Representable = common.isUtf8Representable(
requestBodyBuffer
)
const requestBodyString = requestBodyBuffer.toString(
requestBodyIsUtf8Representable ? 'utf8' : 'hex'
)
const matchedInterceptor = interceptors.find(i =>
i.match(req, options, requestBodyString)
)
if (matchedInterceptor) {
debug('interceptor identified, starting mocking')
// 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({
req,
socket,
options,
requestBodyString,
requestBodyIsUtf8Representable,
response,
interceptor: matchedInterceptor,
})
} else {
globalEmitter.emit('no match', req, options, requestBodyString)
// Try to find a hostname match that allows unmocked.
const allowUnmocked = interceptors.some(
i => i.matchHostName(options) && i.options.allowUnmocked
)
if (allowUnmocked && req instanceof ClientRequest) {
const newReq =
options.proto === 'https'
? originalHttpsRequest(options)
: originalHttpRequest(options)
propagate(newReq, req)
// 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
)}`
)
err.statusCode = err.status = 404
this.emitError(err)
}
}
}
}
module.exports = { InterceptedRequestRouter }

619
node_modules/nock/lib/interceptor.js generated vendored Normal file
View file

@ -0,0 +1,619 @@
'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')
const common = require('./common')
const { remove } = require('./intercept')
const matchBody = require('./match_body')
let fs
try {
fs = require('fs')
} catch (err) {
// do nothing, we're in the browser
}
module.exports = class Interceptor {
/**
*
* Valid argument types for `uri`:
* - A string used for strict comparisons with pathname.
* The search portion of the URI may also be postfixed, in which case the search params
* are striped and added via the `query` method.
* - A RegExp instance that tests against only the pathname of requests.
* - A synchronous function bound to this Interceptor instance. It's provided the pathname
* of requests and must return a boolean denoting if the request is considered a match.
*/
constructor(scope, uri, method, requestBody, interceptorOptions) {
const uriIsStr = typeof uri === 'string'
// Check for leading slash. Uri can be either a string or a regexp, but
// When enabled filteringScope ignores the passed URL entirely so we skip validation.
if (
!scope.scopeOptions.filteringScope &&
uriIsStr &&
!uri.startsWith('/') &&
!uri.startsWith('*')
) {
throw Error(
`Non-wildcard URL path strings must begin with a slash (otherwise they won't match anything) (got: ${uri})`
)
}
if (!method) {
throw new Error(
'The "method" parameter is required for an intercept call.'
)
}
this.scope = scope
this.interceptorMatchHeaders = []
this.method = method.toUpperCase()
this.uri = uri
this._key = `${this.method} ${scope.basePath}${scope.basePathname}${
uriIsStr ? '' : '/'
}${uri}`
this.basePath = this.scope.basePath
this.path = uriIsStr ? scope.basePathname + uri : uri
this.queries = null
this.options = interceptorOptions || {}
this.counter = 1
this._requestBody = requestBody
// We use lower-case header field names throughout Nock.
this.reqheaders = common.headersFieldNamesToLowerCase(
scope.scopeOptions.reqheaders || {}
)
this.badheaders = common.headersFieldsArrayToLowerCase(
scope.scopeOptions.badheaders || []
)
this.delayInMs = 0
this.delayConnectionInMs = 0
this.optional = false
// strip off literal query parameters if they were provided as part of the URI
if (uriIsStr && uri.includes('?')) {
// localhost is a dummy value because the URL constructor errors for only relative inputs
const parsedURL = new URL(this.path, 'http://localhost')
this.path = parsedURL.pathname
this.query(parsedURL.searchParams)
this._key = `${this.method} ${scope.basePath}${this.path}`
}
}
optionally(flag = true) {
// The default behaviour of optionally() with no arguments is to make the mock optional.
if (typeof flag !== 'boolean') {
throw new Error('Invalid arguments: argument should be a boolean')
}
this.optional = flag
return this
}
replyWithError(errorMessage) {
this.errorMessage = errorMessage
this.options = {
...this.scope.scopeOptions,
...this.options,
}
this.scope.add(this._key, this)
return this.scope
}
reply(statusCode, body, rawHeaders) {
// support the format of only passing in a callback
if (typeof statusCode === 'function') {
if (arguments.length > 1) {
// It's not very Javascript-y to throw an error for extra args to a function, but because
// of legacy behavior, this error was added to reduce confusion for those migrating.
throw Error(
'Invalid arguments. When providing a function for the first argument, .reply does not accept other arguments.'
)
}
this.statusCode = null
this.fullReplyFunction = statusCode
} else {
if (statusCode !== undefined && !Number.isInteger(statusCode)) {
throw new Error(`Invalid ${typeof statusCode} value for status code`)
}
this.statusCode = statusCode || 200
if (typeof body === 'function') {
this.replyFunction = body
body = null
}
}
this.options = {
...this.scope.scopeOptions,
...this.options,
}
this.rawHeaders = common.headersInputToRawArray(rawHeaders)
if (this.scope.date) {
// https://tools.ietf.org/html/rfc7231#section-7.1.1.2
this.rawHeaders.push('Date', this.scope.date.toUTCString())
}
// Prepare the headers temporarily so we can make best guesses about content-encoding and content-type
// below as well as while the response is being processed in RequestOverrider.end().
// Including all the default headers is safe for our purposes because of the specific headers we introspect.
// A more thoughtful process is used to merge the default headers when the response headers are finally computed.
this.headers = common.headersArrayToObject(
this.rawHeaders.concat(this.scope._defaultReplyHeaders)
)
// If the content is not encoded we may need to transform the response body.
// Otherwise we leave it as it is.
if (
body &&
typeof body !== 'string' &&
!Buffer.isBuffer(body) &&
!common.isStream(body) &&
!common.isContentEncoded(this.headers)
) {
try {
body = stringify(body)
} catch (err) {
throw new Error('Error encoding response body into JSON')
}
if (!this.headers['content-type']) {
// https://tools.ietf.org/html/rfc7231#section-3.1.1.5
this.rawHeaders.push('Content-Type', 'application/json')
}
if (this.scope.contentLen) {
// https://tools.ietf.org/html/rfc7230#section-3.3.2
this.rawHeaders.push('Content-Length', body.length)
}
}
debug('reply.headers:', this.headers)
debug('reply.rawHeaders:', this.rawHeaders)
this.body = body
this.scope.add(this._key, this)
return this.scope
}
replyWithFile(statusCode, filePath, headers) {
if (!fs) {
throw new Error('No fs')
}
const readStream = fs.createReadStream(filePath)
readStream.pause()
this.filePath = filePath
return this.reply(statusCode, readStream, headers)
}
// Also match request headers
// https://github.com/nock/nock/issues/163
reqheaderMatches(options, key) {
const reqHeader = this.reqheaders[key]
let header = options.headers[key]
// https://github.com/nock/nock/issues/399
// https://github.com/nock/nock/issues/822
if (header && typeof header !== 'string' && header.toString) {
header = header.toString()
}
// We skip 'host' header comparison unless it's available in both mock and
// actual request. This because 'host' may get inserted by Nock itself and
// then get recorded. NOTE: We use lower-case header field names throughout
// Nock. See https://github.com/nock/nock/pull/196.
if (key === 'host' && (header === undefined || reqHeader === undefined)) {
return true
}
if (reqHeader !== undefined && header !== undefined) {
if (typeof reqHeader === 'function') {
return reqHeader(header)
} else if (common.matchStringOrRegexp(header, reqHeader)) {
return true
}
}
debug("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))
}
const method = (options.method || 'GET').toUpperCase()
let { path = '/' } = options
let matches
let matchKey
const { proto } = options
if (this.method !== method) {
debug(
`Method did not match. Request ${method} Interceptor ${this.method}`
)
return false
}
if (this.scope.transformPathFunction) {
path = this.scope.transformPathFunction(path)
}
const requestMatchesFilter = ({ name, value: predicate }) => {
const headerValue = req.getHeader(name)
if (typeof predicate === 'function') {
return predicate(headerValue)
} else {
return common.matchStringOrRegexp(headerValue, predicate)
}
}
if (
!this.scope.matchHeaders.every(requestMatchesFilter) ||
!this.interceptorMatchHeaders.every(requestMatchesFilter)
) {
this.scope.logger("headers don't match")
return false
}
const reqHeadersMatch = Object.keys(this.reqheaders).every(key =>
this.reqheaderMatches(options, key)
)
if (!reqHeadersMatch) {
return false
}
if (
this.scope.scopeOptions.conditionally &&
!this.scope.scopeOptions.conditionally()
) {
return false
}
const reqContainsBadHeaders = this.badheaders.some(
header => header in options.headers
)
if (reqContainsBadHeaders) {
return false
}
// Match query strings when using query()
if (this.queries === null) {
debug('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')
if (!matchQueries) {
return false
}
// If the query string was explicitly checked then subsequent checks against
// the path using a callback or regexp only validate the pathname.
path = pathname
}
// If we have a filtered scope then we use it instead reconstructing the
// scope from the request options (proto, host and port) as these two won't
// necessarily match and we have to remove the scope that was matched (vs.
// that was defined).
if (this.__nock_filteredScope) {
matchKey = this.__nock_filteredScope
} else {
matchKey = common.normalizeOrigin(proto, options.host, options.port)
}
if (typeof this.uri === 'function') {
matches =
common.matchStringOrRegexp(matchKey, this.basePath) &&
// This is a false positive, as `uri` is not bound to `this`.
// eslint-disable-next-line no-useless-call
this.uri.call(this, path)
} else {
matches =
common.matchStringOrRegexp(matchKey, this.basePath) &&
common.matchStringOrRegexp(path, this.path)
}
this.scope.logger(`matching ${matchKey}${path} to ${this._key}: ${matches}`)
if (matches && this._requestBody !== undefined) {
if (this.scope.transformRequestBodyFunction) {
body = this.scope.transformRequestBodyFunction(body, this._requestBody)
}
matches = matchBody(options, this._requestBody, body)
if (!matches) {
this.scope.logger(
"bodies don't match: \n",
this._requestBody,
'\n',
body
)
}
}
return matches
}
/**
* Return true when the interceptor's method, protocol, host, port, and path
* match the provided options.
*/
matchOrigin(options) {
const isPathFn = typeof this.path === 'function'
const isRegex = this.path instanceof RegExp
const isRegexBasePath = this.scope.basePath instanceof RegExp
const method = (options.method || 'GET').toUpperCase()
let { path } = options
const { proto } = options
// NOTE: Do not split off the query params as the regex could use them
if (!isRegex) {
path = path ? path.split('?')[0] : ''
}
if (this.scope.transformPathFunction) {
path = this.scope.transformPathFunction(path)
}
const comparisonKey = isPathFn || isRegex ? this.__nock_scopeKey : this._key
const matchKey = `${method} ${proto}://${options.host}${path}`
if (isPathFn) {
return !!(matchKey.match(comparisonKey) && this.path(path))
}
if (isRegex && !isRegexBasePath) {
return !!matchKey.match(comparisonKey) && this.path.test(path)
}
if (isRegexBasePath) {
return this.scope.basePath.test(matchKey) && !!path.match(this.path)
}
return comparisonKey === matchKey
}
matchHostName(options) {
return options.hostname === this.scope.urlParts.hostname
}
matchQuery(options) {
if (this.queries === true) {
return true
}
const reqQueries = querystring.parse(options.search)
debug('Interceptor queries: %j', this.queries)
debug(' Request queries: %j', reqQueries)
if (typeof this.queries === 'function') {
return this.queries(reqQueries)
}
return common.dataEqual(this.queries, reqQueries)
}
filteringPath(...args) {
this.scope.filteringPath(...args)
return this
}
// TODO filtering by path is valid on the intercept level, but not filtering
// by request body?
markConsumed() {
this.interceptionCounter++
remove(this)
if ((this.scope.shouldPersist() || this.counter > 0) && this.filePath) {
this.body = fs.createReadStream(this.filePath)
this.body.pause()
}
if (!this.scope.shouldPersist() && this.counter < 1) {
this.scope.remove(this._key, this)
}
}
matchHeader(name, value) {
this.interceptorMatchHeaders.push({ name, value })
return this
}
basicAuth({ user, pass = '' }) {
const encoded = Buffer.from(`${user}:${pass}`).toString('base64')
this.matchHeader('authorization', `Basic ${encoded}`)
return this
}
/**
* Set query strings for the interceptor
* @name query
* @param queries Object of query string name,values (accepts regexp values)
* @public
* @example
* // Will match 'http://zombo.com/?q=t'
* nock('http://zombo.com').get('/').query({q: 't'});
*/
query(queries) {
if (this.queries !== null) {
throw Error(`Query parameters have already been defined`)
}
// Allow all query strings to match this route
if (queries === true) {
this.queries = queries
return this
}
if (typeof queries === 'function') {
this.queries = queries
return this
}
let strFormattingFn
if (this.scope.scopeOptions.encodedQueryParams) {
strFormattingFn = common.percentDecode
}
if (queries instanceof URLSearchParams) {
// 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)) {
throw Error(`Argument Error: ${queries}`)
}
this.queries = {}
for (const [key, value] of Object.entries(queries)) {
const formatted = common.formatQueryValue(key, value, strFormattingFn)
const [formattedKey, formattedValue] = formatted
this.queries[formattedKey] = formattedValue
}
return this
}
/**
* Set number of times will repeat the interceptor
* @name times
* @param newCounter Number of times to repeat (should be > 0)
* @public
* @example
* // Will repeat mock 5 times for same king of request
* nock('http://zombo.com).get('/').times(5).reply(200, 'Ok');
*/
times(newCounter) {
if (newCounter < 1) {
return this
}
this.counter = newCounter
return this
}
/**
* An sugar syntax for times(1)
* @name once
* @see {@link times}
* @public
* @example
* nock('http://zombo.com).get('/').once().reply(200, 'Ok');
*/
once() {
return this.times(1)
}
/**
* An sugar syntax for times(2)
* @name twice
* @see {@link times}
* @public
* @example
* nock('http://zombo.com).get('/').twice().reply(200, 'Ok');
*/
twice() {
return this.times(2)
}
/**
* An sugar syntax for times(3).
* @name thrice
* @see {@link times}
* @public
* @example
* nock('http://zombo.com).get('/').thrice().reply(200, 'Ok');
*/
thrice() {
return this.times(3)
}
/**
* Delay the response by a certain number of ms.
*
* @param {(integer|object)} opts - Number of milliseconds to wait, or an object
* @param {integer} [opts.head] - Number of milliseconds to wait before response is sent
* @param {integer} [opts.body] - Number of milliseconds to wait before response body is sent
* @return {Interceptor} - the current interceptor for chaining
*/
delay(opts) {
let headDelay
let bodyDelay
if (typeof opts === 'number') {
headDelay = opts
bodyDelay = 0
} else if (typeof opts === 'object') {
headDelay = opts.head || 0
bodyDelay = opts.body || 0
} else {
throw new Error(`Unexpected input opts ${opts}`)
}
return this.delayConnection(headDelay).delayBody(bodyDelay)
}
/**
* Delay the response body by a certain number of ms.
*
* @param {integer} ms - Number of milliseconds to wait before response is sent
* @return {Interceptor} - the current interceptor for chaining
*/
delayBody(ms) {
this.delayInMs += ms
return this
}
/**
* Delay the connection by a certain number of ms.
*
* @param {integer} ms - Number of milliseconds to wait
* @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
return this
}
}

77
node_modules/nock/lib/match_body.js generated vendored Normal file
View file

@ -0,0 +1,77 @@
'use strict'
const _ = require('lodash')
const querystring = require('querystring')
const common = require('./common')
module.exports = function matchBody(options, spec, body) {
if (spec instanceof RegExp) {
return spec.test(body)
}
if (Buffer.isBuffer(spec)) {
const encoding = common.isUtf8Representable(spec) ? 'utf8' : 'hex'
spec = spec.toString(encoding)
}
const contentType = (
(options.headers &&
(options.headers['Content-Type'] || options.headers['content-type'])) ||
''
).toString()
const isMultipart = contentType.includes('multipart')
const isUrlencoded = contentType.includes('application/x-www-form-urlencoded')
// try to transform body to json
let json
if (typeof spec === 'object' || typeof spec === 'function') {
try {
json = JSON.parse(body)
} catch (err) {
// not a valid JSON string
}
if (json !== undefined) {
body = json
} else if (isUrlencoded) {
body = querystring.parse(body)
}
}
if (typeof spec === 'function') {
return spec.call(options, body)
}
// strip line endings from both so that we get a match no matter what OS we are running on
// if Content-Type does not contains 'multipart'
if (!isMultipart && typeof body === 'string') {
body = body.replace(/\r?\n|\r/g, '')
}
if (!isMultipart && typeof spec === 'string') {
spec = spec.replace(/\r?\n|\r/g, '')
}
// Because the nature of URL encoding, all the values in the body have been cast to strings.
// dataEqual does strict checking so we we have to cast the non-regexp values in the spec too.
if (isUrlencoded) {
spec = mapValuesDeep(spec, val => (val instanceof RegExp ? val : `${val}`))
}
return common.dataEqual(spec, body)
}
/**
* Based on lodash issue discussion
* https://github.com/lodash/lodash/issues/1244
*/
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))
}
return cb(obj)
}

348
node_modules/nock/lib/playback_interceptor.js generated vendored Normal file
View file

@ -0,0 +1,348 @@
'use strict'
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)) {
return requestBody
}
if (common.contentEncoding(req.headers, 'gzip')) {
requestBody = String(zlib.gunzipSync(Buffer.from(requestBody, 'hex')))
} else if (common.contentEncoding(req.headers, 'deflate')) {
requestBody = String(zlib.inflateSync(Buffer.from(requestBody, 'hex')))
}
return JSON.parse(requestBody)
}
function parseFullReplyResult(response, fullReplyResult) {
debug('full response from callback result: %j', fullReplyResult)
if (!Array.isArray(fullReplyResult)) {
throw Error('A single function provided to .reply MUST return an array')
}
if (fullReplyResult.length > 3) {
throw Error(
'The array returned from the .reply callback contains too many values'
)
}
const [status, body = '', headers] = fullReplyResult
if (!Number.isInteger(status)) {
throw new Error(`Invalid ${typeof status} value for status code`)
}
response.statusCode = status
response.rawHeaders.push(...common.headersInputToRawArray(headers))
debug('response.rawHeaders after reply: %j', response.rawHeaders)
return body
}
/**
* Determine which of the default headers should be added to the response.
*
* Don't include any defaults whose case-insensitive keys are already on the response.
*/
function selectDefaultHeaders(existingHeaders, defaultHeaders) {
if (!defaultHeaders.length) {
return [] // return early if we don't need to bother
}
const definedHeaders = new Set()
const result = []
common.forEachHeader(existingHeaders, (_, fieldName) => {
definedHeaders.add(fieldName.toLowerCase())
})
common.forEachHeader(defaultHeaders, (value, fieldName) => {
if (!definedHeaders.has(fieldName.toLowerCase())) {
result.push(fieldName, value)
}
})
return result
}
/**
* Play back an interceptor using the given request and mock response.
*/
function playbackInterceptor({
req,
socket,
options,
requestBodyString,
requestBodyIsUtf8Representable,
response,
interceptor,
}) {
function emitError(error) {
process.nextTick(() => {
req.emit('error', error)
})
}
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())
return
}
// This will be null if we have a fullReplyFunction,
// in that case status code will be set in `parseFullReplyResult`
response.statusCode = interceptor.statusCode
// Clone headers/rawHeaders to not override them when evaluating later
response.rawHeaders = [...interceptor.rawHeaders]
debug('response.rawHeaders:', response.rawHeaders)
if (interceptor.replyFunction) {
const parsedRequestBody = parseJSONRequestBody(req, requestBodyString)
let fn = interceptor.replyFunction
if (fn.length === 3) {
// Handle the case of an async reply function, the third parameter being the callback.
fn = util.promisify(fn)
}
// 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))
return
}
if (interceptor.fullReplyFunction) {
const parsedRequestBody = parseJSONRequestBody(req, requestBodyString)
let fn = interceptor.fullReplyFunction
if (fn.length === 3) {
fn = util.promisify(fn)
}
Promise.resolve(fn.call(interceptor, options.path, parsedRequestBody))
.then(fullReplyResult => continueWithFullResponse({ fullReplyResult }))
.catch(err => emitError(err))
return
}
if (
common.isContentEncoded(interceptor.headers) &&
!common.isStream(interceptor.body)
) {
// If the content is encoded we know that the response body *must* be an array
// 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 })
return
}
// If we get to this point, the body is either a string or an object that
// will eventually be JSON stringified.
let responseBody = interceptor.body
// If the request was not UTF8-representable then we assume that the
// response won't be either. In that case we send the response as a Buffer
// object as that's what the client will expect.
if (!requestBodyIsUtf8Representable && typeof responseBody === 'string') {
// Try to create the buffer from the interceptor's body response as hex.
responseBody = Buffer.from(responseBody, 'hex')
// Creating buffers does not necessarily throw errors; check for difference in size.
if (
!responseBody ||
(interceptor.body.length > 0 && responseBody.length === 0)
) {
// We fallback on constructing buffer from utf8 representation of the body.
responseBody = Buffer.from(interceptor.body, 'utf8')
}
}
return continueWithResponseBody({ responseBody })
}
function continueWithFullResponse({ fullReplyResult }) {
let responseBody
try {
responseBody = parseFullReplyResult(response, fullReplyResult)
} catch (innerErr) {
emitError(innerErr)
return
}
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')
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
)
}
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
}
response.rawHeaders.push(
...selectDefaultHeaders(
response.rawHeaders,
interceptor.scope._defaultReplyHeaders
)
)
// Evaluate functional headers.
common.forEachHeader(response.rawHeaders, (value, fieldName, i) => {
if (typeof value === 'function') {
response.rawHeaders[i + 1] = value(req, response, responseBody)
}
})
response.headers = common.headersArrayToObject(response.rawHeaders)
process.nextTick(() =>
respondUsingInterceptor({
responseBody,
responseBuffers,
})
)
}
function respondUsingInterceptor({ responseBody, responseBuffers }) {
if (req.aborted) {
return
}
function respond() {
if (req.aborted) {
return
}
debug('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)
}
})
}
}
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()
}
}
start()
}
module.exports = { playbackInterceptor }

386
node_modules/nock/lib/recorder.js generated vendored Normal file
View file

@ -0,0 +1,386 @@
'use strict'
const debug = require('debug')('nock.recorder')
const querystring = require('querystring')
const { inspect } = require('util')
const common = require('./common')
const { restoreOverriddenClientRequest } = require('./intercept')
const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'
let recordingInProgress = false
let outputs = []
function getScope(options) {
const { proto, host, port } = common.normalizeRequestOptions(options)
return common.normalizeOrigin(proto, host, port)
}
function getMethod(options) {
return options.method || 'GET'
}
function getBodyFromChunks(chunks, headers) {
// If we have headers and there is content-encoding it means that the body
// shouldn't be merged but instead persisted as an array of hex strings so
// that the response chunks can be mocked one by one.
if (headers && common.isContentEncoded(headers)) {
return {
body: chunks.map(chunk => chunk.toString('hex')),
}
}
const mergedBuffer = Buffer.concat(chunks)
// The merged buffer can be one of three things:
// 1. A UTF-8-representable string buffer which represents a JSON object.
// 2. A UTF-8-representable buffer which doesn't represent a JSON object.
// 3. A non-UTF-8-representable buffer which then has to be recorded as a hex string.
const isUtf8Representable = common.isUtf8Representable(mergedBuffer)
if (isUtf8Representable) {
const maybeStringifiedJson = mergedBuffer.toString('utf8')
try {
return {
isUtf8Representable,
body: JSON.parse(maybeStringifiedJson),
}
} catch (err) {
return {
isUtf8Representable,
body: maybeStringifiedJson,
}
}
} else {
return {
isUtf8Representable,
body: mergedBuffer.toString('hex'),
}
}
}
function generateRequestAndResponseObject({
req,
bodyChunks,
options,
res,
dataChunks,
reqheaders,
}) {
const { body, isUtf8Representable } = getBodyFromChunks(
dataChunks,
res.headers
)
options.path = req.path
return {
scope: getScope(options),
method: getMethod(options),
path: options.path,
// Is it deliberate that `getBodyFromChunks()` is called a second time?
body: getBodyFromChunks(bodyChunks).body,
status: res.statusCode,
response: body,
rawHeaders: res.rawHeaders,
reqheaders: reqheaders || undefined,
// When content-encoding is enabled, isUtf8Representable is `undefined`,
// so we explicitly check for `false`.
responseIsBinary: isUtf8Representable === false,
}
}
function generateRequestAndResponse({
req,
bodyChunks,
options,
res,
dataChunks,
reqheaders,
}) {
const requestBody = getBodyFromChunks(bodyChunks).body
const responseBody = getBodyFromChunks(dataChunks, res.headers).body
// Remove any query params from options.path so they can be added in the query() function
let { path } = options
const queryIndex = req.path.indexOf('?')
let queryObj = {}
if (queryIndex !== -1) {
// Remove the query from the path
path = path.substring(0, queryIndex)
const queryStr = req.path.slice(queryIndex + 1)
queryObj = querystring.parse(queryStr)
}
// Always encode the query parameters when recording.
const encodedQueryObj = {}
for (const key in queryObj) {
const formattedPair = common.formatQueryValue(
key,
queryObj[key],
common.percentEncode
)
encodedQueryObj[formattedPair[0]] = formattedPair[1]
}
const lines = []
// We want a leading newline.
lines.push('')
const scope = getScope(options)
lines.push(`nock('${scope}', {"encodedQueryParams":true})`)
const methodName = getMethod(options).toLowerCase()
if (requestBody) {
lines.push(` .${methodName}('${path}', ${JSON.stringify(requestBody)})`)
} else {
lines.push(` .${methodName}('${path}')`)
}
Object.entries(reqheaders || {}).forEach(([fieldName, fieldValue]) => {
const safeName = JSON.stringify(fieldName)
const safeValue = JSON.stringify(fieldValue)
lines.push(` .matchHeader(${safeName}, ${safeValue})`)
})
if (queryIndex !== -1) {
lines.push(` .query(${JSON.stringify(encodedQueryObj)})`)
}
const statusCode = res.statusCode.toString()
const stringifiedResponseBody = JSON.stringify(responseBody)
const headers = inspect(res.rawHeaders)
lines.push(` .reply(${statusCode}, ${stringifiedResponseBody}, ${headers});`)
return lines.join('\n')
}
// This module variable is used to identify a unique recording ID in order to skip
// spurious requests that sometimes happen. This problem has been, so far,
// exclusively detected in nock's unit testing where 'checks if callback is specified'
// interferes with other tests as its t.end() is invoked without waiting for request
// to finish (which is the point of the test).
let currentRecordingId = 0
const defaultRecordOptions = {
dont_print: false,
enable_reqheaders_recording: false,
logging: console.log,
output_objects: false,
use_separator: true,
}
function record(recOptions) {
// Trying to start recording with recording already in progress implies an error
// in the recording configuration (double recording makes no sense and used to lead
// to duplicates in output)
if (recordingInProgress) {
throw new Error('Nock recording already in progress')
}
recordingInProgress = true
// Set the new current recording ID and capture its value in this instance of record().
currentRecordingId = currentRecordingId + 1
const thisRecordingId = currentRecordingId
// Originally the parameter was a dont_print boolean flag.
// To keep the existing code compatible we take that case into account.
if (typeof recOptions === 'boolean') {
recOptions = { dont_print: recOptions }
}
recOptions = { ...defaultRecordOptions, ...recOptions }
debug('start recording', thisRecordingId, recOptions)
const {
dont_print: dontPrint,
enable_reqheaders_recording: enableReqHeadersRecording,
logging,
output_objects: outputObjects,
use_separator: useSeparator,
} = recOptions
debug(thisRecordingId, 'restoring overridden requests before new overrides')
// To preserve backward compatibility (starting recording wasn't throwing if nock was already active)
// we restore any requests that may have been overridden by other parts of nock (e.g. intercept)
// NOTE: This is hacky as hell but it keeps the backward compatibility *and* allows correct
// behavior in the face of other modules also overriding ClientRequest.
common.restoreOverriddenRequests()
// We restore ClientRequest as it messes with recording of modules that also override ClientRequest (e.g. xhr2)
restoreOverriddenClientRequest()
// We override the requests so that we can save information on them before executing.
common.overrideRequests(function(proto, overriddenRequest, rawArgs) {
const { options, callback } = common.normalizeClientRequestArgs(...rawArgs)
const bodyChunks = []
// Node 0.11 https.request calls http.request -- don't want to record things
// twice.
/* istanbul ignore if */
if (options._recording) {
return overriddenRequest(options, callback)
}
options._recording = true
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() {
debug(thisRecordingId, proto, 'intercepted request ended')
let reqheaders
// Ignore request headers completely unless it was explicitly enabled by the user (see README)
if (enableReqHeadersRecording) {
// We never record user-agent headers as they are worse than useless -
// they actually make testing more difficult without providing any benefit (see README)
reqheaders = req.getHeaders()
common.deleteHeadersField(reqheaders, 'user-agent')
}
const generateFn = outputObjects
? generateRequestAndResponseObject
: generateRequestAndResponse
let out = generateFn({
req,
bodyChunks,
options,
res,
dataChunks,
reqheaders,
})
debug('out:', out)
// Check that the request was made during the current recording.
// If it hasn't then skip it. There is no other simple way to handle
// this as it depends on the timing of requests and responses. Throwing
// will make some recordings/unit tests fail randomly depending on how
// fast/slow the response arrived.
// If you are seeing this error then you need to make sure that all
// the requests made during a single recording session finish before
// ending the same recording session.
if (thisRecordingId !== currentRecordingId) {
debug('skipping recording of an out-of-order request', out)
return
}
outputs.push(out)
if (!dontPrint) {
if (useSeparator) {
if (typeof out !== 'string') {
out = JSON.stringify(out, null, 2)
}
logging(SEPARATOR + out + SEPARATOR)
} else {
logging(out)
}
}
})
let encoding
// 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) {
encoding = newEncoding
return setEncoding.apply(this, arguments)
}
const dataChunks = []
// Replace res.push with our own implementation that stores chunks
const origResPush = res.push
res.push = function(data) {
if (data) {
if (encoding) {
data = Buffer.from(data, encoding)
}
dataChunks.push(data)
}
return origResPush.call(res, data)
}
if (callback) {
callback(res, options, callback)
} else {
res.resume()
}
debug('finished setting up intercepting')
// We override both the http and the https modules; when we are
// serializing the request, we need to know which was called.
// By stuffing the state, we can make sure that nock records
// the intended protocol.
if (proto === 'https') {
options.proto = 'https'
}
})
const recordChunk = (chunk, encoding) => {
debug(thisRecordingId, 'new', proto, 'body chunk')
if (!Buffer.isBuffer(chunk)) {
chunk = Buffer.from(chunk, encoding)
}
bodyChunks.push(chunk)
}
const oldWrite = req.write
req.write = function(chunk, encoding) {
if (typeof chunk !== 'undefined') {
recordChunk(chunk, encoding)
oldWrite.apply(req, arguments)
} else {
throw new Error('Data was undefined.')
}
}
// Starting in Node 8, `OutgoingMessage.end()` directly calls an internal
// `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) {
debug('req.end')
if (typeof chunk === 'function') {
callback = chunk
chunk = null
} else if (typeof encoding === 'function') {
callback = encoding
encoding = null
}
if (chunk) {
recordChunk(chunk, encoding)
}
oldEnd.call(req, chunk, encoding, callback)
}
return req
})
}
// Restore *all* the overridden http/https modules' properties.
function restore() {
debug(
currentRecordingId,
'restoring all the overridden http/https properties'
)
common.restoreOverriddenRequests()
restoreOverriddenClientRequest()
recordingInProgress = false
}
function clear() {
outputs = []
}
module.exports = {
record,
outputs: () => outputs,
restore,
clear,
}

391
node_modules/nock/lib/scope.js generated vendored Normal file
View file

@ -0,0 +1,391 @@
'use strict'
/**
* @module nock/scope
*/
const { addInterceptor, isOn } = require('./intercept')
const common = require('./common')
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
try {
fs = require('fs')
} catch (err) {
// do nothing, we're in the browser
}
/**
* @param {string|RegExp|url.url} basePath
* @param {Object} options
* @param {boolean} options.allowUnmocked
* @param {string[]} options.badheaders
* @param {function} options.conditionally
* @param {boolean} options.encodedQueryParams
* @param {function} options.filteringScope
* @param {Object} options.reqheaders
* @constructor
*/
class Scope extends EventEmitter {
constructor(basePath, options) {
super()
this.keyedInterceptors = {}
this.interceptors = []
this.transformPathFunction = null
this.transformRequestBodyFunction = null
this.matchHeaders = []
this.logger = debug
this.scopeOptions = options || {}
this.urlParts = {}
this._persist = false
this.contentLen = false
this.date = null
this.basePath = basePath
this.basePathname = ''
this.port = null
this._defaultReplyHeaders = []
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}`
}
}
add(key, interceptor) {
if (!(key in this.keyedInterceptors)) {
this.keyedInterceptors[key] = []
}
this.keyedInterceptors[key].push(interceptor)
addInterceptor(
this.basePath,
interceptor,
this,
this.scopeOptions,
this.urlParts.hostname
)
}
remove(key, interceptor) {
if (this._persist) {
return
}
const arr = this.keyedInterceptors[key]
if (arr) {
arr.splice(arr.indexOf(interceptor), 1)
if (arr.length === 0) {
delete this.keyedInterceptors[key]
}
}
}
intercept(uri, method, requestBody, interceptorOptions) {
const ic = new Interceptor(
this,
uri,
method,
requestBody,
interceptorOptions
)
this.interceptors.push(ic)
return ic
}
get(uri, requestBody, options) {
return this.intercept(uri, 'GET', requestBody, options)
}
post(uri, requestBody, options) {
return this.intercept(uri, 'POST', requestBody, options)
}
put(uri, requestBody, options) {
return this.intercept(uri, 'PUT', requestBody, options)
}
head(uri, requestBody, options) {
return this.intercept(uri, 'HEAD', requestBody, options)
}
patch(uri, requestBody, options) {
return this.intercept(uri, 'PATCH', requestBody, options)
}
merge(uri, requestBody, options) {
return this.intercept(uri, 'MERGE', requestBody, options)
}
delete(uri, requestBody, options) {
return this.intercept(uri, 'DELETE', requestBody, options)
}
options(uri, requestBody, options) {
return this.intercept(uri, 'OPTIONS', requestBody, options)
}
// Returns the list of keys for non-optional Interceptors that haven't been completed yet.
// TODO: This assumes that completed mocks are removed from the keyedInterceptors list
// (when persistence is off). We should change that (and this) in future.
pendingMocks() {
return this.activeMocks().filter(key =>
this.keyedInterceptors[key].some(({ interceptionCounter, optional }) => {
const persistedAndUsed = this._persist && interceptionCounter > 0
return !persistedAndUsed && !optional
})
)
}
// Returns all keyedInterceptors that are active.
// This includes incomplete interceptors, persisted but complete interceptors, and
// optional interceptors, but not non-persisted and completed interceptors.
activeMocks() {
return Object.keys(this.keyedInterceptors)
}
isDone() {
if (!isOn()) {
return true
}
return this.pendingMocks().length === 0
}
done() {
assert.ok(
this.isDone(),
`Mocks not yet satisfied:\n${this.pendingMocks().join('\n')}`
)
}
buildFilter() {
const filteringArguments = arguments
if (arguments[0] instanceof RegExp) {
return function(candidate) {
/* istanbul ignore if */
if (typeof candidate !== 'string') {
// Given the way nock is written, it seems like `candidate` will always
// be a string, regardless of what options might be passed to it.
// However the code used to contain a truthiness test of `candidate`.
// The check is being preserved for now.
throw Error(
`Nock internal assertion failed: typeof candidate is ${typeof candidate}. If you encounter this error, please report it as a bug.`
)
}
return candidate.replace(filteringArguments[0], filteringArguments[1])
}
} else if (typeof arguments[0] === 'function') {
return arguments[0]
}
}
filteringPath() {
this.transformPathFunction = this.buildFilter.apply(this, arguments)
if (!this.transformPathFunction) {
throw new Error(
'Invalid arguments: filtering path should be a function or a regular expression'
)
}
return this
}
filteringRequestBody() {
this.transformRequestBodyFunction = this.buildFilter.apply(this, arguments)
if (!this.transformRequestBodyFunction) {
throw new Error(
'Invalid arguments: filtering request body should be a function or a regular expression'
)
}
return this
}
matchHeader(name, value) {
// We use lower-case header field names throughout Nock.
this.matchHeaders.push({ name: name.toLowerCase(), value })
return this
}
defaultReplyHeaders(headers) {
this._defaultReplyHeaders = common.headersInputToRawArray(headers)
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')
}
this._persist = flag
return this
}
/**
* @private
* @returns {boolean}
*/
shouldPersist() {
return this._persist
}
replyContentLength() {
this.contentLen = true
return this
}
replyDate(d) {
this.date = d || new Date()
return this
}
}
function loadDefs(path) {
if (!fs) {
throw new Error('No fs')
}
const contents = fs.readFileSync(path)
return JSON.parse(contents)
}
function load(path) {
return define(loadDefs(path))
}
function getStatusFromDefinition(nockDef) {
// Backward compatibility for when `status` was encoded as string in `reply`.
if (nockDef.reply !== undefined) {
const parsedReply = parseInt(nockDef.reply, 10)
if (isNaN(parsedReply)) {
throw Error('`reply`, when present, must be a numeric string')
}
return parsedReply
}
const DEFAULT_STATUS_OK = 200
return nockDef.status || DEFAULT_STATUS_OK
}
function getScopeFromDefinition(nockDef) {
// Backward compatibility for when `port` was part of definition.
if (nockDef.port !== undefined) {
// Include `port` into scope if it doesn't exist.
const options = url.parse(nockDef.scope)
if (options.port === null) {
return `${nockDef.scope}:${nockDef.port}`
} else {
if (parseInt(options.port) !== parseInt(nockDef.port)) {
throw new Error(
'Mismatched port numbers in scope and port properties of nock definition.'
)
}
}
}
return nockDef.scope
}
function tryJsonParse(string) {
try {
return JSON.parse(string)
} catch (err) {
return 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) {
const nscope = getScopeFromDefinition(nockDef)
const npath = nockDef.path
if (!nockDef.method) {
throw Error('Method is required')
}
const method = nockDef.method.toLowerCase()
const status = getStatusFromDefinition(nockDef)
const rawHeaders = nockDef.rawHeaders || []
const reqheaders = nockDef.reqheaders || {}
const badheaders = nockDef.badheaders || []
const options = { ...nockDef.options }
// We use request headers for both filtering (see below) and mocking.
// Here we are setting up mocked request headers but we don't want to
// be changing the user's options object so we clone it first.
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`.
} else if (nockDef.responseIsBinary) {
response = Buffer.from(nockDef.response, 'hex')
} else {
response =
typeof nockDef.response === 'string'
? tryJsonParse(nockDef.response)
: nockDef.response
}
const scope = new Scope(nscope, options)
// If request headers were specified filter by them.
Object.entries(reqheaders).forEach(([fieldName, value]) => {
scope.matchHeader(fieldName, value)
})
const acceptableFilters = ['filteringRequestBody', 'filteringPath']
acceptableFilters.forEach(filter => {
if (nockDef[filter]) {
scope[filter](nockDef[filter])
}
})
scope.intercept(npath, method, body).reply(status, response, rawHeaders)
scopes.push(scope)
})
return scopes
}
module.exports = {
Scope,
load,
loadDefs,
define,
}

80
node_modules/nock/lib/socket.js generated vendored Normal file
View file

@ -0,0 +1,80 @@
'use strict'
const { EventEmitter } = require('events')
const debug = require('debug')('nock.socket')
module.exports = class Socket extends EventEmitter {
constructor(options) {
super()
if (options.proto === 'https') {
// https://github.com/nock/nock/issues/158
this.authorized = true
}
this.bufferSize = 0
this.writableLength = 0
this.writable = true
this.readable = true
this.pending = false
this.destroyed = false
this.connecting = false
// 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
const ipv6 = options.family === 6
this.remoteFamily = ipv6 ? 'IPv6' : 'IPv4'
this.localAddress = this.remoteAddress = ipv6 ? '::1' : '127.0.0.1'
this.localPort = this.remotePort = parseInt(options.port)
}
setNoDelay() {}
setKeepAlive() {}
resume() {}
ref() {}
unref() {}
address() {
return {
port: this.remotePort,
family: this.remoteFamily,
address: this.remoteAddress,
}
}
setTimeout(timeoutMs, fn) {
this.timeoutMs = timeoutMs
if (fn) {
this.once('timeout', fn)
}
}
applyDelay(delayMs) {
this.totalDelayMs += delayMs
if (this.timeoutMs && this.totalDelayMs > this.timeoutMs) {
debug('socket timeout')
this.emit('timeout')
}
}
getPeerCertificate() {
return Buffer.from(
(Math.random() * 10000 + Date.now()).toString()
).toString('base64')
}
destroy(err) {
this.destroyed = true
this.readable = this.writable = false
if (err) {
this.emit('error', err)
}
return this
}
}