Socket
Socket
Sign inDemoInstall

nock

Package Overview
Dependencies
Maintainers
4
Versions
428
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nock - npm Package Compare versions

Comparing version 14.0.0-beta.7 to 14.0.0-beta.8

1

lib/back.js

@@ -80,3 +80,2 @@ 'use strict'

debug('context:', context)
// If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.

@@ -83,0 +82,0 @@ if (typeof nockedFn === 'function') {

125

lib/common.js

@@ -7,2 +7,3 @@ 'use strict'

const util = require('util')
const http = require('http')

@@ -54,79 +55,3 @@ /**

// 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 = require(proto)
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.

@@ -621,2 +546,3 @@ * However, the port is not included if it's standard and not already present on the host.

function removeAllTimers() {
debug('remove all timers')
clearTimer(clearTimeout, timeouts)

@@ -656,2 +582,27 @@ clearTimer(clearInterval, intervals)

/**
* @param {Request} request
*/
function convertFetchRequestToClientRequest(request) {
const url = new URL(request.url);
const options = {
...urlToOptions(url),
method: request.method,
host: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
proto: url.protocol.slice(0, -1),
headers: Object.fromEntries(request.headers.entries())
};
// By default, Node adds a host header, but for maximum backward compatibility, we are now removing it.
// However, we need to consider leaving the header and fixing the tests.
if (options.headers.host === options.host) {
const { host, ...restHeaders } = options.headers
options.headers = restHeaders
}
return new http.ClientRequest(options);
}
/**
* Returns true if the given value is a plain object and not an Array.

@@ -742,20 +693,2 @@ * @param {*} value

/**
* @param {Request} request
*/
function convertFetchRequestToOptions(request) {
const url = new URL(request.url)
const options = {
...urlToOptions(url),
method: request.method,
host: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
proto: url.protocol.slice(0, -1),
headers: Object.fromEntries(request.headers.entries()),
}
return options
}
module.exports = {

@@ -782,7 +715,5 @@ contentEncoding,

normalizeRequestOptions,
overrideRequests,
percentDecode,
percentEncode,
removeAllTimers,
restoreOverriddenRequests,
setImmediate,

@@ -792,3 +723,3 @@ setInterval,

stringifyRequest,
convertFetchRequestToClientRequest: convertFetchRequestToOptions,
convertFetchRequestToClientRequest,
}
'use strict'
const zlib = require('node:zlib')
const { headersArrayToObject } = require('./common')
const { STATUS_CODES } = require('http')
const { pipeline, Readable } = require('node:stream')

@@ -21,77 +18,35 @@ /**

/**
* @param {import('node:http').IncomingMessage} message
* @param {import('http').IncomingMessage} message
*/
function createResponse(message) {
// https://github.com/Uzlopak/undici/blob/main/lib/fetch/index.js#L2031
const decoders = []
const codings =
message.headers['content-encoding']
?.toLowerCase()
.split(',')
.map(x => x.trim())
.reverse() || []
for (const coding of codings) {
if (coding === 'gzip' || coding === 'x-gzip') {
decoders.push(
zlib.createGunzip({
flush: zlib.constants.Z_SYNC_FLUSH,
finishFlush: zlib.constants.Z_SYNC_FLUSH,
}),
)
} else if (coding === 'deflate') {
decoders.push(zlib.createInflate())
} else if (coding === 'br') {
decoders.push(zlib.createBrotliDecompress())
} else {
decoders.length = 0
break
}
}
let isCanceled = false
const chunks = []
const responseBodyOrNull = responseStatusCodesWithoutBody.includes(
message.statusCode,
message.statusCode || 200
)
? null
: new ReadableStream({
start(controller) {
message.on('data', chunk => chunks.push(chunk))
message.on('end', () => {
pipeline(
Readable.from(chunks),
...decoders,
async function* (source) {
for await (const chunk of source) {
yield controller.enqueue(chunk)
}
},
error => {
if (error) {
controller.error(error)
} else if (!isCanceled) {
controller.close()
}
},
)
})
start(controller) {
message.on('data', (chunk) => controller.enqueue(chunk))
message.on('end', () => controller.close())
message.on('error', (error) => controller.error(error))
},
cancel() {
message.destroy()
},
})
/**
* @todo Should also listen to the "error" on the message
* and forward it to the controller. Otherwise the stream
* will pend indefinitely.
*/
},
cancel() {
isCanceled = true
},
})
const rawHeaders = new Headers()
for (let i = 0; i < message.rawHeaders.length; i += 2) {
rawHeaders.append(message.rawHeaders[i], message.rawHeaders[i + 1])
}
return new Response(responseBodyOrNull, {
// @mswjs/interceptors supports rawHeaders. https://github.com/mswjs/interceptors/pull/598
const response = new Response(responseBodyOrNull, {
status: message.statusCode,
statusText: STATUS_CODES[message.statusCode],
headers: headersArrayToObject(message.rawHeaders),
statusText: message.statusMessage || STATUS_CODES[message.statusCode],
headers: rawHeaders,
})
return response
}
module.exports = { createResponse }

@@ -13,4 +13,14 @@ 'use strict'

const globalEmitter = require('./global_emitter')
const { BatchInterceptor } = require('@mswjs/interceptors')
const { FetchInterceptor } = require('@mswjs/interceptors/fetch')
const { default: nodeInterceptors } = require('@mswjs/interceptors/presets/node')
const { createResponse } = require('./create_response')
const { once } = require('events')
const interceptor = new BatchInterceptor({
name: 'nock-interceptor',
interceptors: [...nodeInterceptors, new FetchInterceptor()],
})
let isNockActive = false
/**

@@ -188,4 +198,3 @@ * @name NetConnectNotAllowedError

debug(
`matched base path (${interceptors.length} interceptor${
interceptors.length > 1 ? 's' : ''
`matched base path (${interceptors.length} interceptor${interceptors.length > 1 ? 's' : ''
})`,

@@ -243,16 +252,3 @@ )

let originalClientRequest
// Variable where we keep the fetch we have overridden
let originalFetch
function ErroringClientRequest(error) {
http.OutgoingMessage.call(this)
process.nextTick(
function () {
this.emit('error', error)
}.bind(this),
)
}
inherits(ErroringClientRequest, http.ClientRequest)
function overrideClientRequest() {

@@ -311,5 +307,3 @@ // Here's some background discussion about overriding ClientRequest:

if (isOff() || isEnabledForNetConnect(options)) {
if (options.isFetchRequest === undefined) {
originalClientRequest.apply(this, arguments)
}
originalClientRequest.apply(this, arguments)
} else {

@@ -345,8 +339,7 @@ common.setImmediate(

} else {
isNockActive = false;
interceptor.dispose()
http.ClientRequest = originalClientRequest
originalClientRequest = undefined
global.fetch = originalFetch
originalFetch = undefined
debug('- ClientRequest restored')

@@ -357,5 +350,3 @@ }

function isActive() {
// If ClientRequest has been overwritten by Nock then originalClientRequest is not undefined.
// This means that Nock has been activated.
return originalClientRequest !== undefined
return isNockActive
}

@@ -367,3 +358,4 @@

)
return [].concat(...nestedInterceptors).map(i => i.scope)
const scopes = new Set([].concat(...nestedInterceptors).map(i => i.scope))
return [...scopes]
}

@@ -384,93 +376,60 @@

function activate() {
if (originalClientRequest) {
if (isNockActive) {
throw new Error('Nock already active')
}
// ----- 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
overrideClientRequest()
interceptor.apply();
// Force msw to forward Nock's error instead of coerce it into 500 error
interceptor.on('unhandledException', ({ controller, error }) => {
controller.errorWith(error)
})
interceptor.on('request', async function ({ request: mswRequest, controller }) {
const request = mswRequest.clone()
const { options } = common.normalizeClientRequestArgs(request.url)
options.proto = options.protocol.slice(0, -1)
options.method = request.method
const interceptors = interceptorsFor(options)
if (isOn() && interceptors) {
const matches = interceptors.some(interceptor =>
interceptor.matchOrigin(options),
interceptor.matchOrigin(options)
)
const allowUnmocked = interceptors.some(
interceptor => interceptor.options.allowUnmocked,
interceptor => interceptor.options.allowUnmocked
)
const nockRequest = common.convertFetchRequestToClientRequest(request);
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
globalEmitter.emit('no match', nockRequest)
} else {
nockRequest.on('response', nockResponse => {
// TODO: Consider put empty headers object as default when create the ClientRequest
if (nockResponse.req.headers) {
// forward Nock request headers to the MSW request
Object.entries(nockResponse.req.headers).map(([k, v]) => mswRequest.headers.set(k, v))
}
const response = createResponse(nockResponse)
controller.respondWith(response)
})
const promise = Promise.race([
// TODO: temp hacky way to handle allowUnmocked in startPlayback
once(nockRequest, 'real-request'),
once(nockRequest, 'error'),
once(nockRequest, 'response'),
])
const buffer = await request.arrayBuffer()
nockRequest.write(buffer)
nockRequest.end()
await promise
}
// 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)
if (!(isOff() || isEnabledForNetConnect(options))) {
throw new NetConnectNotAllowedError(options.host, options.path)
}
}
})
originalFetch = global.fetch
global.fetch = async (input, init = undefined) => {
const request = new Request(input, init)
const options = common.convertFetchRequestToClientRequest(request)
options.isFetchRequest = true
const body = await request.arrayBuffer()
const clientRequest = new http.ClientRequest(options)
// If mock found
if (clientRequest.interceptors) {
return new Promise((resolve, reject) => {
clientRequest.on('response', response => {
resolve(createResponse(response))
})
clientRequest.on('error', reject)
clientRequest.end(body)
})
} else {
return originalFetch(input, init)
}
}
overrideClientRequest()
isNockActive = true
}

@@ -477,0 +436,0 @@

@@ -7,5 +7,3 @@ 'use strict'

ClientRequest,
request: originalHttpRequest,
} = require('http')
const { request: originalHttpsRequest } = require('https')
const propagate = require('propagate')

@@ -337,10 +335,3 @@ const common = require('./common')

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)
req.emit('real-request')
} else {

@@ -347,0 +338,0 @@ const reqStr = common.stringifyRequest(options, requestBodyString)

@@ -201,6 +201,12 @@ 'use strict'

}
const readStream = fs.createReadStream(filePath)
readStream.pause()
this.filePath = filePath
return this.reply(statusCode, readStream, headers)
return this.reply(
statusCode,
() => {
const readStream = fs.createReadStream(filePath)
readStream.pause()
return readStream
},
headers,
)
}

@@ -450,11 +456,2 @@

if (
(this.scope.shouldPersist() || this.counter > 0) &&
this.interceptionCounter > 1 &&
this.filePath
) {
this.body = fs.createReadStream(this.filePath)
this.body.pause()
}
remove(this)

@@ -508,3 +505,3 @@

if (queries instanceof URLSearchParams) {
if (queries instanceof URLSearchParams || typeof queries === 'string') {
// Normalize the data into the shape that is matched against.

@@ -511,0 +508,0 @@ // Duplicate keys are handled by combining the values into an array.

@@ -9,2 +9,6 @@ 'use strict'

const { restoreOverriddenClientRequest } = require('./intercept')
const { BatchInterceptor } = require('@mswjs/interceptors')
const { FetchInterceptor } = require('@mswjs/interceptors/fetch')
const { default: nodeInterceptors } = require('@mswjs/interceptors/presets/node')
const { EventEmitter } = require('stream')

@@ -15,2 +19,8 @@ const SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n'

// TODO: Consider use one BatchInterceptor (and not one for intercept and one for record)
const interceptor = new BatchInterceptor({
name: 'nock-interceptor',
interceptors: [...nodeInterceptors, new FetchInterceptor()],
})
function getScope(options) {

@@ -215,3 +225,3 @@ const { proto, host, port } = common.normalizeRequestOptions(options)

// behavior in the face of other modules also overriding ClientRequest.
common.restoreOverriddenRequests()
// common.restoreOverriddenRequests()
// We restore ClientRequest as it messes with recording of modules that also override ClientRequest (e.g. xhr2)

@@ -221,5 +231,8 @@ 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 = []
interceptor.apply();
interceptor.on('request', async function ({ request: mswRequest, requestId }) {
const request = mswRequest.clone()
const { options } = common.normalizeClientRequestArgs(request.url)
options.method = request.method
const proto = options.protocol.slice(0, -1)

@@ -230,11 +243,13 @@ // Node 0.11 https.request calls http.request -- don't want to record things

if (options._recording) {
return overriddenRequest(options, callback)
return
}
options._recording = true
const req = overriddenRequest(options, function (res) {
const req = new EventEmitter();
req.on('response', function () {
debug(thisRecordingId, 'intercepting', proto, 'request to record')
// We put our 'end' listener to the front of the listener array.
res.once('end', function () {
// Intercept "res.once('end', ...)"-like event
interceptor.once('response', async function ({ response: mswResponse }) {
const response = mswResponse.clone()
debug(thisRecordingId, proto, 'intercepted request ended')

@@ -247,6 +262,13 @@

// they actually make testing more difficult without providing any benefit (see README)
reqheaders = req.getHeaders()
reqheaders = Object.fromEntries(request.headers.entries())
common.deleteHeadersField(reqheaders, 'user-agent')
}
const headers = Object.fromEntries(response.headers.entries())
const res = {
statusCode: response.status,
headers,
rawHeaders: headers,
}
const generateFn = outputObjects

@@ -256,7 +278,7 @@ ? generateRequestAndResponseObject

let out = generateFn({
req,
bodyChunks,
req: options,
bodyChunks: [Buffer.from(await request.arrayBuffer())],
options,
res,
dataChunks,
dataChunks: [Buffer.from(await response.arrayBuffer())],
reqheaders,

@@ -294,29 +316,2 @@ })

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)
}
debug('finished setting up intercepting')

@@ -333,41 +328,6 @@

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
// This is a massive change, we are trying to change minimum code, so we emit end event here
// because mswjs take care for these events
// TODO: refactor the recorder, we no longer need all the listeners and can just record the request we get from MSW
req.emit('response')
})

@@ -383,3 +343,3 @@ }

common.restoreOverriddenRequests()
interceptor.dispose()
restoreOverriddenClientRequest()

@@ -386,0 +346,0 @@ recordingInProgress = false

@@ -10,3 +10,3 @@ {

],
"version": "14.0.0-beta.7",
"version": "14.0.0-beta.8",
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>",

@@ -26,2 +26,3 @@ "repository": {

"dependencies": {
"@mswjs/interceptors": "^0.33.1",
"json-stringify-safe": "^5.0.1",

@@ -49,3 +50,3 @@ "propagate": "^2.0.0"

"nyc": "^15.0.0",
"prettier": "3.1.0",
"prettier": "3.2.5",
"proxyquire": "^2.1.0",

@@ -52,0 +53,0 @@ "rimraf": "^3.0.0",

@@ -259,3 +259,3 @@ // TypeScript Version: 3.5

options: BackOptions,
nockedFn: (nockDone: () => void) => void,
nockedFn: (nockDone: () => Promise<void>) => void,
): void

@@ -266,3 +266,3 @@ (

): Promise<{
nockDone: () => void
nockDone: () => Promise<void>
context: BackContext

@@ -269,0 +269,0 @@ }>

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc