Spaces:
Runtime error
Runtime error
const { getResponseData, buildKey, addMockDispatch } = require('./mock-utils') | |
const { | |
kDispatches, | |
kDispatchKey, | |
kDefaultHeaders, | |
kDefaultTrailers, | |
kContentLength, | |
kMockDispatch | |
} = require('./mock-symbols') | |
const { InvalidArgumentError } = require('../core/errors') | |
const { buildURL } = require('../core/util') | |
/** | |
* Defines the scope API for an interceptor reply | |
*/ | |
class MockScope { | |
constructor (mockDispatch) { | |
this[kMockDispatch] = mockDispatch | |
} | |
/** | |
* Delay a reply by a set amount in ms. | |
*/ | |
delay (waitInMs) { | |
if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) { | |
throw new InvalidArgumentError('waitInMs must be a valid integer > 0') | |
} | |
this[kMockDispatch].delay = waitInMs | |
return this | |
} | |
/** | |
* For a defined reply, never mark as consumed. | |
*/ | |
persist () { | |
this[kMockDispatch].persist = true | |
return this | |
} | |
/** | |
* Allow one to define a reply for a set amount of matching requests. | |
*/ | |
times (repeatTimes) { | |
if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) { | |
throw new InvalidArgumentError('repeatTimes must be a valid integer > 0') | |
} | |
this[kMockDispatch].times = repeatTimes | |
return this | |
} | |
} | |
/** | |
* Defines an interceptor for a Mock | |
*/ | |
class MockInterceptor { | |
constructor (opts, mockDispatches) { | |
if (typeof opts !== 'object') { | |
throw new InvalidArgumentError('opts must be an object') | |
} | |
if (typeof opts.path === 'undefined') { | |
throw new InvalidArgumentError('opts.path must be defined') | |
} | |
if (typeof opts.method === 'undefined') { | |
opts.method = 'GET' | |
} | |
// See https://github.com/nodejs/undici/issues/1245 | |
// As per RFC 3986, clients are not supposed to send URI | |
// fragments to servers when they retrieve a document, | |
if (typeof opts.path === 'string') { | |
if (opts.query) { | |
opts.path = buildURL(opts.path, opts.query) | |
} else { | |
// Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811 | |
const parsedURL = new URL(opts.path, 'data://') | |
opts.path = parsedURL.pathname + parsedURL.search | |
} | |
} | |
if (typeof opts.method === 'string') { | |
opts.method = opts.method.toUpperCase() | |
} | |
this[kDispatchKey] = buildKey(opts) | |
this[kDispatches] = mockDispatches | |
this[kDefaultHeaders] = {} | |
this[kDefaultTrailers] = {} | |
this[kContentLength] = false | |
} | |
createMockScopeDispatchData (statusCode, data, responseOptions = {}) { | |
const responseData = getResponseData(data) | |
const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} | |
const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } | |
const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } | |
return { statusCode, data, headers, trailers } | |
} | |
validateReplyParameters (statusCode, data, responseOptions) { | |
if (typeof statusCode === 'undefined') { | |
throw new InvalidArgumentError('statusCode must be defined') | |
} | |
if (typeof data === 'undefined') { | |
throw new InvalidArgumentError('data must be defined') | |
} | |
if (typeof responseOptions !== 'object') { | |
throw new InvalidArgumentError('responseOptions must be an object') | |
} | |
} | |
/** | |
* Mock an undici request with a defined reply. | |
*/ | |
reply (replyData) { | |
// Values of reply aren't available right now as they | |
// can only be available when the reply callback is invoked. | |
if (typeof replyData === 'function') { | |
// We'll first wrap the provided callback in another function, | |
// this function will properly resolve the data from the callback | |
// when invoked. | |
const wrappedDefaultsCallback = (opts) => { | |
// Our reply options callback contains the parameter for statusCode, data and options. | |
const resolvedData = replyData(opts) | |
// Check if it is in the right format | |
if (typeof resolvedData !== 'object') { | |
throw new InvalidArgumentError('reply options callback must return an object') | |
} | |
const { statusCode, data = '', responseOptions = {} } = resolvedData | |
this.validateReplyParameters(statusCode, data, responseOptions) | |
// Since the values can be obtained immediately we return them | |
// from this higher order function that will be resolved later. | |
return { | |
...this.createMockScopeDispatchData(statusCode, data, responseOptions) | |
} | |
} | |
// Add usual dispatch data, but this time set the data parameter to function that will eventually provide data. | |
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback) | |
return new MockScope(newMockDispatch) | |
} | |
// We can have either one or three parameters, if we get here, | |
// we should have 1-3 parameters. So we spread the arguments of | |
// this function to obtain the parameters, since replyData will always | |
// just be the statusCode. | |
const [statusCode, data = '', responseOptions = {}] = [...arguments] | |
this.validateReplyParameters(statusCode, data, responseOptions) | |
// Send in-already provided data like usual | |
const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions) | |
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData) | |
return new MockScope(newMockDispatch) | |
} | |
/** | |
* Mock an undici request with a defined error. | |
*/ | |
replyWithError (error) { | |
if (typeof error === 'undefined') { | |
throw new InvalidArgumentError('error must be defined') | |
} | |
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error }) | |
return new MockScope(newMockDispatch) | |
} | |
/** | |
* Set default reply headers on the interceptor for subsequent replies | |
*/ | |
defaultReplyHeaders (headers) { | |
if (typeof headers === 'undefined') { | |
throw new InvalidArgumentError('headers must be defined') | |
} | |
this[kDefaultHeaders] = headers | |
return this | |
} | |
/** | |
* Set default reply trailers on the interceptor for subsequent replies | |
*/ | |
defaultReplyTrailers (trailers) { | |
if (typeof trailers === 'undefined') { | |
throw new InvalidArgumentError('trailers must be defined') | |
} | |
this[kDefaultTrailers] = trailers | |
return this | |
} | |
/** | |
* Set reply content length header for replies on the interceptor | |
*/ | |
replyContentLength () { | |
this[kContentLength] = true | |
return this | |
} | |
} | |
module.exports.MockInterceptor = MockInterceptor | |
module.exports.MockScope = MockScope | |