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