UNPKG

@zimic/interceptor

Version:

Next-gen TypeScript-first HTTP intercepting and mocking

1,446 lines (1,401 loc) 99.4 kB
import color2 from 'picocolors'; import { HttpHeaders, HttpSearchParams, HttpFormData, HTTP_METHODS } from '@zimic/http'; import { http, passthrough } from 'msw'; import * as mswBrowser from 'msw/browser'; import * as mswNode from 'msw/node'; import ClientSocket from 'isomorphic-ws'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/http/interceptor/errors/RunningHttpInterceptorError.ts var RunningHttpInterceptorError = class extends Error { static { __name(this, "RunningHttpInterceptorError"); } constructor(additionalMessage) { super(`The interceptor is running. ${additionalMessage}`); this.name = "RunningHttpInterceptorError"; } }; var RunningHttpInterceptorError_default = RunningHttpInterceptorError; // src/http/interceptor/errors/NotRunningHttpInterceptorError.ts var NotRunningHttpInterceptorError = class extends Error { static { __name(this, "NotRunningHttpInterceptorError"); } constructor() { super("Interceptor is not running. Did you forget to call `await interceptor.start()`?"); this.name = "NotRunningHttpInterceptorError"; } }; var NotRunningHttpInterceptorError_default = NotRunningHttpInterceptorError; // src/http/interceptor/errors/UnknownHttpInterceptorPlatformError.ts var UnknownHttpInterceptorPlatformError = class extends Error { static { __name(this, "UnknownHttpInterceptorPlatformError"); } /* istanbul ignore next -- @preserve * Ignoring because checking unknown platforms is currently not possible in our Vitest setup. */ constructor() { super("Unknown interceptor platform."); this.name = "UnknownHttpInterceptorPlatform"; } }; var UnknownHttpInterceptorPlatformError_default = UnknownHttpInterceptorPlatformError; // src/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts var UnknownHttpInterceptorTypeError = class extends TypeError { static { __name(this, "UnknownHttpInterceptorTypeError"); } constructor(unknownType) { super( `Unknown HTTP interceptor type: ${unknownType}. The available options are '${"local"}' and '${"remote"}'.` ); this.name = "UnknownHttpInterceptorTypeError"; } }; var UnknownHttpInterceptorTypeError_default = UnknownHttpInterceptorTypeError; // src/http/interceptor/errors/RequestSavingSafeLimitExceededError.ts var RequestSavingSafeLimitExceededError = class extends TypeError { static { __name(this, "RequestSavingSafeLimitExceededError"); } constructor(numberOfSavedRequests, safeLimit) { super( `The number of intercepted requests saved in memory (${numberOfSavedRequests}) exceeded the safe limit of ${safeLimit}. Did you forget to call \`interceptor.clear()\`? If you need to save requests, make sure to regularly call \`interceptor.clear()\`. Alternatively, you can hide this warning by increasing \`requestSaving.safeLimit\` in your interceptor. Note that saving too many requests in memory can lead to performance issues. If you do not need to save requests, consider setting \`requestSaving.enabled: false\` in your interceptor. Learn more: https://zimic.dev/docs/interceptor/api/create-http-interceptor` ); this.name = "RequestSavingSafeLimitExceededError"; } }; var RequestSavingSafeLimitExceededError_default = RequestSavingSafeLimitExceededError; // src/http/interceptorWorker/errors/InvalidFormDataError.ts var InvalidFormDataError = class extends SyntaxError { static { __name(this, "InvalidFormDataError"); } constructor(value) { super(`Failed to parse value as form data: ${value}`); this.name = "InvalidFormDataError"; } }; var InvalidFormDataError_default = InvalidFormDataError; // src/http/interceptorWorker/errors/InvalidJSONError.ts var InvalidJSONError = class extends SyntaxError { static { __name(this, "InvalidJSONError"); } constructor(value) { super(`Failed to parse value as JSON: ${value}`); this.name = "InvalidJSONError"; } }; var InvalidJSONError_default = InvalidJSONError; // src/cli/browser/shared/constants.ts var SERVICE_WORKER_FILE_NAME = "mockServiceWorker.js"; // src/http/interceptorWorker/errors/UnregisteredBrowserServiceWorkerError.ts var UnregisteredBrowserServiceWorkerError = class extends Error { static { __name(this, "UnregisteredBrowserServiceWorkerError"); } constructor() { super( `Failed to register the browser service worker: script '${window.location.origin}/${SERVICE_WORKER_FILE_NAME}' not found. Did you forget to run \`zimic-interceptor browser init <publicDirectory>\`? Learn more: https://zimic.dev/docs/interceptor/cli/browser#zimic-interceptor-browser-init` ); this.name = "UnregisteredBrowserServiceWorkerError"; } static matchesRawError(error) { return error instanceof Error && error.message.toLowerCase().includes("service worker script does not exist at the given path"); } }; var UnregisteredBrowserServiceWorkerError_default = UnregisteredBrowserServiceWorkerError; // src/http/requestHandler/errors/DisabledRequestSavingError.ts var DisabledRequestSavingError = class extends TypeError { static { __name(this, "DisabledRequestSavingError"); } constructor() { super( "Intercepted requests are not being saved. Did you forget to use `requestSaving.enabled: true` in your interceptor?\n\nLearn more: https://zimic.dev/docs/interceptor/api/create-http-interceptor" ); this.name = "DisabledRequestSavingError"; } }; var DisabledRequestSavingError_default = DisabledRequestSavingError; // ../zimic-utils/dist/chunk-2D3UJWOA.mjs var __defProp2 = Object.defineProperty; var __name2 = /* @__PURE__ */ __name((target, value) => __defProp2(target, "name", { value, configurable: true }), "__name"); // ../zimic-utils/dist/chunk-WC2DBWWR.mjs function isDefined(value) { return value !== void 0 && value !== null; } __name(isDefined, "isDefined"); __name2(isDefined, "isDefined"); var isDefined_default = isDefined; // ../zimic-utils/dist/data/isNonEmpty.mjs function isNonEmpty(value) { return isDefined_default(value) && value !== ""; } __name(isNonEmpty, "isNonEmpty"); __name2(isNonEmpty, "isNonEmpty"); var isNonEmpty_default = isNonEmpty; // ../zimic-utils/dist/import/createCachedDynamicImport.mjs function createCachedDynamicImport(importModuleDynamically) { let cachedImportResult; return /* @__PURE__ */ __name2(/* @__PURE__ */ __name(async function importModuleDynamicallyWithCache() { cachedImportResult ??= await importModuleDynamically(); return cachedImportResult; }, "importModuleDynamicallyWithCache"), "importModuleDynamicallyWithCache"); } __name(createCachedDynamicImport, "createCachedDynamicImport"); __name2(createCachedDynamicImport, "createCachedDynamicImport"); var createCachedDynamicImport_default = createCachedDynamicImport; // ../zimic-utils/dist/logging/Logger.mjs var Logger = class _Logger { static { __name(this, "_Logger"); } static { __name2(this, "Logger"); } prefix; raw; constructor(options = {}) { const { prefix } = options; this.prefix = prefix; this.raw = prefix ? new _Logger({ ...options, prefix: void 0 }) : this; } logWithLevel(level, ...messages) { if (this.prefix) { console[level](this.prefix, ...messages); } else { console[level](...messages); } } info(...messages) { this.logWithLevel("log", ...messages); } warn(...messages) { this.logWithLevel("warn", ...messages); } error(...messages) { this.logWithLevel("error", ...messages); } table(headers, rows) { const columnLengths = headers.map((header) => { let maxValueLength = header.title.length; for (const row of rows) { const value = row[header.property]; if (value.length > maxValueLength) { maxValueLength = value.length; } } return maxValueLength; }); const formattedRows = []; const horizontalLine = columnLengths.map((length) => "\u2500".repeat(length)); formattedRows.push(horizontalLine, []); for (let headerIndex = 0; headerIndex < headers.length; headerIndex++) { const header = headers[headerIndex]; const columnLength = columnLengths[headerIndex]; const value = header.title; formattedRows.at(-1)?.push(value.padEnd(columnLength, " ")); } formattedRows.push(horizontalLine); for (const row of rows) { formattedRows.push([]); for (let headerIndex = 0; headerIndex < headers.length; headerIndex++) { const header = headers[headerIndex]; const columnLength = columnLengths[headerIndex]; const value = row[header.property]; formattedRows.at(-1)?.push(value.padEnd(columnLength, " ")); } } formattedRows.push(horizontalLine); const formattedTable = formattedRows.map((row, index) => { const isFirstLine = index === 0; if (isFirstLine) { return `\u250C\u2500${row.join("\u2500\u252C\u2500")}\u2500\u2510`; } const isLineAfterHeaders = index === 2; if (isLineAfterHeaders) { return `\u251C\u2500${row.join("\u2500\u253C\u2500")}\u2500\u2524`; } const isLastLine = index === formattedRows.length - 1; if (isLastLine) { return `\u2514\u2500${row.join("\u2500\u2534\u2500")}\u2500\u2518`; } return `\u2502 ${row.join(" \u2502 ")} \u2502`; }).join("\n"); this.logWithLevel("log", formattedTable); } }; var Logger_default = Logger; // src/utils/environment.ts function isServerSide() { return typeof process !== "undefined" && typeof process.versions !== "undefined"; } __name(isServerSide, "isServerSide"); function isClientSide() { return typeof window !== "undefined" && typeof document !== "undefined"; } __name(isClientSide, "isClientSide"); function isGlobalFileAvailable() { return globalThis.File !== void 0; } __name(isGlobalFileAvailable, "isGlobalFileAvailable"); // src/utils/logging.ts var logger = new Logger_default({ prefix: color2.cyan("[@zimic/interceptor]") }); function stringifyJSONToLog(value) { return JSON.stringify( value, (_key, value2) => { return stringifyValueToLog(value2, { fallback: /* @__PURE__ */ __name((value3) => value3, "fallback") }); }, 2 ).replace(/\n\s*/g, " ").replace(/"(File { name: '.*?', type: '.*?', size: \d*? })"/g, "$1").replace(/"(Blob { type: '.*?', size: \d*? })"/g, "$1"); } __name(stringifyJSONToLog, "stringifyJSONToLog"); function stringifyValueToLog(value, options = {}) { const { fallback = stringifyJSONToLog, includeClassName } = options; if (value === null || value === void 0 || typeof value !== "object") { return String(value); } if (value instanceof HttpHeaders) { return stringifyValueToLog(value.toObject()); } if (value instanceof HttpHeaders || value instanceof HttpSearchParams) { const prefix = includeClassName?.searchParams ?? false ? "URLSearchParams " : ""; return `${prefix}${stringifyValueToLog(value.toObject())}`; } if (value instanceof HttpFormData) { return `FormData ${stringifyValueToLog(value.toObject())}`; } if (isGlobalFileAvailable() && value instanceof File) { return `File { name: '${value.name}', type: '${value.type}', size: ${value.size} }`; } if (value instanceof Blob) { return `Blob { type: '${value.type}', size: ${value.size} }`; } return fallback(value); } __name(stringifyValueToLog, "stringifyValueToLog"); var importUtil = createCachedDynamicImport_default(() => import('util')); async function formatValueToLog(value, options = {}) { if (isClientSide()) { return value; } const { colors = true } = options; const util = await importUtil(); return util.inspect(value, { colors, compact: true, depth: Infinity, maxArrayLength: Infinity, maxStringLength: Infinity, breakLength: Infinity, sorted: true }); } __name(formatValueToLog, "formatValueToLog"); // src/http/requestHandler/errors/TimesCheckError.ts function createMessageHeader({ requestLimits, hasRestrictions, numberOfMatchedRequests, unmatchedRequestGroups }) { const requestPrefix = hasRestrictions ? "matching " : ""; return [ "Expected ", requestLimits.min === requestLimits.max ? "exactly " : "at least ", requestLimits.min, requestLimits.min === requestLimits.max && (requestLimits.min === 1 ? ` ${requestPrefix}request` : ` ${requestPrefix}requests`), requestLimits.min !== requestLimits.max && Number.isFinite(requestLimits.max) && ` and at most ${requestLimits.max}`, requestLimits.min !== requestLimits.max && (requestLimits.max === 1 ? ` ${requestPrefix}request` : ` ${requestPrefix}requests`), ", but got ", numberOfMatchedRequests, ".", unmatchedRequestGroups.length > 0 && ` Requests evaluated by this handler: ${color2.green("- Expected")} ${color2.red("+ Received")}` ].filter((part) => part !== false).join(""); } __name(createMessageHeader, "createMessageHeader"); function createMessageDiffs({ requestSaving, unmatchedRequestGroups }) { if (!requestSaving.enabled) { return "Tip: use `requestSaving.enabled: true` in your interceptor for more details about the unmatched requests."; } return unmatchedRequestGroups.map(({ request, diff }, index) => { const requestNumber = index + 1; const messageParts = [`${requestNumber}: ${request.method} ${request.url}`]; if (diff.computed) { messageParts.push("Computed restriction:"); const stringifiedExpected = stringifyValueToLog(diff.computed.expected); const stringifiedReceived = stringifyValueToLog(diff.computed.received); messageParts.push(` ${color2.green(`- return ${stringifiedExpected}`)}`); messageParts.push(` ${color2.red(`+ return ${stringifiedReceived}`)}`); } if (diff.headers) { messageParts.push("Headers:"); const stringifiedExpected = stringifyValueToLog(diff.headers.expected); const stringifiedReceived = stringifyValueToLog(diff.headers.received); messageParts.push(` ${color2.green(`- ${stringifiedExpected}`)}`); messageParts.push(` ${color2.red(`+ ${stringifiedReceived}`)}`); } if (diff.searchParams) { messageParts.push("Search params:"); const stringifiedExpected = stringifyValueToLog(diff.searchParams.expected); const stringifiedReceived = stringifyValueToLog(diff.searchParams.received); messageParts.push(` ${color2.green(`- ${stringifiedExpected}`)}`); messageParts.push(` ${color2.red(`+ ${stringifiedReceived}`)}`); } if (diff.body) { messageParts.push("Body:"); const stringifiedExpected = stringifyValueToLog(diff.body.expected, { includeClassName: { searchParams: true } }); const stringifiedReceived = stringifyValueToLog(diff.body.received, { includeClassName: { searchParams: true } }); messageParts.push(` ${color2.green(`- ${stringifiedExpected}`)}`); messageParts.push(` ${color2.red(`+ ${stringifiedReceived}`)}`); } return messageParts.join("\n "); }).join("\n\n"); } __name(createMessageDiffs, "createMessageDiffs"); function createMessageFooter() { return "Learn more: https://zimic.dev/docs/interceptor/api/http-request-handler#handlertimes"; } __name(createMessageFooter, "createMessageFooter"); function createMessage(options) { const messageHeader = createMessageHeader(options); const messageDiffs = createMessageDiffs(options); const messageFooter = createMessageFooter(); return [messageHeader, messageDiffs, messageFooter].filter(isNonEmpty_default).join("\n\n"); } __name(createMessage, "createMessage"); var TimesCheckError = class extends TypeError { static { __name(this, "TimesCheckError"); } constructor(options) { const message = createMessage(options); super(message); this.name = "TimesCheckError"; this.cause = options.declarationPointer; } }; var TimesCheckError_default = TimesCheckError; // ../zimic-utils/dist/chunk-GUOR4PHC.mjs async function blobEquals(blob, otherBlob) { if (blob.type !== otherBlob.type || blob.size !== otherBlob.size) { return false; } const reader = blob.stream().getReader(); const otherReader = otherBlob.stream().getReader(); let buffer = new Uint8Array(0); let otherBuffer = new Uint8Array(0); try { while (true) { await Promise.all([ buffer.length === 0 && reader.read().then((result) => { if (!result.done) { buffer = result.value; } }), otherBuffer.length === 0 && otherReader.read().then((result) => { if (!result.done) { otherBuffer = result.value; } }) ]); const streamsEndedTogether = buffer.length === 0 && otherBuffer.length === 0; if (streamsEndedTogether) { return true; } const oneStreamEndedBeforeTheOther = buffer.length === 0 && otherBuffer.length > 0 || buffer.length > 0 && otherBuffer.length === 0; if (oneStreamEndedBeforeTheOther) { return false; } const minimumByteLength = Math.min(buffer.length, otherBuffer.length); for (let byteIndex = 0; byteIndex < minimumByteLength; byteIndex++) { if (buffer[byteIndex] !== otherBuffer[byteIndex]) { return false; } } buffer = buffer.slice(minimumByteLength); otherBuffer = otherBuffer.slice(minimumByteLength); } } finally { reader.releaseLock(); otherReader.releaseLock(); } } __name(blobEquals, "blobEquals"); __name2(blobEquals, "blobEquals"); var blobEquals_default = blobEquals; // ../zimic-utils/dist/chunk-O7QQJM46.mjs function isPrimitiveJSONValue(value) { return typeof value !== "object" || value === null; } __name(isPrimitiveJSONValue, "isPrimitiveJSONValue"); __name2(isPrimitiveJSONValue, "isPrimitiveJSONValue"); // ../zimic-utils/dist/data/jsonContains.mjs function jsonContains(value, otherValue) { if (isPrimitiveJSONValue(value)) { return value === otherValue; } if (isPrimitiveJSONValue(otherValue)) { return false; } if (Array.isArray(value)) { if (!Array.isArray(otherValue)) { return false; } if (value.length < otherValue.length) { return false; } let lastMatchedIndex = -1; return otherValue.every((otherItem) => { for (let index = lastMatchedIndex + 1; index < value.length; index++) { if (jsonContains(value[index], otherItem)) { lastMatchedIndex = index; return true; } } return false; }); } if (Array.isArray(otherValue)) { return false; } const valueKeys = Object.keys(value); const otherValueKeys = Object.keys(otherValue); if (valueKeys.length < otherValueKeys.length) { return false; } return otherValueKeys.every((key) => { const subValue = value[key]; const subOtherValue = otherValue[key]; return jsonContains(subValue, subOtherValue); }); } __name(jsonContains, "jsonContains"); __name2(jsonContains, "jsonContains"); var jsonContains_default = jsonContains; // ../zimic-utils/dist/data/jsonEquals.mjs function jsonEquals(value, otherValue) { if (isPrimitiveJSONValue(value)) { return value === otherValue; } if (isPrimitiveJSONValue(otherValue)) { return false; } if (Array.isArray(value)) { if (!Array.isArray(otherValue)) { return false; } if (value.length !== otherValue.length) { return false; } return value.every((item, index) => jsonEquals(item, otherValue[index])); } if (Array.isArray(otherValue)) { return false; } const valueKeys = Object.keys(value); const otherValueKeys = Object.keys(otherValue); if (valueKeys.length !== otherValueKeys.length) { return false; } return valueKeys.every((key) => { const subValue = value[key]; const subOtherValue = otherValue[key]; return jsonEquals(subValue, subOtherValue); }); } __name(jsonEquals, "jsonEquals"); __name2(jsonEquals, "jsonEquals"); var jsonEquals_default = jsonEquals; // src/utils/data.ts async function convertReadableStreamToBlob(stream, options) { const chunks = []; const reader = stream.getReader(); while (true) { const result = await reader.read(); if (result.value) { chunks.push(result.value); } if (result.done) { break; } } return new Blob(chunks, options); } __name(convertReadableStreamToBlob, "convertReadableStreamToBlob"); function convertArrayBufferToBlob(buffer, options) { return new Blob([buffer], options); } __name(convertArrayBufferToBlob, "convertArrayBufferToBlob"); function convertArrayBufferToBase64(buffer) { if (isClientSide()) { const bufferBytes = new Uint8Array(buffer); const bufferAsStringArray = []; for (const byte of bufferBytes) { const byteCode = String.fromCharCode(byte); bufferAsStringArray.push(byteCode); } const bufferAsString = bufferAsStringArray.join(""); return btoa(bufferAsString); } else { return Buffer.from(buffer).toString("base64"); } } __name(convertArrayBufferToBase64, "convertArrayBufferToBase64"); function convertBase64ToArrayBuffer(base64Value) { if (isClientSide()) { const bufferAsString = atob(base64Value); const array = Uint8Array.from(bufferAsString, (character) => character.charCodeAt(0)); return array.buffer; } else { return Buffer.from(base64Value, "base64"); } } __name(convertBase64ToArrayBuffer, "convertBase64ToArrayBuffer"); // src/http/requestHandler/errors/NoResponseDefinitionError.ts var NoResponseDefinitionError = class extends TypeError { static { __name(this, "NoResponseDefinitionError"); } constructor() { super("Cannot generate a response without a definition. Use .respond() to set a response."); this.name = "NoResponseDefinitionError"; } }; var NoResponseDefinitionError_default = NoResponseDefinitionError; // src/http/requestHandler/errors/TimesDeclarationPointer.ts var TimesDeclarationPointer = class extends Error { static { __name(this, "TimesDeclarationPointer"); } constructor(minNumberOfRequests, maxNumberOfRequests) { super("declared at:"); this.name = `handler.times(${minNumberOfRequests}${maxNumberOfRequests === void 0 ? "" : `, ${maxNumberOfRequests}`})`; } }; var TimesDeclarationPointer_default = TimesDeclarationPointer; // src/http/requestHandler/HttpRequestHandlerClient.ts var DEFAULT_NUMBER_OF_REQUEST_LIMITS = Object.freeze({ min: 0, max: Infinity }); var HttpRequestHandlerClient = class { constructor(interceptor, method, path, handler) { this.interceptor = interceptor; this.method = method; this.path = path; this.handler = handler; } static { __name(this, "HttpRequestHandlerClient"); } restrictions = []; limits = { numberOfRequests: DEFAULT_NUMBER_OF_REQUEST_LIMITS }; timesDeclarationPointer; numberOfMatchedRequests = 0; unmatchedRequestGroups = []; _requests = []; createResponseDeclaration; with(restriction) { this.restrictions.push(restriction); return this; } respond(declaration) { const newThis = this; newThis.createResponseDeclaration = this.isResponseDeclarationFactory(declaration) ? declaration : () => declaration; newThis.numberOfMatchedRequests = 0; newThis.unmatchedRequestGroups.length = 0; newThis.clearInterceptedRequests(); this.interceptor.registerRequestHandler(this.handler); return newThis; } isResponseDeclarationFactory(declaration) { return typeof declaration === "function"; } times(minNumberOfRequests, maxNumberOfRequests) { this.limits.numberOfRequests = { min: minNumberOfRequests, max: maxNumberOfRequests ?? minNumberOfRequests }; this.timesDeclarationPointer = new TimesDeclarationPointer_default(minNumberOfRequests, maxNumberOfRequests); return this; } checkTimes() { const isWithinLimits = this.numberOfMatchedRequests >= this.limits.numberOfRequests.min && this.numberOfMatchedRequests <= this.limits.numberOfRequests.max; if (!isWithinLimits) { throw new TimesCheckError_default({ requestLimits: this.limits.numberOfRequests, numberOfMatchedRequests: this.numberOfMatchedRequests, declarationPointer: this.timesDeclarationPointer, unmatchedRequestGroups: this.unmatchedRequestGroups, hasRestrictions: this.restrictions.length > 0, requestSaving: this.interceptor.requestSaving }); } } clear() { this.restrictions.length = 0; this.limits = { numberOfRequests: DEFAULT_NUMBER_OF_REQUEST_LIMITS }; this.timesDeclarationPointer = void 0; this.numberOfMatchedRequests = 0; this.unmatchedRequestGroups.length = 0; this.clearInterceptedRequests(); this.createResponseDeclaration = void 0; return this; } async matchesRequest(request) { const hasDeclaredResponse = this.createResponseDeclaration !== void 0; if (!hasDeclaredResponse) { return false; } const restrictionsMatch = await this.matchesRequestRestrictions(request); if (restrictionsMatch.value) { this.numberOfMatchedRequests++; } else { const shouldSaveUnmatchedGroup = this.interceptor.requestSaving.enabled && this.restrictions.length > 0 && this.timesDeclarationPointer !== void 0; if (shouldSaveUnmatchedGroup) { this.unmatchedRequestGroups.push({ request, diff: restrictionsMatch.diff }); } } const isWithinLimits = this.numberOfMatchedRequests <= this.limits.numberOfRequests.max; return restrictionsMatch.value && isWithinLimits; } async matchesRequestRestrictions(request) { for (const restriction of this.restrictions) { if (this.isComputedRequestRestriction(restriction)) { const matchesComputedRestriction = await restriction(request); if (!matchesComputedRestriction) { return { value: false, diff: { computed: { expected: true, received: false } } }; } continue; } const matchesHeadersRestrictions = this.matchesRequestHeadersRestrictions(request, restriction); const matchesSearchParamsRestrictions = this.matchesRequestSearchParamsRestrictions(request, restriction); const matchesBodyRestrictions = await this.matchesRequestBodyRestrictions(request, restriction); const matchesRestriction = matchesHeadersRestrictions.value && matchesSearchParamsRestrictions.value && matchesBodyRestrictions.value; if (!matchesRestriction) { return { value: false, diff: { headers: matchesHeadersRestrictions.diff, searchParams: matchesSearchParamsRestrictions.diff, body: matchesBodyRestrictions.diff } }; } } return { value: true }; } matchesRequestHeadersRestrictions(request, restriction) { if (restriction.headers === void 0) { return { value: true }; } const restrictedHeaders = new HttpHeaders( restriction.headers ); const matchesRestriction = restriction.exact ? request.headers.equals(restrictedHeaders) : request.headers.contains(restrictedHeaders); return matchesRestriction ? { value: true } : { value: false, diff: { expected: restrictedHeaders, received: request.headers } }; } matchesRequestSearchParamsRestrictions(request, restriction) { if (restriction.searchParams === void 0) { return { value: true }; } const restrictedSearchParams = new HttpSearchParams( restriction.searchParams ); const matchesRestriction = restriction.exact ? request.searchParams.equals(restrictedSearchParams) : request.searchParams.contains(restrictedSearchParams); return matchesRestriction ? { value: true } : { value: false, diff: { expected: restrictedSearchParams, received: request.searchParams } }; } async matchesRequestBodyRestrictions(request, restriction) { if (restriction.body === void 0) { return { value: true }; } const body = request.body; const restrictionBody = restriction.body; if (typeof body === "string" && typeof restrictionBody === "string") { const matchesRestriction2 = restriction.exact ? body === restrictionBody : body.includes(restrictionBody); return matchesRestriction2 ? { value: true } : { value: false, diff: { expected: restrictionBody, received: body } }; } if (restrictionBody instanceof HttpFormData) { if (!(body instanceof HttpFormData)) { return { value: false, diff: { expected: restrictionBody, received: body } }; } const matchesRestriction2 = restriction.exact ? await body.equals(restrictionBody) : await body.contains(restrictionBody); return matchesRestriction2 ? { value: true } : { value: false, diff: { expected: restrictionBody, received: body } }; } if (restrictionBody instanceof HttpSearchParams) { if (!(body instanceof HttpSearchParams)) { return { value: false, diff: { expected: restrictionBody, received: body } }; } const matchesRestriction2 = restriction.exact ? body.equals(restrictionBody) : body.contains(restrictionBody); return matchesRestriction2 ? { value: true } : { value: false, diff: { expected: restrictionBody, received: body } }; } if (restrictionBody instanceof Blob || restrictionBody instanceof ArrayBuffer || restrictionBody instanceof ReadableStream) { if (!(body instanceof Blob)) { return { value: false, diff: { expected: restrictionBody, received: body } }; } let restrictionBodyAsBlob; if (restrictionBody instanceof ArrayBuffer) { restrictionBodyAsBlob = convertArrayBufferToBlob(restrictionBody, { type: body.type }); } else if (restrictionBody instanceof ReadableStream) { restrictionBodyAsBlob = await convertReadableStreamToBlob( restrictionBody, { type: body.type } ); } else { restrictionBodyAsBlob = restrictionBody; } const matchesRestriction2 = await blobEquals_default(body, restrictionBodyAsBlob); return matchesRestriction2 ? { value: true } : { value: false, diff: { expected: restrictionBody, received: body } }; } const matchesRestriction = restriction.exact ? jsonEquals_default(request.body, restriction.body) : jsonContains_default(request.body, restriction.body); return matchesRestriction ? { value: true } : { value: false, diff: { expected: restrictionBody, received: body } }; } isComputedRequestRestriction(restriction) { return typeof restriction === "function"; } async applyResponseDeclaration(request) { if (!this.createResponseDeclaration) { throw new NoResponseDefinitionError_default(); } const appliedDeclaration = await this.createResponseDeclaration(request); return appliedDeclaration; } saveInterceptedRequest(request, response) { const interceptedRequest = this.createInterceptedRequest(request, response); this._requests.push(interceptedRequest); this.interceptor.incrementNumberOfSavedRequests(1); } clearInterceptedRequests() { this.interceptor.incrementNumberOfSavedRequests(-this._requests.length); this._requests.length = 0; } createInterceptedRequest(request, response) { const interceptedRequest = request; Object.defineProperty(interceptedRequest, "response", { value: response, enumerable: true, configurable: false, writable: false }); return interceptedRequest; } get requests() { if (!this.interceptor.requestSaving.enabled) { throw new DisabledRequestSavingError_default(); } return this._requests; } }; var HttpRequestHandlerClient_default = HttpRequestHandlerClient; // src/http/requestHandler/LocalHttpRequestHandler.ts var LocalHttpRequestHandler = class { static { __name(this, "LocalHttpRequestHandler"); } type = "local"; client; constructor(interceptor, method, path) { this.client = new HttpRequestHandlerClient_default(interceptor, method, path, this); } get method() { return this.client.method; } get path() { return this.client.path; } with(restriction) { this.client.with(restriction); return this; } respond(declaration) { this.client.respond(declaration); const newThis = this; return newThis; } times(minNumberOfRequests, maxNumberOfRequests) { this.client.times(minNumberOfRequests, maxNumberOfRequests); return this; } checkTimes() { this.client.checkTimes(); } clear() { this.client.clear(); return this; } get requests() { return this.client.requests; } matchesRequest(request) { return this.client.matchesRequest(request); } async applyResponseDeclaration(request) { return this.client.applyResponseDeclaration(request); } saveInterceptedRequest(request, response) { this.client.saveInterceptedRequest(request, response); } }; var LocalHttpRequestHandler_default = LocalHttpRequestHandler; // ../zimic-utils/dist/chunk-4RR2YNYT.mjs var URL_PATH_PARAM_REGEX = /\/:([^/]+)/g; function createRegExpFromURL(url) { URL_PATH_PARAM_REGEX.lastIndex = 0; const urlWithReplacedPathParams = encodeURI(url).replace(/([.()*?+$\\])/g, "\\$1").replace(URL_PATH_PARAM_REGEX, "/(?<$1>[^/]+)").replace(/^(\/)|(\/)$/g, ""); return new RegExp(`^(?:/)?${urlWithReplacedPathParams}(?:/)?$`); } __name(createRegExpFromURL, "createRegExpFromURL"); __name2(createRegExpFromURL, "createRegExpFromURL"); var createRegExpFromURL_default = createRegExpFromURL; // ../zimic-utils/dist/url/excludeURLParams.mjs function excludeURLParams(url) { url.hash = ""; url.search = ""; url.username = ""; url.password = ""; return url; } __name(excludeURLParams, "excludeURLParams"); __name2(excludeURLParams, "excludeURLParams"); var excludeURLParams_default = excludeURLParams; // ../zimic-utils/dist/url/joinURL.mjs function joinURL(...parts) { return parts.map((part, index) => { const isFirstPart = index === 0; const isLastPart = index === parts.length - 1; let partAsString = part.toString(); if (!isFirstPart) { partAsString = partAsString.replace(/^\//, ""); } if (!isLastPart) { partAsString = partAsString.replace(/\/$/, ""); } return partAsString; }).filter((part) => part.length > 0).join("/"); } __name(joinURL, "joinURL"); __name2(joinURL, "joinURL"); var joinURL_default = joinURL; // ../zimic-utils/dist/url/validateURLProtocol.mjs var UnsupportedURLProtocolError = class extends TypeError { static { __name(this, "UnsupportedURLProtocolError"); } static { __name2(this, "UnsupportedURLProtocolError"); } constructor(protocol, availableProtocols) { super( `Unsupported URL protocol: '${protocol}'. The available options are ${availableProtocols.map((protocol2) => `'${protocol2}'`).join(", ")}` ); this.name = "UnsupportedURLProtocolError"; } }; function validateURLProtocol(url, protocols) { const protocol = url.protocol.replace(/:$/, ""); if (!protocols.includes(protocol)) { throw new UnsupportedURLProtocolError(protocol, protocols); } } __name(validateURLProtocol, "validateURLProtocol"); __name2(validateURLProtocol, "validateURLProtocol"); var validateURLProtocol_default = validateURLProtocol; // src/utils/arrays.ts function removeArrayIndex(array, index) { if (index >= 0 && index < array.length) { array.splice(index, 1); } return array; } __name(removeArrayIndex, "removeArrayIndex"); function removeArrayElement(array, element) { const index = array.indexOf(element); return removeArrayIndex(array, index); } __name(removeArrayElement, "removeArrayElement"); var HTTP_METHODS_WITH_RESPONSE_BODY = /* @__PURE__ */ new Set([ "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" ]); function methodCanHaveResponseBody(method) { return HTTP_METHODS_WITH_RESPONSE_BODY.has(method); } __name(methodCanHaveResponseBody, "methodCanHaveResponseBody"); // src/http/requestHandler/types/requests.ts var HTTP_INTERCEPTOR_REQUEST_HIDDEN_PROPERTIES = Object.freeze( /* @__PURE__ */ new Set([ "bodyUsed", "arrayBuffer", "blob", "formData", "json", "text", "clone" ]) ); var HTTP_INTERCEPTOR_RESPONSE_HIDDEN_PROPERTIES = Object.freeze( new Set( HTTP_INTERCEPTOR_REQUEST_HIDDEN_PROPERTIES ) ); // src/http/interceptorWorker/constants.ts var DEFAULT_UNHANDLED_REQUEST_STRATEGY = Object.freeze({ local: Object.freeze({ action: "reject", log: true }), remote: Object.freeze({ action: "reject", log: true }) }); // src/http/interceptorWorker/HttpInterceptorWorker.ts var HttpInterceptorWorker = class _HttpInterceptorWorker { static { __name(this, "HttpInterceptorWorker"); } platform = null; isRunning = false; startingPromise; stoppingPromise; runningInterceptors = []; async sharedStart(internalStart) { if (this.isRunning) { return; } if (this.startingPromise) { return this.startingPromise; } try { this.startingPromise = internalStart(); await this.startingPromise; this.startingPromise = void 0; } catch (error) { if (!isClientSide()) { console.error(error); } await this.stop(); throw error; } } async sharedStop(internalStop) { if (!this.isRunning) { return; } if (this.stoppingPromise) { return this.stoppingPromise; } const stoppingResult = internalStop(); if (stoppingResult instanceof Promise) { this.stoppingPromise = stoppingResult; await this.stoppingPromise; } this.stoppingPromise = void 0; } async logUnhandledRequestIfNecessary(request, strategy) { if (strategy?.log) { await _HttpInterceptorWorker.logUnhandledRequestWarning(request, strategy.action); return { wasLogged: true }; } return { wasLogged: false }; } async getUnhandledRequestStrategy(request, interceptorType) { const candidates = await this.getUnhandledRequestStrategyCandidates(request, interceptorType); const strategy = this.reduceUnhandledRequestStrategyCandidates(candidates); return strategy; } reduceUnhandledRequestStrategyCandidates(candidateStrategies) { if (candidateStrategies.length === 0) { return null; } return candidateStrategies.reduce( (accumulatedStrategy, candidateStrategy) => ({ action: accumulatedStrategy.action, log: accumulatedStrategy.log ?? candidateStrategy.log }) ); } async getUnhandledRequestStrategyCandidates(request, interceptorType) { const globalDefaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[interceptorType]; try { const interceptor = this.findInterceptorByRequestBaseURL(request); if (!interceptor) { return []; } const requestClone = request.clone(); const interceptorStrategy = await this.getInterceptorUnhandledRequestStrategy(requestClone, interceptor); return [interceptorStrategy, globalDefaultStrategy].filter(isDefined_default); } catch (error) { console.error(error); return [globalDefaultStrategy]; } } registerRunningInterceptor(interceptor) { this.runningInterceptors.push(interceptor); } unregisterRunningInterceptor(interceptor) { removeArrayElement(this.runningInterceptors, interceptor); } findInterceptorByRequestBaseURL(request) { const interceptor = this.runningInterceptors.findLast((interceptor2) => { return request.url.startsWith(interceptor2.baseURLAsString); }); return interceptor; } async getInterceptorUnhandledRequestStrategy(request, interceptor) { if (typeof interceptor.onUnhandledRequest === "function") { const parsedRequest = await _HttpInterceptorWorker.parseRawUnhandledRequest(request); return interceptor.onUnhandledRequest(parsedRequest); } return interceptor.onUnhandledRequest; } static createResponseFromDeclaration(request, declaration) { const headers = new HttpHeaders(declaration.headers); const status = declaration.status; const canHaveBody = methodCanHaveResponseBody(request.method) && status !== 204; if (!canHaveBody) { return new Response(null, { headers, status }); } if (typeof declaration.body === "string" || declaration.body === void 0 || declaration.body instanceof FormData || declaration.body instanceof URLSearchParams || declaration.body instanceof Blob || declaration.body instanceof ArrayBuffer || declaration.body instanceof ReadableStream) { return new Response(declaration.body ?? null, { headers, status }); } return Response.json(declaration.body, { headers, status }); } static async parseRawUnhandledRequest(request) { return this.parseRawRequest( request ); } static async parseRawRequest(originalRawRequest, options = {}) { const rawRequest = originalRawRequest.clone(); const rawRequestClone = rawRequest.clone(); const parsedBody = await this.parseRawBody(rawRequest); const headers = new HttpHeaders(rawRequest.headers); const pathParams = options.urlRegex ? this.parseRawPathParams(options.urlRegex, rawRequest) : {}; const parsedURL = new URL(rawRequest.url); const searchParams = new HttpSearchParams(parsedURL.searchParams); const parsedRequest = new Proxy(rawRequest, { has(target, property) { if (_HttpInterceptorWorker.isHiddenRequestProperty(property)) { return false; } return Reflect.has(target, property); }, get(target, property) { if (_HttpInterceptorWorker.isHiddenRequestProperty(property)) { return void 0; } return Reflect.get(target, property, target); } }); Object.defineProperty(parsedRequest, "body", { value: parsedBody, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "headers", { value: headers, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "pathParams", { value: pathParams, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "searchParams", { value: searchParams, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "raw", { value: rawRequestClone, enumerable: true, configurable: false, writable: false }); return parsedRequest; } static isHiddenRequestProperty(property) { return HTTP_INTERCEPTOR_REQUEST_HIDDEN_PROPERTIES.has(property); } static async parseRawResponse(originalRawResponse) { const rawResponse = originalRawResponse.clone(); const rawResponseClone = rawResponse.clone(); const parsedBody = await this.parseRawBody(rawResponse); const headers = new HttpHeaders(rawResponse.headers); const parsedRequest = new Proxy(rawResponse, { has(target, property) { if (_HttpInterceptorWorker.isHiddenResponseProperty(property)) { return false; } return Reflect.has(target, property); }, get(target, property) { if (_HttpInterceptorWorker.isHiddenResponseProperty(property)) { return void 0; } return Reflect.get(target, property, target); } }); Object.defineProperty(parsedRequest, "body", { value: parsedBody, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "headers", { value: headers, enumerable: true, configurable: false, writable: false }); Object.defineProperty(parsedRequest, "raw", { value: rawResponseClone, enumerable: true, configurable: false, writable: false }); return parsedRequest; } static isHiddenResponseProperty(property) { return HTTP_INTERCEPTOR_RESPONSE_HIDDEN_PROPERTIES.has(property); } static parseRawPathParams(matchedURLRegex, request) { const match = request.url.match(matchedURLRegex); const pathParams = { ...match?.groups }; return pathParams; } static async parseRawBody(resource) { const contentType = resource.headers.get("content-type"); try { if (contentType) { if (contentType.startsWith("application/json")) { return await this.parseRawBodyAsJSON(resource); } if (contentType.startsWith("multipart/form-data")) { return await this.parseRawBodyAsFormData(resource); } if (contentType.startsWith("application/x-www-form-urlencoded")) { return await this.parseRawBodyAsSearchParams(resource); } if (contentType.startsWith("text/") || contentType.startsWith("application/xml")) { return await this.parseRawBodyAsText(resource); } if (contentType.startsWith("application/") || contentType.startsWith("image/") || contentType.startsWith("audio/") || contentType.startsWith("font/") || contentType.startsWith("video/") || contentType.startsWith("multipart/")) { return await this.parseRawBodyAsBlob(resource); } } const resourceClone = resource.clone(); try { return await this.parseRawBodyAsJSON(resource); } catch { return await this.parseRawBodyAsBlob(resourceClone); } } catch (error) { console.error(error); return null; } } static async parseRawBodyAsJSON(resource) { const bodyAsText = await resource.text(); if (!bodyAsText.trim()) { return null; } try { const bodyAsJSON = JSON.parse(bodyAsText); return bodyAsJSON; } catch { throw new InvalidJSONError_default(bodyAsText); } } static async parseRawBodyAsSearchParams(resource) { const bodyAsText = await resource.text(); if (!bodyAsText.trim()) { return null; } const bodyAsSearchParams = new HttpSearchParams(bodyAsText); return bodyAsSearchParams; } static async parseRawBodyAsFormData(resource) { const resourceClone = resource.clone(); try { const bodyAsRawFormData = await resource.formData(); const bodyAsFormData = new HttpFormData(); for (const [key, value] of bodyAsRawFormData) { bodyAsFormData.append(key, value); } return bodyAsFormData; } catch { const bodyAsText = await resourceClone.text(); if (!bodyAsText.trim()) { return null; } throw new InvalidFormDataError_default(bodyAsText); } } static async parseRawBodyAsBlob(resource) { const bodyAsBlob = await resource.blob(); return bodyAsBlob; } static async parseRawBodyAsText(resource) { const bodyAsText = await resource.text(); return bodyAsText || null; } static async logUnhandledRequestWarning(rawRequest, action) { const request = await this.parseRawRequest(rawRequest); const [formattedHeaders, formattedSearchParams, formattedBody] = await Promise.all([ formatValueToLog(request.headers.toObject()), formatValueToLog(request.searchParams.toObject()), formatValueToLog(request.body) ]); logger[action === "bypass" ? "warn" : "error"]( `${action === "bypass" ? "Warning:" : "Error:"} Request was not handled and was ${action === "bypass" ? color2.yellow("bypassed") : color2.red("rejected")}. `, `${request.method} ${request.url}`, "\n Headers:", formattedHeaders, "\n Search params:", formattedSearchParams, "\n Body:", formattedBody, "\n\nLearn more: https://zimic.dev/docs/interceptor/guides/http/unhandled-requests" ); } }; var HttpInterceptorWorker_default = HttpInterceptorWorker; // src/http/requestHandler/RemoteHttpRequestHandler.ts var PENDING_PROPERTIES = /* @__PURE__ */ new Set(["then"]); var RemoteHttpRequestHandler = class { static { __name(this, "RemoteHttpRequestHandler"); } type = "remote"; client; syncPromises = []; unsynced; synced; constructor(interceptor, method, path) { this.client = new HttpRequestHandlerClient_default(interceptor, method, path, this); this.unsynced = this; this.synced = this.createSyncedProxy(); } createSyncedProxy() { return new Proxy(this, { has: /* @__PURE__ */ __name((target, property) => { if (this.isHiddenPropertyWhenSynced(property)) { return false; } return Reflect.has(target, property); }, "has"), get: /* @__PURE__ */ __name((target, property) => { if (this.isHiddenPropertyWhenSynced(property)) { return void 0; } return Reflect.get(target, property); }, "get") }); } isHiddenPropertyWhenSynced(property) { return PENDING_PROPERTIES.has(property); } get method() { return this.client.method; } get path() { return this.client.path; } with(restriction) { this.client.with(restriction); return this.unsynced; } respond(declaration) { const newUnsyncedThis = this.unsynced; newUnsyncedThis.client.respond(declaration); return newUnsyncedThis; } times(minNumberOfRequests, maxNumberOfRequests) { this.client.times(minNumberOfRequests, maxNumberOfRequests); return this; } async checkTimes() { return new Promise((resolve, reject) => { try { this.client.checkTimes(); resolve(); } catch (error) { reject(error); } }); } clear() { this.client.clear(); return this.unsynced; } g