Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

224
deps/undici/src/lib/mock/mock-agent.js vendored Normal file
View File

@ -0,0 +1,224 @@
'use strict'
const { kClients } = require('../core/symbols')
const Agent = require('../dispatcher/agent')
const {
kAgent,
kMockAgentSet,
kMockAgentGet,
kDispatches,
kIsMockActive,
kNetConnect,
kGetNetConnect,
kOptions,
kFactory,
kMockAgentRegisterCallHistory,
kMockAgentIsCallHistoryEnabled,
kMockAgentAddCallHistoryLog,
kMockAgentMockCallHistoryInstance,
kMockAgentAcceptsNonStandardSearchParameters,
kMockCallHistoryAddLog
} = require('./mock-symbols')
const MockClient = require('./mock-client')
const MockPool = require('./mock-pool')
const { matchValue, normalizeSearchParams, buildAndValidateMockOptions } = require('./mock-utils')
const { InvalidArgumentError, UndiciError } = require('../core/errors')
const Dispatcher = require('../dispatcher/dispatcher')
const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
const { MockCallHistory } = require('./mock-call-history')
class MockAgent extends Dispatcher {
constructor (opts) {
super(opts)
const mockOptions = buildAndValidateMockOptions(opts)
this[kNetConnect] = true
this[kIsMockActive] = true
this[kMockAgentIsCallHistoryEnabled] = mockOptions?.enableCallHistory ?? false
this[kMockAgentAcceptsNonStandardSearchParameters] = mockOptions?.acceptNonStandardSearchParameters ?? false
// Instantiate Agent and encapsulate
if (opts?.agent && typeof opts.agent.dispatch !== 'function') {
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
}
const agent = opts?.agent ? opts.agent : new Agent(opts)
this[kAgent] = agent
this[kClients] = agent[kClients]
this[kOptions] = mockOptions
if (this[kMockAgentIsCallHistoryEnabled]) {
this[kMockAgentRegisterCallHistory]()
}
}
get (origin) {
let dispatcher = this[kMockAgentGet](origin)
if (!dispatcher) {
dispatcher = this[kFactory](origin)
this[kMockAgentSet](origin, dispatcher)
}
return dispatcher
}
dispatch (opts, handler) {
// Call MockAgent.get to perform additional setup before dispatching as normal
this.get(opts.origin)
this[kMockAgentAddCallHistoryLog](opts)
const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters]
const dispatchOpts = { ...opts }
if (acceptNonStandardSearchParameters && dispatchOpts.path) {
const [path, searchParams] = dispatchOpts.path.split('?')
const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters)
dispatchOpts.path = `${path}?${normalizedSearchParams}`
}
return this[kAgent].dispatch(dispatchOpts, handler)
}
async close () {
this.clearCallHistory()
await this[kAgent].close()
this[kClients].clear()
}
deactivate () {
this[kIsMockActive] = false
}
activate () {
this[kIsMockActive] = true
}
enableNetConnect (matcher) {
if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) {
if (Array.isArray(this[kNetConnect])) {
this[kNetConnect].push(matcher)
} else {
this[kNetConnect] = [matcher]
}
} else if (typeof matcher === 'undefined') {
this[kNetConnect] = true
} else {
throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.')
}
}
disableNetConnect () {
this[kNetConnect] = false
}
enableCallHistory () {
this[kMockAgentIsCallHistoryEnabled] = true
return this
}
disableCallHistory () {
this[kMockAgentIsCallHistoryEnabled] = false
return this
}
getCallHistory () {
return this[kMockAgentMockCallHistoryInstance]
}
clearCallHistory () {
if (this[kMockAgentMockCallHistoryInstance] !== undefined) {
this[kMockAgentMockCallHistoryInstance].clear()
}
}
// This is required to bypass issues caused by using global symbols - see:
// https://github.com/nodejs/undici/issues/1447
get isMockActive () {
return this[kIsMockActive]
}
[kMockAgentRegisterCallHistory] () {
if (this[kMockAgentMockCallHistoryInstance] === undefined) {
this[kMockAgentMockCallHistoryInstance] = new MockCallHistory()
}
}
[kMockAgentAddCallHistoryLog] (opts) {
if (this[kMockAgentIsCallHistoryEnabled]) {
// additional setup when enableCallHistory class method is used after mockAgent instantiation
this[kMockAgentRegisterCallHistory]()
// add call history log on every call (intercepted or not)
this[kMockAgentMockCallHistoryInstance][kMockCallHistoryAddLog](opts)
}
}
[kMockAgentSet] (origin, dispatcher) {
this[kClients].set(origin, { count: 0, dispatcher })
}
[kFactory] (origin) {
const mockOptions = Object.assign({ agent: this }, this[kOptions])
return this[kOptions] && this[kOptions].connections === 1
? new MockClient(origin, mockOptions)
: new MockPool(origin, mockOptions)
}
[kMockAgentGet] (origin) {
// First check if we can immediately find it
const result = this[kClients].get(origin)
if (result?.dispatcher) {
return result.dispatcher
}
// If the origin is not a string create a dummy parent pool and return to user
if (typeof origin !== 'string') {
const dispatcher = this[kFactory]('http://localhost:9999')
this[kMockAgentSet](origin, dispatcher)
return dispatcher
}
// If we match, create a pool and assign the same dispatches
for (const [keyMatcher, result] of Array.from(this[kClients])) {
if (result && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
const dispatcher = this[kFactory](origin)
this[kMockAgentSet](origin, dispatcher)
dispatcher[kDispatches] = result.dispatcher[kDispatches]
return dispatcher
}
}
}
[kGetNetConnect] () {
return this[kNetConnect]
}
pendingInterceptors () {
const mockAgentClients = this[kClients]
return Array.from(mockAgentClients.entries())
.flatMap(([origin, result]) => result.dispatcher[kDispatches].map(dispatch => ({ ...dispatch, origin })))
.filter(({ pending }) => pending)
}
assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) {
const pending = this.pendingInterceptors()
if (pending.length === 0) {
return
}
throw new UndiciError(
pending.length === 1
? `1 interceptor is pending:\n\n${pendingInterceptorsFormatter.format(pending)}`.trim()
: `${pending.length} interceptors are pending:\n\n${pendingInterceptorsFormatter.format(pending)}`.trim()
)
}
}
module.exports = MockAgent

View File

@ -0,0 +1,248 @@
'use strict'
const { kMockCallHistoryAddLog } = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')
function handleFilterCallsWithOptions (criteria, options, handler, store) {
switch (options.operator) {
case 'OR':
store.push(...handler(criteria))
return store
case 'AND':
return handler.call({ logs: store }, criteria)
default:
// guard -- should never happens because buildAndValidateFilterCallsOptions is called before
throw new InvalidArgumentError('options.operator must to be a case insensitive string equal to \'OR\' or \'AND\'')
}
}
function buildAndValidateFilterCallsOptions (options = {}) {
const finalOptions = {}
if ('operator' in options) {
if (typeof options.operator !== 'string' || (options.operator.toUpperCase() !== 'OR' && options.operator.toUpperCase() !== 'AND')) {
throw new InvalidArgumentError('options.operator must to be a case insensitive string equal to \'OR\' or \'AND\'')
}
return {
...finalOptions,
operator: options.operator.toUpperCase()
}
}
return finalOptions
}
function makeFilterCalls (parameterName) {
return (parameterValue) => {
if (typeof parameterValue === 'string' || parameterValue == null) {
return this.logs.filter((log) => {
return log[parameterName] === parameterValue
})
}
if (parameterValue instanceof RegExp) {
return this.logs.filter((log) => {
return parameterValue.test(log[parameterName])
})
}
throw new InvalidArgumentError(`${parameterName} parameter should be one of string, regexp, undefined or null`)
}
}
function computeUrlWithMaybeSearchParameters (requestInit) {
// path can contains query url parameters
// or query can contains query url parameters
try {
const url = new URL(requestInit.path, requestInit.origin)
// requestInit.path contains query url parameters
// requestInit.query is then undefined
if (url.search.length !== 0) {
return url
}
// requestInit.query can be populated here
url.search = new URLSearchParams(requestInit.query).toString()
return url
} catch (error) {
throw new InvalidArgumentError('An error occurred when computing MockCallHistoryLog.url', { cause: error })
}
}
class MockCallHistoryLog {
constructor (requestInit = {}) {
this.body = requestInit.body
this.headers = requestInit.headers
this.method = requestInit.method
const url = computeUrlWithMaybeSearchParameters(requestInit)
this.fullUrl = url.toString()
this.origin = url.origin
this.path = url.pathname
this.searchParams = Object.fromEntries(url.searchParams)
this.protocol = url.protocol
this.host = url.host
this.port = url.port
this.hash = url.hash
}
toMap () {
return new Map([
['protocol', this.protocol],
['host', this.host],
['port', this.port],
['origin', this.origin],
['path', this.path],
['hash', this.hash],
['searchParams', this.searchParams],
['fullUrl', this.fullUrl],
['method', this.method],
['body', this.body],
['headers', this.headers]]
)
}
toString () {
const options = { betweenKeyValueSeparator: '->', betweenPairSeparator: '|' }
let result = ''
this.toMap().forEach((value, key) => {
if (typeof value === 'string' || value === undefined || value === null) {
result = `${result}${key}${options.betweenKeyValueSeparator}${value}${options.betweenPairSeparator}`
}
if ((typeof value === 'object' && value !== null) || Array.isArray(value)) {
result = `${result}${key}${options.betweenKeyValueSeparator}${JSON.stringify(value)}${options.betweenPairSeparator}`
}
// maybe miss something for non Record / Array headers and searchParams here
})
// delete last betweenPairSeparator
return result.slice(0, -1)
}
}
class MockCallHistory {
logs = []
calls () {
return this.logs
}
firstCall () {
return this.logs.at(0)
}
lastCall () {
return this.logs.at(-1)
}
nthCall (number) {
if (typeof number !== 'number') {
throw new InvalidArgumentError('nthCall must be called with a number')
}
if (!Number.isInteger(number)) {
throw new InvalidArgumentError('nthCall must be called with an integer')
}
if (Math.sign(number) !== 1) {
throw new InvalidArgumentError('nthCall must be called with a positive value. use firstCall or lastCall instead')
}
// non zero based index. this is more human readable
return this.logs.at(number - 1)
}
filterCalls (criteria, options) {
// perf
if (this.logs.length === 0) {
return this.logs
}
if (typeof criteria === 'function') {
return this.logs.filter(criteria)
}
if (criteria instanceof RegExp) {
return this.logs.filter((log) => {
return criteria.test(log.toString())
})
}
if (typeof criteria === 'object' && criteria !== null) {
// no criteria - returning all logs
if (Object.keys(criteria).length === 0) {
return this.logs
}
const finalOptions = { operator: 'OR', ...buildAndValidateFilterCallsOptions(options) }
let maybeDuplicatedLogsFiltered = []
if ('protocol' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.protocol, finalOptions, this.filterCallsByProtocol, maybeDuplicatedLogsFiltered)
}
if ('host' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.host, finalOptions, this.filterCallsByHost, maybeDuplicatedLogsFiltered)
}
if ('port' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.port, finalOptions, this.filterCallsByPort, maybeDuplicatedLogsFiltered)
}
if ('origin' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.origin, finalOptions, this.filterCallsByOrigin, maybeDuplicatedLogsFiltered)
}
if ('path' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.path, finalOptions, this.filterCallsByPath, maybeDuplicatedLogsFiltered)
}
if ('hash' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.hash, finalOptions, this.filterCallsByHash, maybeDuplicatedLogsFiltered)
}
if ('fullUrl' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.fullUrl, finalOptions, this.filterCallsByFullUrl, maybeDuplicatedLogsFiltered)
}
if ('method' in criteria) {
maybeDuplicatedLogsFiltered = handleFilterCallsWithOptions(criteria.method, finalOptions, this.filterCallsByMethod, maybeDuplicatedLogsFiltered)
}
const uniqLogsFiltered = [...new Set(maybeDuplicatedLogsFiltered)]
return uniqLogsFiltered
}
throw new InvalidArgumentError('criteria parameter should be one of function, regexp, or object')
}
filterCallsByProtocol = makeFilterCalls.call(this, 'protocol')
filterCallsByHost = makeFilterCalls.call(this, 'host')
filterCallsByPort = makeFilterCalls.call(this, 'port')
filterCallsByOrigin = makeFilterCalls.call(this, 'origin')
filterCallsByPath = makeFilterCalls.call(this, 'path')
filterCallsByHash = makeFilterCalls.call(this, 'hash')
filterCallsByFullUrl = makeFilterCalls.call(this, 'fullUrl')
filterCallsByMethod = makeFilterCalls.call(this, 'method')
clear () {
this.logs = []
}
[kMockCallHistoryAddLog] (requestInit) {
const log = new MockCallHistoryLog(requestInit)
this.logs.push(log)
return log
}
* [Symbol.iterator] () {
for (const log of this.calls()) {
yield log
}
}
}
module.exports.MockCallHistory = MockCallHistory
module.exports.MockCallHistoryLog = MockCallHistoryLog

64
deps/undici/src/lib/mock/mock-client.js vendored Normal file
View File

@ -0,0 +1,64 @@
'use strict'
const { promisify } = require('node:util')
const Client = require('../dispatcher/client')
const { buildMockDispatch } = require('./mock-utils')
const {
kDispatches,
kMockAgent,
kClose,
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
const { InvalidArgumentError } = require('../core/errors')
/**
* MockClient provides an API that extends the Client to influence the mockDispatches.
*/
class MockClient extends Client {
constructor (origin, opts) {
if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') {
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
}
super(origin, opts)
this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
this[kOriginalClose] = this.close.bind(this)
this.dispatch = buildMockDispatch.call(this)
this.close = this[kClose]
}
get [Symbols.kConnected] () {
return this[kConnected]
}
/**
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}
async [kClose] () {
await promisify(this[kOriginalClose])()
this[kConnected] = 0
this[kMockAgent][Symbols.kClients].delete(this[kOrigin])
}
}
module.exports = MockClient

19
deps/undici/src/lib/mock/mock-errors.js vendored Normal file
View File

@ -0,0 +1,19 @@
'use strict'
const { UndiciError } = require('../core/errors')
/**
* The request does not match any registered mock dispatches.
*/
class MockNotMatchedError extends UndiciError {
constructor (message) {
super(message)
this.name = 'MockNotMatchedError'
this.message = message || 'The request does not match any registered mock dispatches'
this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED'
}
}
module.exports = {
MockNotMatchedError
}

View File

@ -0,0 +1,209 @@
'use strict'
const { getResponseData, buildKey, addMockDispatch } = require('./mock-utils')
const {
kDispatches,
kDispatchKey,
kDefaultHeaders,
kDefaultTrailers,
kContentLength,
kMockDispatch,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { InvalidArgumentError } = require('../core/errors')
const { serializePathWithQuery } = 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 = serializePathWithQuery(opts.path, opts.query)
} else {
// Matches https://github.com/nodejs/undici/blob/main/lib/web/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[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
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 (replyParameters) {
if (typeof replyParameters.statusCode === 'undefined') {
throw new InvalidArgumentError('statusCode must be defined')
}
if (typeof replyParameters.responseOptions !== 'object' || replyParameters.responseOptions === null) {
throw new InvalidArgumentError('responseOptions must be an object')
}
}
/**
* Mock an undici request with a defined reply.
*/
reply (replyOptionsCallbackOrStatusCode) {
// Values of reply aren't available right now as they
// can only be available when the reply callback is invoked.
if (typeof replyOptionsCallbackOrStatusCode === '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 = replyOptionsCallbackOrStatusCode(opts)
// Check if it is in the right format
if (typeof resolvedData !== 'object' || resolvedData === null) {
throw new InvalidArgumentError('reply options callback must return an object')
}
const replyParameters = { data: '', responseOptions: {}, ...resolvedData }
this.validateReplyParameters(replyParameters)
// Since the values can be obtained immediately we return them
// from this higher order function that will be resolved later.
return {
...this.createMockScopeDispatchData(replyParameters)
}
}
// 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, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
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 replyParameters = {
statusCode: replyOptionsCallbackOrStatusCode,
data: arguments[1] === undefined ? '' : arguments[1],
responseOptions: arguments[2] === undefined ? {} : arguments[2]
}
this.validateReplyParameters(replyParameters)
// Send in-already provided data like usual
const dispatchData = this.createMockScopeDispatchData(replyParameters)
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
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 }, { ignoreTrailingSlash: this[kIgnoreTrailingSlash] })
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

64
deps/undici/src/lib/mock/mock-pool.js vendored Normal file
View File

@ -0,0 +1,64 @@
'use strict'
const { promisify } = require('node:util')
const Pool = require('../dispatcher/pool')
const { buildMockDispatch } = require('./mock-utils')
const {
kDispatches,
kMockAgent,
kClose,
kOriginalClose,
kOrigin,
kOriginalDispatch,
kConnected,
kIgnoreTrailingSlash
} = require('./mock-symbols')
const { MockInterceptor } = require('./mock-interceptor')
const Symbols = require('../core/symbols')
const { InvalidArgumentError } = require('../core/errors')
/**
* MockPool provides an API that extends the Pool to influence the mockDispatches.
*/
class MockPool extends Pool {
constructor (origin, opts) {
if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') {
throw new InvalidArgumentError('Argument opts.agent must implement Agent')
}
super(origin, opts)
this[kMockAgent] = opts.agent
this[kOrigin] = origin
this[kIgnoreTrailingSlash] = opts.ignoreTrailingSlash ?? false
this[kDispatches] = []
this[kConnected] = 1
this[kOriginalDispatch] = this.dispatch
this[kOriginalClose] = this.close.bind(this)
this.dispatch = buildMockDispatch.call(this)
this.close = this[kClose]
}
get [Symbols.kConnected] () {
return this[kConnected]
}
/**
* Sets up the base interceptor for mocking replies from undici.
*/
intercept (opts) {
return new MockInterceptor(
opts && { ignoreTrailingSlash: this[kIgnoreTrailingSlash], ...opts },
this[kDispatches]
)
}
async [kClose] () {
await promisify(this[kOriginalClose])()
this[kConnected] = 0
this[kMockAgent][Symbols.kClients].delete(this[kOrigin])
}
}
module.exports = MockPool

View File

@ -0,0 +1,31 @@
'use strict'
module.exports = {
kAgent: Symbol('agent'),
kOptions: Symbol('options'),
kFactory: Symbol('factory'),
kDispatches: Symbol('dispatches'),
kDispatchKey: Symbol('dispatch key'),
kDefaultHeaders: Symbol('default headers'),
kDefaultTrailers: Symbol('default trailers'),
kContentLength: Symbol('content length'),
kMockAgent: Symbol('mock agent'),
kMockAgentSet: Symbol('mock agent set'),
kMockAgentGet: Symbol('mock agent get'),
kMockDispatch: Symbol('mock dispatch'),
kClose: Symbol('close'),
kOriginalClose: Symbol('original agent close'),
kOriginalDispatch: Symbol('original dispatch'),
kOrigin: Symbol('origin'),
kIsMockActive: Symbol('is mock active'),
kNetConnect: Symbol('net connect'),
kGetNetConnect: Symbol('get net connect'),
kConnected: Symbol('connected'),
kIgnoreTrailingSlash: Symbol('ignore trailing slash'),
kMockAgentMockCallHistoryInstance: Symbol('mock agent mock call history name'),
kMockAgentRegisterCallHistory: Symbol('mock agent register mock call history'),
kMockAgentAddCallHistoryLog: Symbol('mock agent add call history log'),
kMockAgentIsCallHistoryEnabled: Symbol('mock agent is call history enabled'),
kMockAgentAcceptsNonStandardSearchParameters: Symbol('mock agent accepts non standard search parameters'),
kMockCallHistoryAddLog: Symbol('mock call history add log')
}

433
deps/undici/src/lib/mock/mock-utils.js vendored Normal file
View File

@ -0,0 +1,433 @@
'use strict'
const { MockNotMatchedError } = require('./mock-errors')
const {
kDispatches,
kMockAgent,
kOriginalDispatch,
kOrigin,
kGetNetConnect
} = require('./mock-symbols')
const { serializePathWithQuery } = require('../core/util')
const { STATUS_CODES } = require('node:http')
const {
types: {
isPromise
}
} = require('node:util')
const { InvalidArgumentError } = require('../core/errors')
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 normalizeSearchParams (query) {
if (typeof query !== 'string') {
return query
}
const originalQp = new URLSearchParams(query)
const normalizedQp = new URLSearchParams()
for (let [key, value] of originalQp.entries()) {
key = key.replace('[]', '')
const valueRepresentsString = /^(['"]).*\1$/.test(value)
if (valueRepresentsString) {
normalizedQp.append(key, value)
continue
}
if (value.includes(',')) {
const values = value.split(',')
for (const v of values) {
normalizedQp.append(key, v)
}
continue
}
normalizedQp.append(key, value)
}
return normalizedQp
}
function safeUrl (path) {
if (typeof path !== 'string') {
return path
}
const pathSegments = path.split('?', 3)
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 if (data) {
return data.toString()
} else {
return ''
}
}
function getMockDispatch (mockDispatches, key) {
const basePath = key.query ? serializePathWithQuery(key.path, key.query) : key.path
const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath
const resolvedPathWithoutTrailingSlash = removeTrailingSlash(resolvedPath)
// Match path
let matchedMockDispatches = mockDispatches
.filter(({ consumed }) => !consumed)
.filter(({ path, ignoreTrailingSlash }) => {
return ignoreTrailingSlash
? matchValue(removeTrailingSlash(safeUrl(path)), resolvedPathWithoutTrailingSlash)
: 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, opts) {
const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false, ...opts }
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)
}
}
/**
* @param {string} path Path to remove trailing slash from
*/
function removeTrailingSlash (path) {
while (path.endsWith('/')) {
path = path.slice(0, -1)
}
if (path.length === 0) {
path = '/'
}
return path
}
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 buildAndValidateMockOptions (opts) {
if (opts) {
const { agent, ...mockOptions } = opts
if ('enableCallHistory' in mockOptions && typeof mockOptions.enableCallHistory !== 'boolean') {
throw new InvalidArgumentError('options.enableCallHistory must to be a boolean')
}
if ('acceptNonStandardSearchParameters' in mockOptions && typeof mockOptions.acceptNonStandardSearchParameters !== 'boolean') {
throw new InvalidArgumentError('options.acceptNonStandardSearchParameters must to be a boolean')
}
return mockOptions
}
}
module.exports = {
getResponseData,
getMockDispatch,
addMockDispatch,
deleteMockDispatch,
buildKey,
generateKeyValues,
matchValue,
getResponse,
getStatusText,
mockDispatch,
buildMockDispatch,
checkNetConnect,
buildAndValidateMockOptions,
getHeaderByName,
buildHeadersFromArray,
normalizeSearchParams
}

View File

@ -0,0 +1,43 @@
'use strict'
const { Transform } = require('node:stream')
const { Console } = require('node:console')
const PERSISTENT = process.versions.icu ? '✅' : 'Y '
const NOT_PERSISTENT = process.versions.icu ? '❌' : 'N '
/**
* Gets the output of `console.table(…)` as a string.
*/
module.exports = class PendingInterceptorsFormatter {
constructor ({ disableColors } = {}) {
this.transform = new Transform({
transform (chunk, _enc, cb) {
cb(null, chunk)
}
})
this.logger = new Console({
stdout: this.transform,
inspectOptions: {
colors: !disableColors && !process.env.CI
}
})
}
format (pendingInterceptors) {
const withPrettyHeaders = pendingInterceptors.map(
({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
Method: method,
Origin: origin,
Path: path,
'Status code': statusCode,
Persistent: persist ? PERSISTENT : NOT_PERSISTENT,
Invocations: timesInvoked,
Remaining: persist ? Infinity : times - timesInvoked
}))
this.logger.table(withPrettyHeaders)
return this.transform.read().toString()
}
}