File Explorer

/var/lang/lib/node_modules/npm/node_modules/undici/lib/mock

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

mock-utils.js10.5 KB · 368 lines
'use strict' const { MockNotMatchedError } = require('./mock-errors')const {  kDispatches,  kMockAgent,  kOriginalDispatch,  kOrigin,  kGetNetConnect} = require('./mock-symbols')const { buildURL } = require('../core/util')const { STATUS_CODES } = require('node:http')const {  types: {    isPromise  }} = require('node:util') function matchValue (match, value) {  if (typeof match === 'string') {    return match === value  }  if (match instanceof RegExp) {    return match.test(value)  }  if (typeof match === 'function') {    return match(value) === true  }  return false} function lowerCaseEntries (headers) {  return Object.fromEntries(    Object.entries(headers).map(([headerName, headerValue]) => {      return [headerName.toLocaleLowerCase(), headerValue]    })  )} /** * @param {import('../../index').Headers|string[]|Record<string, string>} headers * @param {string} key */function getHeaderByName (headers, key) {  if (Array.isArray(headers)) {    for (let i = 0; i < headers.length; i += 2) {      if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) {        return headers[i + 1]      }    }     return undefined  } else if (typeof headers.get === 'function') {    return headers.get(key)  } else {    return lowerCaseEntries(headers)[key.toLocaleLowerCase()]  }} /** @param {string[]} headers */function buildHeadersFromArray (headers) { // fetch HeadersList  const clone = headers.slice()  const entries = []  for (let index = 0; index < clone.length; index += 2) {    entries.push([clone[index], clone[index + 1]])  }  return Object.fromEntries(entries)} function matchHeaders (mockDispatch, headers) {  if (typeof mockDispatch.headers === 'function') {    if (Array.isArray(headers)) { // fetch HeadersList      headers = buildHeadersFromArray(headers)    }    return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {})  }  if (typeof mockDispatch.headers === 'undefined') {    return true  }  if (typeof headers !== 'object' || typeof mockDispatch.headers !== 'object') {    return false  }   for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) {    const headerValue = getHeaderByName(headers, matchHeaderName)     if (!matchValue(matchHeaderValue, headerValue)) {      return false    }  }  return true} function safeUrl (path) {  if (typeof path !== 'string') {    return path  }   const pathSegments = path.split('?')   if (pathSegments.length !== 2) {    return path  }   const qp = new URLSearchParams(pathSegments.pop())  qp.sort()  return [...pathSegments, qp.toString()].join('?')} function matchKey (mockDispatch, { path, method, body, headers }) {  const pathMatch = matchValue(mockDispatch.path, path)  const methodMatch = matchValue(mockDispatch.method, method)  const bodyMatch = typeof mockDispatch.body !== 'undefined' ? matchValue(mockDispatch.body, body) : true  const headersMatch = matchHeaders(mockDispatch, headers)  return pathMatch && methodMatch && bodyMatch && headersMatch} function getResponseData (data) {  if (Buffer.isBuffer(data)) {    return data  } else if (data instanceof Uint8Array) {    return data  } else if (data instanceof ArrayBuffer) {    return data  } else if (typeof data === 'object') {    return JSON.stringify(data)  } else {    return data.toString()  }} function getMockDispatch (mockDispatches, key) {  const basePath = key.query ? buildURL(key.path, key.query) : key.path  const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath   // Match path  let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath))  if (matchedMockDispatches.length === 0) {    throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`)  }   // Match method  matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method))  if (matchedMockDispatches.length === 0) {    throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}' on path '${resolvedPath}'`)  }   // Match body  matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== 'undefined' ? matchValue(body, key.body) : true)  if (matchedMockDispatches.length === 0) {    throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}' on path '${resolvedPath}'`)  }   // Match headers  matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers))  if (matchedMockDispatches.length === 0) {    const headers = typeof key.headers === 'object' ? JSON.stringify(key.headers) : key.headers    throw new MockNotMatchedError(`Mock dispatch not matched for headers '${headers}' on path '${resolvedPath}'`)  }   return matchedMockDispatches[0]} function addMockDispatch (mockDispatches, key, data) {  const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false }  const replyData = typeof data === 'function' ? { callback: data } : { ...data }  const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } }  mockDispatches.push(newMockDispatch)  return newMockDispatch} function deleteMockDispatch (mockDispatches, key) {  const index = mockDispatches.findIndex(dispatch => {    if (!dispatch.consumed) {      return false    }    return matchKey(dispatch, key)  })  if (index !== -1) {    mockDispatches.splice(index, 1)  }} function buildKey (opts) {  const { path, method, body, headers, query } = opts  return {    path,    method,    body,    headers,    query  }} function generateKeyValues (data) {  const keys = Object.keys(data)  const result = []  for (let i = 0; i < keys.length; ++i) {    const key = keys[i]    const value = data[key]    const name = Buffer.from(`${key}`)    if (Array.isArray(value)) {      for (let j = 0; j < value.length; ++j) {        result.push(name, Buffer.from(`${value[j]}`))      }    } else {      result.push(name, Buffer.from(`${value}`))    }  }  return result} /** * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status * @param {number} statusCode */function getStatusText (statusCode) {  return STATUS_CODES[statusCode] || 'unknown'} async function getResponse (body) {  const buffers = []  for await (const data of body) {    buffers.push(data)  }  return Buffer.concat(buffers).toString('utf8')} /** * Mock dispatch function used to simulate undici dispatches */function mockDispatch (opts, handler) {  // Get mock dispatch from built key  const key = buildKey(opts)  const mockDispatch = getMockDispatch(this[kDispatches], key)   mockDispatch.timesInvoked++   // Here's where we resolve a callback if a callback is present for the dispatch data.  if (mockDispatch.data.callback) {    mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) }  }   // Parse mockDispatch data  const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch  const { timesInvoked, times } = mockDispatch   // If it's used up and not persistent, mark as consumed  mockDispatch.consumed = !persist && timesInvoked >= times  mockDispatch.pending = timesInvoked < times   // If specified, trigger dispatch error  if (error !== null) {    deleteMockDispatch(this[kDispatches], key)    handler.onError(error)    return true  }   // Handle the request with a delay if necessary  if (typeof delay === 'number' && delay > 0) {    setTimeout(() => {      handleReply(this[kDispatches])    }, delay)  } else {    handleReply(this[kDispatches])  }   function handleReply (mockDispatches, _data = data) {    // fetch's HeadersList is a 1D string array    const optsHeaders = Array.isArray(opts.headers)      ? buildHeadersFromArray(opts.headers)      : opts.headers    const body = typeof _data === 'function'      ? _data({ ...opts, headers: optsHeaders })      : _data     // util.types.isPromise is likely needed for jest.    if (isPromise(body)) {      // If handleReply is asynchronous, throwing an error      // in the callback will reject the promise, rather than      // synchronously throw the error, which breaks some tests.      // Rather, we wait for the callback to resolve if it is a      // promise, and then re-run handleReply with the new body.      body.then((newData) => handleReply(mockDispatches, newData))      return    }     const responseData = getResponseData(body)    const responseHeaders = generateKeyValues(headers)    const responseTrailers = generateKeyValues(trailers)     handler.onConnect?.(err => handler.onError(err), null)    handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode))    handler.onData?.(Buffer.from(responseData))    handler.onComplete?.(responseTrailers)    deleteMockDispatch(mockDispatches, key)  }   function resume () {}   return true} function buildMockDispatch () {  const agent = this[kMockAgent]  const origin = this[kOrigin]  const originalDispatch = this[kOriginalDispatch]   return function dispatch (opts, handler) {    if (agent.isMockActive) {      try {        mockDispatch.call(this, opts, handler)      } catch (error) {        if (error instanceof MockNotMatchedError) {          const netConnect = agent[kGetNetConnect]()          if (netConnect === false) {            throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`)          }          if (checkNetConnect(netConnect, origin)) {            originalDispatch.call(this, opts, handler)          } else {            throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`)          }        } else {          throw error        }      }    } else {      originalDispatch.call(this, opts, handler)    }  }} function checkNetConnect (netConnect, origin) {  const url = new URL(origin)  if (netConnect === true) {    return true  } else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) {    return true  }  return false} function buildMockOptions (opts) {  if (opts) {    const { agent, ...mockOptions } = opts    return mockOptions  }} module.exports = {  getResponseData,  getMockDispatch,  addMockDispatch,  deleteMockDispatch,  buildKey,  generateKeyValues,  matchValue,  getResponse,  getStatusText,  mockDispatch,  buildMockDispatch,  checkNetConnect,  buildMockOptions,  getHeaderByName,  buildHeadersFromArray}