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 | |