@zimic/interceptor
Version:
Next-gen TypeScript-first HTTP intercepting and mocking
1,446 lines (1,401 loc) • 99.4 kB
JavaScript
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