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