Permalink
Cannot retrieve contributors at this time
412 lines (354 sloc)
10.8 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/nock/lib/scope.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 Interceptor = require('./interceptor') | |
const { URL, Url: LegacyUrl } = url | |
let fs | |
try { | |
fs = require('fs') | |
} catch (err) { | |
// do nothing, we're in the browser | |
} | |
/** | |
* Normalizes the passed url for consistent internal processing | |
* @param {string|LegacyUrl|URL} u | |
*/ | |
function normalizeUrl(u) { | |
if (!(u instanceof URL)) { | |
if (u instanceof LegacyUrl) { | |
return normalizeUrl(new URL(url.format(u))) | |
} | |
// If the url is invalid, let the URL library report it | |
return normalizeUrl(new URL(u)) | |
} | |
if (!/https?:/.test(u.protocol)) { | |
throw new TypeError( | |
`Protocol '${u.protocol}' not recognized. This commonly occurs when a hostname and port are included without a protocol, producing a URL that is valid but confusing, and probably not what you want.` | |
) | |
} | |
return { | |
href: u.href, | |
origin: u.origin, | |
protocol: u.protocol, | |
username: u.username, | |
password: u.password, | |
host: u.host, | |
hostname: | |
// strip brackets from IPv6 | |
typeof u.hostname === 'string' && u.hostname.startsWith('[') | |
? u.hostname.slice(1, -1) | |
: u.hostname, | |
port: u.port || (u.protocol === 'http:' ? 80 : 443), | |
pathname: u.pathname, | |
search: u.search, | |
searchParams: u.searchParams, | |
hash: u.hash, | |
} | |
} | |
/** | |
* @param {string|RegExp|LegacyUrl|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.scopeOptions = options || {} | |
this.urlParts = {} | |
this._persist = false | |
this.contentLen = false | |
this.date = null | |
this.basePath = basePath | |
this.basePathname = '' | |
this.port = null | |
this._defaultReplyHeaders = [] | |
let logNamespace = String(basePath) | |
if (!(basePath instanceof RegExp)) { | |
this.urlParts = normalizeUrl(basePath) | |
this.port = this.urlParts.port | |
this.basePathname = this.urlParts.pathname.replace(/\/$/, '') | |
this.basePath = `${this.urlParts.protocol}//${this.urlParts.hostname}:${this.port}` | |
logNamespace = this.urlParts.host | |
} | |
this.logger = debug.extend(logNamespace) | |
} | |
add(key, interceptor) { | |
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 | |
} | |
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 | |
} | |
} | |
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 | |
// 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 `responseIsUtf8Representable`. | |
} 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, nockDef.body) | |
.reply(status, response, rawHeaders) | |
scopes.push(scope) | |
}) | |
return scopes | |
} | |
module.exports = { | |
Scope, | |
load, | |
loadDefs, | |
define, | |
} |