11cb0ef41Sopenharmony_ci'use strict'
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst { getResponseData, buildKey, addMockDispatch } = require('./mock-utils')
41cb0ef41Sopenharmony_ciconst {
51cb0ef41Sopenharmony_ci  kDispatches,
61cb0ef41Sopenharmony_ci  kDispatchKey,
71cb0ef41Sopenharmony_ci  kDefaultHeaders,
81cb0ef41Sopenharmony_ci  kDefaultTrailers,
91cb0ef41Sopenharmony_ci  kContentLength,
101cb0ef41Sopenharmony_ci  kMockDispatch
111cb0ef41Sopenharmony_ci} = require('./mock-symbols')
121cb0ef41Sopenharmony_ciconst { InvalidArgumentError } = require('../core/errors')
131cb0ef41Sopenharmony_ciconst { buildURL } = require('../core/util')
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_ci/**
161cb0ef41Sopenharmony_ci * Defines the scope API for an interceptor reply
171cb0ef41Sopenharmony_ci */
181cb0ef41Sopenharmony_ciclass MockScope {
191cb0ef41Sopenharmony_ci  constructor (mockDispatch) {
201cb0ef41Sopenharmony_ci    this[kMockDispatch] = mockDispatch
211cb0ef41Sopenharmony_ci  }
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ci  /**
241cb0ef41Sopenharmony_ci   * Delay a reply by a set amount in ms.
251cb0ef41Sopenharmony_ci   */
261cb0ef41Sopenharmony_ci  delay (waitInMs) {
271cb0ef41Sopenharmony_ci    if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) {
281cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('waitInMs must be a valid integer > 0')
291cb0ef41Sopenharmony_ci    }
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci    this[kMockDispatch].delay = waitInMs
321cb0ef41Sopenharmony_ci    return this
331cb0ef41Sopenharmony_ci  }
341cb0ef41Sopenharmony_ci
351cb0ef41Sopenharmony_ci  /**
361cb0ef41Sopenharmony_ci   * For a defined reply, never mark as consumed.
371cb0ef41Sopenharmony_ci   */
381cb0ef41Sopenharmony_ci  persist () {
391cb0ef41Sopenharmony_ci    this[kMockDispatch].persist = true
401cb0ef41Sopenharmony_ci    return this
411cb0ef41Sopenharmony_ci  }
421cb0ef41Sopenharmony_ci
431cb0ef41Sopenharmony_ci  /**
441cb0ef41Sopenharmony_ci   * Allow one to define a reply for a set amount of matching requests.
451cb0ef41Sopenharmony_ci   */
461cb0ef41Sopenharmony_ci  times (repeatTimes) {
471cb0ef41Sopenharmony_ci    if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) {
481cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('repeatTimes must be a valid integer > 0')
491cb0ef41Sopenharmony_ci    }
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci    this[kMockDispatch].times = repeatTimes
521cb0ef41Sopenharmony_ci    return this
531cb0ef41Sopenharmony_ci  }
541cb0ef41Sopenharmony_ci}
551cb0ef41Sopenharmony_ci
561cb0ef41Sopenharmony_ci/**
571cb0ef41Sopenharmony_ci * Defines an interceptor for a Mock
581cb0ef41Sopenharmony_ci */
591cb0ef41Sopenharmony_ciclass MockInterceptor {
601cb0ef41Sopenharmony_ci  constructor (opts, mockDispatches) {
611cb0ef41Sopenharmony_ci    if (typeof opts !== 'object') {
621cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('opts must be an object')
631cb0ef41Sopenharmony_ci    }
641cb0ef41Sopenharmony_ci    if (typeof opts.path === 'undefined') {
651cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('opts.path must be defined')
661cb0ef41Sopenharmony_ci    }
671cb0ef41Sopenharmony_ci    if (typeof opts.method === 'undefined') {
681cb0ef41Sopenharmony_ci      opts.method = 'GET'
691cb0ef41Sopenharmony_ci    }
701cb0ef41Sopenharmony_ci    // See https://github.com/nodejs/undici/issues/1245
711cb0ef41Sopenharmony_ci    // As per RFC 3986, clients are not supposed to send URI
721cb0ef41Sopenharmony_ci    // fragments to servers when they retrieve a document,
731cb0ef41Sopenharmony_ci    if (typeof opts.path === 'string') {
741cb0ef41Sopenharmony_ci      if (opts.query) {
751cb0ef41Sopenharmony_ci        opts.path = buildURL(opts.path, opts.query)
761cb0ef41Sopenharmony_ci      } else {
771cb0ef41Sopenharmony_ci        // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
781cb0ef41Sopenharmony_ci        const parsedURL = new URL(opts.path, 'data://')
791cb0ef41Sopenharmony_ci        opts.path = parsedURL.pathname + parsedURL.search
801cb0ef41Sopenharmony_ci      }
811cb0ef41Sopenharmony_ci    }
821cb0ef41Sopenharmony_ci    if (typeof opts.method === 'string') {
831cb0ef41Sopenharmony_ci      opts.method = opts.method.toUpperCase()
841cb0ef41Sopenharmony_ci    }
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci    this[kDispatchKey] = buildKey(opts)
871cb0ef41Sopenharmony_ci    this[kDispatches] = mockDispatches
881cb0ef41Sopenharmony_ci    this[kDefaultHeaders] = {}
891cb0ef41Sopenharmony_ci    this[kDefaultTrailers] = {}
901cb0ef41Sopenharmony_ci    this[kContentLength] = false
911cb0ef41Sopenharmony_ci  }
921cb0ef41Sopenharmony_ci
931cb0ef41Sopenharmony_ci  createMockScopeDispatchData (statusCode, data, responseOptions = {}) {
941cb0ef41Sopenharmony_ci    const responseData = getResponseData(data)
951cb0ef41Sopenharmony_ci    const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {}
961cb0ef41Sopenharmony_ci    const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }
971cb0ef41Sopenharmony_ci    const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers }
981cb0ef41Sopenharmony_ci
991cb0ef41Sopenharmony_ci    return { statusCode, data, headers, trailers }
1001cb0ef41Sopenharmony_ci  }
1011cb0ef41Sopenharmony_ci
1021cb0ef41Sopenharmony_ci  validateReplyParameters (statusCode, data, responseOptions) {
1031cb0ef41Sopenharmony_ci    if (typeof statusCode === 'undefined') {
1041cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('statusCode must be defined')
1051cb0ef41Sopenharmony_ci    }
1061cb0ef41Sopenharmony_ci    if (typeof data === 'undefined') {
1071cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('data must be defined')
1081cb0ef41Sopenharmony_ci    }
1091cb0ef41Sopenharmony_ci    if (typeof responseOptions !== 'object') {
1101cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('responseOptions must be an object')
1111cb0ef41Sopenharmony_ci    }
1121cb0ef41Sopenharmony_ci  }
1131cb0ef41Sopenharmony_ci
1141cb0ef41Sopenharmony_ci  /**
1151cb0ef41Sopenharmony_ci   * Mock an undici request with a defined reply.
1161cb0ef41Sopenharmony_ci   */
1171cb0ef41Sopenharmony_ci  reply (replyData) {
1181cb0ef41Sopenharmony_ci    // Values of reply aren't available right now as they
1191cb0ef41Sopenharmony_ci    // can only be available when the reply callback is invoked.
1201cb0ef41Sopenharmony_ci    if (typeof replyData === 'function') {
1211cb0ef41Sopenharmony_ci      // We'll first wrap the provided callback in another function,
1221cb0ef41Sopenharmony_ci      // this function will properly resolve the data from the callback
1231cb0ef41Sopenharmony_ci      // when invoked.
1241cb0ef41Sopenharmony_ci      const wrappedDefaultsCallback = (opts) => {
1251cb0ef41Sopenharmony_ci        // Our reply options callback contains the parameter for statusCode, data and options.
1261cb0ef41Sopenharmony_ci        const resolvedData = replyData(opts)
1271cb0ef41Sopenharmony_ci
1281cb0ef41Sopenharmony_ci        // Check if it is in the right format
1291cb0ef41Sopenharmony_ci        if (typeof resolvedData !== 'object') {
1301cb0ef41Sopenharmony_ci          throw new InvalidArgumentError('reply options callback must return an object')
1311cb0ef41Sopenharmony_ci        }
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci        const { statusCode, data = '', responseOptions = {} } = resolvedData
1341cb0ef41Sopenharmony_ci        this.validateReplyParameters(statusCode, data, responseOptions)
1351cb0ef41Sopenharmony_ci        // Since the values can be obtained immediately we return them
1361cb0ef41Sopenharmony_ci        // from this higher order function that will be resolved later.
1371cb0ef41Sopenharmony_ci        return {
1381cb0ef41Sopenharmony_ci          ...this.createMockScopeDispatchData(statusCode, data, responseOptions)
1391cb0ef41Sopenharmony_ci        }
1401cb0ef41Sopenharmony_ci      }
1411cb0ef41Sopenharmony_ci
1421cb0ef41Sopenharmony_ci      // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
1431cb0ef41Sopenharmony_ci      const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback)
1441cb0ef41Sopenharmony_ci      return new MockScope(newMockDispatch)
1451cb0ef41Sopenharmony_ci    }
1461cb0ef41Sopenharmony_ci
1471cb0ef41Sopenharmony_ci    // We can have either one or three parameters, if we get here,
1481cb0ef41Sopenharmony_ci    // we should have 1-3 parameters. So we spread the arguments of
1491cb0ef41Sopenharmony_ci    // this function to obtain the parameters, since replyData will always
1501cb0ef41Sopenharmony_ci    // just be the statusCode.
1511cb0ef41Sopenharmony_ci    const [statusCode, data = '', responseOptions = {}] = [...arguments]
1521cb0ef41Sopenharmony_ci    this.validateReplyParameters(statusCode, data, responseOptions)
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci    // Send in-already provided data like usual
1551cb0ef41Sopenharmony_ci    const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions)
1561cb0ef41Sopenharmony_ci    const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
1571cb0ef41Sopenharmony_ci    return new MockScope(newMockDispatch)
1581cb0ef41Sopenharmony_ci  }
1591cb0ef41Sopenharmony_ci
1601cb0ef41Sopenharmony_ci  /**
1611cb0ef41Sopenharmony_ci   * Mock an undici request with a defined error.
1621cb0ef41Sopenharmony_ci   */
1631cb0ef41Sopenharmony_ci  replyWithError (error) {
1641cb0ef41Sopenharmony_ci    if (typeof error === 'undefined') {
1651cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('error must be defined')
1661cb0ef41Sopenharmony_ci    }
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci    const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error })
1691cb0ef41Sopenharmony_ci    return new MockScope(newMockDispatch)
1701cb0ef41Sopenharmony_ci  }
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci  /**
1731cb0ef41Sopenharmony_ci   * Set default reply headers on the interceptor for subsequent replies
1741cb0ef41Sopenharmony_ci   */
1751cb0ef41Sopenharmony_ci  defaultReplyHeaders (headers) {
1761cb0ef41Sopenharmony_ci    if (typeof headers === 'undefined') {
1771cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('headers must be defined')
1781cb0ef41Sopenharmony_ci    }
1791cb0ef41Sopenharmony_ci
1801cb0ef41Sopenharmony_ci    this[kDefaultHeaders] = headers
1811cb0ef41Sopenharmony_ci    return this
1821cb0ef41Sopenharmony_ci  }
1831cb0ef41Sopenharmony_ci
1841cb0ef41Sopenharmony_ci  /**
1851cb0ef41Sopenharmony_ci   * Set default reply trailers on the interceptor for subsequent replies
1861cb0ef41Sopenharmony_ci   */
1871cb0ef41Sopenharmony_ci  defaultReplyTrailers (trailers) {
1881cb0ef41Sopenharmony_ci    if (typeof trailers === 'undefined') {
1891cb0ef41Sopenharmony_ci      throw new InvalidArgumentError('trailers must be defined')
1901cb0ef41Sopenharmony_ci    }
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ci    this[kDefaultTrailers] = trailers
1931cb0ef41Sopenharmony_ci    return this
1941cb0ef41Sopenharmony_ci  }
1951cb0ef41Sopenharmony_ci
1961cb0ef41Sopenharmony_ci  /**
1971cb0ef41Sopenharmony_ci   * Set reply content length header for replies on the interceptor
1981cb0ef41Sopenharmony_ci   */
1991cb0ef41Sopenharmony_ci  replyContentLength () {
2001cb0ef41Sopenharmony_ci    this[kContentLength] = true
2011cb0ef41Sopenharmony_ci    return this
2021cb0ef41Sopenharmony_ci  }
2031cb0ef41Sopenharmony_ci}
2041cb0ef41Sopenharmony_ci
2051cb0ef41Sopenharmony_cimodule.exports.MockInterceptor = MockInterceptor
2061cb0ef41Sopenharmony_cimodule.exports.MockScope = MockScope
207