@zimic/interceptor
Version:
Next-gen TypeScript-first HTTP intercepting and mocking
1,422 lines (1,375 loc) • 99.1 kB
JavaScript
'use strict';
var http = require('@zimic/http');
var color2 = require('picocolors');
var msw = require('msw');
var mswBrowser = require('msw/browser');
var mswNode = require('msw/node');
var ClientSocket = require('isomorphic-ws');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var color2__default = /*#__PURE__*/_interopDefault(color2);
var mswBrowser__namespace = /*#__PURE__*/_interopNamespace(mswBrowser);
var mswNode__namespace = /*#__PURE__*/_interopNamespace(mswNode);
var ClientSocket__default = /*#__PURE__*/_interopDefault(ClientSocket);
// src/http/index.ts
// src/http/interceptor/errors/RunningHttpInterceptorError.ts
var RunningHttpInterceptorError = class extends Error {
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 {
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 {
/* istanbul ignore next -- @preserve
* Ignoring because checking unknown platforms is currently not possible in our Vitest setup. */
constructor() {
super("Unknown interceptor platform.");
this.name = "UnknownHttpInterceptorPlatformError";
}
};
var UnknownHttpInterceptorPlatformError_default = UnknownHttpInterceptorPlatformError;
// src/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts
var UnknownHttpInterceptorTypeError = class extends TypeError {
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 {
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/cli/browser/shared/constants.ts
var SERVICE_WORKER_FILE_NAME = "mockServiceWorker.js";
// src/http/interceptorWorker/errors/UnregisteredBrowserServiceWorkerError.ts
var UnregisteredBrowserServiceWorkerError = class extends Error {
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 {
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-5UH44FTS.mjs
function isDefined(value) {
return value !== void 0 && value !== null;
}
var isDefined_default = isDefined;
// ../zimic-utils/dist/data/isNonEmpty.mjs
function isNonEmpty(value) {
return isDefined_default(value) && value !== "";
}
var isNonEmpty_default = isNonEmpty;
// ../zimic-utils/dist/import/createCachedDynamicImport.mjs
function createCachedDynamicImport(importModuleDynamically) {
let cachedImportResult;
return async function importModuleDynamicallyWithCache() {
cachedImportResult ??= await importModuleDynamically();
return cachedImportResult;
};
}
var createCachedDynamicImport_default = createCachedDynamicImport;
// ../zimic-utils/dist/logging/Logger.mjs
var Logger = class _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";
}
function isClientSide() {
return typeof window !== "undefined" && typeof document !== "undefined";
}
function isGlobalFileAvailable() {
return globalThis.File !== void 0;
}
// src/utils/logging.ts
var logger = new Logger_default({
prefix: color2__default.default.cyan("[@zimic/interceptor]")
});
function stringifyJSONToLog(value) {
return JSON.stringify(
value,
(_key, value2) => {
return stringifyValueToLog(value2, {
fallback: (value3) => value3
});
},
2
).replace(/\n\s*/g, " ").replace(/"(File { name: '.*?', type: '.*?', size: \d*? })"/g, "$1").replace(/"(Blob { type: '.*?', size: \d*? })"/g, "$1");
}
function stringifyValueToLog(value, options = {}) {
const { fallback = stringifyJSONToLog, includeClassName } = options;
if (value === null || value === void 0 || typeof value !== "object") {
return String(value);
}
if (value instanceof http.HttpHeaders) {
return stringifyValueToLog(value.toObject());
}
if (value instanceof http.HttpHeaders || value instanceof http.HttpSearchParams) {
const prefix = includeClassName?.searchParams ?? false ? "URLSearchParams " : "";
return `${prefix}${stringifyValueToLog(value.toObject())}`;
}
if (value instanceof http.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);
}
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
});
}
// 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__default.default.green("- Expected")}
${color2__default.default.red("+ Received")}`
].filter((part) => part !== false).join("");
}
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__default.default.green(`- return ${stringifiedExpected}`)}`);
messageParts.push(` ${color2__default.default.red(`+ return ${stringifiedReceived}`)}`);
}
if (diff.headers) {
messageParts.push("Headers:");
const stringifiedExpected = stringifyValueToLog(diff.headers.expected);
const stringifiedReceived = stringifyValueToLog(diff.headers.received);
messageParts.push(` ${color2__default.default.green(`- ${stringifiedExpected}`)}`);
messageParts.push(` ${color2__default.default.red(`+ ${stringifiedReceived}`)}`);
}
if (diff.searchParams) {
messageParts.push("Search params:");
const stringifiedExpected = stringifyValueToLog(diff.searchParams.expected);
const stringifiedReceived = stringifyValueToLog(diff.searchParams.received);
messageParts.push(` ${color2__default.default.green(`- ${stringifiedExpected}`)}`);
messageParts.push(` ${color2__default.default.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__default.default.green(`- ${stringifiedExpected}`)}`);
messageParts.push(` ${color2__default.default.red(`+ ${stringifiedReceived}`)}`);
}
return messageParts.join("\n ");
}).join("\n\n");
}
function createMessageFooter() {
return "Learn more: https://zimic.dev/docs/interceptor/api/http-request-handler#handlertimes";
}
function createMessage(options) {
const messageHeader = createMessageHeader(options);
const messageDiffs = createMessageDiffs(options);
const messageFooter = createMessageFooter();
return [messageHeader, messageDiffs, messageFooter].filter(isNonEmpty_default).join("\n\n");
}
var TimesCheckError = class extends TypeError {
constructor(options) {
const message = createMessage(options);
super(message);
this.name = "TimesCheckError";
this.cause = options.declarationPointer;
}
};
var TimesCheckError_default = TimesCheckError;
// ../zimic-utils/dist/chunk-BSG74SVQ.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) {
const bufferReadPromises = [];
if (buffer.length === 0) {
bufferReadPromises.push(
reader.read().then((result) => {
if (!result.done) {
buffer = result.value;
}
})
);
}
if (otherBuffer.length === 0) {
bufferReadPromises.push(
otherReader.read().then((result) => {
if (!result.done) {
otherBuffer = result.value;
}
})
);
}
await Promise.all(bufferReadPromises);
const haveStreamsEndedTogether = buffer.length === 0 && otherBuffer.length === 0;
if (haveStreamsEndedTogether) {
return true;
}
const hasOneStreamEndedBeforeTheOther = buffer.length === 0 && otherBuffer.length > 0 || buffer.length > 0 && otherBuffer.length === 0;
if (hasOneStreamEndedBeforeTheOther) {
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();
}
}
var blobEquals_default = blobEquals;
// ../zimic-utils/dist/chunk-LBZAJMTR.mjs
function isPrimitiveJSONValue(value) {
return typeof value !== "object" || value === null;
}
// ../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);
});
}
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);
});
}
var jsonEquals_default = jsonEquals;
// ../zimic-utils/dist/chunk-QISSVK3C.mjs
function waitForDelay(milliseconds) {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds);
});
}
var waitForDelay_default = waitForDelay;
// 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);
}
function convertArrayBufferToBlob(buffer, options) {
return new Blob([buffer], options);
}
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");
}
}
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");
}
}
// src/utils/numbers.ts
function random(lowerLimit, upperLimit) {
const range = Math.max(upperLimit - lowerLimit, 0);
if (range === 0) {
return lowerLimit;
}
return Math.random() * range + lowerLimit;
}
// src/http/requestHandler/errors/TimesDeclarationPointer.ts
var TimesDeclarationPointer = class extends Error {
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;
}
restrictions = [];
limits = {
numberOfRequests: DEFAULT_NUMBER_OF_REQUEST_LIMITS
};
timesPointer;
numberOfMatchedRequests = 0;
unmatchedRequestGroups = [];
_requests = [];
createResponseDeclaration;
createResponseDelay;
with(restriction) {
this.restrictions.push(restriction);
return this;
}
delay(minMilliseconds, maxMilliseconds) {
if (minMilliseconds === maxMilliseconds) {
return this.delay(minMilliseconds);
}
if (typeof minMilliseconds === "number" && typeof maxMilliseconds === "number") {
this.createResponseDelay = () => random(minMilliseconds, maxMilliseconds);
return this;
}
if (typeof minMilliseconds === "number") {
this.createResponseDelay = () => minMilliseconds;
return this;
}
this.createResponseDelay = minMilliseconds;
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.timesPointer = 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.timesPointer,
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.timesPointer = void 0;
this.numberOfMatchedRequests = 0;
this.unmatchedRequestGroups.length = 0;
this.clearInterceptedRequests();
this.createResponseDeclaration = void 0;
this.createResponseDelay = void 0;
return this;
}
async matchesRequest(request) {
const restrictionsMatch = await this.matchesRestrictions(request);
if (!restrictionsMatch.success) {
return { success: false, cause: "unmatchedRestrictions", diff: restrictionsMatch.diff };
}
const hasResponseDeclaration = this.createResponseDeclaration !== void 0;
if (!hasResponseDeclaration) {
return { success: false, cause: "missingResponseDeclaration" };
}
const canAcceptMoreRequests = this.numberOfMatchedRequests < this.limits.numberOfRequests.max;
if (!canAcceptMoreRequests) {
return { success: false, cause: "exceededNumberOfRequests" };
}
return { success: true };
}
markRequestAsMatched(_request) {
this.numberOfMatchedRequests++;
}
markRequestAsUnmatched(request, options) {
const shouldSaveUnmatchedRequests = this.interceptor.requestSaving.enabled && this.restrictions.length > 0 && this.timesPointer !== void 0;
if (shouldSaveUnmatchedRequests) {
this.unmatchedRequestGroups.push({ request, diff: options.diff });
}
}
async matchesRestrictions(request) {
for (const restriction of this.restrictions) {
if (this.isComputedRequestRestriction(restriction)) {
const matchesComputedRestriction = await restriction(request);
if (!matchesComputedRestriction) {
return {
success: 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.success && matchesSearchParamsRestrictions.success && matchesBodyRestrictions.success;
if (!matchesRestriction) {
return {
success: false,
diff: {
headers: matchesHeadersRestrictions.diff,
searchParams: matchesSearchParamsRestrictions.diff,
body: matchesBodyRestrictions.diff
}
};
}
}
return { success: true };
}
matchesRequestHeadersRestrictions(request, restriction) {
if (restriction.headers === void 0) {
return { success: true };
}
const restrictedHeaders = new http.HttpHeaders(
restriction.headers
);
const matchesRestriction = restriction.exact ? request.headers.equals(restrictedHeaders) : request.headers.contains(restrictedHeaders);
return matchesRestriction ? { success: true } : {
success: false,
diff: { expected: restrictedHeaders, received: request.headers }
};
}
matchesRequestSearchParamsRestrictions(request, restriction) {
if (restriction.searchParams === void 0) {
return { success: true };
}
const restrictedSearchParams = new http.HttpSearchParams(
restriction.searchParams
);
const matchesRestriction = restriction.exact ? request.searchParams.equals(restrictedSearchParams) : request.searchParams.contains(restrictedSearchParams);
return matchesRestriction ? { success: true } : {
success: false,
diff: { expected: restrictedSearchParams, received: request.searchParams }
};
}
async matchesRequestBodyRestrictions(request, restriction) {
if (restriction.body === void 0) {
return { success: 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 ? { success: true } : {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
if (restrictionBody instanceof http.HttpFormData) {
if (!(body instanceof http.HttpFormData)) {
return {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
const matchesRestriction2 = restriction.exact ? await body.equals(restrictionBody) : await body.contains(restrictionBody);
return matchesRestriction2 ? { success: true } : {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
if (restrictionBody instanceof http.HttpSearchParams) {
if (!(body instanceof http.HttpSearchParams)) {
return {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
const matchesRestriction2 = restriction.exact ? body.equals(restrictionBody) : body.contains(restrictionBody);
return matchesRestriction2 ? { success: true } : {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
if (restrictionBody instanceof Blob || restrictionBody instanceof ArrayBuffer || restrictionBody instanceof ReadableStream) {
if (!(body instanceof Blob)) {
return {
success: 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 ? { success: true } : {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
const matchesRestriction = restriction.exact ? jsonEquals_default(request.body, restriction.body) : jsonContains_default(request.body, restriction.body);
return matchesRestriction ? { success: true } : {
success: false,
diff: { expected: restrictionBody, received: body }
};
}
isComputedRequestRestriction(restriction) {
return typeof restriction === "function";
}
async applyResponseDeclaration(request) {
if (this.createResponseDelay) {
const delay = await this.createResponseDelay(request);
if (delay > 0) {
await waitForDelay_default(delay);
}
}
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 {
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;
}
delay(minMilliseconds, maxMilliseconds) {
this.client.delay(minMilliseconds, maxMilliseconds);
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;
}
async matchesRequest(request) {
const requestMatch = await this.client.matchesRequest(request);
if (requestMatch.success) {
this.client.markRequestAsMatched(request);
} else if (requestMatch.cause === "unmatchedRestrictions") {
this.client.markRequestAsUnmatched(request, { diff: requestMatch.diff });
} else {
this.client.markRequestAsMatched(request);
}
return requestMatch;
}
async applyResponseDeclaration(request) {
return this.client.applyResponseDeclaration(request);
}
saveInterceptedRequest(request, response) {
this.client.saveInterceptedRequest(request, response);
}
};
var LocalHttpRequestHandler_default = LocalHttpRequestHandler;
// ../zimic-utils/dist/chunk-VPHA4ZCK.mjs
function createPathCharactersToEscapeRegex() {
return /([.(){}+$])/g;
}
function preparePathForRegex(path) {
const pathURLPrefix = `data:${path.startsWith("/") ? "" : "/"}`;
const pathAsURL = new URL(`${pathURLPrefix}${path}`);
const encodedPath = pathAsURL.href.replace(pathURLPrefix, "");
return encodedPath.replace(/^\/+/g, "").replace(/\/+$/g, "").replace(createPathCharactersToEscapeRegex(), "\\$1");
}
function createPathParamRegex() {
return /(?<escape>\\)?:(?<identifier>[$_\p{ID_Start}][$\p{ID_Continue}]+)(?!\\[*+?])/gu;
}
function createRepeatingPathParamRegex() {
return /(?<escape>\\)?:(?<identifier>[$_\p{ID_Start}][$\p{ID_Continue}]+)\\\+/gu;
}
function createOptionalPathParamRegex() {
return /(?<leadingSlash>\/)?(?<escape>\\)?:(?<identifier>[$_\p{ID_Start}][$\p{ID_Continue}]+)\?(?<trailingSlash>\/)?/gu;
}
function createOptionalRepeatingPathParamRegex() {
return /(?<leadingSlash>\/)?(?<escape>\\)?:(?<identifier>[$_\p{ID_Start}][$\p{ID_Continue}]+)\*(?<trailingSlash>\/)?/gu;
}
function createRegexFromPath(path) {
const pathRegexContent = preparePathForRegex(path).replace(
createOptionalRepeatingPathParamRegex(),
(_match, leadingSlash, escape, identifier, trailingSlash) => {
if (escape) {
return `${leadingSlash ?? ""}:${identifier}\\*${trailingSlash ?? ""}`;
}
const hasSegmentBeforePrefix = leadingSlash === "/";
const prefixExpression = hasSegmentBeforePrefix ? "/?" : leadingSlash;
const hasSegmentAfterSuffix = trailingSlash === "/";
const suffixExpression = hasSegmentAfterSuffix ? "/?" : trailingSlash;
if (prefixExpression && suffixExpression) {
return `(?:${prefixExpression}(?<${identifier}>.+?)?${suffixExpression})?`;
} else if (prefixExpression) {
return `(?:${prefixExpression}(?<${identifier}>.+?))?`;
} else if (suffixExpression) {
return `(?:(?<${identifier}>.+?)${suffixExpression})?`;
} else {
return `(?<${identifier}>.+?)?`;
}
}
).replace(createRepeatingPathParamRegex(), (_match, escape, identifier) => {
return escape ? `:${identifier}\\+` : `(?<${identifier}>.+)`;
}).replace(
createOptionalPathParamRegex(),
(_match, leadingSlash, escape, identifier, trailingSlash) => {
if (escape) {
return `${leadingSlash ?? ""}:${identifier}\\?${trailingSlash ?? ""}`;
}
const hasSegmentBeforePrefix = leadingSlash === "/";
const prefixExpression = hasSegmentBeforePrefix ? "/?" : leadingSlash;
const hasSegmentAfterSuffix = trailingSlash === "/";
const suffixExpression = hasSegmentAfterSuffix ? "/?" : trailingSlash;
if (prefixExpression && suffixExpression) {
return `(?:${prefixExpression}(?<${identifier}>[^\\/]+?)?${suffixExpression})`;
} else if (prefixExpression) {
return `(?:${prefixExpression}(?<${identifier}>[^\\/]+?))?`;
} else if (suffixExpression) {
return `(?:(?<${identifier}>[^\\/]+?)${suffixExpression})?`;
} else {
return `(?<${identifier}>[^\\/]+?)?`;
}
}
).replace(createPathParamRegex(), (_match, escape, identifier) => {
return escape ? `:${identifier}` : `(?<${identifier}>[^\\/]+?)`;
});
return new RegExp(`^/?${pathRegexContent}/?$`);
}
var createRegexFromPath_default = createRegexFromPath;
// ../zimic-utils/dist/url/excludeNonPathParams.mjs
function excludeNonPathParams(url) {
url.hash = "";
url.search = "";
url.username = "";
url.password = "";
return url;
}
var excludeNonPathParams_default = excludeNonPathParams;
// ../zimic-utils/dist/url/validateURLProtocol.mjs
var UnsupportedURLProtocolError = class extends TypeError {
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);
}
}
var validateURLProtocol_default = validateURLProtocol;
// src/utils/arrays.ts
function removeArrayIndex(array, index) {
if (index >= 0 && index < array.length) {
array.splice(index, 1);
}
return array;
}
function removeArrayElement(array, element) {
const index = array.indexOf(element);
return removeArrayIndex(array, index);
}
// src/utils/http.ts
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);
}
// 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 {
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 http.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 === null || 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 http.parseHttpBody(rawRequest).catch((error) => {
logger.error("Failed to parse request body:", error);
return null;
});
const headers = new http.HttpHeaders(rawRequest.headers);
const pathParams = this.parseRawPathParams(rawRequest, options);
const parsedURL = new URL(rawRequest.url);
const searchParams = new http.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 http.parseHttpBody(rawResponse).catch((error) => {
logger.error("Failed to parse response body:", error);
return null;
});
const headers = new http.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(request, options) {
const requestPath = request.url.replace(options?.baseURL ?? "", "");
const paramsMatch = options?.pathRegex.exec(requestPath);
const params = {};
for (const [paramName, paramValue] of Object.entries(paramsMatch?.groups ?? {})) {
params[paramName] = typeof paramValue === "string" ? decodeURIComponent(paramValue) : void 0;
}
return params;
}
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__default.default.yellow("bypassed") : color2__default.default.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 {
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: (target, property) => {
if (this.isHiddenPropertyWhenSynced(property)) {
return false;
}
return Reflect.has(target, property);
},
get: (target, property) => {
if (this.isHiddenPropertyWhenSynced(property)) {
return void 0;
}
return Reflect.get(target, property);
}
});
}
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;
}
delay(minMilliseconds, maxMilliseconds) {
this.client.delay(minMilliseconds, maxMilliseconds);
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;
}
get requests() {
return this.client.requests;
}
async matchesRequest(request) {
const requestMatch = await this.client.matchesRequest(request);
if (requestMatch.success) {
this.client.markRequestAsMatched(request);
} else if (requestMatch.cause === "unmatchedRestrictions") {
this.client.markRequestAsUnmatched(request, { diff: requestMatch.diff });
} else {
this.client.markRequestAsMatched(request);
}
return requestMatch;
}
async applyResponseDeclaration(request) {
return this.client.applyResponseDeclaration(request);
}
saveInterceptedRequest(request, response) {
this.client.saveInterceptedRequest(request, response);
}
registerSyncPromise(promise) {
this.syncPromises.push(promise);
}
get isSynced() {
return this.syncPromises.length === 0;
}
then(onFulfilled, onRejected) {
const promisesToWait = new Set(this.syncPromises);
return Promise.all(promisesToWait).then(() => {
this.syncPromises = this.syncPromises.filter((promise) => !promisesToWait.has(promise));
r