update @actions/tool-cache, install semver, nock
This commit is contained in:
parent
74d434c5ca
commit
4c6749115a
678 changed files with 39259 additions and 14619 deletions
3
node_modules/nock/CHANGELOG.md
generated
vendored
Normal file
3
node_modules/nock/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog
|
||||
|
||||
Nock’s changelog can be found directly in the [GitHub release notes](https://github.com/nock/nock/releases). These are automatically created by [semantic-release](https://github.com/semantic-release/semantic-release) based on their [commit message conventions](https://semantic-release.gitbook.io/semantic-release#commit-message-format).
|
||||
21
node_modules/nock/LICENSE
generated
vendored
Normal file
21
node_modules/nock/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2011-2019 Pedro Teixeira and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1617
node_modules/nock/README.md
generated
vendored
Normal file
1617
node_modules/nock/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
53
node_modules/nock/index.js
generated
vendored
Normal file
53
node_modules/nock/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
'use strict'
|
||||
|
||||
const back = require('./lib/back')
|
||||
const emitter = require('./lib/global_emitter')
|
||||
const {
|
||||
activate,
|
||||
isActive,
|
||||
isDone,
|
||||
isOn,
|
||||
pendingMocks,
|
||||
activeMocks,
|
||||
removeInterceptor,
|
||||
disableNetConnect,
|
||||
enableNetConnect,
|
||||
removeAll,
|
||||
abortPendingRequests,
|
||||
} = require('./lib/intercept')
|
||||
const recorder = require('./lib/recorder')
|
||||
const { Scope, load, loadDefs, define } = require('./lib/scope')
|
||||
|
||||
module.exports = (basePath, options) => new Scope(basePath, options)
|
||||
|
||||
Object.assign(module.exports, {
|
||||
activate,
|
||||
isActive,
|
||||
isDone,
|
||||
pendingMocks,
|
||||
activeMocks,
|
||||
removeInterceptor,
|
||||
disableNetConnect,
|
||||
enableNetConnect,
|
||||
cleanAll: removeAll,
|
||||
abortPendingRequests,
|
||||
load,
|
||||
loadDefs,
|
||||
define,
|
||||
emitter,
|
||||
recorder: {
|
||||
rec: recorder.record,
|
||||
clear: recorder.clear,
|
||||
play: recorder.outputs,
|
||||
},
|
||||
restore: recorder.restore,
|
||||
back,
|
||||
})
|
||||
|
||||
// We always activate Nock on import, overriding the globals.
|
||||
// Setting the Back mode "activates" Nock by overriding the global entries in the `http/s` modules.
|
||||
// If Nock Back is configured, we need to honor that setting for backward compatibility,
|
||||
// otherwise we rely on Nock Back's default initializing side effect.
|
||||
if (isOn()) {
|
||||
back.setMode(process.env.NOCK_BACK_MODE || 'dryrun')
|
||||
}
|
||||
279
node_modules/nock/lib/back.js
generated
vendored
Normal file
279
node_modules/nock/lib/back.js
generated
vendored
Normal 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
645
node_modules/nock/lib/common.js
generated
vendored
Normal 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
52
node_modules/nock/lib/delayed_body.js
generated
vendored
Normal 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
5
node_modules/nock/lib/global_emitter.js
generated
vendored
Normal 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
449
node_modules/nock/lib/intercept.js
generated
vendored
Normal 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
320
node_modules/nock/lib/intercepted_request_router.js
generated
vendored
Normal 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
619
node_modules/nock/lib/interceptor.js
generated
vendored
Normal 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
77
node_modules/nock/lib/match_body.js
generated
vendored
Normal 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
348
node_modules/nock/lib/playback_interceptor.js
generated
vendored
Normal 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
386
node_modules/nock/lib/recorder.js
generated
vendored
Normal 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
391
node_modules/nock/lib/scope.js
generated
vendored
Normal 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
80
node_modules/nock/lib/socket.js
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
117
node_modules/nock/package.json
generated
vendored
Normal file
117
node_modules/nock/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"_from": "nock",
|
||||
"_id": "nock@12.0.3",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==",
|
||||
"_location": "/nock",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "nock",
|
||||
"name": "nock",
|
||||
"escapedName": "nock",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#DEV:/",
|
||||
"#USER",
|
||||
"/@types/nock"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/nock/-/nock-12.0.3.tgz",
|
||||
"_shasum": "83f25076dbc4c9aa82b5cdf54c9604c7a778d1c9",
|
||||
"_spec": "nock",
|
||||
"_where": "/Users/alexkappa/Code/ts/github.com/github/codeql-action",
|
||||
"author": {
|
||||
"name": "Pedro Teixeira",
|
||||
"email": "pedro.teixeira@gmail.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/nock/nock/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"lodash": "^4.17.13",
|
||||
"propagate": "^2.0.0"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "HTTP server mocking and expectations library for Node.js",
|
||||
"devDependencies": {
|
||||
"@sinonjs/fake-timers": "^6.0.0",
|
||||
"assert-rejects": "^1.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"dtslint": "^3.0.0",
|
||||
"eslint": "^6.0.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-config-standard": "^14.0.0",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-mocha": "^6.2.0",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"got": "^10.5.2",
|
||||
"mocha": "^7.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "1.19.0",
|
||||
"proxyquire": "^2.1.0",
|
||||
"request": "^2.83.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"semantic-release": "^17.0.2",
|
||||
"sinon": "^9.0.0",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"superagent": "^5.0.2",
|
||||
"tap": "14.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"types/index.d.ts"
|
||||
],
|
||||
"homepage": "https://github.com/nock/nock#readme",
|
||||
"license": "MIT",
|
||||
"main": "./index.js",
|
||||
"name": "nock",
|
||||
"nyc": {
|
||||
"reporter": [
|
||||
"lcov",
|
||||
"text-summary"
|
||||
],
|
||||
"exclude": [
|
||||
"tests/"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nock/nock.git"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --check '**/*.{js,json,md,ts,yml,yaml}'",
|
||||
"format:fix": "prettier --write '**/*.{js,json,md,ts,yml,yaml}'",
|
||||
"lint": "run-p lint:js lint:ts",
|
||||
"lint:js": "eslint --cache --cache-location './.cache/eslint' '**/*.js'",
|
||||
"lint:js:fix": "eslint --cache --cache-location './.cache/eslint' --fix '**/*.js'",
|
||||
"lint:ts": "dtslint types",
|
||||
"semantic-release": "semantic-release",
|
||||
"test": "run-s test:mocha test:tap",
|
||||
"test:coverage": "tap --coverage-report=html && open coverage/lcov-report/index.html",
|
||||
"test:mocha": "nyc mocha $(grep -lr '^\\s*it(' tests)",
|
||||
"test:tap": "tap --100 --coverage --coverage-report=text ./tests/test_*.js"
|
||||
},
|
||||
"tags": [
|
||||
"Mock",
|
||||
"HTTP",
|
||||
"testing",
|
||||
"isolation"
|
||||
],
|
||||
"types": "types",
|
||||
"version": "12.0.3"
|
||||
}
|
||||
278
node_modules/nock/types/index.d.ts
generated
vendored
Normal file
278
node_modules/nock/types/index.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// TypeScript Version: 3.5
|
||||
|
||||
import { ReadStream } from 'fs'
|
||||
import { ClientRequest, IncomingMessage, RequestOptions } from 'http'
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { Url, URLSearchParams } from 'url'
|
||||
|
||||
export = nock
|
||||
|
||||
declare function nock(
|
||||
basePath: string | RegExp | Url,
|
||||
options?: nock.Options
|
||||
): nock.Scope
|
||||
|
||||
declare namespace nock {
|
||||
function cleanAll(): void
|
||||
function activate(): void
|
||||
function isActive(): boolean
|
||||
function isDone(): boolean
|
||||
function pendingMocks(): string[]
|
||||
function activeMocks(): string[]
|
||||
function removeInterceptor(interceptor: Interceptor | ReqOptions): boolean
|
||||
function disableNetConnect(): void
|
||||
function enableNetConnect(
|
||||
matcher?: string | RegExp | ((host: string) => boolean)
|
||||
): void
|
||||
function load(path: string): Scope[]
|
||||
function loadDefs(path: string): Definition[]
|
||||
function define(defs: Definition[]): Scope[]
|
||||
function restore(): void
|
||||
function abortPendingRequests(): void
|
||||
|
||||
let back: Back
|
||||
let emitter: NodeJS.EventEmitter
|
||||
let recorder: Recorder
|
||||
|
||||
type InterceptFunction = (
|
||||
uri: string | RegExp | { (uri: string): boolean },
|
||||
requestBody?: RequestBodyMatcher,
|
||||
interceptorOptions?: Options
|
||||
) => Interceptor
|
||||
|
||||
// Essentially valid, decoded JSON with the addition of possible RegExp. TS doesn't currently have
|
||||
// a great way to represent JSON type data, this data matcher design is based off this comment.
|
||||
// https://github.com/microsoft/TypeScript/issues/1897#issuecomment-338650717
|
||||
type DataMatcher =
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| null
|
||||
| undefined
|
||||
| RegExp
|
||||
| DataMatcherArray
|
||||
| DataMatcherMap
|
||||
interface DataMatcherArray extends Array<DataMatcher> {}
|
||||
interface DataMatcherMap {
|
||||
[key: string]: DataMatcher
|
||||
}
|
||||
|
||||
type RequestBodyMatcher =
|
||||
| string
|
||||
| Buffer
|
||||
| RegExp
|
||||
| DataMatcherArray
|
||||
| DataMatcherMap
|
||||
| { (body: any): boolean }
|
||||
|
||||
type RequestHeaderMatcher =
|
||||
| string
|
||||
| RegExp
|
||||
| { (fieldValue: string): boolean }
|
||||
|
||||
type Body = string | Record<string, any> // a string or decoded JSON
|
||||
type ReplyBody = Body | Buffer | ReadStream
|
||||
|
||||
type ReplyHeaderFunction = (
|
||||
req: ClientRequest,
|
||||
res: IncomingMessage,
|
||||
body: string | Buffer
|
||||
) => string | string[]
|
||||
type ReplyHeaderValue = string | string[] | ReplyHeaderFunction
|
||||
type ReplyHeaders =
|
||||
| Record<string, ReplyHeaderValue>
|
||||
| Map<string, ReplyHeaderValue>
|
||||
| ReplyHeaderValue[]
|
||||
|
||||
type StatusCode = number
|
||||
type ReplyFnResult =
|
||||
| readonly [StatusCode]
|
||||
| readonly [StatusCode, ReplyBody]
|
||||
| readonly [StatusCode, ReplyBody, ReplyHeaders]
|
||||
|
||||
interface ReplyFnContext extends Interceptor {
|
||||
req: ClientRequest & {
|
||||
headers: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
interface Scope extends NodeJS.EventEmitter {
|
||||
get: InterceptFunction
|
||||
post: InterceptFunction
|
||||
put: InterceptFunction
|
||||
head: InterceptFunction
|
||||
patch: InterceptFunction
|
||||
merge: InterceptFunction
|
||||
delete: InterceptFunction
|
||||
options: InterceptFunction
|
||||
|
||||
intercept: (
|
||||
uri: string | RegExp | { (uri: string): boolean },
|
||||
method: string,
|
||||
requestBody?: RequestBodyMatcher,
|
||||
options?: Options
|
||||
) => Interceptor
|
||||
|
||||
defaultReplyHeaders(headers: ReplyHeaders): this
|
||||
matchHeader(name: string, value: RequestHeaderMatcher): this
|
||||
filteringPath(regex: RegExp, replace: string): this
|
||||
filteringPath(fn: (path: string) => string): this
|
||||
filteringRequestBody(regex: RegExp, replace: string): this
|
||||
filteringRequestBody(fn: (body: string) => string): this
|
||||
|
||||
log(out: (message: any, ...optionalParams: any[]) => void): this
|
||||
persist(flag?: boolean): this
|
||||
replyContentLength(): this
|
||||
replyDate(d?: Date): this
|
||||
|
||||
done(): void
|
||||
isDone(): boolean
|
||||
pendingMocks(): string[]
|
||||
activeMocks(): string[]
|
||||
}
|
||||
|
||||
interface Interceptor {
|
||||
query(
|
||||
matcher:
|
||||
| boolean
|
||||
| string
|
||||
| DataMatcherMap
|
||||
| URLSearchParams
|
||||
| { (parsedObj: ParsedUrlQuery): boolean }
|
||||
): this
|
||||
|
||||
// tslint (as of 5.16) is under the impression that the callback types can be unified,
|
||||
// however, doing so causes the params to lose their inherited types during use.
|
||||
// the order of the overrides is important for determining the param types in the replay fns.
|
||||
/* tslint:disable:unified-signatures */
|
||||
reply(
|
||||
replyFnWithCallback: (
|
||||
this: ReplyFnContext,
|
||||
uri: string,
|
||||
body: Body,
|
||||
callback: (
|
||||
err: NodeJS.ErrnoException | null,
|
||||
result: ReplyFnResult
|
||||
) => void
|
||||
) => void
|
||||
): Scope
|
||||
reply(
|
||||
replyFn: (
|
||||
this: ReplyFnContext,
|
||||
uri: string,
|
||||
body: Body
|
||||
) => ReplyFnResult | Promise<ReplyFnResult>
|
||||
): Scope
|
||||
reply(
|
||||
statusCode: StatusCode,
|
||||
replyBodyFnWithCallback: (
|
||||
this: ReplyFnContext,
|
||||
uri: string,
|
||||
body: Body,
|
||||
callback: (err: NodeJS.ErrnoException | null, result: ReplyBody) => void
|
||||
) => void,
|
||||
headers?: ReplyHeaders
|
||||
): Scope
|
||||
reply(
|
||||
statusCode: StatusCode,
|
||||
replyBodyFn: (
|
||||
this: ReplyFnContext,
|
||||
uri: string,
|
||||
body: Body
|
||||
) => ReplyBody | Promise<ReplyBody>,
|
||||
headers?: ReplyHeaders
|
||||
): Scope
|
||||
reply(responseCode?: StatusCode, body?: Body, headers?: ReplyHeaders): Scope
|
||||
/* tslint:enable:unified-signatures */
|
||||
|
||||
replyWithError(errorMessage: string | object): Scope
|
||||
replyWithFile(
|
||||
statusCode: StatusCode,
|
||||
fileName: string,
|
||||
headers?: ReplyHeaders
|
||||
): Scope
|
||||
|
||||
matchHeader(name: string, value: RequestHeaderMatcher): this
|
||||
basicAuth(options: { user: string; pass?: string }): this
|
||||
|
||||
times(newCounter: number): this
|
||||
once(): this
|
||||
twice(): this
|
||||
thrice(): this
|
||||
optionally(): this
|
||||
|
||||
delay(opts: number | { head?: number; body?: number }): this
|
||||
delayBody(timeMs: number): this
|
||||
delayConnection(timeMs: number): this
|
||||
socketDelay(timeMs: number): this
|
||||
}
|
||||
|
||||
interface Options {
|
||||
allowUnmocked?: boolean
|
||||
reqheaders?: Record<string, RequestHeaderMatcher>
|
||||
badheaders?: string[]
|
||||
filteringScope?: { (scope: string): boolean }
|
||||
encodedQueryParams?: boolean
|
||||
}
|
||||
|
||||
interface Recorder {
|
||||
rec(options?: boolean | RecorderOptions): void
|
||||
clear(): void
|
||||
play(): string[] | Definition[]
|
||||
}
|
||||
|
||||
interface RecorderOptions {
|
||||
dont_print?: boolean
|
||||
output_objects?: boolean
|
||||
enable_reqheaders_recording?: boolean
|
||||
logging?: (content: string) => void
|
||||
use_separator?: boolean
|
||||
}
|
||||
|
||||
interface Definition {
|
||||
scope: string
|
||||
path: string
|
||||
port?: number | string
|
||||
method?: string
|
||||
status?: number
|
||||
body?: RequestBodyMatcher
|
||||
reqheaders?: Record<string, RequestHeaderMatcher>
|
||||
response?: ReplyBody
|
||||
headers?: ReplyHeaders
|
||||
options?: Options
|
||||
}
|
||||
|
||||
type BackMode = 'wild' | 'dryrun' | 'record' | 'lockdown'
|
||||
|
||||
interface Back {
|
||||
currentMode: BackMode
|
||||
fixtures: string
|
||||
setMode(mode: BackMode): void
|
||||
|
||||
(fixtureName: string, nockedFn: (nockDone: () => void) => void): void
|
||||
(
|
||||
fixtureName: string,
|
||||
options: BackOptions,
|
||||
nockedFn: (nockDone: () => void) => void
|
||||
): void
|
||||
(fixtureName: string, options?: BackOptions): Promise<{
|
||||
nockDone: () => void
|
||||
context: BackContext
|
||||
}>
|
||||
}
|
||||
|
||||
interface BackContext {
|
||||
isLoaded: boolean
|
||||
scopes: Scope[]
|
||||
assertScopesFinished(): void
|
||||
}
|
||||
|
||||
interface BackOptions {
|
||||
before?: (def: Definition) => void
|
||||
after?: (scope: Scope) => void
|
||||
afterRecord?: (defs: Definition[]) => Definition[]
|
||||
recorder?: RecorderOptions
|
||||
}
|
||||
}
|
||||
|
||||
type ReqOptions = RequestOptions & { proto?: string }
|
||||
Loading…
Add table
Add a link
Reference in a new issue