From cb123d386e0c075d236595f4915b92d57a960a42 Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Sat, 30 Nov 2024 16:12:36 -0300 Subject: [PATCH 01/16] feat(#zimic)!: no global unhandled local request logging (#491) (#493) Closes #491. --- apps/zimic-test-client/tests/setup/shared.ts | 14 - apps/zimic-test-client/vitest.config.mts | 1 - ...342\200\220interceptor\342\200\220http.md" | 21 +- examples/with-jest-node/tests/setup.ts | 12 - .../tests/interceptors/utils.ts | 13 - examples/with-openapi-typegen/tests/setup.ts | 12 - examples/with-vitest-node/tests/setup.ts | 12 - packages/eslint-config/index.js | 5 +- .../http/interceptor/HttpInterceptorClient.ts | 23 +- .../http/interceptor/LocalHttpInterceptor.ts | 4 +- .../http/interceptor/RemoteHttpInterceptor.ts | 6 +- .../HttpInterceptor.handlers.browser.test.ts | 4 +- .../HttpInterceptor.handlers.node.test.ts | 4 +- ...erceptor.unhandledRequests.browser.test.ts | 24 +- ...Interceptor.unhandledRequests.node.test.ts | 24 +- .../interceptor/__tests__/shared/baseURLs.ts | 7 +- .../interceptor/__tests__/shared/handlers.ts | 30 +- .../interceptor/__tests__/shared/lifeCycle.ts | 11 +- .../__tests__/shared/restrictions.ts | 4 +- .../shared/unhandledRequests.factories.ts | 462 +++++++ .../shared/unhandledRequests.logging.ts | 888 +++++++++++++ .../__tests__/shared/unhandledRequests.ts | 1164 ----------------- .../interceptor/__tests__/shared/utils.ts | 30 +- .../HttpInterceptorWorker.ts | 125 +- .../LocalHttpInterceptorWorker.ts | 47 +- .../RemoteHttpInterceptorWorker.ts | 41 +- .../__tests__/shared/methods.ts | 52 +- .../http/interceptorWorker/types/requests.ts | 6 +- .../__tests__/shared/restrictions.ts | 20 +- .../interceptor/server/InterceptorServer.ts | 102 +- .../src/interceptor/server/types/schema.ts | 11 +- packages/zimic/src/utils/arrays.ts | 11 + packages/zimic/src/utils/fetch.ts | 13 +- packages/zimic/src/utils/urls.ts | 61 +- packages/zimic/tests/utils/fetch.ts | 42 +- 35 files changed, 1817 insertions(+), 1489 deletions(-) delete mode 100644 apps/zimic-test-client/tests/setup/shared.ts create mode 100644 packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts create mode 100644 packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts delete mode 100644 packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.ts create mode 100644 packages/zimic/src/utils/arrays.ts diff --git a/apps/zimic-test-client/tests/setup/shared.ts b/apps/zimic-test-client/tests/setup/shared.ts deleted file mode 100644 index c77ca72da..000000000 --- a/apps/zimic-test-client/tests/setup/shared.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { beforeEach } from 'vitest'; -import { httpInterceptor } from 'zimic/interceptor/http'; - -beforeEach(() => { - httpInterceptor.default.local.onUnhandledRequest = { - action: 'reject', - log: true, - }; - - httpInterceptor.default.remote.onUnhandledRequest = { - action: 'reject', - log: true, - }; -}); diff --git a/apps/zimic-test-client/vitest.config.mts b/apps/zimic-test-client/vitest.config.mts index bbc988d6f..5692d2a6e 100644 --- a/apps/zimic-test-client/vitest.config.mts +++ b/apps/zimic-test-client/vitest.config.mts @@ -15,7 +15,6 @@ export default defineConfig({ minWorkers: 1, maxWorkers, maxConcurrency: maxWorkers, - setupFiles: ['./tests/setup/shared.ts'], coverage: { provider: 'istanbul', reporter: ['text', 'html'], diff --git "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" index a41b18944..ca278f6f1 100644 --- "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" +++ "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" @@ -152,7 +152,7 @@ On the other hand, [remote interceptors](getting‐started#remote-http-intercept [interceptor server](cli‐zimic‐server) always reject unhandled requests. This is because the unhandled requests have already reached the interceptor server, so there would be no way of bypassing them at this point. -You can override the default logging behavior per interceptor with `onUnhandledRequest` in +You can override the logging behavior for each interceptor with `onUnhandledRequest` in [`httpInterceptor.create(options)`](#httpinterceptorcreateoptions). `onUnhandledRequest` also accepts a function to dynamically determine which strategy to use for an unhandled request. @@ -212,6 +212,8 @@ const interceptor = httpInterceptor.create({ // Ignore only unhandled requests to /assets if (url.pathname.startsWith('/assets')) { + // Remember: 'bypass' is only available for local interceptors! + // Use 'reject' for remote interceptors. return { action: 'bypass', log: false }; } @@ -223,25 +225,26 @@ const interceptor = httpInterceptor.create({ -If you want to override the default logging behavior for all interceptors, or requests that did not match any known base -URL, you can use `httpInterceptor.default.local.onUnhandledRequest` or -`httpInterceptor.default.remote.onUnhandledRequest`. Keep in mind that defining an `onUnhandledRequest` when creating an -interceptor will take precedence over `httpInterceptor.default.local.onUnhandledRequest` and -`httpInterceptor.default.remote.onUnhandledRequest`. +If you want to override the default logging behavior for all interceptors, you can use +`httpInterceptor.default.local.onUnhandledRequest` or `httpInterceptor.default.remote.onUnhandledRequest`. Keep in mind +that defining an `onUnhandledRequest` strategy when creating an interceptor will take precedence over +`httpInterceptor.default.local.onUnhandledRequest` and `httpInterceptor.default.remote.onUnhandledRequest`.
- Example 4: Ignore all unhandled requests with no logging: + Example 4: Ignore unhandled requests in all interceptors with no logging: ```ts import { httpInterceptor } from 'zimic/interceptor/http'; +// For local interceptors: httpInterceptor.default.local.onUnhandledRequest = { action: 'bypass', log: false, }; +// For remote interceptors: httpInterceptor.default.remote.onUnhandledRequest = { action: 'reject', log: false, @@ -252,7 +255,7 @@ httpInterceptor.default.remote.onUnhandledRequest = {
- Example 5: Reject all unhandled requests with logging: + Example 5: Reject unhandled requests in all interceptors with logging: ```ts @@ -275,7 +278,7 @@ httpInterceptor.default.remote.onUnhandledRequest = {
- Example 6: Dynamically ignore or reject all unhandled requests: + Example 6: Dynamically ignore or reject unhandled requests in all interceptors: ```ts diff --git a/examples/with-jest-node/tests/setup.ts b/examples/with-jest-node/tests/setup.ts index 731f9137c..323f907d1 100644 --- a/examples/with-jest-node/tests/setup.ts +++ b/examples/with-jest-node/tests/setup.ts @@ -1,19 +1,7 @@ import { beforeAll, afterEach, afterAll } from '@jest/globals'; -import { httpInterceptor } from 'zimic/interceptor/http'; import githubInterceptor from './interceptors/github'; -httpInterceptor.default.local.onUnhandledRequest = (request) => { - const url = new URL(request.url); - const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1'; - - if (isLocalhost) { - return { action: 'bypass', log: false }; - } - - return { action: 'reject' }; -}; - beforeAll(async () => { await githubInterceptor.start(); }); diff --git a/examples/with-next-js-pages/tests/interceptors/utils.ts b/examples/with-next-js-pages/tests/interceptors/utils.ts index ee2121678..5d6d2d5dd 100644 --- a/examples/with-next-js-pages/tests/interceptors/utils.ts +++ b/examples/with-next-js-pages/tests/interceptors/utils.ts @@ -1,18 +1,5 @@ -import { httpInterceptor } from 'zimic/interceptor/http'; - import githubInterceptor, { githubFixtures } from './github'; -httpInterceptor.default.local.onUnhandledRequest = (request) => { - const url = new URL(request.url); - const isSameHost = url.host === window.location.host; - - if (isSameHost) { - return { action: 'bypass', log: false }; - } - - return { action: 'reject' }; -}; - export async function loadInterceptors() { await githubInterceptor.start(); githubFixtures.apply(); diff --git a/examples/with-openapi-typegen/tests/setup.ts b/examples/with-openapi-typegen/tests/setup.ts index 49b2178e8..3f5a9f2cd 100644 --- a/examples/with-openapi-typegen/tests/setup.ts +++ b/examples/with-openapi-typegen/tests/setup.ts @@ -1,19 +1,7 @@ import { afterAll, beforeAll, afterEach } from 'vitest'; -import { httpInterceptor } from 'zimic/interceptor/http'; import githubInterceptor from './interceptors/github'; -httpInterceptor.default.local.onUnhandledRequest = (request) => { - const url = new URL(request.url); - const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1'; - - if (isLocalhost) { - return { action: 'bypass', log: false }; - } - - return { action: 'reject' }; -}; - beforeAll(async () => { await githubInterceptor.start(); }); diff --git a/examples/with-vitest-node/tests/setup.ts b/examples/with-vitest-node/tests/setup.ts index 49b2178e8..3f5a9f2cd 100644 --- a/examples/with-vitest-node/tests/setup.ts +++ b/examples/with-vitest-node/tests/setup.ts @@ -1,19 +1,7 @@ import { afterAll, beforeAll, afterEach } from 'vitest'; -import { httpInterceptor } from 'zimic/interceptor/http'; import githubInterceptor from './interceptors/github'; -httpInterceptor.default.local.onUnhandledRequest = (request) => { - const url = new URL(request.url); - const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1'; - - if (isLocalhost) { - return { action: 'bypass', log: false }; - } - - return { action: 'reject' }; -}; - beforeAll(async () => { await githubInterceptor.start(); }); diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js index f123dca3b..4a04f63f8 100644 --- a/packages/eslint-config/index.js +++ b/packages/eslint-config/index.js @@ -337,7 +337,10 @@ export default [ '@typescript-eslint/restrict-template-expressions': [ 'warn', { - allow: [{ name: ['Error', 'URL', 'URLSearchParams', 'unknown'], from: 'lib' }], + allow: [ + { name: ['Error', 'URL', 'URLSearchParams', 'unknown'], from: 'lib' }, + { name: ['HttpSearchParams'], from: 'file' }, + ], allowAny: true, allowBoolean: true, allowNullish: true, diff --git a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts index a6587db55..8385ddb04 100644 --- a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts +++ b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts @@ -11,7 +11,6 @@ import { joinURL, ExtendedURL, createRegexFromURL } from '@/utils/urls'; import HttpInterceptorWorker from '../interceptorWorker/HttpInterceptorWorker'; import LocalHttpInterceptorWorker from '../interceptorWorker/LocalHttpInterceptorWorker'; -import { HttpResponseFactoryResult } from '../interceptorWorker/types/requests'; import HttpRequestHandlerClient, { AnyHttpRequestHandlerClient } from '../requestHandler/HttpRequestHandlerClient'; import LocalHttpRequestHandler from '../requestHandler/LocalHttpRequestHandler'; import RemoteHttpRequestHandler from '../requestHandler/RemoteHttpRequestHandler'; @@ -32,7 +31,7 @@ class HttpInterceptorClient< private store: HttpInterceptorStore; private _baseURL: ExtendedURL; private _isRunning = false; - private onUnhandledRequest?: UnhandledRequestStrategy; + private _onUnhandledRequest?: UnhandledRequestStrategy; private _shouldSaveRequests = false; private Handler: HandlerConstructor; @@ -61,7 +60,7 @@ class HttpInterceptorClient< this.store = options.store; this._baseURL = options.baseURL; this.Handler = options.Handler; - this.onUnhandledRequest = options.onUnhandledRequest; + this._onUnhandledRequest = options.onUnhandledRequest; this._shouldSaveRequests = options.saveRequests ?? false; } @@ -73,6 +72,10 @@ class HttpInterceptorClient< return this.worker.platform(); } + onUnhandledRequest() { + return this._onUnhandledRequest; + } + isRunning() { return this.worker.isRunning() && this._isRunning; } @@ -82,17 +85,15 @@ class HttpInterceptorClient< } async start() { - if (this.onUnhandledRequest) { - this.worker.onUnhandledRequest(this.baseURL().toString(), this.onUnhandledRequest); - } - await this.worker.start(); + + this.worker.registerRunningInterceptor(this); this.markAsRunning(true); } async stop() { this.markAsRunning(false); - this.worker.offUnhandledRequest(this.baseURL().toString()); + this.worker.unregisterRunningInterceptor(this); const wasLastRunningInterceptor = this.numberOfRunningInterceptors() === 0; if (wasLastRunningInterceptor) { @@ -200,7 +201,7 @@ class HttpInterceptorClient< Method extends HttpSchemaMethod, Path extends HttpSchemaPath, Context extends HttpInterceptorRequestContext, - >(matchedURLRegex: RegExp, method: Method, path: Path, { request }: Context): Promise { + >(matchedURLRegex: RegExp, method: Method, path: Path, { request }: Context) { const parsedRequest = await HttpInterceptorWorker.parseRawRequest>(request, { urlRegex: matchedURLRegex, }); @@ -208,7 +209,7 @@ class HttpInterceptorClient< const matchedHandler = await this.findMatchedHandler(method, path, parsedRequest); if (!matchedHandler) { - return { response: null }; + return null; } const responseDeclaration = await matchedHandler.applyResponseDeclaration(parsedRequest); @@ -225,7 +226,7 @@ class HttpInterceptorClient< matchedHandler.saveInterceptedRequest(parsedRequest, parsedResponse); } - return { response }; + return response; } private async findMatchedHandler< diff --git a/packages/zimic/src/interceptor/http/interceptor/LocalHttpInterceptor.ts b/packages/zimic/src/interceptor/http/interceptor/LocalHttpInterceptor.ts index 5b1ad9108..1fd812dd6 100644 --- a/packages/zimic/src/interceptor/http/interceptor/LocalHttpInterceptor.ts +++ b/packages/zimic/src/interceptor/http/interceptor/LocalHttpInterceptor.ts @@ -1,5 +1,5 @@ import { HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; -import { createURL, excludeNonPathParams } from '@/utils/urls'; +import { createURL } from '@/utils/urls'; import LocalHttpRequestHandler from '../requestHandler/LocalHttpRequestHandler'; import HttpInterceptorClient, { SUPPORTED_BASE_URL_PROTOCOLS } from './HttpInterceptorClient'; @@ -19,8 +19,8 @@ class LocalHttpInterceptor implements PublicLocalHttp const baseURL = createURL(options.baseURL, { protocols: SUPPORTED_BASE_URL_PROTOCOLS, + excludeNonPathParams: true, }); - excludeNonPathParams(baseURL); const worker = this.store.getOrCreateLocalWorker({}); diff --git a/packages/zimic/src/interceptor/http/interceptor/RemoteHttpInterceptor.ts b/packages/zimic/src/interceptor/http/interceptor/RemoteHttpInterceptor.ts index 87fb6af43..fff6e0751 100644 --- a/packages/zimic/src/interceptor/http/interceptor/RemoteHttpInterceptor.ts +++ b/packages/zimic/src/interceptor/http/interceptor/RemoteHttpInterceptor.ts @@ -1,5 +1,5 @@ import { HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; -import { createURL, excludeNonPathParams } from '@/utils/urls'; +import { createURL } from '@/utils/urls'; import RemoteHttpRequestHandler from '../requestHandler/RemoteHttpRequestHandler'; import HttpInterceptorClient, { SUPPORTED_BASE_URL_PROTOCOLS } from './HttpInterceptorClient'; @@ -19,13 +19,13 @@ class RemoteHttpInterceptor implements PublicRemoteHt const baseURL = createURL(options.baseURL, { protocols: SUPPORTED_BASE_URL_PROTOCOLS, + excludeNonPathParams: true, }); - excludeNonPathParams(baseURL); const serverURL = createURL(baseURL.origin, { protocols: SUPPORTED_BASE_URL_PROTOCOLS, + excludeNonPathParams: true, }); - excludeNonPathParams(serverURL); const worker = this.store.getOrCreateRemoteWorker({ serverURL }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.browser.test.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.browser.test.ts index 2f00ae2c0..9006c3521 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.browser.test.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.browser.test.ts @@ -6,14 +6,14 @@ import { getBrowserBaseURL } from '@tests/utils/interceptors'; import { declareHandlerHttpInterceptorTests } from './shared/handlers'; import testMatrix from './shared/matrix'; -describe.each(testMatrix)('HttpInterceptor (browser, $type) > Bypass', ({ type }) => { +describe.each(testMatrix)('HttpInterceptor (browser, $type) > Bypass', async ({ type }) => { let baseURL: ExtendedURL; beforeAll(async () => { baseURL = await getBrowserBaseURL(type); }); - declareHandlerHttpInterceptorTests({ + await declareHandlerHttpInterceptorTests({ platform: 'browser', type, getBaseURL: () => baseURL, diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.node.test.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.node.test.ts index b3d15d180..883b4c374 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.node.test.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.handlers.node.test.ts @@ -7,7 +7,7 @@ import { createInternalInterceptorServer } from '@tests/utils/interceptorServers import { declareHandlerHttpInterceptorTests } from './shared/handlers'; import testMatrix from './shared/matrix'; -describe.each(testMatrix)('HttpInterceptor (node, $type) > Handlers', ({ type }) => { +describe.each(testMatrix)('HttpInterceptor (node, $type) > Handlers', async ({ type }) => { const server = createInternalInterceptorServer({ logUnhandledRequests: false }); let baseURL: ExtendedURL; @@ -25,7 +25,7 @@ describe.each(testMatrix)('HttpInterceptor (node, $type) > Handlers', ({ type }) } }); - declareHandlerHttpInterceptorTests({ + await declareHandlerHttpInterceptorTests({ platform: 'node', type, getBaseURL: () => baseURL, diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.browser.test.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.browser.test.ts index f39864956..702656bf6 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.browser.test.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.browser.test.ts @@ -4,7 +4,8 @@ import { ExtendedURL } from '@/utils/urls'; import { getBrowserBaseURL } from '@tests/utils/interceptors'; import testMatrix from './shared/matrix'; -import { declareUnhandledRequestHttpInterceptorTests } from './shared/unhandledRequests'; +import { declareUnhandledRequestFactoriesHttpInterceptorTests } from './shared/unhandledRequests.factories'; +import { declareUnhandledRequestLoggingHttpInterceptorTests } from './shared/unhandledRequests.logging'; describe.each(testMatrix)('HttpInterceptor (browser, $type) > Unhandled requests', ({ type }) => { let baseURL: ExtendedURL; @@ -13,10 +14,21 @@ describe.each(testMatrix)('HttpInterceptor (browser, $type) > Unhandled requests baseURL = await getBrowserBaseURL(type); }); - declareUnhandledRequestHttpInterceptorTests({ - platform: 'browser', - type, - getBaseURL: () => baseURL, - getInterceptorOptions: () => ({ type, baseURL }), + describe('Logging', async () => { + await declareUnhandledRequestLoggingHttpInterceptorTests({ + platform: 'browser', + type, + getBaseURL: () => baseURL, + getInterceptorOptions: () => ({ type, baseURL }), + }); + }); + + describe('Factories', async () => { + await declareUnhandledRequestFactoriesHttpInterceptorTests({ + platform: 'browser', + type, + getBaseURL: () => baseURL, + getInterceptorOptions: () => ({ type, baseURL }), + }); }); }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.node.test.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.node.test.ts index 804a4c9bd..6ce6cf132 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.node.test.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.unhandledRequests.node.test.ts @@ -5,7 +5,8 @@ import { getNodeBaseURL } from '@tests/utils/interceptors'; import { createInternalInterceptorServer } from '@tests/utils/interceptorServers'; import testMatrix from './shared/matrix'; -import { declareUnhandledRequestHttpInterceptorTests } from './shared/unhandledRequests'; +import { declareUnhandledRequestFactoriesHttpInterceptorTests } from './shared/unhandledRequests.factories'; +import { declareUnhandledRequestLoggingHttpInterceptorTests } from './shared/unhandledRequests.logging'; describe.each(testMatrix)('HttpInterceptor (node, $type) > Unhandled requests', ({ type }) => { const server = createInternalInterceptorServer({ logUnhandledRequests: false }); @@ -25,10 +26,21 @@ describe.each(testMatrix)('HttpInterceptor (node, $type) > Unhandled requests', } }); - declareUnhandledRequestHttpInterceptorTests({ - platform: 'node', - type, - getBaseURL: () => baseURL, - getInterceptorOptions: () => ({ type, baseURL }), + describe('Logging', async () => { + await declareUnhandledRequestLoggingHttpInterceptorTests({ + platform: 'node', + type, + getBaseURL: () => baseURL, + getInterceptorOptions: () => ({ type, baseURL }), + }); + }); + + describe('Factories', async () => { + await declareUnhandledRequestFactoriesHttpInterceptorTests({ + platform: 'node', + type, + getBaseURL: () => baseURL, + getInterceptorOptions: () => ({ type, baseURL }), + }); }); }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/baseURLs.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/baseURLs.ts index 082becf5b..182fdf24c 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/baseURLs.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/baseURLs.ts @@ -37,12 +37,7 @@ export function declareBaseURLHttpInterceptorTests(options: RuntimeSharedHttpInt }>({ ...interceptorOptions, baseURL }, async (interceptor) => { expect(interceptor.baseURL()).toBe(baseURL); - const handler = await promiseIfRemote( - interceptor.get(path).respond({ - status: 200, - }), - interceptor, - ); + const handler = await promiseIfRemote(interceptor.get(path).respond({ status: 200 }), interceptor); let requests = await handler.requests(); expect(requests).toHaveLength(0); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts index e7635dc6c..53757f124 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts @@ -8,6 +8,7 @@ import DisabledRequestSavingError from '@/interceptor/http/requestHandler/errors import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttpRequestHandler'; import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; +import { importCrypto } from '@/utils/crypto'; import { joinURL } from '@/utils/urls'; import { usingIgnoredConsole } from '@tests/utils/console'; import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; @@ -16,14 +17,20 @@ import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/ import { HttpInterceptorOptions } from '../../types/options'; import { RuntimeSharedHttpInterceptorTestsOptions, verifyUnhandledRequestMessage } from './utils'; -export function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { +export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { const { platform, type, getBaseURL, getInterceptorOptions } = options; + const crypto = await importCrypto(); + let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + type MethodSchema = HttpSchema.Method<{ + response: { 200: { headers: AccessControlHeaders } }; + }>; + beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); @@ -40,10 +47,6 @@ export function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInt const lowerMethod = method.toLowerCase<'POST'>(); - type MethodSchema = HttpSchema.Method<{ - response: { 200: { headers: AccessControlHeaders } }; - }>; - it(`should support intercepting ${method} requests with a static response`, async () => { await usingHttpInterceptor<{ '/users': { @@ -171,9 +174,8 @@ export function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInt await usingIgnoredConsole(['error', 'warn'], async (spies) => { const request = new Request(joinURL(baseURL, '/users'), { method, - headers: { 'x-value': '1' }, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. }); - const responsePromise = fetch(request); if (overridesPreflightResponse) { @@ -190,22 +192,14 @@ export function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInt expect(spies.error.mock.calls[0]).toEqual([error]); const errorMessage = spies.error.mock.calls[1].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform, - request, - }); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); } else { expect(spies.error).toHaveBeenCalledTimes(1); expect(spies.warn).toHaveBeenCalledTimes(1); expect(spies.error.mock.calls[0]).toEqual([error]); const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { - type: 'warn', - platform, - request, - }); + await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); } }); @@ -320,7 +314,7 @@ export function declareHandlerHttpInterceptorTests(options: RuntimeSharedHttpInt const searchParams = new HttpSearchParams({ tag: 'admin' }); - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); expect(response.status).toBe(200); requests = await promiseIfRemote(handler.requests(), interceptor); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts index 5455d6989..b388f6105 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts @@ -82,13 +82,13 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI let responsePromise = fetchWithTimeout(joinURL(baseURL, '/users'), { method, - timeout: 200, + timeout: overridesPreflightResponse ? 0 : 500, }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (type === 'local') { - await expectBypassedResponse(responsePromise); + await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } @@ -152,12 +152,15 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI expect(interceptor.isRunning()).toBe(false); expect(otherInterceptor.isRunning()).toBe(true); - let responsePromise = fetchWithTimeout(joinURL(baseURL, '/users'), { method, timeout: 500 }); + let responsePromise = fetchWithTimeout(joinURL(baseURL, '/users'), { + method, + timeout: overridesPreflightResponse ? 0 : 500, + }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (type === 'local') { - await expectBypassedResponse(responsePromise); + await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts index 4b49b4d09..acef05eb2 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts @@ -237,7 +237,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh other: 'value', }); - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method, }); expect(response.status).toBe(200); @@ -247,7 +247,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh searchParams.delete('tag'); - const responsePromise = fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { + const responsePromise = fetch(joinURL(baseURL, `/users?${searchParams}`), { method, }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts new file mode 100644 index 000000000..1db2779ef --- /dev/null +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts @@ -0,0 +1,462 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; +import { HTTP_METHODS, HttpSchema } from '@/http/types/schema'; +import { promiseIfRemote } from '@/interceptor/http/interceptorWorker/__tests__/utils/promises'; +import { DEFAULT_UNHANDLED_REQUEST_STRATEGY } from '@/interceptor/http/interceptorWorker/constants'; +import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttpRequestHandler'; +import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; +import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; +import { importCrypto } from '@/utils/crypto'; +import { waitForDelay } from '@/utils/time'; +import { joinURL } from '@/utils/urls'; +import { usingIgnoredConsole } from '@tests/utils/console'; +import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; + +import { HttpInterceptorOptions, UnhandledRequestStrategy } from '../../types/options'; +import { RuntimeSharedHttpInterceptorTestsOptions, verifyUnhandledRequestMessage } from './utils'; + +export async function declareUnhandledRequestFactoriesHttpInterceptorTests( + options: RuntimeSharedHttpInterceptorTestsOptions, +) { + const { platform, type, getBaseURL, getInterceptorOptions } = options; + + const crypto = await importCrypto(); + + let baseURL: URL; + let interceptorOptions: HttpInterceptorOptions; + + let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + + type MethodSchemaWithoutRequestBody = HttpSchema.Method<{ + request: { + searchParams: { value?: string; name?: string }; + }; + response: { 200: { headers: AccessControlHeaders } }; + }>; + + type SchemaWithoutRequestBody = HttpSchema<{ + '/users': { + GET: MethodSchemaWithoutRequestBody; + POST: MethodSchemaWithoutRequestBody; + PUT: MethodSchemaWithoutRequestBody; + PATCH: MethodSchemaWithoutRequestBody; + DELETE: MethodSchemaWithoutRequestBody; + HEAD: MethodSchemaWithoutRequestBody; + OPTIONS: MethodSchemaWithoutRequestBody; + }; + }>; + + beforeEach(() => { + baseURL = getBaseURL(); + interceptorOptions = getInterceptorOptions(); + + Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; + }); + + describe.each(HTTP_METHODS)('Method (%s)', (method) => { + const { overridesPreflightResponse, numberOfRequestsIncludingPreflight } = assessPreflightInterference({ + method, + platform, + type, + }); + + const lowerMethod = method.toLowerCase<'POST'>(); + + it(`should support a synchronous unhandled ${method} request factory`, async () => { + const onUnhandledRequest = vi.fn((request) => { + const url = new URL(request.url); + + return { + action: 'reject', + log: !url.searchParams.has('name'), + }; + }); + + await usingHttpInterceptor( + { ...interceptorOptions, onUnhandledRequest }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ + status: 200, + headers: DEFAULT_ACCESS_CONTROL_HEADERS, + }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams<{ value: string; name?: string }>({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(0); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + searchParams.set('value', '2'); + searchParams.set('name', 'User 1'); + + let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams}`), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + }); + }, + ); + }); + + it(`should support an asynchronous unhandled ${method} request factory`, async () => { + const onUnhandledRequest = vi.fn(async (request) => { + const url = new URL(request.url); + + await waitForDelay(10); + + return { + action: 'reject', + log: !url.searchParams.has('name'), + }; + }); + + await usingHttpInterceptor( + { ...interceptorOptions, type, onUnhandledRequest }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ + status: 200, + headers: DEFAULT_ACCESS_CONTROL_HEADERS, + }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams<{ value: string; name?: string }>({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(0); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + searchParams.set('value', '2'); + searchParams.set('name', 'User 1'); + + let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams}`), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + }); + }, + ); + }); + + it(`should log an error if a synchronous unhandled ${method} request factory throws`, async () => { + const error = new Error('Unhandled request.'); + + const onUnhandledRequest = vi.fn((request) => { + const url = new URL(request.url); + + if (!url.searchParams.has('name')) { + throw error; + } + + return { action: 'reject', log: false }; + }); + + await usingHttpInterceptor( + { ...interceptorOptions, type, onUnhandledRequest }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ + status: 200, + headers: DEFAULT_ACCESS_CONTROL_HEADERS, + }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams<{ value: string; name?: string }>({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(0); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + searchParams.set('value', '2'); + searchParams.set('name', 'User 1'); + + let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams}`), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + responsePromise = fetch(request); + + const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; + + if (defaultStrategy.action === 'bypass') { + await expectBypassedResponse(responsePromise); + } else if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + + if (defaultStrategy.action === 'bypass') { + expect(spies.warn).toHaveBeenCalledTimes(1); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + expect(spies.error).toHaveBeenCalledWith(error); + + await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { + type: 'warn', + platform, + request, + }); + } else { + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + + expect(spies.error).toHaveBeenNthCalledWith(1, error); + + await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { + type: 'error', + platform, + request, + }); + } + }); + }, + ); + }); + + it(`should log an error if an asynchronous unhandled ${method} request factory throws`, async () => { + const error = new Error('Unhandled request.'); + + const onUnhandledRequest = vi.fn(async (request) => { + const url = new URL(request.url); + + await waitForDelay(10); + + if (!url.searchParams.has('name')) { + throw error; + } + + return { action: 'reject', log: false }; + }); + + await usingHttpInterceptor( + { ...interceptorOptions, type, onUnhandledRequest }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ + status: 200, + headers: DEFAULT_ACCESS_CONTROL_HEADERS, + }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams<{ + value: string; + name?: string; + }>({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(0); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + searchParams.set('value', '2'); + searchParams.set('name', 'User 1'); + + let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams}`), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + responsePromise = fetch(request); + + const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; + + if (defaultStrategy.action === 'bypass') { + await expectBypassedResponse(responsePromise); + } else if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + + if (defaultStrategy.action === 'bypass') { + expect(spies.warn).toHaveBeenCalledTimes(1); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + expect(spies.error).toHaveBeenCalledWith(error); + + await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { + type: 'warn', + platform, + request, + }); + } else { + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + + expect(spies.error).toHaveBeenNthCalledWith(1, error); + + await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { + type: 'error', + platform, + request, + }); + } + }); + }, + ); + }); + }); +} diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts new file mode 100644 index 000000000..fb02a4692 --- /dev/null +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts @@ -0,0 +1,888 @@ +import { afterEach, beforeEach, describe, expect, expectTypeOf, it } from 'vitest'; + +import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; +import { HTTP_METHODS, HttpSchema } from '@/http/types/schema'; +import { httpInterceptor } from '@/interceptor/http'; +import { promiseIfRemote } from '@/interceptor/http/interceptorWorker/__tests__/utils/promises'; +import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttpRequestHandler'; +import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; +import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; +import { importCrypto } from '@/utils/crypto'; +import { fetchWithTimeout } from '@/utils/fetch'; +import { methodCanHaveRequestBody } from '@/utils/http'; +import { joinURL } from '@/utils/urls'; +import { usingIgnoredConsole } from '@tests/utils/console'; +import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; + +import { HttpInterceptorOptions, UnhandledRequestStrategy } from '../../types/options'; +import { UnhandledHttpInterceptorRequest } from '../../types/requests'; +import { + RuntimeSharedHttpInterceptorTestsOptions, + verifyUnhandledRequestMessage, + verifyUnhandledRequest, +} from './utils'; + +export async function declareUnhandledRequestLoggingHttpInterceptorTests( + options: RuntimeSharedHttpInterceptorTestsOptions, +) { + const { platform, type, getBaseURL, getInterceptorOptions } = options; + + const crypto = await importCrypto(); + + let baseURL: URL; + let interceptorOptions: HttpInterceptorOptions; + + let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + + type MethodSchemaWithoutRequestBody = HttpSchema.Method<{ + request: { + headers: { 'x-value'?: string }; + searchParams: { value?: string; name?: string }; + }; + response: { 200: { headers: AccessControlHeaders } }; + }>; + + type SchemaWithoutRequestBody = HttpSchema<{ + '/users': { + GET: MethodSchemaWithoutRequestBody; + POST: MethodSchemaWithoutRequestBody; + PUT: MethodSchemaWithoutRequestBody; + PATCH: MethodSchemaWithoutRequestBody; + DELETE: MethodSchemaWithoutRequestBody; + HEAD: MethodSchemaWithoutRequestBody; + OPTIONS: MethodSchemaWithoutRequestBody; + }; + }>; + + type MethodSchemaWithRequestBody = HttpSchema.Method<{ + request: { + headers: { 'x-value'?: string }; + searchParams: { value?: string; name?: string }; + body: { message: string }; + }; + response: { + 200: { headers: AccessControlHeaders }; + }; + }>; + + type SchemaWithRequestBody = HttpSchema<{ + '/users': { + POST: MethodSchemaWithRequestBody; + PUT: MethodSchemaWithRequestBody; + PATCH: MethodSchemaWithRequestBody; + DELETE: MethodSchemaWithRequestBody; + }; + }>; + + beforeEach(() => { + baseURL = getBaseURL(); + interceptorOptions = getInterceptorOptions(); + + Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; + + httpInterceptor.default.local.onUnhandledRequest = { action: 'reject', log: true }; + httpInterceptor.default.remote.onUnhandledRequest = { action: 'reject', log: true }; + }); + + describe.each(HTTP_METHODS)('Method (%s)', (method) => { + const { overridesPreflightResponse, numberOfRequestsIncludingPreflight } = assessPreflightInterference({ + method, + platform, + type, + }); + + const lowerMethod = method.toLowerCase<'POST'>(); + + describe.each([ + { overrideDefault: undefined }, + { overrideDefault: 'static' as const }, + { overrideDefault: 'static-undefined-log' as const }, + { overrideDefault: 'factory' as const }, + { overrideDefault: 'factory-undefined-log' as const }, + ])('Logging enabled: override default $overrideDefault', ({ overrideDefault }) => { + const log = overrideDefault?.endsWith('undefined-log') ? undefined : true; + + const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { + action: 'bypass', + log, + }; + const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { + action: 'reject', + log, + }; + + beforeEach(() => { + if (overrideDefault?.startsWith('static')) { + if (type === 'local') { + httpInterceptor.default.local.onUnhandledRequest = localOnUnhandledRequest; + expect(httpInterceptor.default.local.onUnhandledRequest).toBe(localOnUnhandledRequest); + } else { + httpInterceptor.default.remote.onUnhandledRequest = remoteOnUnhandledRequest; + expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(remoteOnUnhandledRequest); + } + } else if (overrideDefault?.startsWith('factory')) { + if (type === 'local') { + function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { + verifyUnhandledRequest(request, method); + return localOnUnhandledRequest; + } + + httpInterceptor.default.local.onUnhandledRequest = onUnhandledRequest; + expect(httpInterceptor.default.local.onUnhandledRequest).toBe(onUnhandledRequest); + } else { + function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { + verifyUnhandledRequest(request, method); + return remoteOnUnhandledRequest; + } + + httpInterceptor.default.remote.onUnhandledRequest = onUnhandledRequest; + expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(onUnhandledRequest); + } + } + }); + + afterEach(() => { + localOnUnhandledRequest.action = 'bypass'; + remoteOnUnhandledRequest.action = 'reject'; + }); + + if (type === 'local') { + it(`should show a warning when logging is enabled and ${method} requests with no body are unhandled and bypassed`, async () => { + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + await promiseIfRemote(handler.bypass(), interceptor); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.error).toHaveBeenCalledTimes(0); + + const warnMessage = spies.warn.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + }); + }, + ); + }); + } + + if (type === 'local' && methodCanHaveRequestBody(method)) { + it(`should show a warning when logging is enabled and ${method} requests with body are unhandled and bypassed`, async () => { + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { + method, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ message: 'ok' }), + }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf<{ message: string }>(); + expect(interceptedRequest.body).toEqual({ message: 'ok' }); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + await promiseIfRemote(handler.bypass(), interceptor); + + const request = new Request(joinURL(baseURL, '/users'), { + method, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ message: 'ok' }), + }); + const requestClone = request.clone(); + + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.error).toHaveBeenCalledTimes(0); + + const warnMessage = spies.warn.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request: requestClone }); + }); + }, + ); + }); + + it(`should show a warning when logging is enabled and ${method} requests are unhandled due to restrictions and bypassed`, async () => { + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ headers: { 'x-value': '1' } }) + .respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { + method, + headers: { 'x-value': '1' }, + }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.error).toHaveBeenCalledTimes(0); + + const warnMessage = spies.warn.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + }); + }, + ); + }); + + it(`should show a warning when logging is enabled and ${method} requests are unhandled due to unmocked path and bypassed`, async () => { + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users/other'), { method }); + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.error).toHaveBeenCalledTimes(0); + + const warnMessage = spies.warn.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + }); + }, + ); + }); + } + + it(`should show an error when logging is enabled and ${method} requests with no body are unhandled and rejected`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'reject'; + } + + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + await promiseIfRemote(handler.bypass(), interceptor); + + const request = new Request(joinURL(baseURL, '/users'), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + }); + }, + ); + }); + + if (methodCanHaveRequestBody(method)) { + it(`should show an error when logging is enabled and ${method} requests with body are unhandled and rejected`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'reject'; + } + + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { + method, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ message: 'ok' }), + }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf<{ message: string }>(); + expect(interceptedRequest.body).toEqual({ message: 'ok' }); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + await promiseIfRemote(handler.bypass(), interceptor); + + const request = new Request(joinURL(baseURL, '/users'), { + method, + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ message: 'ok' }), + }); + const requestClone = request.clone(); + + const responsePromise = fetch(request); + await expectFetchError(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request: requestClone }); + }); + }, + ); + }); + } + + it(`should show an error when logging is enabled and ${method} requests are unhandled due to restrictions and rejected`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'reject'; + } + + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + }); + }, + ); + }); + + it(`should show an error when logging is enabled and ${method} requests are unhandled due to unmocked path and rejected`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'reject'; + } + + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users/other'), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + } else { + await expectFetchError(responsePromise); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + }); + }, + ); + }); + }); + + describe.each([ + { overrideDefault: undefined }, + { overrideDefault: 'static' as const }, + { overrideDefault: 'factory' as const }, + ])('Logging disabled: override default $overrideDefault', ({ overrideDefault }) => { + const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { + action: 'bypass', + log: false, + }; + const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { + action: 'reject', + log: false, + }; + + beforeEach(() => { + if (overrideDefault === 'static') { + if (type === 'local') { + httpInterceptor.default.local.onUnhandledRequest = localOnUnhandledRequest; + expect(httpInterceptor.default.local.onUnhandledRequest).toBe(localOnUnhandledRequest); + } else { + httpInterceptor.default.remote.onUnhandledRequest = remoteOnUnhandledRequest; + expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(remoteOnUnhandledRequest); + } + } else if (overrideDefault === 'factory') { + if (type === 'local') { + function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { + verifyUnhandledRequest(request, method); + return localOnUnhandledRequest; + } + + httpInterceptor.default.local.onUnhandledRequest = onUnhandledRequest; + expect(httpInterceptor.default.local.onUnhandledRequest).toBe(onUnhandledRequest); + } else { + function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { + verifyUnhandledRequest(request, method); + return remoteOnUnhandledRequest; + } + + httpInterceptor.default.remote.onUnhandledRequest = onUnhandledRequest; + expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(onUnhandledRequest); + } + } + }); + + afterEach(() => { + localOnUnhandledRequest.action = 'bypass'; + remoteOnUnhandledRequest.action = 'reject'; + }); + + if (type === 'local') { + it(`should not show a warning when logging is disabled and ${method} requests are unhandled and bypassed`, async () => { + const extendedInterceptorOptions: HttpInterceptorOptions = { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: false }, + }; + + await usingHttpInterceptor(extendedInterceptorOptions, async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + }); + }); + }); + } + + it(`should not show an error when logging is disabled and ${method} requests are unhandled and rejected`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'reject'; + } + + const extendedInterceptorOptions: HttpInterceptorOptions = { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: false }, + }; + + await usingHttpInterceptor(extendedInterceptorOptions, async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + }); + }); + }); + }); + + it(`should not log unhandled ${method} requests when no interceptors exist`, async () => { + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { method }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else if (type === 'local') { + await expectBypassedResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + }); + }); + + it(`should not log unhandled ${method} requests when no interceptor with matching request base URL was found`, async () => { + const otherBaseURL = joinURL(baseURL, 'other'); + + await usingHttpInterceptor( + { ...interceptorOptions, baseURL: otherBaseURL }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const request = new Request(joinURL(baseURL, '/users'), { method }); + expect(request.url.startsWith(otherBaseURL)).toBe(false); + + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else if (type === 'local') { + await expectBypassedResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + }); + }, + ); + }); + + it(`should not log unhandled ${method} requests when the matched interceptor is not running`, async () => { + await usingHttpInterceptor(interceptorOptions, async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users') + .with({ searchParams: { value: '1' } }) + .respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const searchParams = new HttpSearchParams({ value: '1' }); + + const response = await fetch(joinURL(baseURL, `/users?${searchParams}`), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const request = new Request(joinURL(baseURL, '/users'), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + let responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else { + await expectFetchError(responsePromise); + } + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + + spies.warn.mockClear(); + spies.error.mockClear(); + + await interceptor.stop(); + + responsePromise = fetchWithTimeout(request, { + timeout: overridesPreflightResponse ? 0 : 500, + }); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + } else if (type === 'local') { + await expectBypassedResponse(responsePromise, { canBeAborted: true }); + } else { + await expectFetchError(responsePromise, { canBeAborted: true }); + } + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + }); + }); + }); + }); +} diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.ts deleted file mode 100644 index 1a38c5ffb..000000000 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.ts +++ /dev/null @@ -1,1164 +0,0 @@ -import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest'; - -import { HttpHeaders, HttpRequest } from '@/http'; -import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; -import { HTTP_METHODS, HttpSchema } from '@/http/types/schema'; -import { httpInterceptor } from '@/interceptor/http'; -import { promiseIfRemote } from '@/interceptor/http/interceptorWorker/__tests__/utils/promises'; -import { DEFAULT_UNHANDLED_REQUEST_STRATEGY } from '@/interceptor/http/interceptorWorker/constants'; -import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttpRequestHandler'; -import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; -import { HttpRequestBodySchema } from '@/interceptor/http/requestHandler/types/requests'; -import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; -import { methodCanHaveRequestBody } from '@/utils/http'; -import { waitForDelay } from '@/utils/time'; -import { joinURL } from '@/utils/urls'; -import { usingIgnoredConsole } from '@tests/utils/console'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; -import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; - -import { HttpInterceptorOptions, UnhandledRequestStrategy } from '../../types/options'; -import { UnhandledHttpInterceptorRequest, UnhandledHttpInterceptorRequestMethodSchema } from '../../types/requests'; -import { RuntimeSharedHttpInterceptorTestsOptions, verifyUnhandledRequestMessage } from './utils'; - -const verifyUnhandledRequest = vi.fn((request: UnhandledHttpInterceptorRequest, method: string) => { - expect(request).toBeInstanceOf(Request); - expect(request).not.toHaveProperty('response'); - - expectTypeOf(request.headers).toEqualTypeOf>>(); - expect(request.headers).toBeInstanceOf(HttpHeaders); - - expectTypeOf(request.searchParams).toEqualTypeOf>>(); - expect(request.searchParams).toBeInstanceOf(HttpSearchParams); - - expectTypeOf(request.pathParams).toEqualTypeOf<{}>(); - expect(request.pathParams).toEqual({}); - - type BodySchema = HttpRequestBodySchema; - - expectTypeOf(request.body).toEqualTypeOf(); - expect(request).toHaveProperty('body'); - - expectTypeOf(request.raw).toEqualTypeOf>(); - expect(request.raw).toBeInstanceOf(Request); - expect(request.raw.url).toBe(request.url); - expect(request.raw.method).toBe(method); - expect(Object.fromEntries(request.headers)).toEqual(expect.objectContaining(Object.fromEntries(request.raw.headers))); -}); - -export function declareUnhandledRequestHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { - const { platform, type, getBaseURL, getInterceptorOptions } = options; - - let baseURL: URL; - let interceptorOptions: HttpInterceptorOptions; - - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; - - beforeEach(() => { - baseURL = getBaseURL(); - interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; - - verifyUnhandledRequest.mockClear(); - }); - - describe.each(HTTP_METHODS)('Method (%s)', (method) => { - const { overridesPreflightResponse, numberOfRequestsIncludingPreflight } = assessPreflightInterference({ - method, - platform, - type, - }); - - const lowerMethod = method.toLowerCase<'POST'>(); - - type MethodSchemaWithoutRequestBody = HttpSchema.Method<{ - request: { - headers: { 'x-value'?: string }; - searchParams: { 'x-value'?: string; name?: string }; - }; - response: { 200: { headers: AccessControlHeaders } }; - }>; - - type MethodSchemaWithRequestBody = HttpSchema.Method<{ - request: { - headers: { 'x-value'?: string }; - searchParams: { 'x-value'?: string; name?: string }; - body: { message: string }; - }; - response: { - 200: { headers: AccessControlHeaders }; - }; - }>; - - describe.each([ - { overrideDefault: undefined }, - { overrideDefault: 'static' as const }, - { overrideDefault: 'static-undefined-log' as const }, - { overrideDefault: 'factory' as const }, - { overrideDefault: 'factory-undefined-log' as const }, - ])('Logging enabled: override default $overrideDefault', ({ overrideDefault }) => { - const log = overrideDefault?.endsWith('undefined-log') ? undefined : true; - - const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { - action: 'bypass', - log, - }; - const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { - action: 'reject', - log, - }; - - beforeEach(() => { - if (overrideDefault?.startsWith('static')) { - if (type === 'local') { - httpInterceptor.default.local.onUnhandledRequest = localOnUnhandledRequest; - expect(httpInterceptor.default.local.onUnhandledRequest).toBe(localOnUnhandledRequest); - } else { - httpInterceptor.default.remote.onUnhandledRequest = remoteOnUnhandledRequest; - expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(remoteOnUnhandledRequest); - } - } else if (overrideDefault?.startsWith('factory')) { - if (type === 'local') { - function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { - verifyUnhandledRequest(request, method); - return localOnUnhandledRequest; - } - - httpInterceptor.default.local.onUnhandledRequest = onUnhandledRequest; - expect(httpInterceptor.default.local.onUnhandledRequest).toBe(onUnhandledRequest); - } else { - function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { - verifyUnhandledRequest(request, method); - return remoteOnUnhandledRequest; - } - - httpInterceptor.default.remote.onUnhandledRequest = onUnhandledRequest; - expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(onUnhandledRequest); - } - } - }); - - afterEach(() => { - localOnUnhandledRequest.action = 'bypass'; - remoteOnUnhandledRequest.action = 'reject'; - - if (overrideDefault?.startsWith('factory')) { - expect(verifyUnhandledRequest).toHaveBeenCalled(); - } else { - expect(verifyUnhandledRequest).not.toHaveBeenCalled(); - } - }); - - if (type === 'local') { - it(`should show a warning when logging is enabled and ${method} requests with no body are unhandled and bypassed`, async () => { - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>( - { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, - }, - async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ headers: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const response = await fetch(joinURL(baseURL, '/users'), { - method, - headers: { 'x-value': '1' }, - }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; - expectTypeOf(interceptedRequest.body).toEqualTypeOf(); - expect(interceptedRequest.body).toBe(null); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - const responsePromise = fetch(request); - await expectBypassedResponse(responsePromise); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - expect(spies.error).toHaveBeenCalledTimes(0); - - const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { - type: 'warn', - platform, - request, - }); - }); - }, - ); - }); - } - - if (type === 'local' && methodCanHaveRequestBody(method)) { - it(`should show a warning when logging is enabled and ${method} requests with body are unhandled and bypassed`, async () => { - await usingHttpInterceptor<{ - '/users': { - POST: MethodSchemaWithRequestBody; - PUT: MethodSchemaWithRequestBody; - PATCH: MethodSchemaWithRequestBody; - DELETE: MethodSchemaWithRequestBody; - }; - }>( - { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, - }, - async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ headers: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const response = await fetch(joinURL(baseURL, '/users'), { - method, - headers: { 'x-value': '1', 'content-type': 'application/json' }, - body: JSON.stringify({ message: 'ok' }), - }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; - expectTypeOf(interceptedRequest.body).toEqualTypeOf<{ message: string }>(); - expect(interceptedRequest.body).toEqual({ message: 'ok' }); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { - method, - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ message: 'ok' }), - }); - const requestClone = request.clone(); - - const responsePromise = fetch(request); - await expectBypassedResponse(responsePromise); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - expect(spies.error).toHaveBeenCalledTimes(0); - - const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { - type: 'warn', - platform, - request: requestClone, - }); - }); - }, - ); - }); - } - - it(`should show an error when logging is enabled and ${method} requests with no body are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>( - { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, - }, - async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; - expectTypeOf(interceptedRequest.body).toEqualTypeOf(); - expect(interceptedRequest.body).toBe(null); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { - method, - headers: { 'x-value': '1' }, - }); - const responsePromise = fetch(request); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform, - request, - }); - }); - }, - ); - }); - - if (methodCanHaveRequestBody(method)) { - it(`should show an error when logging is enabled and ${method} requests with body are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - - await usingHttpInterceptor<{ - '/users': { - POST: MethodSchemaWithRequestBody; - PUT: MethodSchemaWithRequestBody; - PATCH: MethodSchemaWithRequestBody; - DELETE: MethodSchemaWithRequestBody; - }; - }>( - { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, - }, - async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { - method, - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ message: 'ok' }), - }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; - expectTypeOf(interceptedRequest.body).toEqualTypeOf<{ message: string }>(); - expect(interceptedRequest.body).toEqual({ message: 'ok' }); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { - method, - headers: { 'x-value': '1', 'content-type': 'application/json' }, - body: JSON.stringify({ message: 'ok' }), - }); - const requestClone = request.clone(); - - const responsePromise = fetch(request); - await expectFetchError(responsePromise); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform, - request: requestClone, - }); - }); - }, - ); - }); - } - }); - - describe.each([ - { overrideDefault: undefined }, - { overrideDefault: 'static' as const }, - { overrideDefault: 'factory' as const }, - ])('Logging disabled: override default $overrideDefault', ({ overrideDefault }) => { - const log = false; - - const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { - action: 'bypass', - log, - }; - const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { - action: 'reject', - log, - }; - - beforeEach(() => { - if (overrideDefault === 'static') { - if (type === 'local') { - httpInterceptor.default.local.onUnhandledRequest = localOnUnhandledRequest; - expect(httpInterceptor.default.local.onUnhandledRequest).toBe(localOnUnhandledRequest); - } else { - httpInterceptor.default.remote.onUnhandledRequest = remoteOnUnhandledRequest; - expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(remoteOnUnhandledRequest); - } - } else if (overrideDefault === 'factory') { - if (type === 'local') { - function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { - verifyUnhandledRequest(request, method); - return localOnUnhandledRequest; - } - - httpInterceptor.default.local.onUnhandledRequest = onUnhandledRequest; - expect(httpInterceptor.default.local.onUnhandledRequest).toBe(onUnhandledRequest); - } else { - function onUnhandledRequest(request: UnhandledHttpInterceptorRequest) { - verifyUnhandledRequest(request, method); - return remoteOnUnhandledRequest; - } - - httpInterceptor.default.remote.onUnhandledRequest = onUnhandledRequest; - expect(httpInterceptor.default.remote.onUnhandledRequest).toBe(onUnhandledRequest); - } - } - }); - - afterEach(() => { - localOnUnhandledRequest.action = 'bypass'; - remoteOnUnhandledRequest.action = 'reject'; - - if (overrideDefault?.startsWith('factory')) { - expect(verifyUnhandledRequest).toHaveBeenCalled(); - } else { - expect(verifyUnhandledRequest).not.toHaveBeenCalled(); - } - }); - - if (type === 'local') { - it(`should not show a warning when logging is disabled and ${method} requests are unhandled and bypassed`, async () => { - const extendedInterceptorOptions: HttpInterceptorOptions = { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: false }, - }; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - const responsePromise = fetch(request); - await expectBypassedResponse(responsePromise); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - }); - }); - }); - } - - it(`should not show an error when logging is disabled and ${method} requests are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - - const extendedInterceptorOptions: HttpInterceptorOptions = { - ...interceptorOptions, - type, - onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: false }, - }; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - const responsePromise = fetch(request); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - }); - }); - }); - }); - - it(`should support a synchronous unhandled ${method} request factory`, async () => { - const localOnUnhandledRequest = vi.fn((request) => { - const url = new URL(request.url); - return { - action: 'reject', - log: !url.searchParams.has('name'), - }; - }); - - const remoteOnUnhandledRequest = vi.fn((request) => { - const url = new URL(request.url); - return { - action: 'reject', - log: !url.searchParams.has('name'), - }; - }); - - const extendedInterceptorOptions = ( - type === 'local' - ? { ...interceptorOptions, type, onUnhandledRequest: localOnUnhandledRequest } - : { ...interceptorOptions, type, onUnhandledRequest: remoteOnUnhandledRequest } - ) satisfies HttpInterceptorOptions; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams<{ - 'x-value': string; - name?: string; - }>({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes(0); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - searchParams.set('x-value', '2'); - searchParams.set('name', 'User 1'); - - let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { - method, - headers: { 'x-value': '1' }, - }); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - responsePromise = fetch(request); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight * 2, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform, - request, - }); - }); - }); - }); - - it(`should support an asynchronous unhandled ${method} request factory`, async () => { - const localOnUnhandledRequest = vi.fn(async (request) => { - const url = new URL(request.url); - - await waitForDelay(10); - - return { - action: 'reject', - log: !url.searchParams.has('name'), - }; - }); - - const remoteOnUnhandledRequest = vi.fn(async (request) => { - const url = new URL(request.url); - - await waitForDelay(10); - - return { - action: 'reject', - log: !url.searchParams.has('name'), - }; - }); - - const extendedInterceptorOptions = ( - type === 'local' - ? { ...interceptorOptions, type, onUnhandledRequest: localOnUnhandledRequest } - : { ...interceptorOptions, type, onUnhandledRequest: remoteOnUnhandledRequest } - ) satisfies HttpInterceptorOptions; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams<{ - 'x-value': string; - name?: string; - }>({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes(0); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - searchParams.set('x-value', '2'); - searchParams.set('name', 'User 1'); - - let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { - method, - headers: { 'x-value': '1' }, - }); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - responsePromise = fetch(request); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight * 2, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform, - request, - }); - }); - }); - }); - - it(`should log an error if a synchronous unhandled ${method} request factory throws`, async () => { - const error = new Error('Unhandled request.'); - - const localOnUnhandledRequest = vi.fn((request) => { - const url = new URL(request.url); - - if (!url.searchParams.has('name')) { - throw error; - } - - return { action: 'reject', log: false }; - }); - - const remoteOnUnhandledRequest = vi.fn((request) => { - const url = new URL(request.url); - - if (!url.searchParams.has('name')) { - throw error; - } - - return { action: 'reject', log: false }; - }); - - const extendedInterceptorOptions = ( - type === 'local' - ? { ...interceptorOptions, type, onUnhandledRequest: localOnUnhandledRequest } - : { ...interceptorOptions, type, onUnhandledRequest: remoteOnUnhandledRequest } - ) satisfies HttpInterceptorOptions; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams<{ - 'x-value': string; - name?: string; - }>({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes(0); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - searchParams.set('x-value', '2'); - searchParams.set('name', 'User 1'); - - let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { - method, - headers: { 'x-value': '1' }, - }); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - responsePromise = fetch(request); - - const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; - - if (defaultStrategy.action === 'bypass') { - await expectBypassedResponse(responsePromise); - } else if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight * 2, - ); - - if (defaultStrategy.action === 'bypass') { - expect(spies.warn).toHaveBeenCalledTimes(1); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - expect(spies.error).toHaveBeenCalledWith(error); - - await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { - type: 'warn', - platform, - request, - }); - } else { - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - - expect(spies.error).toHaveBeenNthCalledWith(1, error); - - await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, - request, - }); - } - }); - }); - }); - - it(`should log an error if an asynchronous unhandled ${method} request factory throws`, async () => { - const error = new Error('Unhandled request.'); - - const localOnUnhandledRequest = vi.fn(async (request) => { - const url = new URL(request.url); - - await waitForDelay(10); - - if (!url.searchParams.has('name')) { - throw error; - } - - return { action: 'reject', log: false }; - }); - - const remoteOnUnhandledRequest = vi.fn(async (request) => { - const url = new URL(request.url); - - await waitForDelay(10); - - if (!url.searchParams.has('name')) { - throw error; - } - - return { action: 'reject', log: false }; - }); - - const extendedInterceptorOptions = ( - type === 'local' - ? { ...interceptorOptions, type, onUnhandledRequest: localOnUnhandledRequest } - : { ...interceptorOptions, type, onUnhandledRequest: remoteOnUnhandledRequest } - ) satisfies HttpInterceptorOptions; - - await usingHttpInterceptor<{ - '/users': { - GET: MethodSchemaWithoutRequestBody; - POST: MethodSchemaWithoutRequestBody; - PUT: MethodSchemaWithoutRequestBody; - PATCH: MethodSchemaWithoutRequestBody; - DELETE: MethodSchemaWithoutRequestBody; - HEAD: MethodSchemaWithoutRequestBody; - OPTIONS: MethodSchemaWithoutRequestBody; - }; - }>(extendedInterceptorOptions, async (interceptor) => { - const handler = await promiseIfRemote( - interceptor[lowerMethod]('/users') - .with({ searchParams: { 'x-value': '1' } }) - .respond({ - status: 200, - headers: DEFAULT_ACCESS_CONTROL_HEADERS, - }), - interceptor, - ); - expect(handler).toBeInstanceOf(Handler); - - let requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(0); - - await usingIgnoredConsole(['warn', 'error'], async (spies) => { - const searchParams = new HttpSearchParams<{ - 'x-value': string; - name?: string; - }>({ 'x-value': '1' }); - - const response = await fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { method }); - expect(response.status).toBe(200); - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes(0); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - searchParams.set('x-value', '2'); - searchParams.set('name', 'User 1'); - - let responsePromise = fetch(joinURL(baseURL, `/users?${searchParams.toString()}`), { - method, - headers: { 'x-value': '1' }, - }); - - if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight, - ); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(0); - - const request = new Request(joinURL(baseURL, '/users'), { method }); - responsePromise = fetch(request); - - const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; - - if (defaultStrategy.action === 'bypass') { - await expectBypassedResponse(responsePromise); - } else if (overridesPreflightResponse) { - await expectPreflightResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } - - requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); - - expect(extendedInterceptorOptions.onUnhandledRequest).toHaveBeenCalledTimes( - numberOfRequestsIncludingPreflight * 2, - ); - - if (defaultStrategy.action === 'bypass') { - expect(spies.warn).toHaveBeenCalledTimes(1); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - expect(spies.error).toHaveBeenCalledWith(error); - - await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { - type: 'warn', - platform, - request, - }); - } else { - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - - expect(spies.error).toHaveBeenNthCalledWith(1, error); - - await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, - request, - }); - } - }); - }); - }); - }); -} diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts index cc0e3f2f5..57c4766a0 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts @@ -1,12 +1,15 @@ -import { expect } from 'vitest'; +import { expect, expectTypeOf } from 'vitest'; +import { HttpHeaders, HttpSearchParams } from '@/http'; import { HttpRequest } from '@/http/types/requests'; import HttpInterceptorWorker from '@/interceptor/http/interceptorWorker/HttpInterceptorWorker'; +import { HttpRequestBodySchema } from '@/interceptor/http/requestHandler/types/requests'; import { PossiblePromise } from '@/types/utils'; import { formatObjectToLog } from '@/utils/console'; import { ExtendedURL } from '@/utils/urls'; import { HttpInterceptorOptions, HttpInterceptorPlatform, HttpInterceptorType } from '../../types/options'; +import { UnhandledHttpInterceptorRequest, UnhandledHttpInterceptorRequestMethodSchema } from '../../types/requests'; export interface SharedHttpInterceptorTestsOptions { platform: HttpInterceptorPlatform; @@ -71,3 +74,28 @@ export async function verifyUnhandledRequestMessage( ); } } + +export function verifyUnhandledRequest(request: UnhandledHttpInterceptorRequest, method: string) { + expect(request).toBeInstanceOf(Request); + expect(request).not.toHaveProperty('response'); + + expectTypeOf(request.headers).toEqualTypeOf>>(); + expect(request.headers).toBeInstanceOf(HttpHeaders); + + expectTypeOf(request.searchParams).toEqualTypeOf>>(); + expect(request.searchParams).toBeInstanceOf(HttpSearchParams); + + expectTypeOf(request.pathParams).toEqualTypeOf<{}>(); + expect(request.pathParams).toEqual({}); + + type BodySchema = HttpRequestBodySchema; + + expectTypeOf(request.body).toEqualTypeOf(); + expect(request).toHaveProperty('body'); + + expectTypeOf(request.raw).toEqualTypeOf>(); + expect(request.raw).toBeInstanceOf(Request); + expect(request.raw.url).toBe(request.url); + expect(request.raw.method).toBe(method); + expect(Object.fromEntries(request.headers)).toEqual(expect.objectContaining(Object.fromEntries(request.raw.headers))); +} diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts index a271dd4f8..3eb773fec 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts @@ -14,11 +14,12 @@ import { InferPathParams, } from '@/http/types/schema'; import { Default, PossiblePromise } from '@/types/utils'; +import { removeArrayElement } from '@/utils/arrays'; import { formatObjectToLog, logWithPrefix } from '@/utils/console'; import { isDefined } from '@/utils/data'; import { isClientSide } from '@/utils/environment'; import { methodCanHaveResponseBody } from '@/utils/http'; -import { createURL, excludeNonPathParams } from '@/utils/urls'; +import { createURL } from '@/utils/urls'; import HttpSearchParams from '../../../http/searchParams/HttpSearchParams'; import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient'; @@ -47,10 +48,7 @@ abstract class HttpInterceptorWorker { private store = new HttpInterceptorWorkerStore(); - private unhandledRequestStrategies: { - baseURL: string; - declarationOrFactory: UnhandledRequestStrategy; - }[] = []; + private runningInterceptors: AnyHttpInterceptorClient[] = []; platform() { return this._platform; @@ -124,86 +122,109 @@ abstract class HttpInterceptorWorker { createResponse: HttpResponseFactory, ): PossiblePromise; - protected async handleUnhandledRequest(request: Request, strategy: UnhandledRequestStrategy.Declaration) { - if (strategy.log) { + protected async logUnhandledRequestIfNecessary( + request: HttpRequest, + strategy: UnhandledRequestStrategy.Declaration | null, + ) { + if (strategy?.log) { await HttpInterceptorWorker.logUnhandledRequestWarning(request, strategy.action); + return { wasLogged: true }; } + return { wasLogged: false }; } - protected async getUnhandledRequestStrategy( - request: Request, - interceptorType: HttpInterceptorType, - ): Promise { + protected async getUnhandledRequestStrategy(request: HttpRequest, interceptorType: HttpInterceptorType) { const candidates = await this.getUnhandledRequestStrategyCandidates(request, interceptorType); const strategy = this.reduceUnhandledRequestStrategyCandidates(candidates); return strategy; } - private reduceUnhandledRequestStrategyCandidates(candidates: UnhandledRequestStrategy.Declaration[]) { - return candidates.reduce( - (strategy, candidate) => ({ - action: candidate.action, - log: candidate.log ?? strategy.log, + private reduceUnhandledRequestStrategyCandidates(candidateStrategies: UnhandledRequestStrategy.Declaration[]) { + if (candidateStrategies.length === 0) { + return null; + } + + // Prefer strategies from first to last, overriding undefined values with the next candidate. + return candidateStrategies.reduce( + (accumulatedStrategy, candidateStrategy): UnhandledRequestStrategy.Declaration => ({ + action: accumulatedStrategy.action, + log: accumulatedStrategy.log ?? candidateStrategy.log, }), - candidates[0], ); } private async getUnhandledRequestStrategyCandidates( - request: Request, + request: HttpRequest, interceptorType: HttpInterceptorType, ): Promise { - const originalDefaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[interceptorType]; + const globalDefaultStrategy = this.getGlobalDefaultUnhandledRequestStrategy(interceptorType); try { - const requestURL = excludeNonPathParams(createURL(request.url)).toString(); - const defaultDeclarationOrFactory = this.store.defaultOnUnhandledRequest(interceptorType); - - const requestClone = request.clone(); - - let customDefaultStrategy: PossiblePromise | null = null; + const interceptor = this.findInterceptorByRequestBaseURL(request); - if (typeof defaultDeclarationOrFactory === 'function') { - const parsedRequest = await HttpInterceptorWorker.parseRawUnhandledRequest(request); - customDefaultStrategy = defaultDeclarationOrFactory(parsedRequest); - } else { - customDefaultStrategy = defaultDeclarationOrFactory; + if (!interceptor) { + return []; } - const { declarationOrFactory = null } = this.findUnhandledRequestStrategy(requestURL) ?? {}; - - let interceptorStrategy: PossiblePromise | null = null; + const requestClone = request.clone(); - if (typeof declarationOrFactory === 'function') { - const parsedRequest = await HttpInterceptorWorker.parseRawUnhandledRequest(requestClone); - interceptorStrategy = declarationOrFactory(parsedRequest); - } else { - interceptorStrategy = declarationOrFactory; - } + const [defaultStrategy, interceptorStrategy] = await Promise.all([ + this.getDefaultUnhandledRequestStrategy(request, interceptorType), + this.getInterceptorUnhandledRequestStrategy(requestClone, interceptor), + ]); - const candidatesOrPromises = [originalDefaultStrategy, customDefaultStrategy, interceptorStrategy]; - const candidates = await Promise.all(candidatesOrPromises.filter(isDefined)); - return candidates; + const candidatesOrPromises = [interceptorStrategy, defaultStrategy, globalDefaultStrategy]; + const candidateStrategies = await Promise.all(candidatesOrPromises.filter(isDefined)); + return candidateStrategies; } catch (error) { console.error(error); - const candidates = [originalDefaultStrategy]; - return candidates; + const candidateStrategies = [globalDefaultStrategy]; + return candidateStrategies; } } - onUnhandledRequest(baseURL: string, declarationOrFactory: UnhandledRequestStrategy) { - this.unhandledRequestStrategies.push({ baseURL, declarationOrFactory }); + registerRunningInterceptor(interceptor: AnyHttpInterceptorClient) { + this.runningInterceptors.push(interceptor); } - offUnhandledRequest(baseURL: string) { - this.unhandledRequestStrategies = this.unhandledRequestStrategies.filter( - (strategy) => strategy.baseURL !== baseURL, - ); + unregisterRunningInterceptor(interceptor: AnyHttpInterceptorClient) { + removeArrayElement(this.runningInterceptors, interceptor); } - private findUnhandledRequestStrategy(baseURL: string) { - return this.unhandledRequestStrategies.findLast((strategy) => baseURL.startsWith(strategy.baseURL)); + private findInterceptorByRequestBaseURL(request: HttpRequest) { + const interceptor = this.runningInterceptors.findLast((interceptor) => { + const baseURL = interceptor.baseURL().toString(); + return request.url.startsWith(baseURL); + }); + + return interceptor; + } + + private getGlobalDefaultUnhandledRequestStrategy(interceptorType: HttpInterceptorType) { + return DEFAULT_UNHANDLED_REQUEST_STRATEGY[interceptorType]; + } + + private async getDefaultUnhandledRequestStrategy(request: HttpRequest, interceptorType: HttpInterceptorType) { + const defaultStrategyOrFactory = this.store.defaultOnUnhandledRequest(interceptorType); + + if (typeof defaultStrategyOrFactory === 'function') { + const parsedRequest = await HttpInterceptorWorker.parseRawUnhandledRequest(request); + return defaultStrategyOrFactory(parsedRequest); + } + + return defaultStrategyOrFactory; + } + + private async getInterceptorUnhandledRequestStrategy(request: HttpRequest, interceptor: AnyHttpInterceptorClient) { + const interceptorStrategyOrFactory = interceptor.onUnhandledRequest(); + + if (typeof interceptorStrategyOrFactory === 'function') { + const parsedRequest = await HttpInterceptorWorker.parseRawUnhandledRequest(request); + return interceptorStrategyOrFactory(parsedRequest); + } + + return interceptorStrategyOrFactory; } abstract clearHandlers(): PossiblePromise; diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/LocalHttpInterceptorWorker.ts b/packages/zimic/src/interceptor/http/interceptorWorker/LocalHttpInterceptorWorker.ts index 53bd96210..e4ace544b 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/LocalHttpInterceptorWorker.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/LocalHttpInterceptorWorker.ts @@ -4,7 +4,8 @@ import * as mswNode from 'msw/node'; import { HttpRequest, HttpResponse } from '@/http/types/requests'; import { HttpMethod, HttpSchema } from '@/http/types/schema'; -import { createURL, ensureUniquePathParams, excludeNonPathParams } from '@/utils/urls'; +import { removeArrayIndex } from '@/utils/arrays'; +import { createURL } from '@/utils/urls'; import NotStartedHttpInterceptorError from '../interceptor/errors/NotStartedHttpInterceptorError'; import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError'; @@ -12,13 +13,7 @@ import HttpInterceptorClient from '../interceptor/HttpInterceptorClient'; import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError'; import HttpInterceptorWorker from './HttpInterceptorWorker'; import { LocalHttpInterceptorWorkerOptions } from './types/options'; -import { - BrowserHttpWorker, - HttpResponseFactory, - HttpResponseFactoryResult, - HttpWorker, - NodeHttpWorker, -} from './types/requests'; +import { BrowserHttpWorker, HttpResponseFactory, HttpWorker, NodeHttpWorker } from './types/requests'; class LocalHttpInterceptorWorker extends HttpInterceptorWorker { readonly type: 'local'; @@ -154,34 +149,36 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker { const internalWorker = this.internalWorkerOrThrow(); const lowercaseMethod = method.toLowerCase(); - const url = excludeNonPathParams(createURL(rawURL)).toString(); - ensureUniquePathParams(url); + const url = createURL(rawURL, { + excludeNonPathParams: true, + ensureUniquePathParams: true, + }); - const httpHandler = http[lowercaseMethod](url, async (context): Promise => { + const httpHandler = http[lowercaseMethod](url.toString(), async (context): Promise => { const request = context.request satisfies Request as HttpRequest; const requestClone = request.clone(); - let result: HttpResponseFactoryResult | null = null; + let response: HttpResponse | null = null; try { - result = await createResponse({ ...context, request }); + response = await createResponse({ ...context, request }); } catch (error) { console.error(error); } - if (!result?.response) { + if (!response) { return this.bypassOrRejectUnhandledRequest(requestClone); } if (context.request.method === 'HEAD') { return new Response(null, { - status: result.response.status, - statusText: result.response.statusText, - headers: result.response.headers, + status: response.status, + statusText: response.statusText, + headers: response.headers, }); } - return result.response; + return response; }); internalWorker.use(httpHandler); @@ -193,9 +190,9 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker { const requestClone = request.clone(); const strategy = await super.getUnhandledRequestStrategy(request, 'local'); - await super.handleUnhandledRequest(requestClone, strategy); + await super.logUnhandledRequestIfNecessary(requestClone, strategy); - if (strategy.action === 'reject') { + if (strategy?.action === 'reject') { return Response.error(); } else { return passthrough(); @@ -211,16 +208,14 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker { clearInterceptorHandlers(interceptor: HttpInterceptorClient) { const internalWorker = this.internalWorkerOrThrow(); - const httpHandlerGroupsToKeep = this.httpHandlerGroups.filter((group) => group.interceptor !== interceptor); - const httpHandlersToKeep = httpHandlerGroupsToKeep.map((group) => group.httpHandler); + const groupToRemoveIndex = this.httpHandlerGroups.findIndex((group) => group.interceptor === interceptor); + removeArrayIndex(this.httpHandlerGroups, groupToRemoveIndex); internalWorker.resetHandlers(); - for (const handler of httpHandlersToKeep) { - internalWorker.use(handler); + for (const { httpHandler } of this.httpHandlerGroups) { + internalWorker.use(httpHandler); } - - this.httpHandlerGroups = httpHandlerGroupsToKeep; } interceptorsWithHandlers() { diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/RemoteHttpInterceptorWorker.ts b/packages/zimic/src/interceptor/http/interceptorWorker/RemoteHttpInterceptorWorker.ts index 404e642b8..1f2b13038 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/RemoteHttpInterceptorWorker.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/RemoteHttpInterceptorWorker.ts @@ -6,7 +6,7 @@ import { HttpMethod, HttpSchema } from '@/http/types/schema'; import { HttpHandlerCommit, InterceptorServerWebSocketSchema } from '@/interceptor/server/types/schema'; import { importCrypto } from '@/utils/crypto'; import { deserializeRequest, serializeResponse } from '@/utils/fetch'; -import { createURL, ensureUniquePathParams, excludeNonPathParams, ExtendedURL } from '@/utils/urls'; +import { createURL, ExtendedURL } from '@/utils/urls'; import { WebSocket } from '@/webSocket/types'; import WebSocketClient from '@/webSocket/WebSocketClient'; @@ -20,7 +20,7 @@ import { HttpResponseFactory, HttpResponseFactoryContext } from './types/request interface HttpHandler { id: string; - url: string; + url: { base: string; full: string }; method: HttpMethod; interceptor: AnyHttpInterceptorClient; createResponse: (context: HttpResponseFactoryContext) => Promise; @@ -55,7 +55,9 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker { async start() { await super.sharedStart(async () => { await this._webSocketClient.start(); + this._webSocketClient.onEvent('interceptors/responses/create', this.createResponse); + this._webSocketClient.onEvent('interceptors/responses/unhandled', this.handleUnhandledServerRequest); const platform = this.readPlatform(); super.setPlatform(platform); @@ -83,11 +85,23 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker { } const strategy = await super.getUnhandledRequestStrategy(request, 'remote'); - await super.handleUnhandledRequest(request, strategy); + await super.logUnhandledRequestIfNecessary(request, strategy); return { response: null }; }; + private handleUnhandledServerRequest = async ( + message: WebSocket.ServiceEventMessage, + ) => { + const { request: serializedRequest } = message.data; + const request = deserializeRequest(serializedRequest); + + const strategy = await super.getUnhandledRequestStrategy(request, 'remote'); + const { wasLogged } = await super.logUnhandledRequestIfNecessary(request, strategy); + + return { wasLogged }; + }; + private readPlatform(): HttpInterceptorPlatform { if (typeof mswNode.setupServer !== 'undefined') { return 'node'; @@ -106,7 +120,10 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker { async stop() { await super.sharedStop(async () => { await this.clearHandlers(); + this._webSocketClient.offEvent('interceptors/responses/create', this.createResponse); + this._webSocketClient.offEvent('interceptors/responses/unhandled', this.handleUnhandledServerRequest); + await this._webSocketClient.stop(); super.setIsRunning(false); @@ -124,17 +141,23 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker { } const crypto = await importCrypto(); - const url = excludeNonPathParams(createURL(rawURL)).toString(); - ensureUniquePathParams(url); + + const url = createURL(rawURL, { + excludeNonPathParams: true, + ensureUniquePathParams: true, + }); const handler: HttpHandler = { id: crypto.randomUUID(), - url, + url: { + base: interceptor.baseURL().toString(), + full: url.toString(), + }, method, interceptor, async createResponse(context) { - const result = await createResponse(context); - return result.response; + const response = await createResponse(context); + return response; }, }; @@ -142,7 +165,7 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker { await this._webSocketClient.request('interceptors/workers/use/commit', { id: handler.id, - url, + url: handler.url, method, }); } diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/shared/methods.ts b/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/shared/methods.ts index 2c8327ded..fc429433c 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/shared/methods.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/shared/methods.ts @@ -1,5 +1,6 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { HttpResponse } from '@/http'; import HttpHeaders from '@/http/headers/HttpHeaders'; import { HTTP_METHODS } from '@/http/types/schema'; import NotStartedHttpInterceptorError from '@/interceptor/http/interceptor/errors/NotStartedHttpInterceptorError'; @@ -17,7 +18,7 @@ import { import HttpInterceptorWorker from '../../HttpInterceptorWorker'; import { LocalHttpInterceptorWorkerOptions, RemoteHttpInterceptorWorkerOptions } from '../../types/options'; -import { HttpResponseFactoryContext, HttpResponseFactoryResult } from '../../types/requests'; +import { HttpResponseFactoryContext } from '../../types/requests'; import { promiseIfRemote } from '../utils/promises'; import { SharedHttpInterceptorWorkerTestOptions } from './types'; @@ -72,12 +73,12 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter } } - function requestHandler(_context: HttpResponseFactoryContext): PossiblePromise { + function requestHandler(_context: HttpResponseFactoryContext): PossiblePromise { const response = Response.json(responseBody, { status: responseStatus, headers: defaultHeaders, }); - return { response }; + return response; } const spiedRequestHandler = vi.fn(requestHandler); @@ -467,14 +468,14 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter const urlExpectedToFail = joinURL(baseURL, path.fetch); - const responsePromise = fetchWithTimeout(urlExpectedToFail, { method, timeout: 500 }); + const responsePromise = fetch(urlExpectedToFail, { method }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { await expectBypassedResponse(responsePromise); } else { - await expectFetchError(responsePromise, { canBeAborted: true }); + await expectFetchError(responsePromise); } expect(spiedRequestHandler).not.toHaveBeenCalled(); @@ -505,14 +506,14 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter const urlExpectedToFail = joinURL(baseURL, paths.fetch); - const responsePromise = fetchWithTimeout(urlExpectedToFail, { method, timeout: 500 }); + const responsePromise = fetch(urlExpectedToFail, { method }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { await expectBypassedResponse(responsePromise); } else { - await expectFetchError(responsePromise, { canBeAborted: true }); + await expectFetchError(responsePromise); } expect(spiedRequestHandler).not.toHaveBeenCalled(); @@ -523,7 +524,7 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter it(`should not intercept ${method} requests that resulted in no mocked response`, async () => { await usingHttpInterceptorWorker(workerOptions, async (worker) => { const interceptor = createDefaultHttpInterceptor(); - const emptySpiedRequestHandler = vi.fn(requestHandler).mockImplementation(() => ({ response: null })); + const emptySpiedRequestHandler = vi.fn(requestHandler).mockImplementation(() => null); await promiseIfRemote(worker.use(interceptor.client(), method, baseURL, emptySpiedRequestHandler), worker); @@ -562,7 +563,7 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter let responsePromise = fetchWithTimeout(baseURL, { method, timeout: 20 }); await expectFetchError(responsePromise, { canBeAborted: true }); - responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + responsePromise = fetch(baseURL, { method }); await expect(responsePromise).resolves.toBeInstanceOf(Response); expect(delayedSpiedRequestHandler).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight + 1); @@ -583,12 +584,15 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter expect(spiedRequestHandler).not.toHaveBeenCalled(); - const responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + const responsePromise = fetchWithTimeout(baseURL, { + method, + timeout: overridesPreflightResponse ? 0 : 500, + }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { - await expectBypassedResponse(responsePromise); + await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } @@ -604,12 +608,15 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter await worker.stop(); - const responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + const responsePromise = fetchWithTimeout(baseURL, { + method, + timeout: overridesPreflightResponse ? 0 : 500, + }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { - await expectBypassedResponse(responsePromise); + await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } @@ -626,12 +633,15 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter await worker.stop(); await worker.start(); - const responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + const responsePromise = fetchWithTimeout(baseURL, { + method, + timeout: overridesPreflightResponse ? 0 : 500, + }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { - await expectBypassedResponse(responsePromise); + await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } @@ -647,14 +657,14 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter await promiseIfRemote(worker.clearHandlers(), worker); - const responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + const responsePromise = fetch(baseURL, { method }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { await expectBypassedResponse(responsePromise); } else { - await expectFetchError(responsePromise, { canBeAborted: true }); + await expectFetchError(responsePromise); } expect(spiedRequestHandler).not.toHaveBeenCalled(); @@ -680,11 +690,11 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter await usingHttpInterceptorWorker(workerOptions, async (worker) => { const okSpiedRequestHandler = vi.fn(requestHandler).mockImplementation(() => { const response = new Response(null, { status: 200, headers: defaultHeaders }); - return { response }; + return response; }); const noContentSpiedRequestHandler = vi.fn(requestHandler).mockImplementation(() => { const response = new Response(null, { status: 204, headers: defaultHeaders }); - return { response }; + return response; }); const interceptor = createDefaultHttpInterceptor(); @@ -748,14 +758,14 @@ export function declareMethodHttpInterceptorWorkerTests(options: SharedHttpInter interceptorsWithHandlers = worker.interceptorsWithHandlers(); expect(interceptorsWithHandlers).toHaveLength(0); - const responsePromise = fetchWithTimeout(baseURL, { method, timeout: 500 }); + const responsePromise = fetch(baseURL, { method }); if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else if (defaultWorkerOptions.type === 'local') { await expectBypassedResponse(responsePromise); } else { - await expectFetchError(responsePromise, { canBeAborted: true }); + await expectFetchError(responsePromise); } expect(okSpiedRequestHandler).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/types/requests.ts b/packages/zimic/src/interceptor/http/interceptorWorker/types/requests.ts index 919319020..9e0558e4c 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/types/requests.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/types/requests.ts @@ -11,10 +11,6 @@ export interface HttpResponseFactoryContext { request: HttpRequest; } -export interface HttpResponseFactoryResult { - response: HttpResponse | null; -} - export type HttpResponseFactory = ( context: HttpResponseFactoryContext, -) => PossiblePromise>; +) => PossiblePromise | null>; diff --git a/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/restrictions.ts b/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/restrictions.ts index acba1e8b1..ad91f0071 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/restrictions.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/restrictions.ts @@ -66,7 +66,7 @@ export function declareRestrictionHttpRequestHandlerTests( }); for (const matchingSearchParams of [new HttpSearchParams({ name })]) { - const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams.toString()}`)); + const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(matchingRequest); expect(await handler.matchesRequest(parsedRequest)).toBe(true); } @@ -76,7 +76,7 @@ export function declareRestrictionHttpRequestHandlerTests( new HttpSearchParams({ name: `${name} other` }), new HttpSearchParams({}), ]) { - const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams.toString()}`)); + const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); expect(await handler.matchesRequest(parsedRequest)).toBe(false); } @@ -102,7 +102,7 @@ export function declareRestrictionHttpRequestHandlerTests( new HttpSearchParams({ name }), new HttpSearchParams({ name, other: 'param' }), ]) { - const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams.toString()}`)); + const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(matchingRequest); expect(await handler.matchesRequest(parsedRequest)).toBe(true); } @@ -112,7 +112,7 @@ export function declareRestrictionHttpRequestHandlerTests( new HttpSearchParams({ name: 'other' }), new HttpSearchParams({}), ]) { - const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams.toString()}`)); + const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); expect(await handler.matchesRequest(parsedRequest)).toBe(false); } @@ -140,7 +140,7 @@ export function declareRestrictionHttpRequestHandlerTests( new HttpSearchParams({ name, other: 'param' }), new HttpSearchParams({ name: `${name} other` }), ]) { - const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams.toString()}`)); + const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(matchingRequest); expect(await handler.matchesRequest(parsedRequest)).toBe(true); } @@ -149,7 +149,7 @@ export function declareRestrictionHttpRequestHandlerTests( new HttpSearchParams({ name: `Other ${name}` }), new HttpSearchParams({}), ]) { - const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams.toString()}`)); + const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams}`)); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); expect(await handler.matchesRequest(parsedRequest)).toBe(false); } @@ -441,7 +441,7 @@ export function declareRestrictionHttpRequestHandlerTests( for (const matchingHeaders of matchingHeadersSamples) { for (const matchingSearchParams of matchingSearchParamsSamples) { - const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams.toString()}`), { + const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams}`), { headers: matchingHeaders, }); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(matchingRequest); @@ -449,7 +449,7 @@ export function declareRestrictionHttpRequestHandlerTests( } for (const mismatchingSearchParams of mismatchingSearchParamsSamples) { - const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams.toString()}`), { + const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams}`), { headers: matchingHeaders, }); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); @@ -459,7 +459,7 @@ export function declareRestrictionHttpRequestHandlerTests( for (const mismatchingHeaders of mismatchingHeadersSamples) { for (const matchingSearchParams of matchingSearchParamsSamples) { - const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams.toString()}`), { + const matchingRequest = new Request(joinURL(baseURL, `?${matchingSearchParams}`), { headers: mismatchingHeaders, }); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(matchingRequest); @@ -467,7 +467,7 @@ export function declareRestrictionHttpRequestHandlerTests( } for (const mismatchingSearchParams of mismatchingSearchParamsSamples) { - const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams.toString()}`), { + const request = new Request(joinURL(baseURL, `?${mismatchingSearchParams}`), { headers: mismatchingHeaders, }); const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); diff --git a/packages/zimic/src/interceptor/server/InterceptorServer.ts b/packages/zimic/src/interceptor/server/InterceptorServer.ts index fc25ff2ea..96bc31051 100644 --- a/packages/zimic/src/interceptor/server/InterceptorServer.ts +++ b/packages/zimic/src/interceptor/server/InterceptorServer.ts @@ -2,11 +2,13 @@ import { normalizeNodeRequest, sendNodeResponse } from '@whatwg-node/server'; import { createServer, Server as HttpServer, IncomingMessage, ServerResponse } from 'http'; import type { WebSocket as Socket } from 'isomorphic-ws'; +import { HttpRequest } from '@/http'; import { HttpMethod } from '@/http/types/schema'; import HttpInterceptorWorker from '@/interceptor/http/interceptorWorker/HttpInterceptorWorker'; -import { deserializeResponse, serializeRequest } from '@/utils/fetch'; +import { removeArrayIndex } from '@/utils/arrays'; +import { deserializeResponse, SerializedHttpRequest, serializeRequest } from '@/utils/fetch'; import { getHttpServerPort, startHttpServer, stopHttpServer } from '@/utils/http'; -import { createRegexFromURL, createURL, excludeNonPathParams } from '@/utils/urls'; +import { createRegexFromURL, createURL } from '@/utils/urls'; import { WebSocketMessageAbortError } from '@/utils/webSocket'; import { WebSocket } from '@/webSocket/types'; import WebSocketServer from '@/webSocket/WebSocketServer'; @@ -24,7 +26,10 @@ import { getFetchAPI } from './utils/fetch'; interface HttpHandler { id: string; - url: { regex: RegExp }; + url: { + base: string; + fullRegex: RegExp; + }; socket: Socket; } @@ -139,7 +144,7 @@ class InterceptorServer implements PublicInterceptorServer { socket: Socket, ) => { const commit = message.data; - this.registerHttpHandlerGroup(commit, socket); + this.registerHttpHandler(commit, socket); this.registerWorkerSocketIfUnknown(socket); return {}; }; @@ -148,7 +153,7 @@ class InterceptorServer implements PublicInterceptorServer { message: WebSocket.ServiceEventMessage, socket: Socket, ) => { - this.removeHttpHandlerGroupsBySocket(socket); + this.removeHttpHandlersBySocket(socket); const handlersToResetTo = message.data; const isWorkerNoLongerCommitted = handlersToResetTo === undefined; @@ -159,7 +164,7 @@ class InterceptorServer implements PublicInterceptorServer { this.webSocketServer().abortSocketMessages([socket]); } else { for (const handler of handlersToResetTo) { - this.registerHttpHandlerGroup(handler, socket); + this.registerHttpHandler(handler, socket); } } @@ -168,14 +173,17 @@ class InterceptorServer implements PublicInterceptorServer { return {}; }; - private registerHttpHandlerGroup({ id, url: rawURL, method }: HttpHandlerCommit, socket: Socket) { + private registerHttpHandler({ id, url, method }: HttpHandlerCommit, socket: Socket) { const handlerGroups = this.httpHandlerGroups[method]; - const url = excludeNonPathParams(createURL(rawURL)).toString(); + const fullURL = createURL(url.full, { excludeNonPathParams: true }); handlerGroups.push({ id, - url: { regex: createRegexFromURL(url) }, + url: { + base: url.base, + fullRegex: createRegexFromURL(fullURL.toString()), + }, socket, }); } @@ -186,16 +194,17 @@ class InterceptorServer implements PublicInterceptorServer { } socket.addEventListener('close', () => { - this.removeHttpHandlerGroupsBySocket(socket); + this.removeHttpHandlersBySocket(socket); this.knownWorkerSockets.delete(socket); }); this.knownWorkerSockets.add(socket); } - private removeHttpHandlerGroupsBySocket(socket: Socket) { - for (const [method, handlerGroups] of Object.entries(this.httpHandlerGroups)) { - this.httpHandlerGroups[method] = handlerGroups.filter((handlerGroup) => handlerGroup.socket !== socket); + private removeHttpHandlersBySocket(socket: Socket) { + for (const handlerGroups of Object.values(this.httpHandlerGroups)) { + const socketIndex = handlerGroups.findIndex((handlerGroup) => handlerGroup.socket === socket); + removeArrayIndex(handlerGroups, socketIndex); } } @@ -229,9 +238,10 @@ class InterceptorServer implements PublicInterceptorServer { private handleHttpRequest = async (nodeRequest: IncomingMessage, nodeResponse: ServerResponse) => { const request = normalizeNodeRequest(nodeRequest, await getFetchAPI()); + const serializedRequest = await serializeRequest(request); try { - const { response, matchedAnyInterceptor } = await this.createResponseForRequest(request); + const { response, matchedSomeInterceptor } = await this.createResponseForRequest(serializedRequest); if (response) { this.setDefaultAccessControlHeaders(response, ['access-control-allow-origin', 'access-control-expose-headers']); @@ -247,9 +257,10 @@ class InterceptorServer implements PublicInterceptorServer { await sendNodeResponse(defaultPreflightResponse, nodeResponse, nodeRequest); } - const shouldWarnUnhandledRequest = !isUnhandledPreflightResponse && !matchedAnyInterceptor; + const shouldWarnUnhandledRequest = !isUnhandledPreflightResponse && !matchedSomeInterceptor; + if (shouldWarnUnhandledRequest) { - await this.logUnhandledRequestWarning(request); + await this.logUnhandledRequestIfNecessary(request, serializedRequest); } nodeResponse.destroy(); @@ -258,45 +269,44 @@ class InterceptorServer implements PublicInterceptorServer { if (!isMessageAbortError) { console.error(error); - await this.logUnhandledRequestWarning(request); + await this.logUnhandledRequestIfNecessary(request, serializedRequest); } nodeResponse.destroy(); } }; - private async createResponseForRequest(request: Request) { + private async createResponseForRequest(request: SerializedHttpRequest) { const webSocketServer = this.webSocketServer(); - const handlerGroup = this.httpHandlerGroups[request.method as HttpMethod]; + const methodHandlers = this.httpHandlerGroups[request.method as HttpMethod]; - const url = excludeNonPathParams(createURL(request.url)).toString(); - const serializedRequest = await serializeRequest(request); + const requestURL = createURL(request.url, { excludeNonPathParams: true }).toString(); - let matchedAnyInterceptor = false; + let matchedSomeInterceptor = false; - for (let index = handlerGroup.length - 1; index >= 0; index--) { - const handler = handlerGroup[index]; + for (let index = methodHandlers.length - 1; index >= 0; index--) { + const handler = methodHandlers[index]; - const matchesHandlerURL = handler.url.regex.test(url); + const matchesHandlerURL = handler.url.fullRegex.test(requestURL); if (!matchesHandlerURL) { continue; } - matchedAnyInterceptor = true; + matchedSomeInterceptor = true; const { response: serializedResponse } = await webSocketServer.request( 'interceptors/responses/create', - { handlerId: handler.id, request: serializedRequest }, + { handlerId: handler.id, request }, { sockets: [handler.socket] }, ); if (serializedResponse) { const response = deserializeResponse(serializedResponse); - return { response, matchedAnyInterceptor }; + return { response, matchedSomeInterceptor }; } } - return { response: null, matchedAnyInterceptor }; + return { response: null, matchedSomeInterceptor }; } private setDefaultAccessControlHeaders( @@ -317,10 +327,38 @@ class InterceptorServer implements PublicInterceptorServer { } } - private async logUnhandledRequestWarning(request: Request) { - if (this._logUnhandledRequests) { - await HttpInterceptorWorker.logUnhandledRequestWarning(request, 'reject'); + private async logUnhandledRequestIfNecessary(request: HttpRequest, serializedRequest: SerializedHttpRequest) { + const webSocketServer = this.webSocketServer(); + + const handler = this.findHttpHandlerByRequestBaseURL(request); + + if (handler) { + const { wasLogged: wasRequestLoggedByRemoteInterceptor } = await webSocketServer.request( + 'interceptors/responses/unhandled', + { request: serializedRequest }, + { sockets: [handler.socket] }, + ); + + if (wasRequestLoggedByRemoteInterceptor) { + return; + } } + + if (!this._logUnhandledRequests) { + return; + } + + await HttpInterceptorWorker.logUnhandledRequestWarning(request, 'reject'); + } + + private findHttpHandlerByRequestBaseURL(request: HttpRequest) { + const methodHandlers = this.httpHandlerGroups[request.method as HttpMethod]; + + const handler = methodHandlers.findLast((handler) => { + return request.url.startsWith(handler.url.base); + }); + + return handler; } } diff --git a/packages/zimic/src/interceptor/server/types/schema.ts b/packages/zimic/src/interceptor/server/types/schema.ts index 849e8c215..2aab09b81 100644 --- a/packages/zimic/src/interceptor/server/types/schema.ts +++ b/packages/zimic/src/interceptor/server/types/schema.ts @@ -4,7 +4,7 @@ import { WebSocket } from '@/webSocket/types'; export interface HttpHandlerCommit { id: string; - url: string; + url: { base: string; full: string }; method: HttpMethod; } @@ -28,4 +28,13 @@ export type InterceptorServerWebSocketSchema = WebSocket.ServiceSchema<{ response: SerializedResponse | null; }; }; + + 'interceptors/responses/unhandled': { + event: { + request: SerializedHttpRequest; + }; + reply: { + wasLogged: boolean; + }; + }; }>; diff --git a/packages/zimic/src/utils/arrays.ts b/packages/zimic/src/utils/arrays.ts new file mode 100644 index 000000000..7bc0fb759 --- /dev/null +++ b/packages/zimic/src/utils/arrays.ts @@ -0,0 +1,11 @@ +export function removeArrayIndex(array: Item[], index: number) { + if (index >= 0 && index < array.length) { + array.splice(index, 1); + } + return array; +} + +export function removeArrayElement(array: Item[], element: Item) { + const index = array.indexOf(element); + return removeArrayIndex(array, index); +} diff --git a/packages/zimic/src/utils/fetch.ts b/packages/zimic/src/utils/fetch.ts index 71609945a..797dffd71 100644 --- a/packages/zimic/src/utils/fetch.ts +++ b/packages/zimic/src/utils/fetch.ts @@ -3,14 +3,19 @@ import { JSONValue } from '@/types/json'; import { convertArrayBufferToBase64, convertBase64ToArrayBuffer } from './data'; export async function fetchWithTimeout(url: URL | RequestInfo, options: RequestInit & { timeout: number }) { + const { timeout: timeoutDuration, ...fetchOptions } = options; + const abort = new AbortController(); - const timeout = setTimeout(() => { - abort.abort(); - }, options.timeout); + const timeout = + timeoutDuration > 0 + ? setTimeout(() => { + abort.abort(); + }, timeoutDuration) + : undefined; try { - const result = await fetch(url, { ...options, signal: abort.signal }); + const result = await fetch(url, { ...fetchOptions, signal: abort.signal }); return result; } finally { clearTimeout(timeout); diff --git a/packages/zimic/src/utils/urls.ts b/packages/zimic/src/utils/urls.ts index 5a618a335..cffd4c81f 100644 --- a/packages/zimic/src/utils/urls.ts +++ b/packages/zimic/src/utils/urls.ts @@ -36,26 +36,7 @@ function createURLOrThrow(rawURL: string | URL) { } } -export function createURL( - rawURL: string | URL, - options: { protocols?: string[] | readonly string[] } = {}, -): ExtendedURL { - const url = createURLOrThrow(rawURL); - - const protocol = url.protocol.replace(/:$/, ''); - - if (options.protocols && !options.protocols.includes(protocol)) { - throw new UnsupportedURLProtocolError(protocol, options.protocols); - } - - return url; -} - -export function createFileURL(filePath: string) { - return createURL(`file://${filePath}`); -} - -export function excludeNonPathParams(url: URL) { +function excludeNonPathParams(url: URL) { url.hash = ''; url.search = ''; url.username = ''; @@ -72,9 +53,10 @@ export class DuplicatedPathParamError extends Error { this.name = 'DuplicatedPathParamError'; } } + const URL_PATH_PARAM_REGEX = /\/:([^/]+)/g; -export function ensureUniquePathParams(url: string) { +function ensureUniquePathParams(url: string) { const matches = url.matchAll(URL_PATH_PARAM_REGEX); const uniqueParamNames = new Set(); @@ -88,6 +70,43 @@ export function ensureUniquePathParams(url: string) { } } +export function createURL( + rawURL: string | URL, + options: { + protocols?: string[] | readonly string[]; + excludeNonPathParams?: boolean; + ensureUniquePathParams?: boolean; + } = {}, +): ExtendedURL { + const { + protocols, + excludeNonPathParams: shouldExcludeNonPathParams = false, + ensureUniquePathParams: shouldEnsureUniquePathParams = false, + } = options; + + const url = createURLOrThrow(rawURL); + + const protocol = url.protocol.replace(/:$/, ''); + + if (protocols && !protocols.includes(protocol)) { + throw new UnsupportedURLProtocolError(protocol, protocols); + } + + if (shouldExcludeNonPathParams) { + excludeNonPathParams(url); + } + + if (shouldEnsureUniquePathParams) { + ensureUniquePathParams(url.toString()); + } + + return url; +} + +export function createFileURL(filePath: string) { + return createURL(`file://${filePath}`); +} + function prepareURLForRegex(url: string) { const encodedURL = encodeURI(url); return encodedURL.replace(/([.()*?+$\\])/g, '\\$1'); diff --git a/packages/zimic/tests/utils/fetch.ts b/packages/zimic/tests/utils/fetch.ts index 775c59cf4..e4c8b9674 100644 --- a/packages/zimic/tests/utils/fetch.ts +++ b/packages/zimic/tests/utils/fetch.ts @@ -14,25 +14,53 @@ export async function expectPreflightResponse(responsePromise: Promise } interface ExpectFetchErrorOptions { + canBeGenericFailure?: boolean; canBeAborted?: boolean; } -export async function expectFetchError(responsePromise: Promise, options: ExpectFetchErrorOptions = {}) { - const { canBeAborted = false } = options; +function getFetchErrorMessageRegex(options: ExpectFetchErrorOptions) { + const { canBeGenericFailure = true, canBeAborted = false } = options; const errorMessageOptions = [ - 'fetch failed', - 'Failed to fetch', + canBeGenericFailure && 'fetch failed', + canBeGenericFailure && 'Failed to fetch', canBeAborted && 'This operation was aborted', canBeAborted && 'signal is aborted without reason', ].filter((option) => option !== false); - const errorMessageExpression = new RegExp(`^${errorMessageOptions.join('|')}$`); + return new RegExp(`^${errorMessageOptions.join('|')}$`); +} + +export async function expectFetchError(responsePromise: Promise, options: ExpectFetchErrorOptions = {}) { + const errorMessageExpression = getFetchErrorMessageRegex(options); await expect(responsePromise).rejects.toThrowError(errorMessageExpression); } -export async function expectBypassedResponse(responsePromise: Promise) { - const response = await responsePromise; +export async function expectBypassedResponse( + responsePromise: Promise, + options: Pick = {}, +) { + let response: Response; + + /* istanbul ignore next -- @preserve */ + // In rare cases, bypassed responses in browser local interceptors may time out. We do not know why that happens. + // Thus, if a fetch error occurs, we check if it is a timeout. If so, we ignore it if the test does not expect it. + try { + response = await responsePromise; + } catch (error) { + const errorMessageExpression = getFetchErrorMessageRegex({ + canBeGenericFailure: false, + canBeAborted: options.canBeAborted, + }); + + const isExpectedAbortError = error instanceof Error && errorMessageExpression.test(error.message); + + if (isExpectedAbortError) { + return; + } else { + throw error; + } + } expect(response.status).toBe(GLOBAL_FALLBACK_SERVER_RESPONSE_STATUS); expect(Object.fromEntries(response.headers.entries())).toEqual( From 1f7f18d166abf44ff159538ac9bc40f181b81fa5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Nov 2024 19:13:51 +0000 Subject: [PATCH 02/16] chore(release): zimic@0.11.0-canary.0 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a9d71d958..f6100e08e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.10.0", + "version": "0.11.0-canary.0", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 9cc66b220..b72ec508b 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.10.0", + "version": "0.11.0-canary.0", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 671703ffc58cdb86e64a98ca426f61791fdc4e2a Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Sat, 30 Nov 2024 18:52:59 -0300 Subject: [PATCH 03/16] feat(#zimic)!: reject local unhandled requests by default (#492) (#494) Closes #492. --- ...342\200\220interceptor\342\200\220http.md" | 89 ++++++++++++++----- .../interceptor/__tests__/shared/bypass.ts | 4 +- .../interceptor/__tests__/shared/clear.ts | 4 +- .../interceptor/__tests__/shared/handlers.ts | 36 ++------ .../interceptor/__tests__/shared/lifeCycle.ts | 6 -- .../__tests__/shared/pathParams.ts | 4 +- .../__tests__/shared/restrictions.ts | 70 +++------------ .../shared/unhandledRequests.factories.ts | 73 ++++----------- .../shared/unhandledRequests.logging.ts | 66 ++++++-------- .../http/interceptorWorker/constants.ts | 2 +- packages/zimic/tests/setup/shared.ts | 2 +- 11 files changed, 138 insertions(+), 218 deletions(-) diff --git "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" index ca278f6f1..7f884621e 100644 --- "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" +++ "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" @@ -144,21 +144,21 @@ console by default. > and [restrictions](#http-handlerwithrestriction) correctly match the request. Additionally, confirm that no errors > occurred while creating the response. -In a [local interceptor](getting‐started#local-http-interceptors), unhandled requests can be either bypassed or -rejected. Bypassed requests reach the real network, whereas rejected requests fail with an network error. The default -behavior in local interceptors is to bypass unhandled requests. +In a [local interceptor](getting‐started#local-http-interceptors), unhandled requests can be either **bypassed** or +**rejected**. Bypassed requests reach the real network, whereas rejected requests fail with an network error. The +default behavior in local interceptors is to **reject** unhandled requests. -On the other hand, [remote interceptors](getting‐started#remote-http-interceptors) and -[interceptor server](cli‐zimic‐server) always reject unhandled requests. This is because the unhandled requests have -already reached the interceptor server, so there would be no way of bypassing them at this point. +[Remote interceptors](getting‐started#remote-http-interceptors) and [interceptor server](cli‐zimic‐server) always +**reject** unhandled requests. This is because the unhandled requests have already reached the interceptor server, so +there would be no way of bypassing them at this point. -You can override the logging behavior for each interceptor with `onUnhandledRequest` in +You can override the logging behavior per interceptor with `onUnhandledRequest` in [`httpInterceptor.create(options)`](#httpinterceptorcreateoptions). `onUnhandledRequest` also accepts a function to dynamically determine which strategy to use for an unhandled request. -
+
- Example 1: Ignore all unhandled requests with no logging: + Example 1: Ignore unhandled requests in an interceptor without logging: ```ts @@ -176,9 +176,9 @@ const interceptor = httpInterceptor.create({
-
+
- Example 2: Reject all unhandled requests with logging: + Example 2: Reject unhandled requests in an interceptor with logging: ```ts @@ -196,9 +196,9 @@ const interceptor = httpInterceptor.create({
-
+
- Example 3: Dynamically ignore or reject unhandled requests: + Example 3: Dynamically ignore or reject unhandled requests in an interceptor: ```ts @@ -225,14 +225,14 @@ const interceptor = httpInterceptor.create({
-If you want to override the default logging behavior for all interceptors, you can use +If you want to override the default logging behavior for all interceptors, use `httpInterceptor.default.local.onUnhandledRequest` or `httpInterceptor.default.remote.onUnhandledRequest`. Keep in mind -that defining an `onUnhandledRequest` strategy when creating an interceptor will take precedence over +that `onUnhandledRequest` strategies declared when creating an interceptor will take precedence over `httpInterceptor.default.local.onUnhandledRequest` and `httpInterceptor.default.remote.onUnhandledRequest`.
- Example 4: Ignore unhandled requests in all interceptors with no logging: + Example 4: Ignore unhandled requests without logging in all interceptors: ```ts @@ -255,7 +255,7 @@ httpInterceptor.default.remote.onUnhandledRequest = {
- Example 5: Reject unhandled requests in all interceptors with logging: + Example 5: Reject unhandled requests with logging in all interceptors: ```ts @@ -301,15 +301,32 @@ httpInterceptor.default.local.onUnhandledRequest = (request) => { httpInterceptor.default.remote.onUnhandledRequest = (request) => { const url = new URL(request.url); - return { - action: 'reject', // Reject all unhandled requests - log: !url.pathname.startsWith('/assets'), // Log warnings for all unhandled requests except /assets - }; + // Reject without logging only unhandled requests to /assets + if (url.pathname.startsWith('/assets')) { + return { action: 'reject', log: false }; + } + + // Reject with logging all other unhandled requests + return { action: 'reject', log: true }; }; ```
+> [!NOTE] +> +> When a request is unhandled, Zimic looks for a running interceptor whose base URL is the prefix of the unhandled +> request URL. If such interceptor is found, its strategy is used, or the default strategy if none was defined. If +> multiple interceptors match the request URL, the **last** one started with `await interceptor.start()` will be used, +> regardless of existing another interceptor with a more specific base URL. +> +> If no running interceptor matches the request, one of two things may happen: +> +> - If it was targeted to an interceptor server, it will be **rejected** with a network error. In this case, the logging +> behavior is configured with the option [`--log-unhandled-requests`](cli‐zimic‐server.md#zimic-server-start) in the +> interceptor server. +> - If it was not targeted to an interceptor server, it will be **bypassed** and reach the real network. + #### Saving requests The option `saveRequests` indicates whether [request handlers](#httprequesthandler) should save their intercepted @@ -329,6 +346,8 @@ their intercepted requests in memory. > > See [Testing](guides‐testing) for an example of how to manage the lifecycle of interceptors in your tests. +
Using a local interceptor + ```ts import { httpInterceptor } from 'zimic/interceptor/http'; @@ -337,8 +356,34 @@ const interceptor = httpInterceptor.create({ baseURL: 'http://localhost:3000', saveRequests: true, }); + +// Recommended: Clear the interceptor after each test. +// Use the equivalent of `afterEach` in your test framework. +afterEach(() => { + interceptor.clear(); +}); +``` + +
Using a remote interceptor + +```ts +import { httpInterceptor } from 'zimic/interceptor/http'; + +const interceptor = httpInterceptor.create({ + type: 'remote', + baseURL: 'http://localhost:3000', + saveRequests: true, +}); + +// Recommended: Clear the interceptor after each test. +// Use the equivalent of `afterEach` in your test framework. +afterEach(async () => { + await interceptor.clear(); +}); ``` +
+ > [!TIP] > > If you use an interceptor both in tests and as a standalone mock server, consider setting `saveRequests` based on an @@ -349,7 +394,7 @@ const interceptor = httpInterceptor.create({ import { httpInterceptor } from 'zimic/interceptor/http'; const interceptor = httpInterceptor.create({ - type: 'local', + type: 'remote', baseURL: 'http://localhost:3000', saveRequests: process.env.NODE_ENV === 'test', }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts index 879537353..4ef938ff6 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts @@ -6,7 +6,7 @@ import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttp import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; import { joinURL } from '@/utils/urls'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions } from '../../types/options'; @@ -74,8 +74,6 @@ export function declareBypassHttpInterceptorTests(options: RuntimeSharedHttpInte if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts index 51e52979f..5b5a246ca 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts @@ -6,7 +6,7 @@ import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttp import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; import { joinURL } from '@/utils/urls'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions } from '../../types/options'; @@ -76,8 +76,6 @@ export function declareClearHttpInterceptorTests(options: RuntimeSharedHttpInter if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts index 53757f124..edf375313 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts @@ -11,7 +11,7 @@ import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/intercep import { importCrypto } from '@/utils/crypto'; import { joinURL } from '@/utils/urls'; import { usingIgnoredConsole } from '@tests/utils/console'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions } from '../../types/options'; @@ -142,11 +142,6 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH }); it(`should log an error if a ${method} request is intercepted with a computed response and the handler throws`, async () => { - const extendedInterceptorOptions: HttpInterceptorOptions = - type === 'local' - ? { ...interceptorOptions, type, onUnhandledRequest: { action: 'bypass', log: true } } - : { ...interceptorOptions, type, onUnhandledRequest: { action: 'reject', log: true } }; - await usingHttpInterceptor<{ '/users': { GET: MethodSchema; @@ -157,7 +152,7 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH HEAD: MethodSchema; OPTIONS: MethodSchema; }; - }>(extendedInterceptorOptions, async (interceptor) => { + }>({ ...interceptorOptions, onUnhandledRequest: { action: 'reject', log: true } }, async (interceptor) => { const error = new Error('An error occurred.'); const handler = await promiseIfRemote( @@ -180,27 +175,18 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } - if (type === 'remote') { - expect(spies.error).toHaveBeenCalledTimes(method === 'OPTIONS' && platform === 'browser' ? 4 : 2); - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error.mock.calls[0]).toEqual([error]); - - const errorMessage = spies.error.mock.calls[1].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); - } else { - expect(spies.error).toHaveBeenCalledTimes(1); - expect(spies.warn).toHaveBeenCalledTimes(1); - expect(spies.error.mock.calls[0]).toEqual([error]); + expect(spies.error).toHaveBeenCalledTimes( + method === 'OPTIONS' && type === 'remote' && platform === 'browser' ? 4 : 2, + ); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error.mock.calls[0]).toEqual([error]); - const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); - } + const errorMessage = spies.error.mock.calls[1].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); }); requests = await promiseIfRemote(handler.requests(), interceptor); @@ -349,8 +335,6 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -369,8 +353,6 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts index b388f6105..27036fb4b 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts @@ -103,8 +103,6 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -159,8 +157,6 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise, { canBeAborted: true }); } else { await expectFetchError(responsePromise, { canBeAborted: true }); } @@ -176,8 +172,6 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts index 2a8451659..f572f85c8 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts @@ -7,7 +7,7 @@ import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHt import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; import { importCrypto } from '@/utils/crypto'; import { joinURL } from '@/utils/urls'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions } from '../../types/options'; @@ -142,8 +142,6 @@ export async function declarePathParamsHttpInterceptorTests(options: RuntimeShar if (overridesPreflightResponse) { await expectPreflightResponse(unmatchedResponsePromise); - } else if (type === 'local') { - await expectBypassedResponse(unmatchedResponsePromise); } else { await expectFetchError(unmatchedResponsePromise); } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts index acef05eb2..3749ba7f0 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts @@ -12,7 +12,7 @@ import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/intercep import { importFile } from '@/utils/files'; import { joinURL } from '@/utils/urls'; import { usingIgnoredConsole } from '@tests/utils/console'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions } from '../../types/options'; @@ -147,8 +147,6 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -162,8 +160,6 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -178,8 +174,6 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -253,8 +247,6 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); - } else if (type === 'local') { - await expectBypassedResponse(responsePromise); } else { await expectFetchError(responsePromise); } @@ -318,11 +310,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(1); @@ -390,11 +378,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(2); @@ -469,11 +453,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(1); @@ -568,11 +548,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(2); @@ -648,11 +624,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(1); @@ -732,11 +704,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(2); @@ -794,11 +762,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(1); @@ -865,11 +829,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(2); @@ -937,11 +897,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(1); @@ -1018,11 +974,7 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh body, }); - if (type === 'local') { - await expectBypassedResponse(responsePromise); - } else { - await expectFetchError(responsePromise); - } + await expectFetchError(responsePromise); requests = await promiseIfRemote(handler.requests(), interceptor); expect(requests).toHaveLength(2); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts index 1db2779ef..b86967aa2 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts @@ -3,7 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; import { HTTP_METHODS, HttpSchema } from '@/http/types/schema'; import { promiseIfRemote } from '@/interceptor/http/interceptorWorker/__tests__/utils/promises'; -import { DEFAULT_UNHANDLED_REQUEST_STRATEGY } from '@/interceptor/http/interceptorWorker/constants'; import LocalHttpRequestHandler from '@/interceptor/http/requestHandler/LocalHttpRequestHandler'; import RemoteHttpRequestHandler from '@/interceptor/http/requestHandler/RemoteHttpRequestHandler'; import { AccessControlHeaders, DEFAULT_ACCESS_CONTROL_HEADERS } from '@/interceptor/server/constants'; @@ -11,7 +10,7 @@ import { importCrypto } from '@/utils/crypto'; import { waitForDelay } from '@/utils/time'; import { joinURL } from '@/utils/urls'; import { usingIgnoredConsole } from '@tests/utils/console'; -import { expectBypassedResponse, expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; +import { expectPreflightResponse, expectFetchError } from '@tests/utils/fetch'; import { assessPreflightInterference, usingHttpInterceptor } from '@tests/utils/interceptors'; import { HttpInterceptorOptions, UnhandledRequestStrategy } from '../../types/options'; @@ -301,11 +300,7 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( const request = new Request(joinURL(baseURL, '/users'), { method }); responsePromise = fetch(request); - const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; - - if (defaultStrategy.action === 'bypass') { - await expectBypassedResponse(responsePromise); - } else if (overridesPreflightResponse) { + if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else { await expectFetchError(responsePromise); @@ -316,29 +311,16 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - if (defaultStrategy.action === 'bypass') { - expect(spies.warn).toHaveBeenCalledTimes(1); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - expect(spies.error).toHaveBeenCalledWith(error); - - await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { - type: 'warn', - platform, - request, - }); - } else { - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - expect(spies.error).toHaveBeenNthCalledWith(1, error); + expect(spies.error).toHaveBeenNthCalledWith(1, error); - await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, - request, - }); - } + await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { + type: 'error', + platform, + request, + }); }); }, ); @@ -416,11 +398,7 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( const request = new Request(joinURL(baseURL, '/users'), { method }); responsePromise = fetch(request); - const defaultStrategy = DEFAULT_UNHANDLED_REQUEST_STRATEGY[type]; - - if (defaultStrategy.action === 'bypass') { - await expectBypassedResponse(responsePromise); - } else if (overridesPreflightResponse) { + if (overridesPreflightResponse) { await expectPreflightResponse(responsePromise); } else { await expectFetchError(responsePromise); @@ -431,29 +409,16 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(onUnhandledRequest).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - if (defaultStrategy.action === 'bypass') { - expect(spies.warn).toHaveBeenCalledTimes(1); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); - - expect(spies.error).toHaveBeenCalledWith(error); - - await verifyUnhandledRequestMessage(spies.warn.mock.calls[0].join(' '), { - type: 'warn', - platform, - request, - }); - } else { - expect(spies.warn).toHaveBeenCalledTimes(0); - expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight * 2); - expect(spies.error).toHaveBeenNthCalledWith(1, error); + expect(spies.error).toHaveBeenNthCalledWith(1, error); - await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, - request, - }); - } + await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { + type: 'error', + platform, + request, + }); }); }, ); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts index fb02a4692..7d38aa52b 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts @@ -103,14 +103,8 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( ])('Logging enabled: override default $overrideDefault', ({ overrideDefault }) => { const log = overrideDefault?.endsWith('undefined-log') ? undefined : true; - const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { - action: 'bypass', - log, - }; - const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { - action: 'reject', - log, - }; + const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { action: 'reject', log }; + const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { action: 'reject', log }; beforeEach(() => { if (overrideDefault?.startsWith('static')) { @@ -143,12 +137,16 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); afterEach(() => { - localOnUnhandledRequest.action = 'bypass'; + localOnUnhandledRequest.action = 'reject'; remoteOnUnhandledRequest.action = 'reject'; }); if (type === 'local') { it(`should show a warning when logging is enabled and ${method} requests with no body are unhandled and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + await usingHttpInterceptor( { ...interceptorOptions, @@ -200,6 +198,10 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( if (type === 'local' && methodCanHaveRequestBody(method)) { it(`should show a warning when logging is enabled and ${method} requests with body are unhandled and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + await usingHttpInterceptor( { ...interceptorOptions, @@ -259,6 +261,10 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); it(`should show a warning when logging is enabled and ${method} requests are unhandled due to restrictions and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + await usingHttpInterceptor( { ...interceptorOptions, @@ -311,6 +317,10 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); it(`should show a warning when logging is enabled and ${method} requests are unhandled due to unmocked path and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + await usingHttpInterceptor( { ...interceptorOptions, @@ -359,10 +369,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( } it(`should show an error when logging is enabled and ${method} requests with no body are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - await usingHttpInterceptor( { ...interceptorOptions, @@ -421,10 +427,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( if (methodCanHaveRequestBody(method)) { it(`should show an error when logging is enabled and ${method} requests with body are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - await usingHttpInterceptor( { ...interceptorOptions, @@ -485,10 +487,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( } it(`should show an error when logging is enabled and ${method} requests are unhandled due to restrictions and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - await usingHttpInterceptor( { ...interceptorOptions, @@ -548,10 +546,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); it(`should show an error when logging is enabled and ${method} requests are unhandled due to unmocked path and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - await usingHttpInterceptor( { ...interceptorOptions, @@ -615,14 +609,8 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( { overrideDefault: 'static' as const }, { overrideDefault: 'factory' as const }, ])('Logging disabled: override default $overrideDefault', ({ overrideDefault }) => { - const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { - action: 'bypass', - log: false, - }; - const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { - action: 'reject', - log: false, - }; + const localOnUnhandledRequest: UnhandledRequestStrategy.LocalDeclaration = { action: 'reject', log: false }; + const remoteOnUnhandledRequest: UnhandledRequestStrategy.RemoteDeclaration = { action: 'reject', log: false }; beforeEach(() => { if (overrideDefault === 'static') { @@ -655,12 +643,16 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); afterEach(() => { - localOnUnhandledRequest.action = 'bypass'; + localOnUnhandledRequest.action = 'reject'; remoteOnUnhandledRequest.action = 'reject'; }); if (type === 'local') { it(`should not show a warning when logging is disabled and ${method} requests are unhandled and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + const extendedInterceptorOptions: HttpInterceptorOptions = { ...interceptorOptions, type, @@ -706,10 +698,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( } it(`should not show an error when logging is disabled and ${method} requests are unhandled and rejected`, async () => { - if (overrideDefault) { - localOnUnhandledRequest.action = 'reject'; - } - const extendedInterceptorOptions: HttpInterceptorOptions = { ...interceptorOptions, type, @@ -780,7 +768,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( }); }); - it(`should not log unhandled ${method} requests when no interceptor with matching request base URL was found`, async () => { + it(`should not log unhandled ${method} requests when no interceptor matching the base URL of the request was found`, async () => { const otherBaseURL = joinURL(baseURL, 'other'); await usingHttpInterceptor( diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/constants.ts b/packages/zimic/src/interceptor/http/interceptorWorker/constants.ts index 7344dc36d..a8b9f64c8 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/constants.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/constants.ts @@ -2,7 +2,7 @@ import { HttpInterceptorType, UnhandledRequestStrategy } from '../interceptor/ty export const DEFAULT_UNHANDLED_REQUEST_STRATEGY = Object.freeze({ local: Object.freeze({ - action: 'bypass', + action: 'reject', log: true, }), remote: Object.freeze({ diff --git a/packages/zimic/tests/setup/shared.ts b/packages/zimic/tests/setup/shared.ts index 2219f8254..db4f2cf90 100644 --- a/packages/zimic/tests/setup/shared.ts +++ b/packages/zimic/tests/setup/shared.ts @@ -4,7 +4,7 @@ import { httpInterceptor } from '@/interceptor/http'; beforeEach(() => { httpInterceptor.default.local.onUnhandledRequest = { - action: 'bypass', + action: 'reject', log: false, }; From 26e4f3cc3829313dbf33ba830581e9ab36fc7e79 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Nov 2024 21:53:44 +0000 Subject: [PATCH 04/16] chore(release): zimic@0.11.0-canary.1 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f6100e08e..e6a72fa96 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.0", + "version": "0.11.0-canary.1", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index b72ec508b..7c9487cdc 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.0", + "version": "0.11.0-canary.1", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 40133daa137cf5333dac2ea24a2c484458d94f26 Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Sat, 30 Nov 2024 19:23:47 -0300 Subject: [PATCH 05/16] chore(ci): include typescript 5.7 in ci matrix (#495) (#496) Closes #495. --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2b750b386..0d24ff9a0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -155,6 +155,7 @@ jobs: - '5.4' - '5.5' - '5.6' + - '5.7' env: TYPESCRIPT_VERSION: ${{ matrix.typescript-version }} From 2ae698da3282cf969eed51f9c54bc814cbd639d1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 30 Nov 2024 22:24:22 +0000 Subject: [PATCH 06/16] chore(release): zimic@0.11.0-canary.2 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e6a72fa96..f2087e024 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.1", + "version": "0.11.0-canary.2", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 7c9487cdc..54d5f6314 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.1", + "version": "0.11.0-canary.2", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 7cc9ec7c51848b523e72c30e7c2e35eb7d402d8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:31:33 -0300 Subject: [PATCH 07/16] chore(root): bump the npm group with 13 updates (#497) Bumps the npm group with 13 updates: | Package | From | To | | --- | --- | --- | | [prettier](https://github.com/prettier/prettier) | `3.3.3` | `3.4.1` | | [turbo](https://github.com/vercel/turborepo) | `2.3.1` | `2.3.3` | | [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) | `2.1.5` | `2.1.6` | | [@vitest/coverage-istanbul](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-istanbul) | `2.1.5` | `2.1.6` | | [axios](https://github.com/axios/axios) | `1.7.7` | `1.7.8` | | [eslint](https://github.com/eslint/eslint) | `9.15.0` | `9.16.0` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.5` | `2.1.6` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.15.0` | `8.16.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.15.0` | `8.16.0` | | [@whatwg-node/server](https://github.com/ardatan/whatwg-node/tree/HEAD/packages/server) | `0.9.56` | `0.9.60` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.9.3` | `22.10.1` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.61.3` | `5.62.0` | | [@vitest/spy](https://github.com/vitest-dev/vitest/tree/HEAD/packages/spy) | `2.1.5` | `2.1.6` | Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/zimic-test-client/package.json | 10 +- examples/package.json | 2 +- examples/with-next-js-app/package.json | 4 +- examples/with-next-js-pages/package.json | 4 +- examples/with-openapi-typegen/package.json | 4 +- examples/with-playwright/package.json | 2 +- examples/with-vitest-browser/package.json | 4 +- examples/with-vitest-jsdom/package.json | 4 +- examples/with-vitest-node/package.json | 4 +- package.json | 4 +- packages/eslint-config/package.json | 4 +- packages/zimic/package.json | 14 +- pnpm-lock.yaml | 713 +++++++++++---------- 13 files changed, 392 insertions(+), 381 deletions(-) diff --git a/apps/zimic-test-client/package.json b/apps/zimic-test-client/package.json index b3bd8858e..f2c7aa19f 100644 --- a/apps/zimic-test-client/package.json +++ b/apps/zimic-test-client/package.json @@ -17,21 +17,21 @@ }, "devDependencies": { "@types/superagent": "^8.1.9", - "@vitest/browser": "2.1.5", - "@vitest/coverage-istanbul": "2.1.5", + "@vitest/browser": "2.1.6", + "@vitest/coverage-istanbul": "2.1.6", "@zimic/eslint-config-node": "workspace:*", "@zimic/lint-staged-config": "workspace:*", "@zimic/tsconfig": "workspace:*", - "axios": "^1.7.7", + "axios": "^1.7.8", "concurrently": "^9.1.0", "dotenv-cli": "^7.4.4", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "execa": "^9.5.1", "node-fetch": "^3.3.2", "playwright": "^1.49.0", "superagent": "^10.1.1", "typescript": "^5.7.2", - "vitest": "2.1.5", + "vitest": "2.1.6", "zimic": "workspace:*" } } diff --git a/examples/package.json b/examples/package.json index c6a48357a..a4f68a2cb 100644 --- a/examples/package.json +++ b/examples/package.json @@ -16,6 +16,6 @@ "@zimic/eslint-config-react": "workspace:*", "@zimic/lint-staged-config": "workspace:*", "@zimic/tsconfig": "workspace:*", - "eslint": "^9.15.0" + "eslint": "^9.16.0" } } diff --git a/examples/with-next-js-app/package.json b/examples/with-next-js-app/package.json index 75c3f1dbf..2c696701a 100644 --- a/examples/with-next-js-app/package.json +++ b/examples/with-next-js-app/package.json @@ -16,7 +16,7 @@ "postinstall": "pnpm deps:install-playwright" }, "dependencies": { - "@tanstack/react-query": "^5.61.3", + "@tanstack/react-query": "^5.62.0", "clsx": "^2.1.1", "next": "15.0.3", "react": "^18.3.1", @@ -24,7 +24,7 @@ }, "devDependencies": { "@playwright/test": "^1.49.0", - "@types/node": "^22.9.3", + "@types/node": "^22.10.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "dotenv-cli": "^7.4.4", diff --git a/examples/with-next-js-pages/package.json b/examples/with-next-js-pages/package.json index c438d4dc9..efe5a884b 100644 --- a/examples/with-next-js-pages/package.json +++ b/examples/with-next-js-pages/package.json @@ -12,7 +12,7 @@ "postinstall": "concurrently --names playwright,zimic 'pnpm deps:install-playwright' 'pnpm deps:init-zimic'" }, "dependencies": { - "@tanstack/react-query": "^5.61.3", + "@tanstack/react-query": "^5.62.0", "clsx": "^2.1.1", "next": "15.0.3", "react": "^18.3.1", @@ -20,7 +20,7 @@ }, "devDependencies": { "@playwright/test": "^1.49.0", - "@types/node": "^22.9.3", + "@types/node": "^22.10.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "concurrently": "^9.1.0", diff --git a/examples/with-openapi-typegen/package.json b/examples/with-openapi-typegen/package.json index d2038e1d0..269fc20f5 100644 --- a/examples/with-openapi-typegen/package.json +++ b/examples/with-openapi-typegen/package.json @@ -15,11 +15,11 @@ }, "devDependencies": { "@types/supertest": "^6.0.2", - "@vitest/spy": "^2.1.5", + "@vitest/spy": "^2.1.6", "dotenv-cli": "^7.4.4", "supertest": "^7.0.0", "typescript": "^5.7.2", - "vitest": "2.1.5", + "vitest": "2.1.6", "zimic": "latest" } } diff --git a/examples/with-playwright/package.json b/examples/with-playwright/package.json index 8c84bab2a..981ddf768 100644 --- a/examples/with-playwright/package.json +++ b/examples/with-playwright/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@playwright/test": "^1.49.0", - "@types/node": "^22.9.3", + "@types/node": "^22.10.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "dotenv-cli": "^7.4.4", diff --git a/examples/with-vitest-browser/package.json b/examples/with-vitest-browser/package.json index 2d8d69688..e792e9397 100644 --- a/examples/with-vitest-browser/package.json +++ b/examples/with-vitest-browser/package.json @@ -14,12 +14,12 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.5.2", - "@vitest/browser": "2.1.5", + "@vitest/browser": "2.1.6", "concurrently": "^9.1.0", "dotenv-cli": "^7.4.4", "playwright": "^1.49.0", "typescript": "^5.7.2", - "vitest": "2.1.5", + "vitest": "2.1.6", "zimic": "latest" } } diff --git a/examples/with-vitest-jsdom/package.json b/examples/with-vitest-jsdom/package.json index 0270f38f0..befcb5cd9 100644 --- a/examples/with-vitest-jsdom/package.json +++ b/examples/with-vitest-jsdom/package.json @@ -11,10 +11,10 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.5.2", - "@vitest/spy": "^2.1.5", + "@vitest/spy": "^2.1.6", "dotenv-cli": "^7.4.4", "typescript": "^5.7.2", - "vitest": "2.1.5", + "vitest": "2.1.6", "zimic": "latest" } } diff --git a/examples/with-vitest-node/package.json b/examples/with-vitest-node/package.json index 26fbd4eee..576d149e7 100644 --- a/examples/with-vitest-node/package.json +++ b/examples/with-vitest-node/package.json @@ -13,11 +13,11 @@ }, "devDependencies": { "@types/supertest": "^6.0.2", - "@vitest/spy": "^2.1.5", + "@vitest/spy": "^2.1.6", "dotenv-cli": "^7.4.4", "supertest": "^7.0.0", "typescript": "^5.7.2", - "vitest": "2.1.5", + "vitest": "2.1.6", "zimic": "latest" } } diff --git a/package.json b/package.json index f2087e024..e9a0e1e64 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "concurrently": "^9.1.0", "husky": "^9.1.7", "lint-staged": "^15.2.10", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "prettier-plugin-jsdoc": "^1.3.0", "prettier-plugin-sh": "^0.14.0", - "turbo": "^2.3.1" + "turbo": "^2.3.3" } } diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 7a8c84555..6f12ce9c3 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -10,8 +10,8 @@ ".": "./index.js" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-import": "^2.31.0", "eslint-plugin-import-helpers": "^2.0.1" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 54d5f6314..6944f1a05 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -99,7 +99,7 @@ "prepublishOnly": "cp ../../README.md ../../LICENSE.md . && pnpm prepublish:patch-relative-paths README.md" }, "dependencies": { - "@whatwg-node/server": "0.9.56", + "@whatwg-node/server": "0.9.60", "chalk": "4.1.2", "execa": "9.5.1", "isomorphic-ws": "5.0.0", @@ -114,23 +114,23 @@ "devDependencies": { "@types/cross-spawn": "^6.0.6", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.9.3", + "@types/node": "^22.10.1", "@types/ws": "^8.5.13", "@types/yargs": "^17.0.33", - "@vitest/browser": "^2.1.5", - "@vitest/coverage-istanbul": "^2.1.5", + "@vitest/browser": "^2.1.6", + "@vitest/coverage-istanbul": "^2.1.6", "@zimic/eslint-config-node": "workspace:*", "@zimic/lint-staged-config": "workspace:*", "@zimic/tsconfig": "workspace:*", "dotenv-cli": "^7.4.4", - "eslint": "^9.15.0", + "eslint": "^9.16.0", "js-yaml": "^4.1.0", "playwright": "^1.49.0", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "tsup": "^8.3.5", "tsx": "^4.19.2", "typescript": "^5.7.2", - "vitest": "^2.1.5" + "vitest": "^2.1.6" }, "peerDependencies": { "typescript": ">=4.8.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d92c514e3..04a3377e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^19.6.0 - version: 19.6.0(@types/node@22.9.3)(typescript@5.7.2) + version: 19.6.0(@types/node@22.10.1)(typescript@5.7.2) '@commitlint/config-conventional': specifier: ^19.6.0 version: 19.6.0 @@ -27,17 +27,17 @@ importers: specifier: ^15.2.10 version: 15.2.10 prettier: - specifier: ^3.3.3 - version: 3.3.3 + specifier: ^3.4.1 + version: 3.4.1 prettier-plugin-jsdoc: specifier: ^1.3.0 - version: 1.3.0(prettier@3.3.3) + version: 1.3.0(prettier@3.4.1) prettier-plugin-sh: specifier: ^0.14.0 - version: 0.14.0(prettier@3.3.3) + version: 0.14.0(prettier@3.4.1) turbo: - specifier: ^2.3.1 - version: 2.3.1 + specifier: ^2.3.3 + version: 2.3.3 apps/zimic-test-client: devDependencies: @@ -45,11 +45,11 @@ importers: specifier: ^8.1.9 version: 8.1.9 '@vitest/browser': - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) '@vitest/coverage-istanbul': - specifier: 2.1.5 - version: 2.1.5(vitest@2.1.5) + specifier: 2.1.6 + version: 2.1.6(vitest@2.1.6) '@zimic/eslint-config-node': specifier: workspace:* version: link:../../packages/eslint-config-node @@ -60,8 +60,8 @@ importers: specifier: workspace:* version: link:../../packages/tsconfig axios: - specifier: ^1.7.7 - version: 1.7.7 + specifier: ^1.7.8 + version: 1.7.8 concurrently: specifier: ^9.1.0 version: 9.1.0 @@ -69,8 +69,8 @@ importers: specifier: ^7.4.4 version: 7.4.4 eslint: - specifier: ^9.15.0 - version: 9.15.0(jiti@1.21.6) + specifier: ^9.16.0 + version: 9.16.0(jiti@1.21.6) execa: specifier: ^9.5.1 version: 9.5.1 @@ -87,8 +87,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: workspace:* version: link:../../packages/zimic @@ -109,8 +109,8 @@ importers: specifier: workspace:* version: link:../packages/tsconfig eslint: - specifier: ^9.15.0 - version: 9.15.0(jiti@1.21.6) + specifier: ^9.16.0 + version: 9.16.0(jiti@1.21.6) examples/with-jest-jsdom: devDependencies: @@ -137,7 +137,7 @@ importers: version: 7.4.4 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.9.3) + version: 29.7.0(@types/node@22.10.1) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0(bufferutil@4.0.8) @@ -174,7 +174,7 @@ importers: version: 7.4.4 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.9.3) + version: 29.7.0(@types/node@22.10.1) supertest: specifier: ^7.0.0 version: 7.0.0 @@ -188,8 +188,8 @@ importers: examples/with-next-js-app: dependencies: '@tanstack/react-query': - specifier: ^5.61.3 - version: 5.61.3(react@18.3.1) + specifier: ^5.62.0 + version: 5.62.0(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -207,8 +207,8 @@ importers: specifier: ^1.49.0 version: 1.49.0 '@types/node': - specifier: ^22.9.3 - version: 22.9.3 + specifier: ^22.10.1 + version: 22.10.1 '@types/react': specifier: ^18.3.12 version: 18.3.12 @@ -240,8 +240,8 @@ importers: examples/with-next-js-pages: dependencies: '@tanstack/react-query': - specifier: ^5.61.3 - version: 5.61.3(react@18.3.1) + specifier: ^5.62.0 + version: 5.62.0(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -259,8 +259,8 @@ importers: specifier: ^1.49.0 version: 1.49.0 '@types/node': - specifier: ^22.9.3 - version: 22.9.3 + specifier: ^22.10.1 + version: 22.10.1 '@types/react': specifier: ^18.3.12 version: 18.3.12 @@ -299,8 +299,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 '@vitest/spy': - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.1.6 + version: 2.1.6 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -311,8 +311,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -336,8 +336,8 @@ importers: specifier: ^1.49.0 version: 1.49.0 '@types/node': - specifier: ^22.9.3 - version: 22.9.3 + specifier: ^22.10.1 + version: 22.10.1 '@types/react': specifier: ^18.3.12 version: 18.3.12 @@ -378,8 +378,8 @@ importers: specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitest/browser': - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) concurrently: specifier: ^9.1.0 version: 9.1.0 @@ -393,8 +393,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -411,8 +411,8 @@ importers: specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitest/spy': - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.1.6 + version: 2.1.6 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -420,8 +420,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -439,8 +439,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 '@vitest/spy': - specifier: ^2.1.5 - version: 2.1.5 + specifier: ^2.1.6 + version: 2.1.6 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -451,8 +451,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + specifier: 2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -460,32 +460,32 @@ importers: packages/eslint-config: dependencies: '@typescript-eslint/eslint-plugin': - specifier: ^8.15.0 - version: 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + specifier: ^8.16.0 + version: 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) '@typescript-eslint/parser': - specifier: ^8.15.0 - version: 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + specifier: ^8.16.0 + version: 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) eslint-import-resolver-typescript: specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@1.21.6)) + version: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-import-helpers: specifier: ^2.0.1 - version: 2.0.1(eslint@9.15.0(jiti@1.21.6)) + version: 2.0.1(eslint@9.16.0(jiti@1.21.6)) packages/eslint-config-node: dependencies: '@eslint/compat': specifier: ^1.2.3 - version: 1.2.3(eslint@9.15.0(jiti@1.21.6)) + version: 1.2.3(eslint@9.16.0(jiti@1.21.6)) '@zimic/eslint-config': specifier: workspace:* version: link:../eslint-config eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) packages/eslint-config-react: dependencies: @@ -494,16 +494,16 @@ importers: version: link:../eslint-config eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: specifier: ^6.10.2 - version: 6.10.2(eslint@9.15.0(jiti@1.21.6)) + version: 6.10.2(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-react: specifier: ^7.37.2 - version: 7.37.2(eslint@9.15.0(jiti@1.21.6)) + version: 7.37.2(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-react-hooks: specifier: ^5.0.0 - version: 5.0.0(eslint@9.15.0(jiti@1.21.6)) + version: 5.0.0(eslint@9.16.0(jiti@1.21.6)) packages/lint-staged-config: {} @@ -512,8 +512,8 @@ importers: packages/zimic: dependencies: '@whatwg-node/server': - specifier: 0.9.56 - version: 0.9.56 + specifier: 0.9.60 + version: 0.9.60 chalk: specifier: 4.1.2 version: 4.1.2 @@ -547,8 +547,8 @@ importers: specifier: ^4.0.9 version: 4.0.9 '@types/node': - specifier: ^22.9.3 - version: 22.9.3 + specifier: ^22.10.1 + version: 22.10.1 '@types/ws': specifier: ^8.5.13 version: 8.5.13 @@ -556,11 +556,11 @@ importers: specifier: ^17.0.33 version: 17.0.33 '@vitest/browser': - specifier: ^2.1.5 - version: 2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5) + specifier: ^2.1.6 + version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) '@vitest/coverage-istanbul': - specifier: ^2.1.5 - version: 2.1.5(vitest@2.1.5) + specifier: ^2.1.6 + version: 2.1.6(vitest@2.1.6) '@zimic/eslint-config-node': specifier: workspace:* version: link:../eslint-config-node @@ -574,8 +574,8 @@ importers: specifier: ^7.4.4 version: 7.4.4 eslint: - specifier: ^9.15.0 - version: 9.15.0(jiti@1.21.6) + specifier: ^9.16.0 + version: 9.16.0(jiti@1.21.6) js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -583,8 +583,8 @@ importers: specifier: ^1.49.0 version: 1.49.0 prettier: - specifier: ^3.3.3 - version: 3.3.3 + specifier: ^3.4.1 + version: 3.4.1 tsup: specifier: ^8.3.5 version: 8.3.5(@swc/core@1.9.3(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.5.1) @@ -595,8 +595,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: ^2.1.5 - version: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)) + specifier: ^2.1.6 + version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)) packages: @@ -1335,8 +1335,8 @@ packages: resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.15.0': - resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==} + '@eslint/js@9.16.0': + resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': @@ -2006,11 +2006,11 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} - '@tanstack/query-core@5.60.6': - resolution: {integrity: sha512-tI+k0KyCo1EBJ54vxK1kY24LWj673ujTydCZmzEZKAew4NqZzTaVQJEuaG1qKj2M03kUHN46rchLRd+TxVq/zQ==} + '@tanstack/query-core@5.62.0': + resolution: {integrity: sha512-sx38bGrqF9bop92AXOvzDr0L9fWDas5zXdPglxa9cuqeVSWS7lY6OnVyl/oodfXjgOGRk79IfCpgVmxrbHuFHg==} - '@tanstack/react-query@5.61.3': - resolution: {integrity: sha512-c3Oz9KaCBapGkRewu7AJLhxE9BVqpMcHsd3KtFxSd7FSCu2qGwqfIN37zbSGoyk6Ix9LGZBNHQDPI6GpWABnmA==} + '@tanstack/react-query@5.62.0': + resolution: {integrity: sha512-tj2ltjAn2a3fs+Dqonlvs6GyLQ/LKVJE2DVSYW+8pJ3P6/VCVGrfqv5UEchmlP7tLOvvtZcOuSyI2ooVlR5Yqw==} peerDependencies: react: ^18 || ^19 @@ -2101,6 +2101,9 @@ packages: '@types/mute-stream@0.0.4': resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node@22.10.1': + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + '@types/node@22.9.3': resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==} @@ -2143,8 +2146,8 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.15.0': - resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==} + '@typescript-eslint/eslint-plugin@8.16.0': + resolution: {integrity: sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2154,8 +2157,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.15.0': - resolution: {integrity: sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==} + '@typescript-eslint/parser@8.16.0': + resolution: {integrity: sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2164,12 +2167,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.15.0': - resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==} + '@typescript-eslint/scope-manager@8.16.0': + resolution: {integrity: sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.15.0': - resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==} + '@typescript-eslint/type-utils@8.16.0': + resolution: {integrity: sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2178,12 +2181,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.15.0': - resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==} + '@typescript-eslint/types@8.16.0': + resolution: {integrity: sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.15.0': - resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==} + '@typescript-eslint/typescript-estree@8.16.0': + resolution: {integrity: sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2191,8 +2194,8 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.15.0': - resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==} + '@typescript-eslint/utils@8.16.0': + resolution: {integrity: sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2201,16 +2204,16 @@ packages: typescript: optional: true - '@typescript-eslint/visitor-keys@8.15.0': - resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==} + '@typescript-eslint/visitor-keys@8.16.0': + resolution: {integrity: sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/browser@2.1.5': - resolution: {integrity: sha512-JrpnxvkrjlBrF7oXbK/YytWVYfJIzWYeDKppANlUaisBKwDso+yXlWocAJrANx8gUxyirF355Yx80S+SKQqayg==} + '@vitest/browser@2.1.6': + resolution: {integrity: sha512-GTBY78bkRd5BL3jnwQr9J+uIkZjpvRzq0G1uHNy2GeJYfTvmYFh4QE5W2HH6AfEKLFQwPXHFiXgI2o4CEtAtJw==} peerDependencies: playwright: '*' safaridriver: '*' - vitest: 2.1.5 + vitest: 2.1.6 webdriverio: '*' peerDependenciesMeta: playwright: @@ -2220,39 +2223,43 @@ packages: webdriverio: optional: true - '@vitest/coverage-istanbul@2.1.5': - resolution: {integrity: sha512-jJsS5jeHncmSvzMNE03F1pk8F9etmjzGmGyQnGMkdHdVek/bxK/3vo8Qr3e9XmVuDM3UZKOy1ObeQHgC2OxvHg==} + '@vitest/coverage-istanbul@2.1.6': + resolution: {integrity: sha512-tJBsODJ8acDxwt4J+HERV0+FxOhjOXn+yVYWHQ25iFqcbQNyvptQLBFxEoRvaMbqJqSaYatGOg0/ctiQdB3mSg==} peerDependencies: - vitest: 2.1.5 + vitest: 2.1.6 - '@vitest/expect@2.1.5': - resolution: {integrity: sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==} + '@vitest/expect@2.1.6': + resolution: {integrity: sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==} - '@vitest/mocker@2.1.5': - resolution: {integrity: sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==} + '@vitest/mocker@2.1.6': + resolution: {integrity: sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@2.1.5': - resolution: {integrity: sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==} + '@vitest/pretty-format@2.1.6': + resolution: {integrity: sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==} + + '@vitest/runner@2.1.6': + resolution: {integrity: sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==} - '@vitest/runner@2.1.5': - resolution: {integrity: sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==} + '@vitest/snapshot@2.1.6': + resolution: {integrity: sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==} - '@vitest/snapshot@2.1.5': - resolution: {integrity: sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==} + '@vitest/spy@2.1.6': + resolution: {integrity: sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==} - '@vitest/spy@2.1.5': - resolution: {integrity: sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==} + '@vitest/utils@2.1.6': + resolution: {integrity: sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==} - '@vitest/utils@2.1.5': - resolution: {integrity: sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==} + '@whatwg-node/disposablestack@0.0.5': + resolution: {integrity: sha512-9lXugdknoIequO4OYvIjhygvfSEgnO8oASLqLelnDhkRjgBZhc39shC3QSlZuyDO9bgYSIVa2cHAiN+St3ty4w==} + engines: {node: '>=18.0.0'} '@whatwg-node/fetch@0.10.1': resolution: {integrity: sha512-gmPOLrsjSZWEZlr9Oe5+wWFBq3CG6fN13rGlM91Jsj/vZ95G9CCvrORGBAxMXy0AJGiC83aYiHXn3JzTzXQmbA==} @@ -2262,8 +2269,8 @@ packages: resolution: {integrity: sha512-+IVKtQhHnaOS39ErbdeQUh+pVKqCJxO0OokBxCo7SNtTHOZB2Tb+YN+YWwp4WizXwTWy85tsEv1AAprugpxNmg==} engines: {node: '>=18.0.0'} - '@whatwg-node/server@0.9.56': - resolution: {integrity: sha512-qHvoug7dTStxDvJKhivO4V/+3Ac+RrMYPwSJ1Sqbq3qbDB0s8xRtlRU+jciOvUJrQQDzcQZJ736yLiKayWscsQ==} + '@whatwg-node/server@0.9.60': + resolution: {integrity: sha512-JH3eK3aGnBwTT2qQwFrmx6RPXxsjrk99kDWOM98H1aayFMV70nsHIltmyuKRnPmf/avuVRe53bkiu2wsc5Eykw==} engines: {node: '>=18.0.0'} JSONStream@1.3.5: @@ -2442,8 +2449,8 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + axios@1.7.8: + resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2748,10 +2755,6 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - cross-spawn@7.0.5: - resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3137,8 +3140,8 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.15.0: - resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==} + eslint@9.16.0: + resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -4665,8 +4668,8 @@ packages: peerDependencies: prettier: ^3.0.3 - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + prettier@3.4.1: + resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} engines: {node: '>=14'} hasBin: true @@ -5264,38 +5267,38 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.3.1: - resolution: {integrity: sha512-tjHfjW/Gs8Q9IO+9gPdIsSStZ8I09QYDRT/SyhFTPLnc7O2ZlxHPBVFfjUkHUjanHNYO8CpRGt+zdp1PaMCruw==} + turbo-darwin-64@2.3.3: + resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.3.1: - resolution: {integrity: sha512-At1WStnxCfrBQ4M2g6ynre8WsusGwA11okhVolBxyFUemYozDTtbZwelr+IqNggjT251vviokxOkcFzzogbiFw==} + turbo-darwin-arm64@2.3.3: + resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.3.1: - resolution: {integrity: sha512-COwEev7s9fsxLM2eoRCyRLPj+BXvZjFIS+GxzdAubYhoSoZit8B8QGKczyDl6448xhuFEWKrpHhcR9aBuwB4ag==} + turbo-linux-64@2.3.3: + resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.3.1: - resolution: {integrity: sha512-AP0uE15Rhxza2Jl+Q3gxdXRA92IIeFAYaufz6CMcZuGy9yZsBlLt9w6T47H6g7XQPzWuw8pzfjM1omcTKkkDpQ==} + turbo-linux-arm64@2.3.3: + resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==} cpu: [arm64] os: [linux] - turbo-windows-64@2.3.1: - resolution: {integrity: sha512-HDSneq0dNZYZch74c2eygq+OiJE/JYDs7OsGM0yRYVj336383xkUnxz6W2I7qiyMCQXzp4UVUDZXvZhUYcX3BA==} + turbo-windows-64@2.3.3: + resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.3.1: - resolution: {integrity: sha512-7/2/sJZiquwoT/jWBCfV0qKq4NarsJPmDRjMcR9dDMIwCYsGM8ljomkDRTCtkNeFcUvYw54MiRWHehWgbcRPsw==} + turbo-windows-arm64@2.3.3: + resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==} cpu: [arm64] os: [win32] - turbo@2.3.1: - resolution: {integrity: sha512-vHZe/e6k1HZVKiMQPQ1BWFn53vjVQDFKdkjUq/pBKlRWi1gw9LQO6ntH4qZCcHY1rH6TXgsRmexXdgWl96YvVQ==} + turbo@2.3.3: + resolution: {integrity: sha512-DUHWQAcC8BTiUZDRzAYGvpSpGLiaOQPfYXlCieQbwUvmml/LRGIe3raKdrOPOoiX0DYlzxs2nH6BoWJoZrj8hA==} hasBin: true type-check@0.4.0: @@ -5341,6 +5344,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -5381,9 +5387,9 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} - vite-node@2.1.5: - resolution: {integrity: sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@2.1.6: + resolution: {integrity: sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@5.4.9: @@ -5417,15 +5423,15 @@ packages: terser: optional: true - vitest@2.1.5: - resolution: {integrity: sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@2.1.6: + resolution: {integrity: sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.5 - '@vitest/ui': 2.1.5 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 2.1.6 + '@vitest/ui': 2.1.6 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5621,7 +5627,7 @@ snapshots: '@babel/core@7.24.9': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.24.10 '@babel/helper-compilation-targets': 7.24.8 '@babel/helper-module-transforms': 7.24.9(@babel/core@7.24.9) @@ -5804,13 +5810,13 @@ snapshots: '@babel/template@7.24.7': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/parser': 7.26.2 '@babel/types': 7.26.0 '@babel/traverse@7.24.8': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.24.10 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 @@ -5847,11 +5853,11 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@commitlint/cli@19.6.0(@types/node@22.9.3)(typescript@5.7.2)': + '@commitlint/cli@19.6.0(@types/node@22.10.1)(typescript@5.7.2)': dependencies: '@commitlint/format': 19.5.0 '@commitlint/lint': 19.6.0 - '@commitlint/load': 19.5.0(@types/node@22.9.3)(typescript@5.7.2) + '@commitlint/load': 19.5.0(@types/node@22.10.1)(typescript@5.7.2) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.1 @@ -5898,7 +5904,7 @@ snapshots: '@commitlint/rules': 19.6.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.5.0(@types/node@22.9.3)(typescript@5.7.2)': + '@commitlint/load@19.5.0(@types/node@22.10.1)(typescript@5.7.2)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 @@ -5906,7 +5912,7 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.7.2) - cosmiconfig-typescript-loader: 5.1.0(@types/node@22.9.3)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) + cosmiconfig-typescript-loader: 5.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -6175,16 +6181,16 @@ snapshots: '@esbuild/win32-x64@0.24.0': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@1.21.6))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0(jiti@1.21.6))': dependencies: - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.3(eslint@9.15.0(jiti@1.21.6))': + '@eslint/compat@1.2.3(eslint@9.16.0(jiti@1.21.6))': optionalDependencies: - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) '@eslint/config-array@0.19.0': dependencies: @@ -6210,7 +6216,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.15.0': {} + '@eslint/js@9.16.0': {} '@eslint/object-schema@2.1.4': {} @@ -6327,16 +6333,16 @@ snapshots: '@inquirer/core': 9.2.1 '@inquirer/type': 1.5.5 - '@inquirer/confirm@5.0.1(@types/node@22.9.3)': + '@inquirer/confirm@5.0.1(@types/node@22.10.1)': dependencies: - '@inquirer/core': 10.0.1(@types/node@22.9.3) - '@inquirer/type': 3.0.0(@types/node@22.9.3) - '@types/node': 22.9.3 + '@inquirer/core': 10.0.1(@types/node@22.10.1) + '@inquirer/type': 3.0.0(@types/node@22.10.1) + '@types/node': 22.10.1 - '@inquirer/core@10.0.1(@types/node@22.9.3)': + '@inquirer/core@10.0.1(@types/node@22.10.1)': dependencies: '@inquirer/figures': 1.0.7 - '@inquirer/type': 3.0.0(@types/node@22.9.3) + '@inquirer/type': 3.0.0(@types/node@22.10.1) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -6352,7 +6358,7 @@ snapshots: '@inquirer/figures': 1.0.6 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -6374,9 +6380,9 @@ snapshots: dependencies: mute-stream: 1.0.0 - '@inquirer/type@3.0.0(@types/node@22.9.3)': + '@inquirer/type@3.0.0(@types/node@22.10.1)': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@isaacs/cliui@8.0.2': dependencies: @@ -6400,7 +6406,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -6413,14 +6419,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.9.3) + jest-config: 29.7.0(@types/node@22.10.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -6449,7 +6455,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -6467,7 +6473,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.9.3 + '@types/node': 22.10.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -6489,7 +6495,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -6559,7 +6565,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -6866,11 +6872,11 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.60.6': {} + '@tanstack/query-core@5.62.0': {} - '@tanstack/react-query@5.61.3(react@18.3.1)': + '@tanstack/react-query@5.62.0(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.60.6 + '@tanstack/query-core': 5.62.0 react: 18.3.1 '@testing-library/dom@10.4.0': @@ -6925,7 +6931,7 @@ snapshots: '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/cookie@0.6.0': {} @@ -6933,7 +6939,7 @@ snapshots: '@types/cross-spawn@6.0.6': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/debug@4.1.12': dependencies: @@ -6943,7 +6949,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/istanbul-lib-coverage@2.0.6': {} @@ -6959,7 +6965,7 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 @@ -6977,7 +6983,11 @@ snapshots: '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 + + '@types/node@22.10.1': + dependencies: + undici-types: 6.20.0 '@types/node@22.9.3': dependencies: @@ -7018,7 +7028,7 @@ snapshots: '@types/ws@8.5.13': dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 '@types/yargs-parser@21.0.3': {} @@ -7026,15 +7036,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.15.0 - '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.15.0 - eslint: 9.15.0(jiti@1.21.6) + '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.16.0 + '@typescript-eslint/type-utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.16.0 + eslint: 9.16.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -7044,42 +7054,42 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: - '@typescript-eslint/scope-manager': 8.15.0 - '@typescript-eslint/types': 8.15.0 - '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.15.0 + '@typescript-eslint/scope-manager': 8.16.0 + '@typescript-eslint/types': 8.16.0 + '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.16.0 debug: 4.3.7(supports-color@9.4.0) - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.15.0': + '@typescript-eslint/scope-manager@8.16.0': dependencies: - '@typescript-eslint/types': 8.15.0 - '@typescript-eslint/visitor-keys': 8.15.0 + '@typescript-eslint/types': 8.16.0 + '@typescript-eslint/visitor-keys': 8.16.0 - '@typescript-eslint/type-utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/type-utils@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) + '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) debug: 4.3.7(supports-color@9.4.0) - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) ts-api-utils: 1.3.0(typescript@5.7.2) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.15.0': {} + '@typescript-eslint/types@8.16.0': {} - '@typescript-eslint/typescript-estree@8.15.0(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.16.0(typescript@5.7.2)': dependencies: - '@typescript-eslint/types': 8.15.0 - '@typescript-eslint/visitor-keys': 8.15.0 + '@typescript-eslint/types': 8.16.0 + '@typescript-eslint/visitor-keys': 8.16.0 debug: 4.3.7(supports-color@9.4.0) fast-glob: 3.3.2 is-glob: 4.0.3 @@ -7091,34 +7101,34 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) - '@typescript-eslint/scope-manager': 8.15.0 - '@typescript-eslint/types': 8.15.0 - '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.7.2) - eslint: 9.15.0(jiti@1.21.6) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.16.0 + '@typescript-eslint/types': 8.16.0 + '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) + eslint: 9.16.0(jiti@1.21.6) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.15.0': + '@typescript-eslint/visitor-keys@8.16.0': dependencies: - '@typescript-eslint/types': 8.15.0 + '@typescript-eslint/types': 8.16.0 eslint-visitor-keys: 4.2.0 - '@vitest/browser@2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5)': + '@vitest/browser@2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) - '@vitest/mocker': 2.1.5(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2))(vite@5.4.9(@types/node@22.9.3)) - '@vitest/utils': 2.1.5 + '@vitest/mocker': 2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/utils': 2.1.6 magic-string: 0.30.12 - msw: 2.6.5(@types/node@22.9.3)(typescript@5.7.2) + msw: 2.6.5(@types/node@22.10.1)(typescript@5.7.2) sirv: 3.0.0 tinyrainbow: 1.2.0 - vitest: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + vitest: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) ws: 8.18.0(bufferutil@4.0.8) optionalDependencies: playwright: 1.49.0 @@ -7129,7 +7139,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-istanbul@2.1.5(vitest@2.1.5)': + '@vitest/coverage-istanbul@2.1.6(vitest@2.1.6)': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.3.7(supports-color@9.4.0) @@ -7141,60 +7151,64 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)) + vitest: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.5': + '@vitest/expect@2.1.6': dependencies: - '@vitest/spy': 2.1.5 - '@vitest/utils': 2.1.5 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.5(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.9.3))': + '@vitest/mocker@2.1.6(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': dependencies: - '@vitest/spy': 2.1.5 + '@vitest/spy': 2.1.6 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: msw: 2.4.3(typescript@5.7.2) - vite: 5.4.9(@types/node@22.9.3) + vite: 5.4.9(@types/node@22.10.1) - '@vitest/mocker@2.1.5(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2))(vite@5.4.9(@types/node@22.9.3))': + '@vitest/mocker@2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': dependencies: - '@vitest/spy': 2.1.5 + '@vitest/spy': 2.1.6 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - msw: 2.6.5(@types/node@22.9.3)(typescript@5.7.2) - vite: 5.4.9(@types/node@22.9.3) + msw: 2.6.5(@types/node@22.10.1)(typescript@5.7.2) + vite: 5.4.9(@types/node@22.10.1) - '@vitest/pretty-format@2.1.5': + '@vitest/pretty-format@2.1.6': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.5': + '@vitest/runner@2.1.6': dependencies: - '@vitest/utils': 2.1.5 + '@vitest/utils': 2.1.6 pathe: 1.1.2 - '@vitest/snapshot@2.1.5': + '@vitest/snapshot@2.1.6': dependencies: - '@vitest/pretty-format': 2.1.5 + '@vitest/pretty-format': 2.1.6 magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.5': + '@vitest/spy@2.1.6': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.5': + '@vitest/utils@2.1.6': dependencies: - '@vitest/pretty-format': 2.1.5 + '@vitest/pretty-format': 2.1.6 loupe: 3.1.2 tinyrainbow: 1.2.0 + '@whatwg-node/disposablestack@0.0.5': + dependencies: + tslib: 2.8.1 + '@whatwg-node/fetch@0.10.1': dependencies: '@whatwg-node/node-fetch': 0.7.1 @@ -7207,8 +7221,9 @@ snapshots: fast-querystring: 1.1.2 tslib: 2.8.1 - '@whatwg-node/server@0.9.56': + '@whatwg-node/server@0.9.60': dependencies: + '@whatwg-node/disposablestack': 0.0.5 '@whatwg-node/fetch': 0.10.1 tslib: 2.8.1 @@ -7227,7 +7242,7 @@ snapshots: acorn-globals@7.0.1: dependencies: - acorn: 8.11.3 + acorn: 8.14.0 acorn-walk: 8.3.2 acorn-jsx@5.3.2(acorn@8.14.0): @@ -7405,7 +7420,7 @@ snapshots: axe-core@4.10.2: {} - axios@1.7.7: + axios@1.7.8: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -7703,9 +7718,9 @@ snapshots: cookiejar@2.1.4: {} - cosmiconfig-typescript-loader@5.1.0(@types/node@22.9.3)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): + cosmiconfig-typescript-loader@5.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2): dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 cosmiconfig: 9.0.0(typescript@5.7.2) jiti: 1.21.6 typescript: 5.7.2 @@ -7719,13 +7734,13 @@ snapshots: optionalDependencies: typescript: 5.7.2 - create-jest@29.7.0(@types/node@22.9.3): + create-jest@29.7.0(@types/node@22.10.1): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.9.3) + jest-config: 29.7.0(@types/node@22.10.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -7740,12 +7755,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - cross-spawn@7.0.5: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -8113,51 +8122,51 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.0 - eslint: 9.15.0(jiti@1.21.6) - eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + eslint: 9.16.0(jiti@1.21.6) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-bun-module: 1.1.0 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) - eslint: 9.15.0(jiti@1.21.6) + '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + eslint: 9.16.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)): + eslint-module-utils@2.8.1(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) - eslint: 9.15.0(jiti@1.21.6) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.15.0(jiti@1.21.6)) + '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + eslint: 9.16.0(jiti@1.21.6) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-plugin-import-helpers@2.0.1(eslint@9.15.0(jiti@1.21.6)): + eslint-plugin-import-helpers@2.0.1(eslint@9.16.0(jiti@1.21.6)): dependencies: - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -8166,9 +8175,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.15.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8180,13 +8189,13 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.15.0(jiti@1.21.6)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0(jiti@1.21.6)): dependencies: aria-query: 5.3.2 array-includes: 3.1.8 @@ -8196,7 +8205,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -8205,11 +8214,11 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.0.0(eslint@9.15.0(jiti@1.21.6)): + eslint-plugin-react-hooks@5.0.0(eslint@9.16.0(jiti@1.21.6)): dependencies: - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) - eslint-plugin-react@7.37.2(eslint@9.15.0(jiti@1.21.6)): + eslint-plugin-react@7.37.2(eslint@9.16.0(jiti@1.21.6)): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -8217,7 +8226,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.0 - eslint: 9.15.0(jiti@1.21.6) + eslint: 9.16.0(jiti@1.21.6) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -8240,14 +8249,14 @@ snapshots: eslint-visitor-keys@4.2.0: {} - eslint@9.15.0(jiti@1.21.6): + eslint@9.16.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.0 '@eslint/core': 0.9.0 '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.15.0 + '@eslint/js': 9.16.0 '@eslint/plugin-kit': 0.2.3 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -8256,7 +8265,7 @@ snapshots: '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 debug: 4.3.7(supports-color@9.4.0) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 @@ -8935,7 +8944,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -8955,16 +8964,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.9.3): + jest-cli@29.7.0(@types/node@22.10.1): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.9.3) + create-jest: 29.7.0(@types/node@22.10.1) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.9.3) + jest-config: 29.7.0(@types/node@22.10.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -8974,7 +8983,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.9.3): + jest-config@29.7.0(@types/node@22.10.1): dependencies: '@babel/core': 7.24.9 '@jest/test-sequencer': 29.7.0 @@ -8999,7 +9008,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -9043,7 +9052,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -9053,7 +9062,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.9.3 + '@types/node': 22.10.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -9092,7 +9101,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -9127,7 +9136,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -9155,7 +9164,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -9201,7 +9210,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -9220,7 +9229,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.9.3 + '@types/node': 22.10.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -9229,17 +9238,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.9.3): + jest@29.7.0(@types/node@22.10.1): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.9.3) + jest-cli: 29.7.0(@types/node@22.10.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -9689,12 +9698,12 @@ snapshots: optionalDependencies: typescript: 5.7.2 - msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2): + msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.0.1(@types/node@22.9.3) + '@inquirer/confirm': 5.0.1(@types/node@22.10.1) '@mswjs/interceptors': 0.37.0 '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 @@ -10050,22 +10059,22 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-jsdoc@1.3.0(prettier@3.3.3): + prettier-plugin-jsdoc@1.3.0(prettier@3.4.1): dependencies: binary-searching: 2.0.5 comment-parser: 1.4.1 mdast-util-from-markdown: 2.0.1 - prettier: 3.3.3 + prettier: 3.4.1 transitivePeerDependencies: - supports-color - prettier-plugin-sh@0.14.0(prettier@3.3.3): + prettier-plugin-sh@0.14.0(prettier@3.4.1): dependencies: mvdan-sh: 0.10.1 - prettier: 3.3.3 + prettier: 3.4.1 sh-syntax: 0.4.2 - prettier@3.3.3: {} + prettier@3.4.1: {} pretty-format@27.5.1: dependencies: @@ -10762,32 +10771,32 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo-darwin-64@2.3.1: + turbo-darwin-64@2.3.3: optional: true - turbo-darwin-arm64@2.3.1: + turbo-darwin-arm64@2.3.3: optional: true - turbo-linux-64@2.3.1: + turbo-linux-64@2.3.3: optional: true - turbo-linux-arm64@2.3.1: + turbo-linux-arm64@2.3.3: optional: true - turbo-windows-64@2.3.1: + turbo-windows-64@2.3.3: optional: true - turbo-windows-arm64@2.3.1: + turbo-windows-arm64@2.3.3: optional: true - turbo@2.3.1: + turbo@2.3.3: optionalDependencies: - turbo-darwin-64: 2.3.1 - turbo-darwin-arm64: 2.3.1 - turbo-linux-64: 2.3.1 - turbo-linux-arm64: 2.3.1 - turbo-windows-64: 2.3.1 - turbo-windows-arm64: 2.3.1 + turbo-darwin-64: 2.3.3 + turbo-darwin-arm64: 2.3.3 + turbo-linux-64: 2.3.3 + turbo-linux-arm64: 2.3.3 + turbo-windows-64: 2.3.3 + turbo-windows-arm64: 2.3.3 type-check@0.4.0: dependencies: @@ -10842,6 +10851,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@6.20.0: {} + unicorn-magic@0.1.0: {} unicorn-magic@0.3.0: {} @@ -10879,13 +10890,13 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - vite-node@2.1.5(@types/node@22.9.3): + vite-node@2.1.6(@types/node@22.10.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@9.4.0) es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.9(@types/node@22.9.3) + vite: 5.4.9(@types/node@22.10.1) transitivePeerDependencies: - '@types/node' - less @@ -10897,24 +10908,24 @@ snapshots: - supports-color - terser - vite@5.4.9(@types/node@22.9.3): + vite@5.4.9(@types/node@22.10.1): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.24.3 optionalDependencies: - '@types/node': 22.9.3 + '@types/node': 22.10.1 fsevents: 2.3.3 - vitest@2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)): + vitest@2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)): dependencies: - '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.9.3)) - '@vitest/pretty-format': 2.1.5 - '@vitest/runner': 2.1.5 - '@vitest/snapshot': 2.1.5 - '@vitest/spy': 2.1.5 - '@vitest/utils': 2.1.5 + '@vitest/expect': 2.1.6 + '@vitest/mocker': 2.1.6(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.6 + '@vitest/runner': 2.1.6 + '@vitest/snapshot': 2.1.6 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 chai: 5.1.2 debug: 4.3.7(supports-color@9.4.0) expect-type: 1.1.0 @@ -10925,12 +10936,12 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.9(@types/node@22.9.3) - vite-node: 2.1.5(@types/node@22.9.3) + vite: 5.4.9(@types/node@22.10.1) + vite-node: 2.1.6(@types/node@22.10.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.9.3 - '@vitest/browser': 2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5) + '@types/node': 22.10.1 + '@vitest/browser': 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) jsdom: 20.0.3(bufferutil@4.0.8) transitivePeerDependencies: - less @@ -10943,15 +10954,15 @@ snapshots: - supports-color - terser - vitest@2.1.5(@types/node@22.9.3)(@vitest/browser@2.1.5)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2)): + vitest@2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)): dependencies: - '@vitest/expect': 2.1.5 - '@vitest/mocker': 2.1.5(msw@2.6.5(@types/node@22.9.3)(typescript@5.7.2))(vite@5.4.9(@types/node@22.9.3)) - '@vitest/pretty-format': 2.1.5 - '@vitest/runner': 2.1.5 - '@vitest/snapshot': 2.1.5 - '@vitest/spy': 2.1.5 - '@vitest/utils': 2.1.5 + '@vitest/expect': 2.1.6 + '@vitest/mocker': 2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.6 + '@vitest/runner': 2.1.6 + '@vitest/snapshot': 2.1.6 + '@vitest/spy': 2.1.6 + '@vitest/utils': 2.1.6 chai: 5.1.2 debug: 4.3.7(supports-color@9.4.0) expect-type: 1.1.0 @@ -10962,12 +10973,12 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.9(@types/node@22.9.3) - vite-node: 2.1.5(@types/node@22.9.3) + vite: 5.4.9(@types/node@22.10.1) + vite-node: 2.1.6(@types/node@22.10.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.9.3 - '@vitest/browser': 2.1.5(@types/node@22.9.3)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.9.3))(vitest@2.1.5) + '@types/node': 22.10.1 + '@vitest/browser': 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) jsdom: 20.0.3(bufferutil@4.0.8) transitivePeerDependencies: - less From 681473dcb2709084cd88c59b4bbc57040d645e7c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:32:33 +0000 Subject: [PATCH 08/16] chore(release): zimic@0.11.0-canary.3 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e9a0e1e64..556ff8d9a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.2", + "version": "0.11.0-canary.3", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 6944f1a05..e35d88b34 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.2", + "version": "0.11.0-canary.3", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 7ec0a8f7eeb7dc0b4e5f915365cbe303cf341c68 Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Fri, 6 Dec 2024 17:00:37 -0300 Subject: [PATCH 09/16] fix: array search params log (#499) (#501) Closes #499. --- .../tests/typegen/typegen.node.test.ts | 4 +- .../cli/server/__tests__/server.node.test.ts | 8 +- .../zimic/src/http/headers/HttpHeaders.ts | 23 +++ .../headers/__tests__/HttpHeaders.test.ts | 52 +++++++ .../src/http/searchParams/HttpSearchParams.ts | 47 +++++- .../__tests__/HttpSearchParams.test.ts | 88 ++++++++++- .../interceptor/__tests__/shared/handlers.ts | 2 +- .../shared/unhandledRequests.factories.ts | 12 +- .../shared/unhandledRequests.logging.ts | 139 ++++++++++++++++-- .../interceptor/__tests__/shared/utils.ts | 44 +++--- .../HttpInterceptorWorker.ts | 12 +- packages/zimic/src/utils/console.ts | 6 +- 12 files changed, 377 insertions(+), 60 deletions(-) diff --git a/apps/zimic-test-client/tests/typegen/typegen.node.test.ts b/apps/zimic-test-client/tests/typegen/typegen.node.test.ts index af2a6ef6d..661dd6459 100644 --- a/apps/zimic-test-client/tests/typegen/typegen.node.test.ts +++ b/apps/zimic-test-client/tests/typegen/typegen.node.test.ts @@ -20,7 +20,7 @@ async function normalizeStripeTypes(generatedFilePath: string) { await filesystem.writeFile(generatedFilePath, normalizedOutput); } -describe('Typegen', { timeout: 45 * 1000 }, () => { +describe('Typegen', () => { const generatedDirectory = path.join(__dirname, 'generated'); const tsconfigFilePath = path.join(generatedDirectory, 'tsconfig.json'); const eslintConfigFilePath = path.join(generatedDirectory, 'eslint.config.mjs'); @@ -48,7 +48,7 @@ describe('Typegen', { timeout: 45 * 1000 }, () => { await Promise.all([typesCheckPromise, lintPromise]); }, 60 * 1000); - describe('OpenAPI', () => { + describe('OpenAPI', { timeout: 60 * 1000 }, () => { it.concurrent.each([ { input: diff --git a/packages/zimic/src/cli/server/__tests__/server.node.test.ts b/packages/zimic/src/cli/server/__tests__/server.node.test.ts index ee39cf0a4..66a81e04e 100644 --- a/packages/zimic/src/cli/server/__tests__/server.node.test.ts +++ b/packages/zimic/src/cli/server/__tests__/server.node.test.ts @@ -617,9 +617,9 @@ describe('CLI (server)', async () => { const errorMessage = spies.error.mock.calls[0].join(' '); await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform: 'node', request, + platform: 'node', + type: 'reject', }); }); }, @@ -715,9 +715,9 @@ describe('CLI (server)', async () => { const errorMessage = spies.error.mock.calls[1].join(' '); await verifyUnhandledRequestMessage(errorMessage, { - type: 'error', - platform: 'node', request, + platform: 'node', + type: 'reject', }); } finally { webSocketServerRequestSpy.mockRestore(); diff --git a/packages/zimic/src/http/headers/HttpHeaders.ts b/packages/zimic/src/http/headers/HttpHeaders.ts index 58c3a4b2d..35af022c2 100644 --- a/packages/zimic/src/http/headers/HttpHeaders.ts +++ b/packages/zimic/src/http/headers/HttpHeaders.ts @@ -183,6 +183,29 @@ class HttpHeaders extends return true; } + /** + * Converts this headers object to a plain object. This method is useful for serialization and debugging purposes. + * + * @example + * const headers = new HttpHeaders({ + * accept: 'application/json', + * 'content-type': 'application/json', + * }); + * const object = headers.toObject(); + * console.log(object); // { accept: 'application/json', 'content-type': 'application/json' } + * + * @returns A plain object representation of this headers object. + */ + toObject(): Schema { + const object = {} as Schema; + + for (const [key, value] of this.entries()) { + object[key] = value; + } + + return object; + } + private splitHeaderValues(value: string) { return value .split(',') diff --git a/packages/zimic/src/http/headers/__tests__/HttpHeaders.test.ts b/packages/zimic/src/http/headers/__tests__/HttpHeaders.test.ts index b24824c3f..e494cbd36 100644 --- a/packages/zimic/src/http/headers/__tests__/HttpHeaders.test.ts +++ b/packages/zimic/src/http/headers/__tests__/HttpHeaders.test.ts @@ -1,5 +1,9 @@ import { describe, expect, expectTypeOf, it } from 'vitest'; +import { HttpSchema } from '@/http/types/schema'; +import { formatObjectToLog } from '@/utils/console'; +import { isClientSide } from '@/utils/environment'; + import HttpHeaders from '../HttpHeaders'; import { HttpHeadersSerialized } from '../types'; @@ -58,6 +62,28 @@ describe('HttpHeaders', () => { expect(contentTypeHeader).toBe(null); }); + it('should support being converted to an object', () => { + type Schema = HttpSchema.Headers<{ + accept: string; + other: string; + 'content-type'?: `application/${string}`; + }>; + + const headers = new HttpHeaders({ + accept: '*/*', + other: 'value, other', + 'content-type': undefined, + }); + + const object = headers.toObject(); + expectTypeOf(object).toEqualTypeOf(); + + expect(object).toEqual({ + accept: '*/*', + other: 'value, other', + }); + }); + it('should support being created from another HttpHeaders', () => { const otherHeaders = new HttpHeaders<{ accept?: string; @@ -561,4 +587,30 @@ describe('HttpHeaders', () => { expectTypeOf>>().toEqualTypeOf(); }); }); + + describe('Formatting', () => { + it('should be correctly formatted to log', async () => { + const headers = new HttpHeaders<{ + accept: string; + other: string; + 'content-type'?: `application/${string}`; + }>({ + accept: '*/*', + other: 'value, other', + 'content-type': undefined, + }); + + const formattedHeaders = String( + await formatObjectToLog(headers.toObject(), { + colors: false, + }), + ); + + if (isClientSide()) { + expect(formattedHeaders).toBe('[object Object]'); + } else { + expect(formattedHeaders).toBe("{ accept: '*/*', other: 'value, other' }"); + } + }); + }); }); diff --git a/packages/zimic/src/http/searchParams/HttpSearchParams.ts b/packages/zimic/src/http/searchParams/HttpSearchParams.ts index df2cc2dd5..1a547ae7e 100644 --- a/packages/zimic/src/http/searchParams/HttpSearchParams.ts +++ b/packages/zimic/src/http/searchParams/HttpSearchParams.ts @@ -162,8 +162,8 @@ class HttpSearchParams]; + + for (const [key, value] of this.entries()) { + if (key in object) { + const existingValue = object[key]; + + if (Array.isArray(existingValue)) { + existingValue.push(value as SchemaValue); + } else { + object[key] = [existingValue, value] as SchemaValue; + } + } else { + object[key] = value as SchemaValue; + } + } + + return object; + } } export default HttpSearchParams; diff --git a/packages/zimic/src/http/searchParams/__tests__/HttpSearchParams.test.ts b/packages/zimic/src/http/searchParams/__tests__/HttpSearchParams.test.ts index f20cb8ff6..b35e5dc3b 100644 --- a/packages/zimic/src/http/searchParams/__tests__/HttpSearchParams.test.ts +++ b/packages/zimic/src/http/searchParams/__tests__/HttpSearchParams.test.ts @@ -1,5 +1,9 @@ import { describe, expect, expectTypeOf, it } from 'vitest'; +import { HttpSchema } from '@/http/types/schema'; +import { formatObjectToLog } from '@/utils/console'; +import { isClientSide } from '@/utils/environment'; + import HttpSearchParams from '../HttpSearchParams'; import { HttpSearchParamsSerialized } from '../types'; @@ -57,22 +61,68 @@ describe('HttpSearchParams', () => { }); const searchParams = new HttpSearchParams<{ - names?: string[]; + oneName: string[]; + twoNames: string[]; + threeNames: string[]; page?: `${number}`; + other?: string; }>({ - names: ['User1', 'User2'], + oneName: ['User1'], + twoNames: ['User1', 'User2'], + threeNames: ['User1', 'User2', 'User3'], page: '1', + other: undefined, }); - expect(searchParams.size).toBe(3); + expect(searchParams.size).toBe(7); - const names = searchParams.getAll('names'); - expectTypeOf(names).toEqualTypeOf(); - expect(names).toEqual(['User1', 'User2']); + const oneName = searchParams.getAll('oneName'); + expectTypeOf(oneName).toEqualTypeOf(); + expect(oneName).toEqual(['User1']); + + const twoNames = searchParams.getAll('twoNames'); + expectTypeOf(twoNames).toEqualTypeOf(); + expect(twoNames).toEqual(['User1', 'User2']); + + const threeNames = searchParams.getAll('threeNames'); + expectTypeOf(threeNames).toEqualTypeOf(); + expect(threeNames).toEqual(['User1', 'User2', 'User3']); const page = searchParams.get('page'); expectTypeOf(page).toEqualTypeOf<`${number}` | null>(); expect(page).toBe('1'); + + const other = searchParams.get('other'); + expectTypeOf(other).toEqualTypeOf(); + expect(other).toBe(null); + }); + + it('should support being converted to an object', () => { + type Schema = HttpSchema.SearchParams<{ + name: string[]; + names: string[]; + threeNames: string[]; + page?: `${number}`; + other?: string; + }>; + + const searchParams = new HttpSearchParams({ + name: ['User1'], + names: ['User1', 'User2'], + threeNames: ['User1', 'User2', 'User3'], + page: '1', + other: undefined, + }); + + const object = searchParams.toObject(); + expectTypeOf(object).toEqualTypeOf(); + + expect(object).toEqual({ + name: 'User1', + names: ['User1', 'User2'], + threeNames: ['User1', 'User2', 'User3'], + page: '1', + }); }); it('should support being created from another HttpSearchParams', () => { @@ -559,4 +609,30 @@ describe('HttpSearchParams', () => { expectTypeOf>>().toEqualTypeOf(); }); }); + + describe('Formatting', () => { + it('should be correctly formatted to log', async () => { + const searchParams = new HttpSearchParams({ + oneName: ['User1'], + twoNames: ['User1', 'User2'], + threeNames: ['User1', 'User2', 'User3'], + page: '1', + other: undefined, + }); + + const formattedSearchParams = String( + await formatObjectToLog(searchParams.toObject(), { + colors: false, + }), + ); + + if (isClientSide()) { + expect(formattedSearchParams).toBe('[object Object]'); + } else { + expect(formattedSearchParams).toBe( + "{ oneName: 'User1', page: '1', threeNames: [ 'User1', 'User2', 'User3' ], twoNames: [ 'User1', 'User2' ] }", + ); + } + }); + }); }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts index edf375313..f48639553 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts @@ -186,7 +186,7 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH expect(spies.error.mock.calls[0]).toEqual([error]); const errorMessage = spies.error.mock.calls[1].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); }); requests = await promiseIfRemote(handler.requests(), interceptor); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts index b86967aa2..10170eab4 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts @@ -141,7 +141,7 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); }); }, ); @@ -227,7 +227,7 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); }); }, ); @@ -317,9 +317,9 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(spies.error).toHaveBeenNthCalledWith(1, error); await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, request, + platform, + type: 'reject', }); }); }, @@ -415,9 +415,9 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( expect(spies.error).toHaveBeenNthCalledWith(1, error); await verifyUnhandledRequestMessage(spies.error.mock.calls[1].join(' '), { - type: 'error', - platform, request, + platform, + type: 'reject', }); }); }, diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts index 7d38aa52b..fda38e0e5 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts @@ -189,7 +189,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(0); const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + await verifyUnhandledRequestMessage(warnMessage, { request, platform, type: 'bypass' }); }); }, ); @@ -254,7 +254,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(0); const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request: requestClone }); + await verifyUnhandledRequestMessage(warnMessage, { request: requestClone, platform, type: 'bypass' }); }); }, ); @@ -310,7 +310,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(0); const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + await verifyUnhandledRequestMessage(warnMessage, { request, platform, type: 'bypass' }); }); }, ); @@ -361,7 +361,64 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(0); const warnMessage = spies.warn.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(warnMessage, { type: 'warn', platform, request }); + await verifyUnhandledRequestMessage(warnMessage, { request, platform, type: 'bypass' }); + }); + }, + ); + }); + + it(`should show a warning when logging is enabled and ${method} requests with array search params are unhandled and bypassed`, async () => { + if (overrideDefault) { + localOnUnhandledRequest.action = 'bypass'; + } + + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'bypass', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const searchParams = new HttpSearchParams({ + singleValue: 'value', + arrayWithOneValue: ['value-1'], + arrayWithMultipleValues: ['value-1', 'value-2'], + }); + + const request = new Request(joinURL(baseURL, `/users/other?${searchParams}`), { method }); + const responsePromise = fetch(request); + await expectBypassedResponse(responsePromise); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + + expect(spies.warn).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + expect(spies.error).toHaveBeenCalledTimes(0); + + const warnMessage = spies.warn.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(warnMessage, { request, platform, type: 'bypass' }); }); }, ); @@ -419,7 +476,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); }); }, ); @@ -479,7 +536,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request: requestClone }); + await verifyUnhandledRequestMessage(errorMessage, { request: requestClone, platform, type: 'reject' }); }); }, ); @@ -539,7 +596,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); }); }, ); @@ -593,7 +650,71 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); + } + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + }); + }, + ); + }); + + it(`should show an error when logging is enabled and ${method} requests with array search params are unhandled and rejected`, async () => { + await usingHttpInterceptor( + { + ...interceptorOptions, + type, + onUnhandledRequest: overrideDefault ? undefined : { action: 'reject', log: true }, + }, + async (interceptor) => { + const handler = await promiseIfRemote( + interceptor[lowerMethod]('/users').respond({ status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS }), + interceptor, + ); + expect(handler).toBeInstanceOf(Handler); + + let requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(0); + + await usingIgnoredConsole(['warn', 'error'], async (spies) => { + const response = await fetch(joinURL(baseURL, '/users'), { method }); + expect(response.status).toBe(200); + + requests = await promiseIfRemote(handler.requests(), interceptor); + expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + const interceptedRequest = requests[numberOfRequestsIncludingPreflight - 1]; + expectTypeOf(interceptedRequest.body).toEqualTypeOf(); + expect(interceptedRequest.body).toBe(null); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + + const searchParams = new HttpSearchParams({ + singleValue: 'value', + arrayWithOneValue: ['value-1'], + arrayWithMultipleValues: ['value-1', 'value-2'], + }); + + const request = new Request(joinURL(baseURL, `/users/other?${searchParams}`), { + method, + headers: { 'x-id': crypto.randomUUID() }, // Ensure the request is unique. + }); + const responsePromise = fetch(request); + + if (overridesPreflightResponse) { + await expectPreflightResponse(responsePromise); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(0); + } else { + await expectFetchError(responsePromise); + + expect(spies.warn).toHaveBeenCalledTimes(0); + expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); + + const errorMessage = spies.error.mock.calls[0].join(' '); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); } requests = await promiseIfRemote(handler.requests(), interceptor); @@ -848,7 +969,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.error).toHaveBeenCalledTimes(numberOfRequestsIncludingPreflight); const errorMessage = spies.error.mock.calls[0].join(' '); - await verifyUnhandledRequestMessage(errorMessage, { type: 'error', platform, request }); + await verifyUnhandledRequestMessage(errorMessage, { request, platform, type: 'reject' }); spies.warn.mockClear(); spies.error.mockClear(); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts index 57c4766a0..d204b8413 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/utils.ts @@ -28,50 +28,46 @@ export interface RuntimeSharedHttpInterceptorTestsOptions { export async function verifyUnhandledRequestMessage( message: string, options: { - type: 'warn' | 'error'; - platform: HttpInterceptorPlatform; request: HttpRequest; + platform: HttpInterceptorPlatform; + type: 'bypass' | 'reject'; }, ) { const { type, platform, request: rawRequest } = options; const request = await HttpInterceptorWorker.parseRawRequest(rawRequest); + const body: unknown = request.body; - expect(message).toMatch(/.*\[zimic\].* /); - expect(message).toContain(type === 'warn' ? 'Warning:' : 'Error:'); - expect(message).toContain(type === 'warn' ? 'bypassed' : 'rejected'); - expect(message).toContain(`${request.method} ${request.url}`); + const firstLineRegex = new RegExp( + `^[^\\s]*\\[zimic\\][^\\s]* ${type === 'bypass' ? 'Warning:' : 'Error:'} ` + + `Request was not handled and was [^\\s]*${type === 'bypass' ? 'bypassed' : 'rejected'}[^\\s]*\n`, + ); + expect(message).toMatch(firstLineRegex); - expect(message).toContain(platform === 'node' ? 'Headers: ' : 'Headers: [object Object]'); + expect(message).toContain(`${request.method} ${request.url}`); if (platform === 'node') { const headersLine = /Headers: (?[^\n]*)\n/.exec(message)!; expect(headersLine).not.toBe(null); - const formattedHeaders = (await formatObjectToLog(Object.fromEntries(request.headers))) as string; + const formattedHeaders = (await formatObjectToLog(request.headers.toObject())) as string; const formattedHeadersIgnoringWrapperBrackets = formattedHeaders.slice(1, -1); for (const headerKeyValuePair of formattedHeadersIgnoringWrapperBrackets.split(', ')) { expect(headersLine.groups!.headers).toContain(headerKeyValuePair.trim()); } - } - expect(message).toContain( - platform === 'node' - ? `Search params: ${await formatObjectToLog(Object.fromEntries(request.searchParams))}` - : 'Search params: [object Object]', - ); - - const body: unknown = request.body; - - if (body === null) { - expect(message).toContain(platform === 'node' ? `Body: ${await formatObjectToLog(body)}` : 'Body: '); + expect(message).toContain(`Search params: ${await formatObjectToLog(request.searchParams.toObject())}`); + expect(message).toContain(`Body: ${await formatObjectToLog(body)}`); } else { - expect(message).toContain( - platform === 'node' || typeof body !== 'object' - ? `Body: ${await formatObjectToLog(body)}` - : 'Body: [object Object]', - ); + expect(message).toContain('Headers: [object Object]'); + expect(message).toContain('Search params: [object Object]'); + + if (body === null) { + expect(message).toContain('Body: '); + } else { + expect(message).toContain('Body: [object Object]'); + } } } diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts index 3eb773fec..ac143a5e0 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts @@ -484,17 +484,23 @@ abstract class HttpInterceptorWorker { static async logUnhandledRequestWarning(rawRequest: HttpRequest, action: UnhandledRequestStrategy.Action) { const request = await this.parseRawRequest(rawRequest); + const [formattedHeaders, formattedSearchParams, formattedBody] = await Promise.all([ + formatObjectToLog(request.headers.toObject()), + formatObjectToLog(request.searchParams.toObject()), + formatObjectToLog(request.body), + ]); + logWithPrefix( [ `${action === 'bypass' ? 'Warning:' : 'Error:'} Request was not handled and was ` + `${action === 'bypass' ? chalk.yellow('bypassed') : chalk.red('rejected')}.\n\n `, `${request.method} ${request.url}`, '\n Headers:', - await formatObjectToLog(Object.fromEntries(request.headers)), + formattedHeaders, '\n Search params:', - await formatObjectToLog(Object.fromEntries(request.searchParams)), + formattedSearchParams, '\n Body:', - await formatObjectToLog(request.body), + formattedBody, '\n\nLearn more: https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#unhandled-requests', ], { method: action === 'bypass' ? 'warn' : 'error' }, diff --git a/packages/zimic/src/utils/console.ts b/packages/zimic/src/utils/console.ts index 4baf76b10..b2383e88e 100644 --- a/packages/zimic/src/utils/console.ts +++ b/packages/zimic/src/utils/console.ts @@ -5,15 +5,17 @@ import { createCachedDynamicImport } from './imports'; const importUtil = createCachedDynamicImport(() => import('util')); -export async function formatObjectToLog(value: unknown) { +export async function formatObjectToLog(value: unknown, options: { colors?: boolean } = {}) { if (isClientSide()) { return value; } + const { colors = true } = options; + const util = await importUtil(); return util.inspect(value, { - colors: true, + colors, compact: true, depth: Infinity, maxArrayLength: Infinity, From ebe82e7b65306c91dec6cc5d8878345638467099 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:48:41 +0000 Subject: [PATCH 10/16] chore(release): zimic@0.11.0-canary.4 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 556ff8d9a..2ff0593dc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.3", + "version": "0.11.0-canary.4", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index e35d88b34..e7402bb94 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.3", + "version": "0.11.0-canary.4", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 3274fe5f842dbd55b56c74190915726962f5a609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 12:26:23 +0000 Subject: [PATCH 11/16] chore(root): bump the npm group with 21 updates (#503) Bumps the npm group with 21 updates: | Package | From | To | | --- | --- | --- | | [prettier](https://github.com/prettier/prettier) | `3.4.1` | `3.4.2` | | [@vitest/browser](https://github.com/vitest-dev/vitest/tree/HEAD/packages/browser) | `2.1.6` | `2.1.8` | | [@vitest/coverage-istanbul](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-istanbul) | `2.1.6` | `2.1.8` | | [axios](https://github.com/axios/axios) | `1.7.8` | `1.7.9` | | [execa](https://github.com/sindresorhus/execa) | `9.5.1` | `9.5.2` | | [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `2.1.6` | `2.1.8` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.16.0` | `8.17.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.16.0` | `8.17.0` | | [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) | `3.6.3` | `3.7.0` | | [@eslint/compat](https://github.com/eslint/rewrite) | `1.2.3` | `1.2.4` | | [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) | `5.0.0` | `5.1.0` | | [openapi-typescript](https://github.com/openapi-ts/openapi-typescript/tree/HEAD/packages/openapi-typescript) | `7.4.3` | `7.4.4` | | [@swc/core](https://github.com/swc-project/swc) | `1.9.3` | `1.10.0` | | [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.62.0` | `5.62.3` | | [next](https://github.com/vercel/next.js) | `15.0.3` | `15.0.4` | | [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `18.3.1` | `19.0.0` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `18.3.12` | `19.0.1` | | [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `18.3.1` | `19.0.0` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `18.3.1` | `19.0.1` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss) | `3.4.15` | `3.4.16` | | [@vitest/spy](https://github.com/vitest-dev/vitest/tree/HEAD/packages/spy) | `2.1.6` | `2.1.8` | Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Diego Aquino --- apps/zimic-test-client/package.json | 10 +- examples/with-jest-jsdom/package.json | 2 +- examples/with-jest-node/package.json | 4 +- examples/with-next-js-app/package.json | 16 +- examples/with-next-js-pages/package.json | 14 +- examples/with-openapi-typegen/package.json | 6 +- examples/with-playwright/package.json | 14 +- examples/with-vitest-browser/package.json | 4 +- examples/with-vitest-jsdom/package.json | 4 +- examples/with-vitest-node/package.json | 6 +- package.json | 2 +- packages/eslint-config-node/package.json | 2 +- packages/eslint-config-react/package.json | 2 +- packages/eslint-config/package.json | 6 +- packages/zimic/package.json | 12 +- pnpm-lock.yaml | 864 ++++++++++----------- 16 files changed, 450 insertions(+), 518 deletions(-) diff --git a/apps/zimic-test-client/package.json b/apps/zimic-test-client/package.json index f2c7aa19f..890e74c92 100644 --- a/apps/zimic-test-client/package.json +++ b/apps/zimic-test-client/package.json @@ -17,21 +17,21 @@ }, "devDependencies": { "@types/superagent": "^8.1.9", - "@vitest/browser": "2.1.6", - "@vitest/coverage-istanbul": "2.1.6", + "@vitest/browser": "^2.1.8", + "@vitest/coverage-istanbul": "^2.1.8", "@zimic/eslint-config-node": "workspace:*", "@zimic/lint-staged-config": "workspace:*", "@zimic/tsconfig": "workspace:*", - "axios": "^1.7.8", + "axios": "^1.7.9", "concurrently": "^9.1.0", "dotenv-cli": "^7.4.4", "eslint": "^9.16.0", - "execa": "^9.5.1", + "execa": "^9.5.2", "node-fetch": "^3.3.2", "playwright": "^1.49.0", "superagent": "^10.1.1", "typescript": "^5.7.2", - "vitest": "2.1.6", + "vitest": "^2.1.8", "zimic": "workspace:*" } } diff --git a/examples/with-jest-jsdom/package.json b/examples/with-jest-jsdom/package.json index 5c123b9f3..e85ab626a 100644 --- a/examples/with-jest-jsdom/package.json +++ b/examples/with-jest-jsdom/package.json @@ -9,7 +9,7 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", - "@swc/core": "^1.9.3", + "@swc/core": "^1.10.0", "@swc/jest": "^0.2.37", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", diff --git a/examples/with-jest-node/package.json b/examples/with-jest-node/package.json index 7ff497abf..3209303ec 100644 --- a/examples/with-jest-node/package.json +++ b/examples/with-jest-node/package.json @@ -8,12 +8,12 @@ "types:check": "tsc --noEmit" }, "dependencies": { - "fastify": "5.1.0", + "fastify": "^5.1.0", "zod": "^3.23.8" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@swc/core": "^1.9.3", + "@swc/core": "^1.10.0", "@swc/jest": "^0.2.37", "@types/supertest": "^6.0.2", "dotenv-cli": "^7.4.4", diff --git a/examples/with-next-js-app/package.json b/examples/with-next-js-app/package.json index 2c696701a..8d3f4840b 100644 --- a/examples/with-next-js-app/package.json +++ b/examples/with-next-js-app/package.json @@ -16,21 +16,21 @@ "postinstall": "pnpm deps:install-playwright" }, "dependencies": { - "@tanstack/react-query": "^5.62.0", + "@tanstack/react-query": "^5.62.3", "clsx": "^2.1.1", - "next": "15.0.3", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "next": "^15.0.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "devDependencies": { "@playwright/test": "^1.49.0", "@types/node": "^22.10.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", "dotenv-cli": "^7.4.4", - "execa": "^9.5.1", + "execa": "^9.5.2", "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", + "tailwindcss": "^3.4.16", "tsx": "^4.19.2", "typescript": "^5.7.2", "zimic": "latest" diff --git a/examples/with-next-js-pages/package.json b/examples/with-next-js-pages/package.json index efe5a884b..ca2680cdb 100644 --- a/examples/with-next-js-pages/package.json +++ b/examples/with-next-js-pages/package.json @@ -12,21 +12,21 @@ "postinstall": "concurrently --names playwright,zimic 'pnpm deps:install-playwright' 'pnpm deps:init-zimic'" }, "dependencies": { - "@tanstack/react-query": "^5.62.0", + "@tanstack/react-query": "^5.62.3", "clsx": "^2.1.1", - "next": "15.0.3", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "next": "^15.0.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "devDependencies": { "@playwright/test": "^1.49.0", "@types/node": "^22.10.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", "concurrently": "^9.1.0", "dotenv-cli": "^7.4.4", "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", + "tailwindcss": "^3.4.16", "typescript": "^5.7.2", "zimic": "latest" } diff --git a/examples/with-openapi-typegen/package.json b/examples/with-openapi-typegen/package.json index 269fc20f5..c21b39883 100644 --- a/examples/with-openapi-typegen/package.json +++ b/examples/with-openapi-typegen/package.json @@ -10,16 +10,16 @@ "typegen:github-no-env": "zimic typegen openapi $GITHUB_OPENAPI_SPEC_URL --output ./src/types/github/typegen/generated.ts --service-name GitHub --filter-file ./src/types/github/typegen/filters.txt --no-comments" }, "dependencies": { - "fastify": "5.1.0", + "fastify": "^5.1.0", "zod": "^3.23.8" }, "devDependencies": { "@types/supertest": "^6.0.2", - "@vitest/spy": "^2.1.6", + "@vitest/spy": "^2.1.8", "dotenv-cli": "^7.4.4", "supertest": "^7.0.0", "typescript": "^5.7.2", - "vitest": "2.1.6", + "vitest": "^2.1.8", "zimic": "latest" } } diff --git a/examples/with-playwright/package.json b/examples/with-playwright/package.json index 981ddf768..a2a1b993a 100644 --- a/examples/with-playwright/package.json +++ b/examples/with-playwright/package.json @@ -17,19 +17,19 @@ }, "dependencies": { "clsx": "^2.1.1", - "next": "15.0.3", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "next": "^15.0.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "devDependencies": { "@playwright/test": "^1.49.0", "@types/node": "^22.10.1", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", "dotenv-cli": "^7.4.4", - "execa": "^9.5.1", + "execa": "^9.5.2", "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", + "tailwindcss": "^3.4.16", "tsx": "^4.19.2", "typescript": "^5.7.2", "zimic": "latest" diff --git a/examples/with-vitest-browser/package.json b/examples/with-vitest-browser/package.json index e792e9397..3742cb0d9 100644 --- a/examples/with-vitest-browser/package.json +++ b/examples/with-vitest-browser/package.json @@ -14,12 +14,12 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.5.2", - "@vitest/browser": "2.1.6", + "@vitest/browser": "^2.1.8", "concurrently": "^9.1.0", "dotenv-cli": "^7.4.4", "playwright": "^1.49.0", "typescript": "^5.7.2", - "vitest": "2.1.6", + "vitest": "^2.1.8", "zimic": "latest" } } diff --git a/examples/with-vitest-jsdom/package.json b/examples/with-vitest-jsdom/package.json index befcb5cd9..e5366d63f 100644 --- a/examples/with-vitest-jsdom/package.json +++ b/examples/with-vitest-jsdom/package.json @@ -11,10 +11,10 @@ "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.5.2", - "@vitest/spy": "^2.1.6", + "@vitest/spy": "^2.1.8", "dotenv-cli": "^7.4.4", "typescript": "^5.7.2", - "vitest": "2.1.6", + "vitest": "^2.1.8", "zimic": "latest" } } diff --git a/examples/with-vitest-node/package.json b/examples/with-vitest-node/package.json index 576d149e7..dea7a4071 100644 --- a/examples/with-vitest-node/package.json +++ b/examples/with-vitest-node/package.json @@ -8,16 +8,16 @@ "types:check": "tsc --noEmit" }, "dependencies": { - "fastify": "5.1.0", + "fastify": "^5.1.0", "zod": "^3.23.8" }, "devDependencies": { "@types/supertest": "^6.0.2", - "@vitest/spy": "^2.1.6", + "@vitest/spy": "^2.1.8", "dotenv-cli": "^7.4.4", "supertest": "^7.0.0", "typescript": "^5.7.2", - "vitest": "2.1.6", + "vitest": "^2.1.8", "zimic": "latest" } } diff --git a/package.json b/package.json index 2ff0593dc..cf25f325d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "concurrently": "^9.1.0", "husky": "^9.1.7", "lint-staged": "^15.2.10", - "prettier": "^3.4.1", + "prettier": "^3.4.2", "prettier-plugin-jsdoc": "^1.3.0", "prettier-plugin-sh": "^0.14.0", "turbo": "^2.3.3" diff --git a/packages/eslint-config-node/package.json b/packages/eslint-config-node/package.json index 8ba4c5890..e0c8e261d 100644 --- a/packages/eslint-config-node/package.json +++ b/packages/eslint-config-node/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "eslint-plugin-import": "^2.31.0", - "@eslint/compat": "^1.2.3", + "@eslint/compat": "^1.2.4", "@zimic/eslint-config": "workspace:*" }, "peerDependencies": { diff --git a/packages/eslint-config-react/package.json b/packages/eslint-config-react/package.json index 376103a23..16e5c4fdb 100644 --- a/packages/eslint-config-react/package.json +++ b/packages/eslint-config-react/package.json @@ -14,7 +14,7 @@ "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^5.1.0" }, "peerDependencies": { "eslint": ">=9.0.0", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 6f12ce9c3..dc5ced9bf 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -10,9 +10,9 @@ ".": "./index.js" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^8.16.0", - "@typescript-eslint/parser": "^8.16.0", - "eslint-import-resolver-typescript": "^3.6.3", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", + "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-import-helpers": "^2.0.1" }, diff --git a/packages/zimic/package.json b/packages/zimic/package.json index e7402bb94..74ae1e2c5 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -101,10 +101,10 @@ "dependencies": { "@whatwg-node/server": "0.9.60", "chalk": "4.1.2", - "execa": "9.5.1", + "execa": "9.5.2", "isomorphic-ws": "5.0.0", "msw": "2.4.3", - "openapi-typescript": "7.4.3", + "openapi-typescript": "7.4.4", "ws": "8.18.0", "yargs": "17.7.2" }, @@ -117,8 +117,8 @@ "@types/node": "^22.10.1", "@types/ws": "^8.5.13", "@types/yargs": "^17.0.33", - "@vitest/browser": "^2.1.6", - "@vitest/coverage-istanbul": "^2.1.6", + "@vitest/browser": "^2.1.8", + "@vitest/coverage-istanbul": "^2.1.8", "@zimic/eslint-config-node": "workspace:*", "@zimic/lint-staged-config": "workspace:*", "@zimic/tsconfig": "workspace:*", @@ -126,11 +126,11 @@ "eslint": "^9.16.0", "js-yaml": "^4.1.0", "playwright": "^1.49.0", - "prettier": "^3.4.1", + "prettier": "^3.4.2", "tsup": "^8.3.5", "tsx": "^4.19.2", "typescript": "^5.7.2", - "vitest": "^2.1.6" + "vitest": "^2.1.8" }, "peerDependencies": { "typescript": ">=4.8.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04a3377e9..8bf0d27ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,14 +27,14 @@ importers: specifier: ^15.2.10 version: 15.2.10 prettier: - specifier: ^3.4.1 - version: 3.4.1 + specifier: ^3.4.2 + version: 3.4.2 prettier-plugin-jsdoc: specifier: ^1.3.0 - version: 1.3.0(prettier@3.4.1) + version: 1.3.0(prettier@3.4.2) prettier-plugin-sh: specifier: ^0.14.0 - version: 0.14.0(prettier@3.4.1) + version: 0.14.0(prettier@3.4.2) turbo: specifier: ^2.3.3 version: 2.3.3 @@ -45,11 +45,11 @@ importers: specifier: ^8.1.9 version: 8.1.9 '@vitest/browser': - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8) '@vitest/coverage-istanbul': - specifier: 2.1.6 - version: 2.1.6(vitest@2.1.6) + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8) '@zimic/eslint-config-node': specifier: workspace:* version: link:../../packages/eslint-config-node @@ -60,8 +60,8 @@ importers: specifier: workspace:* version: link:../../packages/tsconfig axios: - specifier: ^1.7.8 - version: 1.7.8 + specifier: ^1.7.9 + version: 1.7.9 concurrently: specifier: ^9.1.0 version: 9.1.0 @@ -72,8 +72,8 @@ importers: specifier: ^9.16.0 version: 9.16.0(jiti@1.21.6) execa: - specifier: ^9.5.1 - version: 9.5.1 + specifier: ^9.5.2 + version: 9.5.2 node-fetch: specifier: ^3.3.2 version: 3.3.2 @@ -87,8 +87,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: workspace:* version: link:../../packages/zimic @@ -118,11 +118,11 @@ importers: specifier: ^29.7.0 version: 29.7.0 '@swc/core': - specifier: ^1.9.3 - version: 1.9.3(@swc/helpers@0.5.13) + specifier: ^1.10.0 + version: 1.10.0(@swc/helpers@0.5.13) '@swc/jest': specifier: ^0.2.37 - version: 0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13)) + version: 0.2.37(@swc/core@1.10.0(@swc/helpers@0.5.13)) '@testing-library/dom': specifier: ^10.4.0 version: 10.4.0 @@ -151,7 +151,7 @@ importers: examples/with-jest-node: dependencies: fastify: - specifier: 5.1.0 + specifier: ^5.1.0 version: 5.1.0 zod: specifier: ^3.23.8 @@ -161,11 +161,11 @@ importers: specifier: ^29.7.0 version: 29.7.0 '@swc/core': - specifier: ^1.9.3 - version: 1.9.3(@swc/helpers@0.5.13) + specifier: ^1.10.0 + version: 1.10.0(@swc/helpers@0.5.13) '@swc/jest': specifier: ^0.2.37 - version: 0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13)) + version: 0.2.37(@swc/core@1.10.0(@swc/helpers@0.5.13)) '@types/supertest': specifier: ^6.0.2 version: 6.0.2 @@ -188,20 +188,20 @@ importers: examples/with-next-js-app: dependencies: '@tanstack/react-query': - specifier: ^5.62.0 - version: 5.62.0(react@18.3.1) + specifier: ^5.62.3 + version: 5.62.3(react@19.0.0) clsx: specifier: ^2.1.1 version: 2.1.1 next: - specifier: 15.0.3 - version: 15.0.3(@playwright/test@1.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.0.4 + version: 15.0.4(@playwright/test@1.49.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) devDependencies: '@playwright/test': specifier: ^1.49.0 @@ -210,23 +210,23 @@ importers: specifier: ^22.10.1 version: 22.10.1 '@types/react': - specifier: ^18.3.12 - version: 18.3.12 + specifier: ^19.0.1 + version: 19.0.1 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.1 + version: 19.0.1 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 execa: - specifier: ^9.5.1 - version: 9.5.1 + specifier: ^9.5.2 + version: 9.5.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: - specifier: ^3.4.15 - version: 3.4.15 + specifier: ^3.4.16 + version: 3.4.16 tsx: specifier: ^4.19.2 version: 4.19.2 @@ -240,20 +240,20 @@ importers: examples/with-next-js-pages: dependencies: '@tanstack/react-query': - specifier: ^5.62.0 - version: 5.62.0(react@18.3.1) + specifier: ^5.62.3 + version: 5.62.3(react@19.0.0) clsx: specifier: ^2.1.1 version: 2.1.1 next: - specifier: 15.0.3 - version: 15.0.3(@playwright/test@1.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.0.4 + version: 15.0.4(@playwright/test@1.49.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) devDependencies: '@playwright/test': specifier: ^1.49.0 @@ -262,11 +262,11 @@ importers: specifier: ^22.10.1 version: 22.10.1 '@types/react': - specifier: ^18.3.12 - version: 18.3.12 + specifier: ^19.0.1 + version: 19.0.1 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.1 + version: 19.0.1 concurrently: specifier: ^9.1.0 version: 9.1.0 @@ -277,8 +277,8 @@ importers: specifier: ^8.4.49 version: 8.4.49 tailwindcss: - specifier: ^3.4.15 - version: 3.4.15 + specifier: ^3.4.16 + version: 3.4.16 typescript: specifier: ^5.7.2 version: 5.7.2 @@ -289,7 +289,7 @@ importers: examples/with-openapi-typegen: dependencies: fastify: - specifier: 5.1.0 + specifier: ^5.1.0 version: 5.1.0 zod: specifier: ^3.23.8 @@ -299,8 +299,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 '@vitest/spy': - specifier: ^2.1.6 - version: 2.1.6 + specifier: ^2.1.8 + version: 2.1.8 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -311,8 +311,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -323,14 +323,14 @@ importers: specifier: ^2.1.1 version: 2.1.1 next: - specifier: 15.0.3 - version: 15.0.3(@playwright/test@1.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.0.4 + version: 15.0.4(@playwright/test@1.49.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) devDependencies: '@playwright/test': specifier: ^1.49.0 @@ -339,23 +339,23 @@ importers: specifier: ^22.10.1 version: 22.10.1 '@types/react': - specifier: ^18.3.12 - version: 18.3.12 + specifier: ^19.0.1 + version: 19.0.1 '@types/react-dom': - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.1 + version: 19.0.1 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 execa: - specifier: ^9.5.1 - version: 9.5.1 + specifier: ^9.5.2 + version: 9.5.2 postcss: specifier: ^8.4.49 version: 8.4.49 tailwindcss: - specifier: ^3.4.15 - version: 3.4.15 + specifier: ^3.4.16 + version: 3.4.16 tsx: specifier: ^4.19.2 version: 4.19.2 @@ -378,8 +378,8 @@ importers: specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitest/browser': - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8) concurrently: specifier: ^9.1.0 version: 9.1.0 @@ -393,8 +393,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -411,8 +411,8 @@ importers: specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitest/spy': - specifier: ^2.1.6 - version: 2.1.6 + specifier: ^2.1.8 + version: 2.1.8 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -420,8 +420,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -429,7 +429,7 @@ importers: examples/with-vitest-node: dependencies: fastify: - specifier: 5.1.0 + specifier: ^5.1.0 version: 5.1.0 zod: specifier: ^3.23.8 @@ -439,8 +439,8 @@ importers: specifier: ^6.0.2 version: 6.0.2 '@vitest/spy': - specifier: ^2.1.6 - version: 2.1.6 + specifier: ^2.1.8 + version: 2.1.8 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -451,8 +451,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: 2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) zimic: specifier: latest version: link:../../packages/zimic @@ -460,17 +460,17 @@ importers: packages/eslint-config: dependencies: '@typescript-eslint/eslint-plugin': - specifier: ^8.16.0 - version: 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + specifier: ^8.17.0 + version: 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) '@typescript-eslint/parser': - specifier: ^8.16.0 - version: 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + specifier: ^8.17.0 + version: 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) eslint-import-resolver-typescript: - specifier: ^3.6.3 - version: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) + specifier: ^3.7.0 + version: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-import-helpers: specifier: ^2.0.1 version: 2.0.1(eslint@9.16.0(jiti@1.21.6)) @@ -478,14 +478,14 @@ importers: packages/eslint-config-node: dependencies: '@eslint/compat': - specifier: ^1.2.3 - version: 1.2.3(eslint@9.16.0(jiti@1.21.6)) + specifier: ^1.2.4 + version: 1.2.4(eslint@9.16.0(jiti@1.21.6)) '@zimic/eslint-config': specifier: workspace:* version: link:../eslint-config eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)) packages/eslint-config-react: dependencies: @@ -494,7 +494,7 @@ importers: version: link:../eslint-config eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) + version: 2.31.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: specifier: ^6.10.2 version: 6.10.2(eslint@9.16.0(jiti@1.21.6)) @@ -502,8 +502,8 @@ importers: specifier: ^7.37.2 version: 7.37.2(eslint@9.16.0(jiti@1.21.6)) eslint-plugin-react-hooks: - specifier: ^5.0.0 - version: 5.0.0(eslint@9.16.0(jiti@1.21.6)) + specifier: ^5.1.0 + version: 5.1.0(eslint@9.16.0(jiti@1.21.6)) packages/lint-staged-config: {} @@ -518,8 +518,8 @@ importers: specifier: 4.1.2 version: 4.1.2 execa: - specifier: 9.5.1 - version: 9.5.1 + specifier: 9.5.2 + version: 9.5.2 isomorphic-ws: specifier: 5.0.0 version: 5.0.0(ws@8.18.0(bufferutil@4.0.8)) @@ -527,8 +527,8 @@ importers: specifier: 2.4.3 version: 2.4.3(typescript@5.7.2) openapi-typescript: - specifier: 7.4.3 - version: 7.4.3(typescript@5.7.2) + specifier: 7.4.4 + version: 7.4.4(typescript@5.7.2) ws: specifier: 8.18.0 version: 8.18.0(bufferutil@4.0.8) @@ -556,11 +556,11 @@ importers: specifier: ^17.0.33 version: 17.0.33 '@vitest/browser': - specifier: ^2.1.6 - version: 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8) '@vitest/coverage-istanbul': - specifier: ^2.1.6 - version: 2.1.6(vitest@2.1.6) + specifier: ^2.1.8 + version: 2.1.8(vitest@2.1.8) '@zimic/eslint-config-node': specifier: workspace:* version: link:../eslint-config-node @@ -583,11 +583,11 @@ importers: specifier: ^1.49.0 version: 1.49.0 prettier: - specifier: ^3.4.1 - version: 3.4.1 + specifier: ^3.4.2 + version: 3.4.2 tsup: specifier: ^8.3.5 - version: 8.3.5(@swc/core@1.9.3(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.5.1) + version: 8.3.5(@swc/core@1.10.0(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.5.1) tsx: specifier: ^4.19.2 version: 4.19.2 @@ -595,8 +595,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 vitest: - specifier: ^2.1.6 - version: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)) packages: @@ -1314,8 +1314,8 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.2.3': - resolution: {integrity: sha512-wlZhwlDFxkxIZ571aH0FoK4h4Vwx7P3HJx62Gp8hTc10bfpwT2x0nULuAHmQSJBOWPgPeVf+9YtnD4j50zVHmA==} + '@eslint/compat@1.2.4': + resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^9.10.0 @@ -1635,53 +1635,53 @@ packages: resolution: {integrity: sha512-lDiHQMCBV9qz8c7+zxaNFQtWWaSogTYkqJ3Pg+FGYYC76nsfSxkMQ0df8fojyz16E+w4vp57NLjN2muNG7LugQ==} engines: {node: '>=18'} - '@next/env@15.0.3': - resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} + '@next/env@15.0.4': + resolution: {integrity: sha512-WNRvtgnRVDD4oM8gbUcRc27IAhaL4eXQ/2ovGbgLnPGUvdyDr8UdXP4Q/IBDdAdojnD2eScryIDirv0YUCjUVw==} - '@next/swc-darwin-arm64@15.0.3': - resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + '@next/swc-darwin-arm64@15.0.4': + resolution: {integrity: sha512-QecQXPD0yRHxSXWL5Ff80nD+A56sUXZG9koUsjWJwA2Z0ZgVQfuy7gd0/otjxoOovPVHR2eVEvPMHbtZP+pf9w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.0.3': - resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + '@next/swc-darwin-x64@15.0.4': + resolution: {integrity: sha512-pb7Bye3y1Og3PlCtnz2oO4z+/b3pH2/HSYkLbL0hbVuTGil7fPen8/3pyyLjdiTLcFJ+ymeU3bck5hd4IPFFCA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.0.3': - resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} + '@next/swc-linux-arm64-gnu@15.0.4': + resolution: {integrity: sha512-12oSaBFjGpB227VHzoXF3gJoK2SlVGmFJMaBJSu5rbpaoT5OjP5OuCLuR9/jnyBF1BAWMs/boa6mLMoJPRriMA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.3': - resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + '@next/swc-linux-arm64-musl@15.0.4': + resolution: {integrity: sha512-QARO88fR/a+wg+OFC3dGytJVVviiYFEyjc/Zzkjn/HevUuJ7qGUUAUYy5PGVWY1YgTzeRYz78akQrVQ8r+sMjw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.3': - resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} + '@next/swc-linux-x64-gnu@15.0.4': + resolution: {integrity: sha512-Z50b0gvYiUU1vLzfAMiChV8Y+6u/T2mdfpXPHraqpypP7yIT2UV9YBBhcwYkxujmCvGEcRTVWOj3EP7XW/wUnw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.3': - resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} + '@next/swc-linux-x64-musl@15.0.4': + resolution: {integrity: sha512-7H9C4FAsrTAbA/ENzvFWsVytqRYhaJYKa2B3fyQcv96TkOGVMcvyS6s+sj4jZlacxxTcn7ygaMXUPkEk7b78zw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.0.3': - resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} + '@next/swc-win32-arm64-msvc@15.0.4': + resolution: {integrity: sha512-Z/v3WV5xRaeWlgJzN9r4PydWD8sXV35ywc28W63i37G2jnUgScA4OOgS8hQdiXLxE3gqfSuHTicUhr7931OXPQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.3': - resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} + '@next/swc-win32-x64-msvc@15.0.4': + resolution: {integrity: sha512-NGLchGruagh8lQpDr98bHLyWJXOBSmkEAfK980OiNBa7vNm6PsNoPvzTfstT78WyOeMRQphEQ455rggd7Eo+Dw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1922,68 +1922,68 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@swc/core-darwin-arm64@1.9.3': - resolution: {integrity: sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==} + '@swc/core-darwin-arm64@1.10.0': + resolution: {integrity: sha512-wCeUpanqZyzvgqWRtXIyhcFK3CqukAlYyP+fJpY2gWc/+ekdrenNIfZMwY7tyTFDkXDYEKzvn3BN/zDYNJFowQ==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.9.3': - resolution: {integrity: sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==} + '@swc/core-darwin-x64@1.10.0': + resolution: {integrity: sha512-0CZPzqTynUBO+SHEl/qKsFSahp2Jv/P2ZRjFG0gwZY5qIcr1+B/v+o74/GyNMBGz9rft+F2WpU31gz2sJwyF4A==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.9.3': - resolution: {integrity: sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==} + '@swc/core-linux-arm-gnueabihf@1.10.0': + resolution: {integrity: sha512-oq+DdMu5uJOFPtRkeiITc4kxmd+QSmK+v+OBzlhdGkSgoH3yRWZP+H2ao0cBXo93ZgCr2LfjiER0CqSKhjGuNA==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.9.3': - resolution: {integrity: sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==} + '@swc/core-linux-arm64-gnu@1.10.0': + resolution: {integrity: sha512-Y6+PC8knchEViRxiCUj3j8wsGXaIhuvU+WqrFqV834eiItEMEI9+Vh3FovqJMBE3L7d4E4ZQtgImHCXjrHfxbw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.9.3': - resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==} + '@swc/core-linux-arm64-musl@1.10.0': + resolution: {integrity: sha512-EbrX9A5U4cECCQQfky7945AW9GYnTXtCUXElWTkTYmmyQK87yCyFfY8hmZ9qMFIwxPOH6I3I2JwMhzdi8Qoz7g==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.9.3': - resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==} + '@swc/core-linux-x64-gnu@1.10.0': + resolution: {integrity: sha512-TaxpO6snTjjfLXFYh5EjZ78se69j2gDcqEM8yB9gguPYwkCHi2Ylfmh7iVaNADnDJFtjoAQp0L41bTV/Pfq9Cg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.9.3': - resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==} + '@swc/core-linux-x64-musl@1.10.0': + resolution: {integrity: sha512-IEGvDd6aEEKEyZFZ8oCKuik05G5BS7qwG5hO5PEMzdGeh8JyFZXxsfFXbfeAqjue4UaUUrhnoX+Ze3M2jBVMHw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.9.3': - resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==} + '@swc/core-win32-arm64-msvc@1.10.0': + resolution: {integrity: sha512-UkQ952GSpY+Z6XONj9GSW8xGSkF53jrCsuLj0nrcuw7Dvr1a816U/9WYZmmcYS8tnG2vHylhpm6csQkyS8lpCw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.9.3': - resolution: {integrity: sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==} + '@swc/core-win32-ia32-msvc@1.10.0': + resolution: {integrity: sha512-a2QpIZmTiT885u/mUInpeN2W9ClCnqrV2LnMqJR1/Fgx1Afw/hAtiDZPtQ0SqS8yDJ2VR5gfNZo3gpxWMrqdVA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.9.3': - resolution: {integrity: sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==} + '@swc/core-win32-x64-msvc@1.10.0': + resolution: {integrity: sha512-tZcCmMwf483nwsEBfUk5w9e046kMa1iSik4bP9Kwi2FGtOfHuDfIcwW4jek3hdcgF5SaBW1ktnK/lgQLDi5AtA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.9.3': - resolution: {integrity: sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==} + '@swc/core@1.10.0': + resolution: {integrity: sha512-+CuuTCmQFfzaNGg1JmcZvdUVITQXJk9sMnl1C2TiDLzOSVOJRwVD4dNo5dljX/qxpMAN+2BIYlwjlSkoGi6grg==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -2006,11 +2006,11 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} - '@tanstack/query-core@5.62.0': - resolution: {integrity: sha512-sx38bGrqF9bop92AXOvzDr0L9fWDas5zXdPglxa9cuqeVSWS7lY6OnVyl/oodfXjgOGRk79IfCpgVmxrbHuFHg==} + '@tanstack/query-core@5.62.3': + resolution: {integrity: sha512-Jp/nYoz8cnO7kqhOlSv8ke/0MJRJVGuZ0P/JO9KQ+f45mpN90hrerzavyTKeSoT/pOzeoOUkv1Xd0wPsxAWXfg==} - '@tanstack/react-query@5.62.0': - resolution: {integrity: sha512-tj2ltjAn2a3fs+Dqonlvs6GyLQ/LKVJE2DVSYW+8pJ3P6/VCVGrfqv5UEchmlP7tLOvvtZcOuSyI2ooVlR5Yqw==} + '@tanstack/react-query@5.62.3': + resolution: {integrity: sha512-y2zDNKuhgiuMgsKkqd4AcsLIBiCfEO8U11AdrtAUihmLbRNztPrlcZqx2lH1GacZsx+y1qRRbCcJLYTtF1vKsw==} peerDependencies: react: ^18 || ^19 @@ -2107,14 +2107,11 @@ packages: '@types/node@22.9.3': resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/react-dom@19.0.1': + resolution: {integrity: sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==} - '@types/react-dom@18.3.1': - resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - - '@types/react@18.3.12': - resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + '@types/react@19.0.1': + resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -2146,8 +2143,8 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.16.0': - resolution: {integrity: sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==} + '@typescript-eslint/eslint-plugin@8.17.0': + resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2157,8 +2154,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.16.0': - resolution: {integrity: sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==} + '@typescript-eslint/parser@8.17.0': + resolution: {integrity: sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2167,12 +2164,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.16.0': - resolution: {integrity: sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==} + '@typescript-eslint/scope-manager@8.17.0': + resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.16.0': - resolution: {integrity: sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==} + '@typescript-eslint/type-utils@8.17.0': + resolution: {integrity: sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2181,12 +2178,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.16.0': - resolution: {integrity: sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==} + '@typescript-eslint/types@8.17.0': + resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.16.0': - resolution: {integrity: sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==} + '@typescript-eslint/typescript-estree@8.17.0': + resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2194,8 +2191,8 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.16.0': - resolution: {integrity: sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==} + '@typescript-eslint/utils@8.17.0': + resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2204,16 +2201,16 @@ packages: typescript: optional: true - '@typescript-eslint/visitor-keys@8.16.0': - resolution: {integrity: sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==} + '@typescript-eslint/visitor-keys@8.17.0': + resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/browser@2.1.6': - resolution: {integrity: sha512-GTBY78bkRd5BL3jnwQr9J+uIkZjpvRzq0G1uHNy2GeJYfTvmYFh4QE5W2HH6AfEKLFQwPXHFiXgI2o4CEtAtJw==} + '@vitest/browser@2.1.8': + resolution: {integrity: sha512-OWVvEJThRgxlNMYNVLEK/9qVkpRcLvyuKLngIV3Hob01P56NjPHprVBYn+rx4xAJudbM9yrCrywPIEuA3Xyo8A==} peerDependencies: playwright: '*' safaridriver: '*' - vitest: 2.1.6 + vitest: 2.1.8 webdriverio: '*' peerDependenciesMeta: playwright: @@ -2223,39 +2220,39 @@ packages: webdriverio: optional: true - '@vitest/coverage-istanbul@2.1.6': - resolution: {integrity: sha512-tJBsODJ8acDxwt4J+HERV0+FxOhjOXn+yVYWHQ25iFqcbQNyvptQLBFxEoRvaMbqJqSaYatGOg0/ctiQdB3mSg==} + '@vitest/coverage-istanbul@2.1.8': + resolution: {integrity: sha512-cSaCd8KcWWvgDwEJSXm0NEWZ1YTiJzjicKHy+zOEbUm0gjbbkz+qJf1p8q71uBzSlS7vdnZA8wRLeiwVE3fFTA==} peerDependencies: - vitest: 2.1.6 + vitest: 2.1.8 - '@vitest/expect@2.1.6': - resolution: {integrity: sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==} + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/mocker@2.1.6': - resolution: {integrity: sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 + vite: ^5.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@2.1.6': - resolution: {integrity: sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==} + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/runner@2.1.6': - resolution: {integrity: sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==} + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} - '@vitest/snapshot@2.1.6': - resolution: {integrity: sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==} + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} - '@vitest/spy@2.1.6': - resolution: {integrity: sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==} + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} - '@vitest/utils@2.1.6': - resolution: {integrity: sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==} + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} '@whatwg-node/disposablestack@0.0.5': resolution: {integrity: sha512-9lXugdknoIequO4OYvIjhygvfSEgnO8oASLqLelnDhkRjgBZhc39shC3QSlZuyDO9bgYSIVa2cHAiN+St3ty4w==} @@ -2449,8 +2446,8 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} - axios@1.7.8: - resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -2751,10 +2748,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2815,15 +2808,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -3040,8 +3024,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-import-resolver-typescript@3.6.3: - resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} + eslint-import-resolver-typescript@3.7.0: + resolution: {integrity: sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -3074,27 +3058,6 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - eslint-plugin-import-helpers@2.0.1: resolution: {integrity: sha512-MSiQZV0k+bQlt7RnHZrJxDBE948urZthusQt78FKmQ5/WLm5PpS8L16+2JVGBiSffneOAe4fud8RdYmqPveo8A==} peerDependencies: @@ -3116,8 +3079,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@5.0.0: - resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + eslint-plugin-react-hooks@5.1.0: + resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -3197,8 +3160,8 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.5.1: - resolution: {integrity: sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==} + execa@9.5.2: + resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} exit@0.1.2: @@ -4018,14 +3981,14 @@ packages: light-my-request@6.3.0: resolution: {integrity: sha512-bWTAPJmeWQH5suJNYwG0f5cs0p6ho9e6f1Ppoxv5qMosY+s9Ir2+ZLvvHcgA7VTDop4zl/NCHhOVVqU+kd++Ow==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - lilconfig@3.1.2: resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4264,9 +4227,6 @@ packages: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4312,16 +4272,16 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@15.0.3: - resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + next@15.0.4: + resolution: {integrity: sha512-nuy8FH6M1FG0lktGotamQDCXhh5hZ19Vo0ht1AOIQWrYJLP598TIUagKtvJrfJ5AGwB/WmDqkKaKhMpVifvGPA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -4433,8 +4393,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - openapi-typescript@7.4.3: - resolution: {integrity: sha512-xTIjMIIOv9kNhsr8JxaC00ucbIY/6ZwuJPJBZMSh5FA2dicZN5uM805DWVJojXdom8YI4AQTavPDPHMx/3g0vQ==} + openapi-typescript@7.4.4: + resolution: {integrity: sha512-7j3nktnRzlQdlHnHsrcr6Gqz8f80/RhfA2I8s1clPI+jkY0hLNmnYVKBfuUEli5EEgK1B6M+ibdS5REasPlsUw==} hasBin: true peerDependencies: typescript: ^5.x @@ -4668,8 +4628,8 @@ packages: peerDependencies: prettier: ^3.0.3 - prettier@3.4.1: - resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} hasBin: true @@ -4732,10 +4692,10 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^18.3.1 + react: ^19.0.0 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4746,8 +4706,8 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -4878,8 +4838,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -4984,6 +4944,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash@0.0.4: + resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -5139,8 +5102,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwindcss@3.4.15: - resolution: {integrity: sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==} + tailwindcss@3.4.16: + resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} engines: {node: '>=14.0.0'} hasBin: true @@ -5387,9 +5350,9 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} - vite-node@2.1.6: - resolution: {integrity: sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true vite@5.4.9: @@ -5423,15 +5386,15 @@ packages: terser: optional: true - vitest@2.1.6: - resolution: {integrity: sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 2.1.6 - '@vitest/ui': 2.1.6 + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -6188,7 +6151,7 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.2.3(eslint@9.16.0(jiti@1.21.6))': + '@eslint/compat@1.2.4(eslint@9.16.0(jiti@1.21.6))': optionalDependencies: eslint: 9.16.0(jiti@1.21.6) @@ -6606,30 +6569,30 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@next/env@15.0.3': {} + '@next/env@15.0.4': {} - '@next/swc-darwin-arm64@15.0.3': + '@next/swc-darwin-arm64@15.0.4': optional: true - '@next/swc-darwin-x64@15.0.3': + '@next/swc-darwin-x64@15.0.4': optional: true - '@next/swc-linux-arm64-gnu@15.0.3': + '@next/swc-linux-arm64-gnu@15.0.4': optional: true - '@next/swc-linux-arm64-musl@15.0.3': + '@next/swc-linux-arm64-musl@15.0.4': optional: true - '@next/swc-linux-x64-gnu@15.0.3': + '@next/swc-linux-x64-gnu@15.0.4': optional: true - '@next/swc-linux-x64-musl@15.0.3': + '@next/swc-linux-x64-musl@15.0.4': optional: true - '@next/swc-win32-arm64-msvc@15.0.3': + '@next/swc-win32-arm64-msvc@15.0.4': optional: true - '@next/swc-win32-x64-msvc@15.0.3': + '@next/swc-win32-x64-msvc@15.0.4': optional: true '@nodelib/fs.scandir@2.1.5': @@ -6808,51 +6771,51 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@swc/core-darwin-arm64@1.9.3': + '@swc/core-darwin-arm64@1.10.0': optional: true - '@swc/core-darwin-x64@1.9.3': + '@swc/core-darwin-x64@1.10.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.9.3': + '@swc/core-linux-arm-gnueabihf@1.10.0': optional: true - '@swc/core-linux-arm64-gnu@1.9.3': + '@swc/core-linux-arm64-gnu@1.10.0': optional: true - '@swc/core-linux-arm64-musl@1.9.3': + '@swc/core-linux-arm64-musl@1.10.0': optional: true - '@swc/core-linux-x64-gnu@1.9.3': + '@swc/core-linux-x64-gnu@1.10.0': optional: true - '@swc/core-linux-x64-musl@1.9.3': + '@swc/core-linux-x64-musl@1.10.0': optional: true - '@swc/core-win32-arm64-msvc@1.9.3': + '@swc/core-win32-arm64-msvc@1.10.0': optional: true - '@swc/core-win32-ia32-msvc@1.9.3': + '@swc/core-win32-ia32-msvc@1.10.0': optional: true - '@swc/core-win32-x64-msvc@1.9.3': + '@swc/core-win32-x64-msvc@1.10.0': optional: true - '@swc/core@1.9.3(@swc/helpers@0.5.13)': + '@swc/core@1.10.0(@swc/helpers@0.5.13)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.9.3 - '@swc/core-darwin-x64': 1.9.3 - '@swc/core-linux-arm-gnueabihf': 1.9.3 - '@swc/core-linux-arm64-gnu': 1.9.3 - '@swc/core-linux-arm64-musl': 1.9.3 - '@swc/core-linux-x64-gnu': 1.9.3 - '@swc/core-linux-x64-musl': 1.9.3 - '@swc/core-win32-arm64-msvc': 1.9.3 - '@swc/core-win32-ia32-msvc': 1.9.3 - '@swc/core-win32-x64-msvc': 1.9.3 + '@swc/core-darwin-arm64': 1.10.0 + '@swc/core-darwin-x64': 1.10.0 + '@swc/core-linux-arm-gnueabihf': 1.10.0 + '@swc/core-linux-arm64-gnu': 1.10.0 + '@swc/core-linux-arm64-musl': 1.10.0 + '@swc/core-linux-x64-gnu': 1.10.0 + '@swc/core-linux-x64-musl': 1.10.0 + '@swc/core-win32-arm64-msvc': 1.10.0 + '@swc/core-win32-ia32-msvc': 1.10.0 + '@swc/core-win32-x64-msvc': 1.10.0 '@swc/helpers': 0.5.13 '@swc/counter@0.1.3': {} @@ -6861,10 +6824,10 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/jest@0.2.37(@swc/core@1.9.3(@swc/helpers@0.5.13))': + '@swc/jest@0.2.37(@swc/core@1.10.0(@swc/helpers@0.5.13))': dependencies: '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.9.3(@swc/helpers@0.5.13) + '@swc/core': 1.10.0(@swc/helpers@0.5.13) '@swc/counter': 0.1.3 jsonc-parser: 3.2.1 @@ -6872,12 +6835,12 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.62.0': {} + '@tanstack/query-core@5.62.3': {} - '@tanstack/react-query@5.62.0(react@18.3.1)': + '@tanstack/react-query@5.62.3(react@19.0.0)': dependencies: - '@tanstack/query-core': 5.62.0 - react: 18.3.1 + '@tanstack/query-core': 5.62.3 + react: 19.0.0 '@testing-library/dom@10.4.0': dependencies: @@ -6993,15 +6956,12 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/prop-types@15.7.12': {} - - '@types/react-dom@18.3.1': + '@types/react-dom@19.0.1': dependencies: - '@types/react': 18.3.12 + '@types/react': 19.0.1 - '@types/react@18.3.12': + '@types/react@19.0.1': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/stack-utils@2.0.3': {} @@ -7036,14 +6996,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.16.0 - '@typescript-eslint/type-utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.16.0 + '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/type-utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.17.0 eslint: 9.16.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 @@ -7054,12 +7014,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: - '@typescript-eslint/scope-manager': 8.16.0 - '@typescript-eslint/types': 8.16.0 - '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.16.0 + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.17.0 debug: 4.3.7(supports-color@9.4.0) eslint: 9.16.0(jiti@1.21.6) optionalDependencies: @@ -7067,15 +7027,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.16.0': + '@typescript-eslint/scope-manager@8.17.0': dependencies: - '@typescript-eslint/types': 8.16.0 - '@typescript-eslint/visitor-keys': 8.16.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/visitor-keys': 8.17.0 - '@typescript-eslint/type-utils@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/type-utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) debug: 4.3.7(supports-color@9.4.0) eslint: 9.16.0(jiti@1.21.6) ts-api-utils: 1.3.0(typescript@5.7.2) @@ -7084,12 +7044,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.16.0': {} + '@typescript-eslint/types@8.17.0': {} - '@typescript-eslint/typescript-estree@8.16.0(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.17.0(typescript@5.7.2)': dependencies: - '@typescript-eslint/types': 8.16.0 - '@typescript-eslint/visitor-keys': 8.16.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/visitor-keys': 8.17.0 debug: 4.3.7(supports-color@9.4.0) fast-glob: 3.3.2 is-glob: 4.0.3 @@ -7101,34 +7061,34 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': + '@typescript-eslint/utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6)) - '@typescript-eslint/scope-manager': 8.16.0 - '@typescript-eslint/types': 8.16.0 - '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) eslint: 9.16.0(jiti@1.21.6) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.16.0': + '@typescript-eslint/visitor-keys@8.17.0': dependencies: - '@typescript-eslint/types': 8.16.0 + '@typescript-eslint/types': 8.17.0 eslint-visitor-keys: 4.2.0 - '@vitest/browser@2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6)': + '@vitest/browser@2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) - '@vitest/mocker': 2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) - '@vitest/utils': 2.1.6 + '@vitest/mocker': 2.1.8(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/utils': 2.1.8 magic-string: 0.30.12 msw: 2.6.5(@types/node@22.10.1)(typescript@5.7.2) sirv: 3.0.0 tinyrainbow: 1.2.0 - vitest: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + vitest: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) ws: 8.18.0(bufferutil@4.0.8) optionalDependencies: playwright: 1.49.0 @@ -7139,7 +7099,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-istanbul@2.1.6(vitest@2.1.6)': + '@vitest/coverage-istanbul@2.1.8(vitest@2.1.8)': dependencies: '@istanbuljs/schema': 0.1.3 debug: 4.3.7(supports-color@9.4.0) @@ -7151,57 +7111,57 @@ snapshots: magicast: 0.3.5 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) + vitest: 2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.6': + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 2.1.6 - '@vitest/utils': 2.1.6 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.6(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': + '@vitest/mocker@2.1.8(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': dependencies: - '@vitest/spy': 2.1.6 + '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: msw: 2.4.3(typescript@5.7.2) vite: 5.4.9(@types/node@22.10.1) - '@vitest/mocker@2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': + '@vitest/mocker@2.1.8(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1))': dependencies: - '@vitest/spy': 2.1.6 + '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: msw: 2.6.5(@types/node@22.10.1)(typescript@5.7.2) vite: 5.4.9(@types/node@22.10.1) - '@vitest/pretty-format@2.1.6': + '@vitest/pretty-format@2.1.8': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.6': + '@vitest/runner@2.1.8': dependencies: - '@vitest/utils': 2.1.6 + '@vitest/utils': 2.1.8 pathe: 1.1.2 - '@vitest/snapshot@2.1.6': + '@vitest/snapshot@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.6 + '@vitest/pretty-format': 2.1.8 magic-string: 0.30.12 pathe: 1.1.2 - '@vitest/spy@2.1.6': + '@vitest/spy@2.1.8': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.6': + '@vitest/utils@2.1.8': dependencies: - '@vitest/pretty-format': 2.1.6 + '@vitest/pretty-format': 2.1.8 loupe: 3.1.2 tinyrainbow: 1.2.0 @@ -7420,7 +7380,7 @@ snapshots: axe-core@4.10.2: {} - axios@1.7.8: + axios@1.7.9: dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -7749,12 +7709,6 @@ snapshots: - supports-color - ts-node - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7809,10 +7763,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.3.7(supports-color@9.4.0): dependencies: ms: 2.1.3 @@ -8122,43 +8072,30 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.3.6 + debug: 4.3.7(supports-color@9.4.0) enhanced-resolve: 5.17.0 eslint: 9.16.0(jiti@1.21.6) - eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-bun-module: 1.1.0 is-glob: 4.0.3 + stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)) transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) eslint: 9.16.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.8.1(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - eslint: 9.16.0(jiti@1.21.6) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color @@ -8166,7 +8103,7 @@ snapshots: dependencies: eslint: 9.16.0(jiti@1.21.6) - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -8177,7 +8114,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.16.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.16.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -8189,7 +8126,7 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.16.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) + '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -8214,7 +8151,7 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.0.0(eslint@9.16.0(jiti@1.21.6)): + eslint-plugin-react-hooks@5.1.0(eslint@9.16.0(jiti@1.21.6)): dependencies: eslint: 9.16.0(jiti@1.21.6) @@ -8344,10 +8281,10 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.5.1: + execa@9.5.2: dependencies: '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 figures: 6.1.0 get-stream: 9.0.1 human-signals: 8.0.0 @@ -9088,7 +9025,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -9363,10 +9300,10 @@ snapshots: process-warning: 4.0.0 set-cookie-parser: 2.6.0 - lilconfig@2.1.0: {} - lilconfig@3.1.2: {} + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} lint-staged@15.2.10: @@ -9672,8 +9609,6 @@ snapshots: mrmime@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} msw@2.4.3(typescript@5.7.2): @@ -9739,26 +9674,26 @@ snapshots: natural-compare@1.4.0: {} - next@15.0.3(@playwright/test@1.49.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.0.4(@playwright/test@1.49.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 15.0.3 + '@next/env': 15.0.4 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.13 busboy: 1.6.0 caniuse-lite: 1.0.30001643 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.3 - '@next/swc-darwin-x64': 15.0.3 - '@next/swc-linux-arm64-gnu': 15.0.3 - '@next/swc-linux-arm64-musl': 15.0.3 - '@next/swc-linux-x64-gnu': 15.0.3 - '@next/swc-linux-x64-musl': 15.0.3 - '@next/swc-win32-arm64-msvc': 15.0.3 - '@next/swc-win32-x64-msvc': 15.0.3 + '@next/swc-darwin-arm64': 15.0.4 + '@next/swc-darwin-x64': 15.0.4 + '@next/swc-linux-arm64-gnu': 15.0.4 + '@next/swc-linux-arm64-musl': 15.0.4 + '@next/swc-linux-x64-gnu': 15.0.4 + '@next/swc-linux-x64-musl': 15.0.4 + '@next/swc-win32-arm64-msvc': 15.0.4 + '@next/swc-win32-x64-msvc': 15.0.4 '@playwright/test': 1.49.0 sharp: 0.33.5 transitivePeerDependencies: @@ -9859,7 +9794,7 @@ snapshots: dependencies: mimic-function: 5.0.1 - openapi-typescript@7.4.3(typescript@5.7.2): + openapi-typescript@7.4.4(typescript@5.7.2): dependencies: '@redocly/openapi-core': 1.25.11(supports-color@9.4.0) ansi-colors: 4.1.3 @@ -9923,7 +9858,7 @@ snapshots: parse-json@8.1.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 index-to-position: 0.1.2 type-fest: 4.26.1 @@ -10019,14 +9954,14 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.49): dependencies: - lilconfig: 3.1.2 + lilconfig: 3.1.3 yaml: 2.5.1 optionalDependencies: postcss: 8.4.49 postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.5.1): dependencies: - lilconfig: 3.1.2 + lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.6 postcss: 8.4.49 @@ -10059,22 +9994,22 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-jsdoc@1.3.0(prettier@3.4.1): + prettier-plugin-jsdoc@1.3.0(prettier@3.4.2): dependencies: binary-searching: 2.0.5 comment-parser: 1.4.1 mdast-util-from-markdown: 2.0.1 - prettier: 3.4.1 + prettier: 3.4.2 transitivePeerDependencies: - supports-color - prettier-plugin-sh@0.14.0(prettier@3.4.1): + prettier-plugin-sh@0.14.0(prettier@3.4.2): dependencies: mvdan-sh: 0.10.1 - prettier: 3.4.1 + prettier: 3.4.2 sh-syntax: 0.4.2 - prettier@3.4.1: {} + prettier@3.4.2: {} pretty-format@27.5.1: dependencies: @@ -10132,11 +10067,10 @@ snapshots: quick-format-unescaped@4.0.4: {} - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 react-is@16.13.1: {} @@ -10144,9 +10078,7 @@ snapshots: react-is@18.3.1: {} - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -10314,9 +10246,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} secure-json-parse@2.7.0: {} @@ -10440,6 +10370,8 @@ snapshots: sprintf-js@1.0.3: {} + stable-hash@0.0.4: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -10552,10 +10484,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(react@18.3.1): + styled-jsx@5.1.6(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 sucrase@3.35.0: dependencies: @@ -10620,7 +10552,7 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@3.4.15: + tailwindcss@3.4.16: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -10631,7 +10563,7 @@ snapshots: glob-parent: 6.0.2 is-glob: 4.0.3 jiti: 1.21.6 - lilconfig: 2.1.0 + lilconfig: 3.1.3 micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 @@ -10736,7 +10668,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.3.5(@swc/core@1.9.3(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.5.1): + tsup@8.3.5(@swc/core@1.10.0(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.5.1): dependencies: bundle-require: 5.0.0(esbuild@0.24.0) cac: 6.7.14 @@ -10755,7 +10687,7 @@ snapshots: tinyglobby: 0.2.10 tree-kill: 1.2.2 optionalDependencies: - '@swc/core': 1.9.3(@swc/helpers@0.5.13) + '@swc/core': 1.10.0(@swc/helpers@0.5.13) postcss: 8.4.49 typescript: 5.7.2 transitivePeerDependencies: @@ -10890,7 +10822,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - vite-node@2.1.6(@types/node@22.10.1): + vite-node@2.1.8(@types/node@22.10.1): dependencies: cac: 6.7.14 debug: 4.3.7(supports-color@9.4.0) @@ -10917,15 +10849,15 @@ snapshots: '@types/node': 22.10.1 fsevents: 2.3.3 - vitest@2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)): + vitest@2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.4.3(typescript@5.7.2)): dependencies: - '@vitest/expect': 2.1.6 - '@vitest/mocker': 2.1.6(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) - '@vitest/pretty-format': 2.1.6 - '@vitest/runner': 2.1.6 - '@vitest/snapshot': 2.1.6 - '@vitest/spy': 2.1.6 - '@vitest/utils': 2.1.6 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(msw@2.4.3(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 debug: 4.3.7(supports-color@9.4.0) expect-type: 1.1.0 @@ -10937,11 +10869,11 @@ snapshots: tinypool: 1.0.1 tinyrainbow: 1.2.0 vite: 5.4.9(@types/node@22.10.1) - vite-node: 2.1.6(@types/node@22.10.1) + vite-node: 2.1.8(@types/node@22.10.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.10.1 - '@vitest/browser': 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) + '@vitest/browser': 2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8) jsdom: 20.0.3(bufferutil@4.0.8) transitivePeerDependencies: - less @@ -10954,15 +10886,15 @@ snapshots: - supports-color - terser - vitest@2.1.6(@types/node@22.10.1)(@vitest/browser@2.1.6)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)): + vitest@2.1.8(@types/node@22.10.1)(@vitest/browser@2.1.8)(jsdom@20.0.3(bufferutil@4.0.8))(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2)): dependencies: - '@vitest/expect': 2.1.6 - '@vitest/mocker': 2.1.6(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) - '@vitest/pretty-format': 2.1.6 - '@vitest/runner': 2.1.6 - '@vitest/snapshot': 2.1.6 - '@vitest/spy': 2.1.6 - '@vitest/utils': 2.1.6 + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(msw@2.6.5(@types/node@22.10.1)(typescript@5.7.2))(vite@5.4.9(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 chai: 5.1.2 debug: 4.3.7(supports-color@9.4.0) expect-type: 1.1.0 @@ -10974,11 +10906,11 @@ snapshots: tinypool: 1.0.1 tinyrainbow: 1.2.0 vite: 5.4.9(@types/node@22.10.1) - vite-node: 2.1.6(@types/node@22.10.1) + vite-node: 2.1.8(@types/node@22.10.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.10.1 - '@vitest/browser': 2.1.6(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.6) + '@vitest/browser': 2.1.8(@types/node@22.10.1)(bufferutil@4.0.8)(playwright@1.49.0)(typescript@5.7.2)(vite@5.4.9(@types/node@22.10.1))(vitest@2.1.8) jsdom: 20.0.3(bufferutil@4.0.8) transitivePeerDependencies: - less From 4c73dd3a043f59b5f87c1f641bfbda0ca5f06885 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 12:30:29 +0000 Subject: [PATCH 12/16] chore(release): zimic@0.11.0-canary.5 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf25f325d..3dc612b1b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.4", + "version": "0.11.0-canary.5", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 74ae1e2c5..12ad19fb9 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.4", + "version": "0.11.0-canary.5", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 4d910a7002f3d0f17838ada9aa465a574f1bfc96 Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Sun, 8 Dec 2024 09:59:36 -0300 Subject: [PATCH 13/16] perf(#zimic): improve interceptor typing performance (#502) (#504) Closes #502. --- .../zimic/src/http/formData/HttpFormData.ts | 32 +-- .../zimic/src/http/headers/HttpHeaders.ts | 24 +- packages/zimic/src/http/headers/types.ts | 4 +- .../src/http/searchParams/HttpSearchParams.ts | 30 +-- packages/zimic/src/http/searchParams/types.ts | 4 +- packages/zimic/src/http/types/schema.ts | 12 +- .../http/interceptor/HttpInterceptorClient.ts | 14 +- .../__tests__/shared/bodies/blob.ts | 4 +- .../__tests__/shared/bodies/formData.ts | 6 +- .../__tests__/shared/bodies/json.ts | 6 +- .../__tests__/shared/bodies/plainText.ts | 6 +- .../__tests__/shared/bodies/searchParams.ts | 6 +- .../interceptor/__tests__/shared/bypass.ts | 4 +- .../interceptor/__tests__/shared/clear.ts | 4 +- .../interceptor/__tests__/shared/handlers.ts | 4 +- .../interceptor/__tests__/shared/lifeCycle.ts | 4 +- .../__tests__/shared/pathParams.ts | 4 +- .../__tests__/shared/restrictions.ts | 4 +- .../interceptor/__tests__/shared/types.ts | 3 +- .../shared/unhandledRequests.factories.ts | 4 +- .../shared/unhandledRequests.logging.ts | 4 +- .../http/interceptor/types/options.ts | 4 +- .../http/interceptor/types/public.ts | 151 +++++++++---- .../HttpInterceptorWorker.ts | 27 +-- .../HttpRequestHandlerClient.ts | 38 ++-- .../requestHandler/LocalHttpRequestHandler.ts | 21 +- .../RemoteHttpRequestHandler.ts | 24 +- .../http/requestHandler/types/public.ts | 211 +++++++++++------- .../http/requestHandler/types/requests.ts | 29 +-- packages/zimic/src/types/utils.ts | 6 - packages/zimic/src/utils/data.ts | 6 +- 31 files changed, 377 insertions(+), 323 deletions(-) diff --git a/packages/zimic/src/http/formData/HttpFormData.ts b/packages/zimic/src/http/formData/HttpFormData.ts index ff36dcf82..0c4de9e86 100644 --- a/packages/zimic/src/http/formData/HttpFormData.ts +++ b/packages/zimic/src/http/formData/HttpFormData.ts @@ -1,4 +1,4 @@ -import { ArrayItemIfArray, ArrayKey, Defined, NonArrayKey, ReplaceBy } from '@/types/utils'; +import { ArrayItemIfArray, ArrayKey, NonArrayKey, ReplaceBy } from '@/types/utils'; import { fileEquals } from '@/utils/files'; import { HttpFormDataSchema } from './types'; @@ -33,16 +33,16 @@ class HttpFormData exten /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/set MDN Reference} */ set( name: Name, - value: Exclude>, Blob>, + value: Exclude>, Blob>, ): void; set( name: Name, - blob: Exclude>, string>, + blob: Exclude>, string>, fileName?: string, ): void; set( name: Name, - blobOrValue: ArrayItemIfArray>, + blobOrValue: ArrayItemIfArray>, fileName?: string, ): void { if (fileName === undefined) { @@ -55,16 +55,16 @@ class HttpFormData exten /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/append MDN Reference} */ append( name: Name, - value: Exclude>, Blob>, + value: Exclude>, Blob>, ): void; append( name: Name, - blob: Exclude>, string>, + blob: Exclude>, string>, fileName?: string, ): void; append( name: Name, - blobOrValue: ArrayItemIfArray>, + blobOrValue: ArrayItemIfArray>, fileName?: string, ): void { if (fileName === undefined) { @@ -100,8 +100,8 @@ class HttpFormData exten */ getAll & string>( name: Name, - ): ReplaceBy>, Blob, File>[] { - return super.getAll(name) as ReplaceBy>, Blob, File>[]; + ): ReplaceBy>, Blob, File>[] { + return super.getAll(name) as ReplaceBy>, Blob, File>[]; } /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/has MDN Reference} */ @@ -116,7 +116,7 @@ class HttpFormData exten forEach>( callback: ( - value: ReplaceBy>, Blob, File>, + value: ReplaceBy>, Blob, File>, key: Key, parent: HttpFormData, ) => void, @@ -131,26 +131,26 @@ class HttpFormData exten } /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/values MDN Reference} */ - values(): FormDataIterator>, Blob, File>> { + values(): FormDataIterator>, Blob, File>> { return super.values() as FormDataIterator< - ReplaceBy>, Blob, File> + ReplaceBy>, Blob, File> >; } /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/entries MDN Reference} */ entries(): FormDataIterator< - [keyof Schema & string, ReplaceBy>, Blob, File>] + [keyof Schema & string, ReplaceBy>, Blob, File>] > { return super.entries() as FormDataIterator< - [keyof Schema & string, ReplaceBy>, Blob, File>] + [keyof Schema & string, ReplaceBy>, Blob, File>] >; } [Symbol.iterator](): FormDataIterator< - [keyof Schema & string, ReplaceBy>, Blob, File>] + [keyof Schema & string, ReplaceBy>, Blob, File>] > { return super[Symbol.iterator]() as FormDataIterator< - [keyof Schema & string, ReplaceBy>, Blob, File>] + [keyof Schema & string, ReplaceBy>, Blob, File>] >; } diff --git a/packages/zimic/src/http/headers/HttpHeaders.ts b/packages/zimic/src/http/headers/HttpHeaders.ts index 35af022c2..84d579f37 100644 --- a/packages/zimic/src/http/headers/HttpHeaders.ts +++ b/packages/zimic/src/http/headers/HttpHeaders.ts @@ -1,4 +1,4 @@ -import { Default, Defined, ReplaceBy } from '@/types/utils'; +import { Default, ReplaceBy } from '@/types/utils'; import { HttpHeadersSchema, HttpHeadersInit, HttpHeadersSchemaName } from './types'; @@ -44,12 +44,12 @@ class HttpHeaders extends } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/set MDN Reference} */ - set>(name: Name, value: Defined): void { + set>(name: Name, value: NonNullable): void { super.set(name, value); } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/append MDN Reference} */ - append>(name: Name, value: Defined): void { + append>(name: Name, value: NonNullable): void { super.append(name, value); } @@ -59,8 +59,8 @@ class HttpHeaders extends } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/has MDN Reference} */ - getSetCookie(): Defined>[] { - return super.getSetCookie() as Defined>[]; + getSetCookie(): NonNullable>[] { + return super.getSetCookie() as NonNullable>[]; } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/has MDN Reference} */ @@ -75,7 +75,7 @@ class HttpHeaders extends forEach>( callback: >( - value: Defined, + value: NonNullable, key: Key, parent: Headers, ) => void, @@ -90,22 +90,22 @@ class HttpHeaders extends } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/values MDN Reference} */ - values(): HeadersIterator]>> { - return super.values() as HeadersIterator]>>; + values(): HeadersIterator]>> { + return super.values() as HeadersIterator]>>; } /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/entries MDN Reference} */ - entries(): HeadersIterator<[HttpHeadersSchemaName, Defined]>]> { + entries(): HeadersIterator<[HttpHeadersSchemaName, NonNullable]>]> { return super.entries() as HeadersIterator< - [HttpHeadersSchemaName, Defined]>] + [HttpHeadersSchemaName, NonNullable]>] >; } [Symbol.iterator](): HeadersIterator< - [HttpHeadersSchemaName, Defined]>] + [HttpHeadersSchemaName, NonNullable]>] > { return super[Symbol.iterator]() as HeadersIterator< - [HttpHeadersSchemaName, Defined]>] + [HttpHeadersSchemaName, NonNullable]>] >; } diff --git a/packages/zimic/src/http/headers/types.ts b/packages/zimic/src/http/headers/types.ts index d1a57949f..d17449ceb 100644 --- a/packages/zimic/src/http/headers/types.ts +++ b/packages/zimic/src/http/headers/types.ts @@ -1,4 +1,4 @@ -import { Defined, IfNever } from '@/types/utils'; +import { IfNever } from '@/types/utils'; import { HttpPathParamsSerialized } from '../pathParams/types'; import HttpHeaders from './HttpHeaders'; @@ -16,7 +16,7 @@ export namespace HttpHeadersSchema { /** A strict tuple representation of a {@link HttpHeadersSchema}. */ export type HttpHeadersSchemaTuple = { - [Key in keyof Schema & string]: [Key, Defined]; + [Key in keyof Schema & string]: [Key, NonNullable]; }[keyof Schema & string]; /** An initialization value for {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐http#httpheaders `HttpHeaders`}. */ diff --git a/packages/zimic/src/http/searchParams/HttpSearchParams.ts b/packages/zimic/src/http/searchParams/HttpSearchParams.ts index 1a547ae7e..3a8331f46 100644 --- a/packages/zimic/src/http/searchParams/HttpSearchParams.ts +++ b/packages/zimic/src/http/searchParams/HttpSearchParams.ts @@ -1,4 +1,4 @@ -import { ReplaceBy, Defined, ArrayItemIfArray } from '@/types/utils'; +import { ReplaceBy, ArrayItemIfArray } from '@/types/utils'; import { HttpSearchParamsSchema, HttpSearchParamsInit, HttpSearchParamsSchemaName } from './types'; @@ -64,7 +64,7 @@ class HttpSearchParams>( name: Name, - value: ArrayItemIfArray>, + value: ArrayItemIfArray>, ): void { super.set(name, value); } @@ -72,7 +72,7 @@ class HttpSearchParams>( name: Name, - value: ArrayItemIfArray>, + value: ArrayItemIfArray>, ): void { super.append(name, value); } @@ -101,14 +101,16 @@ class HttpSearchParams>(name: Name): ArrayItemIfArray>[] { - return super.getAll(name) as ArrayItemIfArray>[]; + getAll>( + name: Name, + ): ArrayItemIfArray>[] { + return super.getAll(name) as ArrayItemIfArray>[]; } /** @see {@link https://developer.mozilla.org/docs/Web/API/URLSearchParams/has MDN Reference} */ has>( name: Name, - value?: ArrayItemIfArray>, + value?: ArrayItemIfArray>, ): boolean { return super.has(name, value); } @@ -116,14 +118,14 @@ class HttpSearchParams>( name: Name, - value?: ArrayItemIfArray>, + value?: ArrayItemIfArray>, ): void { super.delete(name, value); } forEach>( callback: >( - value: ArrayItemIfArray>, + value: ArrayItemIfArray>, key: Key, parent: HttpSearchParams, ) => void, @@ -138,26 +140,26 @@ class HttpSearchParams]>>> { + values(): URLSearchParamsIterator]>>> { return super.values() as URLSearchParamsIterator< - ArrayItemIfArray]>> + ArrayItemIfArray]>> >; } /** @see {@link https://developer.mozilla.org/docs/Web/API/URLSearchParams/entries MDN Reference} */ entries(): URLSearchParamsIterator< - [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] + [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] > { return super.entries() as URLSearchParamsIterator< - [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] + [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] >; } [Symbol.iterator](): URLSearchParamsIterator< - [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] + [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] > { return super[Symbol.iterator]() as URLSearchParamsIterator< - [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] + [HttpSearchParamsSchemaName, ArrayItemIfArray]>>] >; } diff --git a/packages/zimic/src/http/searchParams/types.ts b/packages/zimic/src/http/searchParams/types.ts index 85c5e53d9..e466b57b4 100644 --- a/packages/zimic/src/http/searchParams/types.ts +++ b/packages/zimic/src/http/searchParams/types.ts @@ -1,4 +1,4 @@ -import { Defined, ArrayItemIfArray, IfNever, NonArrayKey, ArrayKey } from '@/types/utils'; +import { ArrayItemIfArray, IfNever, NonArrayKey, ArrayKey } from '@/types/utils'; import HttpSearchParams from './HttpSearchParams'; @@ -15,7 +15,7 @@ export namespace HttpSearchParamsSchema { /** A strict tuple representation of a {@link HttpSearchParamsSchema}. */ export type HttpSearchParamsSchemaTuple = { - [Key in keyof Schema & string]: [Key, ArrayItemIfArray>]; + [Key in keyof Schema & string]: [Key, ArrayItemIfArray>]; }[keyof Schema & string]; /** diff --git a/packages/zimic/src/http/types/schema.ts b/packages/zimic/src/http/types/schema.ts index bf2816dd1..1936571a5 100644 --- a/packages/zimic/src/http/types/schema.ts +++ b/packages/zimic/src/http/types/schema.ts @@ -218,10 +218,8 @@ type ConvertToStrictHttpResponseSchemaByStatusCode = { * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http‐schemas Declaring HTTP interceptor schemas} */ -export type HttpResponseSchemaStatusCode = Extract< - keyof ResponseSchemaByStatusCode, - HttpStatusCode ->; +export type HttpResponseSchemaStatusCode = + keyof ResponseSchemaByStatusCode & HttpStatusCode; /** * A schema representing the structure of an HTTP request and response for a given method. @@ -588,7 +586,7 @@ export namespace HttpSchema { export type HttpSchemaMethod = IfAny< Schema, any, // eslint-disable-line @typescript-eslint/no-explicit-any - Extract, HttpMethod> + keyof UnionToIntersection & HttpMethod >; type AllowAnyStringInPathParams = Path extends `${infer Prefix}:${string}/${infer Suffix}` @@ -627,8 +625,8 @@ type AllowAnyStringInPathParams = Path extends `${infer Pre */ export namespace HttpSchemaPath { type LooseLiteral = { - [Path in Extract]: Method extends keyof Schema[Path] ? Path : never; - }[Extract]; + [Path in keyof Schema & string]: Method extends keyof Schema[Path] ? Path : never; + }[keyof Schema & string]; /** * Extracts the literal paths from an HTTP service schema. Optionally receives a second argument with one or more diff --git a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts index 8385ddb04..751f24e7b 100644 --- a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts +++ b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts @@ -1,10 +1,10 @@ import { HTTP_METHODS, HttpMethod, - HttpResponseSchemaStatusCode, HttpSchema, HttpSchemaMethod, HttpSchemaPath, + HttpStatusCode, } from '@/http/types/schema'; import { Default, PossiblePromise } from '@/types/utils'; import { joinURL, ExtendedURL, createRegexFromURL } from '@/utils/urls'; @@ -14,7 +14,7 @@ import LocalHttpInterceptorWorker from '../interceptorWorker/LocalHttpIntercepto import HttpRequestHandlerClient, { AnyHttpRequestHandlerClient } from '../requestHandler/HttpRequestHandlerClient'; import LocalHttpRequestHandler from '../requestHandler/LocalHttpRequestHandler'; import RemoteHttpRequestHandler from '../requestHandler/RemoteHttpRequestHandler'; -import { HttpRequestHandler } from '../requestHandler/types/public'; +import { HttpRequestHandler, InternalHttpRequestHandler } from '../requestHandler/types/public'; import { HttpInterceptorRequest } from '../requestHandler/types/requests'; import NotStartedHttpInterceptorError from './errors/NotStartedHttpInterceptorError'; import HttpInterceptorStore from './HttpInterceptorStore'; @@ -161,12 +161,8 @@ class HttpInterceptorClient< registerRequestHandler< Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, - >( - handler: - | LocalHttpRequestHandler - | RemoteHttpRequestHandler, - ) { + StatusCode extends HttpStatusCode = never, + >(handler: InternalHttpRequestHandler) { const handlerClients = this.handlerClientsByMethod[handler.method()].get(handler.path()) ?? []; if (!handlerClients.includes(handler.client())) { handlerClients.push(handler.client()); @@ -201,7 +197,7 @@ class HttpInterceptorClient< Method extends HttpSchemaMethod, Path extends HttpSchemaPath, Context extends HttpInterceptorRequestContext, - >(matchedURLRegex: RegExp, method: Method, path: Path, { request }: Context) { + >(matchedURLRegex: RegExp, method: Method, path: Path, { request }: Context): Promise { const parsedRequest = await HttpInterceptorWorker.parseRawRequest>(request, { urlRegex: matchedURLRegex, }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/blob.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/blob.ts index 729e621f9..4fe9aa4f4 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/blob.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/blob.ts @@ -61,13 +61,11 @@ export async function declareBlobBodyHttpInterceptorTests(options: RuntimeShared let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS_WITH_REQUEST_BODY)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/formData.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/formData.ts index 0180ce4eb..d48145adb 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/formData.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/formData.ts @@ -17,7 +17,7 @@ import { HttpInterceptorOptions } from '../../../types/options'; import { RuntimeSharedHttpInterceptorTestsOptions } from '../utils'; export async function declareFormDataBodyHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { - const { getBaseURL, getInterceptorOptions } = options; + const { type, getBaseURL, getInterceptorOptions } = options; const crypto = await importCrypto(); @@ -34,13 +34,11 @@ export async function declareFormDataBodyHttpInterceptorTests(options: RuntimeSh let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS_WITH_REQUEST_BODY)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/json.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/json.ts index ba07b1735..03ccb8d5a 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/json.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/json.ts @@ -16,7 +16,7 @@ import { HttpInterceptorOptions } from '../../../types/options'; import { RuntimeSharedHttpInterceptorTestsOptions } from '../utils'; export async function declareJSONBodyHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { - const { getBaseURL, getInterceptorOptions } = options; + const { type, getBaseURL, getInterceptorOptions } = options; const crypto = await importCrypto(); @@ -38,13 +38,11 @@ export async function declareJSONBodyHttpInterceptorTests(options: RuntimeShared let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS_WITH_REQUEST_BODY)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/plainText.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/plainText.ts index 10774b7e7..89db33e75 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/plainText.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/plainText.ts @@ -13,7 +13,7 @@ import { HttpInterceptorOptions } from '../../../types/options'; import { RuntimeSharedHttpInterceptorTestsOptions } from '../utils'; export async function declarePlainTextBodyHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { - const { getBaseURL, getInterceptorOptions } = options; + const { type, getBaseURL, getInterceptorOptions } = options; const crypto = await importCrypto(); @@ -30,13 +30,11 @@ export async function declarePlainTextBodyHttpInterceptorTests(options: RuntimeS let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS_WITH_REQUEST_BODY)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/searchParams.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/searchParams.ts index 096f6305e..afa4747d1 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/searchParams.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bodies/searchParams.ts @@ -15,7 +15,7 @@ import { HttpInterceptorOptions } from '../../../types/options'; import { RuntimeSharedHttpInterceptorTestsOptions } from '../utils'; export async function declareSearchParamsBodyHttpInterceptorTests(options: RuntimeSharedHttpInterceptorTestsOptions) { - const { getBaseURL, getInterceptorOptions } = options; + const { type, getBaseURL, getInterceptorOptions } = options; const crypto = await importCrypto(); @@ -32,13 +32,11 @@ export async function declareSearchParamsBodyHttpInterceptorTests(options: Runti let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = options.type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS_WITH_REQUEST_BODY)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts index 4ef938ff6..d526c99fa 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts @@ -18,13 +18,11 @@ export function declareBypassHttpInterceptorTests(options: RuntimeSharedHttpInte let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts index 5b5a246ca..c237f1f19 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts @@ -18,13 +18,11 @@ export function declareClearHttpInterceptorTests(options: RuntimeSharedHttpInter let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts index f48639553..2ce487f42 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/handlers.ts @@ -25,7 +25,7 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; type MethodSchema = HttpSchema.Method<{ response: { 200: { headers: AccessControlHeaders } }; @@ -34,8 +34,6 @@ export async function declareHandlerHttpInterceptorTests(options: RuntimeSharedH beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts index 27036fb4b..2d75beace 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts @@ -24,13 +24,11 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts index f572f85c8..2c525cebe 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts @@ -31,13 +31,11 @@ export async function declarePathParamsHttpInterceptorTests(options: RuntimeShar let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts index 3749ba7f0..a758b3ea3 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/restrictions.ts @@ -26,13 +26,11 @@ export async function declareRestrictionsHttpInterceptorTests(options: RuntimeSh let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); interface RequestHeadersSchema { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/types.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/types.ts index cea530105..9d4858391 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/types.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/types.ts @@ -5,7 +5,6 @@ import HttpHeaders from '@/http/headers/HttpHeaders'; import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; import { HttpSchema, HttpStatusCode, MergeHttpResponsesByStatusCode } from '@/http/types/schema'; import { HttpRequestHandlerPath } from '@/interceptor/http/requestHandler/types/utils'; -import { Prettify } from '@/types/utils'; import { usingHttpInterceptor } from '@tests/utils/interceptors'; import { createHttpInterceptor } from '../../factory'; @@ -1023,7 +1022,7 @@ export function declareTypeHttpInterceptorTests( type CompositeInterceptorSchema = InferHttpInterceptorSchema; type InlineInterceptorSchema = InferHttpInterceptorSchema; - expectTypeOf>().toEqualTypeOf>(); + expectTypeOf().toEqualTypeOf(); }); describe('Dynamic paths', () => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts index 10170eab4..7f255039b 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.factories.ts @@ -26,7 +26,7 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; type MethodSchemaWithoutRequestBody = HttpSchema.Method<{ request: { @@ -50,8 +50,6 @@ export async function declareUnhandledRequestFactoriesHttpInterceptorTests( beforeEach(() => { baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; }); describe.each(HTTP_METHODS)('Method (%s)', (method) => { diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts index fda38e0e5..a226f964b 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts @@ -33,7 +33,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( let baseURL: URL; let interceptorOptions: HttpInterceptorOptions; - let Handler: typeof LocalHttpRequestHandler | typeof RemoteHttpRequestHandler; + const Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; type MethodSchemaWithoutRequestBody = HttpSchema.Method<{ request: { @@ -79,8 +79,6 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( baseURL = getBaseURL(); interceptorOptions = getInterceptorOptions(); - Handler = type === 'local' ? LocalHttpRequestHandler : RemoteHttpRequestHandler; - httpInterceptor.default.local.onUnhandledRequest = { action: 'reject', log: true }; httpInterceptor.default.remote.onUnhandledRequest = { action: 'reject', log: true }; }); diff --git a/packages/zimic/src/interceptor/http/interceptor/types/options.ts b/packages/zimic/src/interceptor/http/interceptor/types/options.ts index 68518e784..fc3d22890 100644 --- a/packages/zimic/src/interceptor/http/interceptor/types/options.ts +++ b/packages/zimic/src/interceptor/http/interceptor/types/options.ts @@ -84,14 +84,14 @@ export namespace UnhandledRequestStrategy { * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#unhandled-requests Unhandled requests} */ - export type RemoteDeclaration = Declaration>; + export type RemoteDeclaration = Declaration<'reject'>; /** * A factory to create dynamic unhandled request strategies based on the intercepted request in remote interceptors. * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#unhandled-requests Unhandled requests} */ - export type RemoteDeclarationFactory = DeclarationFactory>; + export type RemoteDeclarationFactory = DeclarationFactory<'reject'>; /** The static declaration or a factory of the strategy to use for unhandled requests in local interceptors. */ export type Local = LocalDeclaration | LocalDeclarationFactory; diff --git a/packages/zimic/src/interceptor/http/interceptor/types/public.ts b/packages/zimic/src/interceptor/http/interceptor/types/public.ts index ad36bdcb9..a99213a86 100644 --- a/packages/zimic/src/interceptor/http/interceptor/types/public.ts +++ b/packages/zimic/src/interceptor/http/interceptor/types/public.ts @@ -1,10 +1,6 @@ import { HttpSchema } from '@/http/types/schema'; -import { - HttpInterceptorMethodHandler, - SyncHttpInterceptorMethodHandler, - AsyncHttpInterceptorMethodHandler, -} from './handlers'; +import { SyncHttpInterceptorMethodHandler, AsyncHttpInterceptorMethodHandler } from './handlers'; import { HttpInterceptorPlatform } from './options'; /** @@ -13,7 +9,9 @@ import { HttpInterceptorPlatform } from './options'; * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httpinterceptor `HttpInterceptor` API reference} */ -export interface HttpInterceptor { +// The schema is still a generic type for backward compatibility. +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface HttpInterceptor<_Schema extends HttpSchema> { /** * @returns The base URL used by the interceptor. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorbaseurl `interceptor.baseURL()` API reference} @@ -51,6 +49,32 @@ export interface HttpInterceptor { */ stop: () => Promise; + /** + * Clears all of the + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * instances created by this interceptor. After calling this method, the interceptor will no longer intercept any + * requests until new mock responses are registered. + * + * This method is useful to reset the interceptor mocks between tests. + * + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorclear `interceptor.clear()` API reference} + */ + clear: (() => void) | (() => Promise); +} + +/** + * A local interceptor to handle HTTP requests and return mock responses. The methods, paths, status codes, parameters, + * and responses are statically-typed based on the provided service schema. + * + * To intercept HTTP requests, the interceptor must have been started with + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorstart `interceptor.start()`}. + * + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httpinterceptor `HttpInterceptor` API reference} + */ +export interface LocalHttpInterceptor extends HttpInterceptor { + readonly type: 'local'; + /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need * to specify the original path as a type parameter to get type-inference and type-validation. @@ -60,7 +84,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - get: HttpInterceptorMethodHandler; + get: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -71,7 +95,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - post: HttpInterceptorMethodHandler; + post: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -82,7 +106,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - patch: HttpInterceptorMethodHandler; + patch: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -93,7 +117,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - put: HttpInterceptorMethodHandler; + put: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -104,7 +128,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - delete: HttpInterceptorMethodHandler; + delete: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -115,7 +139,7 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - head: HttpInterceptorMethodHandler; + head: SyncHttpInterceptorMethodHandler; /** * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need @@ -126,40 +150,6 @@ export interface HttpInterceptor { * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} */ - options: HttpInterceptorMethodHandler; - - /** - * Clears all of the - * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} - * instances created by this interceptor. After calling this method, the interceptor will no longer intercept any - * requests until new mock responses are registered. - * - * This method is useful to reset the interceptor mocks between tests. - * - * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. - * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorclear `interceptor.clear()` API reference} - */ - clear: () => void; -} - -/** - * A local interceptor to handle HTTP requests and return mock responses. The methods, paths, status codes, parameters, - * and responses are statically-typed based on the provided service schema. - * - * To intercept HTTP requests, the interceptor must have been started with - * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorstart `interceptor.start()`}. - * - * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httpinterceptor `HttpInterceptor` API reference} - */ -export interface LocalHttpInterceptor extends HttpInterceptor { - readonly type: 'local'; - - get: SyncHttpInterceptorMethodHandler; - post: SyncHttpInterceptorMethodHandler; - patch: SyncHttpInterceptorMethodHandler; - put: SyncHttpInterceptorMethodHandler; - delete: SyncHttpInterceptorMethodHandler; - head: SyncHttpInterceptorMethodHandler; options: SyncHttpInterceptorMethodHandler; clear: () => void; @@ -179,12 +169,81 @@ export interface LocalHttpInterceptor extends HttpInt export interface RemoteHttpInterceptor extends HttpInterceptor { readonly type: 'remote'; + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A GET + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ get: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A POST + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ post: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A PATCH + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ patch: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A PUT + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ put: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A DELETE + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ delete: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns A HEAD + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ head: AsyncHttpInterceptorMethodHandler; + + /** + * @param path The path to intercept. Paths with dynamic parameters, such as `/users/:id`, are supported, but you need + * to specify the original path as a type parameter to get type-inference and type-validation. + * @returns An OPTIONS + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} + * for the provided path. The path and method must be declared in the interceptor schema. + * @throws {NotStartedHttpInterceptorError} If the interceptor is not running. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference} + */ options: AsyncHttpInterceptorMethodHandler; clear: () => Promise; diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts index ac143a5e0..80f71f103 100644 --- a/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts +++ b/packages/zimic/src/interceptor/http/interceptorWorker/HttpInterceptorWorker.ts @@ -1,18 +1,12 @@ import chalk from 'chalk'; import InvalidJSONError from '@/errors/InvalidJSONError'; +import { HttpHeadersInit } from '@/http'; import InvalidFormDataError from '@/http/errors/InvalidFormDataError'; import HttpFormData from '@/http/formData/HttpFormData'; import HttpHeaders from '@/http/headers/HttpHeaders'; -import { HttpHeadersInit, HttpHeadersSchema } from '@/http/headers/types'; import { HttpBody, HttpRequest, HttpResponse } from '@/http/types/requests'; -import { - HttpMethod, - HttpMethodSchema, - HttpResponseSchemaStatusCode, - HttpSchema, - InferPathParams, -} from '@/http/types/schema'; +import { HttpMethod, HttpMethodSchema, HttpSchema, HttpStatusCode, InferPathParams } from '@/http/types/schema'; import { Default, PossiblePromise } from '@/types/utils'; import { removeArrayElement } from '@/utils/arrays'; import { formatObjectToLog, logWithPrefix } from '@/utils/console'; @@ -235,14 +229,14 @@ abstract class HttpInterceptorWorker { abstract interceptorsWithHandlers(): AnyHttpInterceptorClient[]; - static createResponseFromDeclaration< - Declaration extends { + static createResponseFromDeclaration( + request: HttpRequest, + declaration: { status: number; - headers?: HttpHeadersInit; + headers?: HttpHeadersInit; body?: HttpBody; }, - HeadersSchema extends HttpHeadersSchema, - >(request: HttpRequest, declaration: Declaration) { + ): Response { const headers = new HttpHeaders(declaration.headers); const status = declaration.status; @@ -329,10 +323,9 @@ abstract class HttpInterceptorWorker { return HTTP_INTERCEPTOR_REQUEST_HIDDEN_PROPERTIES.has(property as never); } - static async parseRawResponse< - MethodSchema extends HttpMethodSchema, - StatusCode extends HttpResponseSchemaStatusCode>, - >(originalRawResponse: HttpResponse): Promise> { + static async parseRawResponse( + originalRawResponse: HttpResponse, + ): Promise> { const rawResponse = originalRawResponse.clone(); const rawResponseClone = rawResponse.clone(); diff --git a/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts b/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts index 5448ca39c..319405000 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts @@ -1,17 +1,19 @@ import HttpFormData from '@/http/formData/HttpFormData'; import HttpHeaders from '@/http/headers/HttpHeaders'; import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; -import { HttpResponseSchemaStatusCode, HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; -import { Default, IfAny } from '@/types/utils'; +import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpStatusCode } from '@/http/types/schema'; +import { Default } from '@/types/utils'; import { blobContains, blobEquals } from '@/utils/data'; import { jsonContains, jsonEquals } from '@/utils/json'; import HttpInterceptorClient from '../interceptor/HttpInterceptorClient'; import DisabledRequestSavingError from './errors/DisabledRequestSavingError'; import NoResponseDefinitionError from './errors/NoResponseDefinitionError'; -import LocalHttpRequestHandler from './LocalHttpRequestHandler'; -import RemoteHttpRequestHandler from './RemoteHttpRequestHandler'; -import { HttpRequestHandlerRestriction, HttpRequestHandlerStaticRestriction } from './types/public'; +import { + HttpRequestHandlerRestriction, + HttpRequestHandlerStaticRestriction, + InternalHttpRequestHandler, +} from './types/public'; import { HttpInterceptorRequest, HttpInterceptorResponse, @@ -26,11 +28,7 @@ class HttpRequestHandlerClient< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends IfAny< - Schema, - any, // eslint-disable-line @typescript-eslint/no-explicit-any - HttpResponseSchemaStatusCode['response']>> - > = never, + StatusCode extends HttpStatusCode = never, > { private restrictions: HttpRequestHandlerRestriction[] = []; private interceptedRequests: TrackedHttpInterceptorRequest, StatusCode>[] = []; @@ -45,9 +43,7 @@ class HttpRequestHandlerClient< private interceptor: HttpInterceptorClient, private _method: Method, private _path: Path, - private handler: - | LocalHttpRequestHandler - | RemoteHttpRequestHandler, + private handler: InternalHttpRequestHandler, ) {} method() { @@ -65,14 +61,14 @@ class HttpRequestHandlerClient< return this; } - respond['response']>>>( + respond( declaration: | HttpRequestHandlerResponseDeclaration, NewStatusCode> | HttpRequestHandlerResponseDeclarationFactory, NewStatusCode>, ): HttpRequestHandlerClient { const newThis = this as unknown as HttpRequestHandlerClient; - newThis.createResponseDeclaration = this.isResponseDeclarationFactory(declaration) + newThis.createResponseDeclaration = this.isResponseDeclarationFactory(declaration) ? declaration : () => declaration; newThis.interceptedRequests = []; @@ -82,12 +78,10 @@ class HttpRequestHandlerClient< return newThis; } - private isResponseDeclarationFactory< - StatusCode extends HttpResponseSchemaStatusCode['response']>>, - >( + private isResponseDeclarationFactory( declaration: - | HttpRequestHandlerResponseDeclaration, StatusCode> - | HttpRequestHandlerResponseDeclarationFactory, StatusCode>, + | HttpRequestHandlerResponseDeclaration> + | HttpRequestHandlerResponseDeclarationFactory>, ) { return typeof declaration === 'function'; } @@ -164,8 +158,8 @@ class HttpRequestHandlerClient< return true; } - const body: unknown = request.body; - const restrictionBody: unknown = restriction.body; + const body = request.body as unknown; + const restrictionBody = restriction.body as unknown; if (typeof body === 'string' && typeof restrictionBody === 'string') { return restriction.exact ? body === restrictionBody : body.includes(restrictionBody); diff --git a/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts b/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts index fd16ffd54..f989ce017 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts @@ -1,12 +1,9 @@ -import { HttpResponseSchemaStatusCode, HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; +import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpStatusCode } from '@/http/types/schema'; import { Default } from '@/types/utils'; import HttpInterceptorClient from '../interceptor/HttpInterceptorClient'; import HttpRequestHandlerClient from './HttpRequestHandlerClient'; -import { - HttpRequestHandlerRestriction, - LocalHttpRequestHandler as PublicLocalHttpRequestHandler, -} from './types/public'; +import { HttpRequestHandlerRestriction, InternalHttpRequestHandler } from './types/public'; import { HttpInterceptorRequest, HttpInterceptorResponse, @@ -19,8 +16,8 @@ class LocalHttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, -> implements PublicLocalHttpRequestHandler + StatusCode extends HttpStatusCode = never, +> implements InternalHttpRequestHandler { readonly type = 'local'; @@ -42,14 +39,12 @@ class LocalHttpRequestHandler< return this._client.path(); } - with( - restriction: HttpRequestHandlerRestriction, - ): LocalHttpRequestHandler { + with(restriction: HttpRequestHandlerRestriction): this { this._client.with(restriction); return this; } - respond['response']>>>( + respond( declaration: | HttpRequestHandlerResponseDeclaration, NewStatusCode> | HttpRequestHandlerResponseDeclarationFactory, NewStatusCode>, @@ -60,12 +55,12 @@ class LocalHttpRequestHandler< return newThis; } - bypass(): LocalHttpRequestHandler { + bypass(): this { this._client.bypass(); return this; } - clear(): LocalHttpRequestHandler { + clear(): this { this._client.clear(); return this; } diff --git a/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts b/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts index ff40e358c..889dc04da 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts @@ -1,11 +1,11 @@ -import { HttpResponseSchemaStatusCode, HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; +import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpStatusCode } from '@/http/types/schema'; import { Default, PossiblePromise } from '@/types/utils'; import HttpInterceptorClient from '../interceptor/HttpInterceptorClient'; import HttpRequestHandlerClient from './HttpRequestHandlerClient'; import { HttpRequestHandlerRestriction, - RemoteHttpRequestHandler as PublicRemoteHttpRequestHandler, + InternalHttpRequestHandler, SyncedRemoteHttpRequestHandler as PublicSyncedRemoteHttpRequestHandler, } from './types/public'; import { @@ -22,8 +22,8 @@ class RemoteHttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, -> implements PublicRemoteHttpRequestHandler + StatusCode extends HttpStatusCode = never, +> implements InternalHttpRequestHandler { readonly type = 'remote'; @@ -42,14 +42,14 @@ class RemoteHttpRequestHandler< private createSyncedProxy() { return new Proxy(this, { - has: (target, property: keyof RemoteHttpRequestHandler) => { + has: (target, property) => { if (this.isHiddenPropertyWhenSynced(property)) { return false; } return Reflect.has(target, property); }, - get: (target, property: keyof RemoteHttpRequestHandler) => { + get: (target, property) => { if (this.isHiddenPropertyWhenSynced(property)) { return undefined; } @@ -58,7 +58,7 @@ class RemoteHttpRequestHandler< }); } - private isHiddenPropertyWhenSynced(property: string) { + private isHiddenPropertyWhenSynced(property: string | symbol) { return PENDING_PROPERTIES.has(property); } @@ -74,14 +74,12 @@ class RemoteHttpRequestHandler< return this._client.path(); } - with( - restriction: HttpRequestHandlerRestriction, - ): RemoteHttpRequestHandler { + with(restriction: HttpRequestHandlerRestriction): this { this._client.with(restriction); return this.unsynced; } - respond['response']>>>( + respond( declaration: | HttpRequestHandlerResponseDeclaration, NewStatusCode> | HttpRequestHandlerResponseDeclarationFactory, NewStatusCode>, @@ -91,12 +89,12 @@ class RemoteHttpRequestHandler< return newUnsyncedThis; } - bypass(): RemoteHttpRequestHandler { + bypass(): this { this._client.bypass(); return this.unsynced; } - clear(): RemoteHttpRequestHandler { + clear(): this { this._client.clear(); return this.unsynced; } diff --git a/packages/zimic/src/interceptor/http/requestHandler/types/public.ts b/packages/zimic/src/interceptor/http/requestHandler/types/public.ts index 22d76e8ee..1734ee96d 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/types/public.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/types/public.ts @@ -4,9 +4,16 @@ import { HttpHeadersSchema } from '@/http/headers/types'; import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; import { HttpSearchParamsSchema } from '@/http/searchParams/types'; import { HttpBody } from '@/http/types/requests'; -import { HttpResponseSchemaStatusCode, HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@/http/types/schema'; -import { DeepPartial, Default, IfAny, IfNever, PossiblePromise } from '@/types/utils'; - +import { + HttpResponseSchemaStatusCode, + HttpSchema, + HttpSchemaMethod, + HttpSchemaPath, + HttpStatusCode, +} from '@/http/types/schema'; +import { DeepPartial, Default, IfNever, PossiblePromise } from '@/types/utils'; + +import HttpRequestHandlerClient from '../HttpRequestHandlerClient'; import { HttpInterceptorRequest, HttpRequestBodySchema, @@ -17,10 +24,11 @@ import { TrackedHttpInterceptorRequest, } from './requests'; -type PartialHttpHeadersOrSchema = - | Partial - | HttpHeaders> - | HttpHeaders; +type PartialHttpHeadersOrSchema = IfNever< + Schema, + never, + Partial | HttpHeaders> | HttpHeaders +>; /** * A static headers restriction to match intercepted requests. @@ -142,7 +150,6 @@ export interface HttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, > { /** * @returns The method that matches this handler. @@ -156,6 +163,34 @@ export interface HttpRequestHandler< * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerpath `handler.path()` API reference} */ path: () => Path; +} + +export interface InternalHttpRequestHandler< + Schema extends HttpSchema, + Method extends HttpSchemaMethod, + Path extends HttpSchemaPath, + StatusCode extends HttpStatusCode = never, +> extends HttpRequestHandler { + client: () => HttpRequestHandlerClient; +} + +/** + * A local HTTP request handler to declare responses for intercepted requests. In a local handler, the mocking + * operations are synchronous and are executed in the same process where it was created. + * + * When multiple handlers of the same interceptor match the same method and path, the _last_ handler created with + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)`} + * will be used. + * + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler` API reference} + */ +export interface LocalHttpRequestHandler< + Schema extends HttpSchema, + Method extends HttpSchemaMethod, + Path extends HttpSchemaPath, + StatusCode extends HttpStatusCode = never, +> extends HttpRequestHandler { + readonly type: 'local'; /** * Declares a restriction to intercepted request matches. `headers`, `searchParams`, and `body` are supported to limit @@ -173,10 +208,7 @@ export interface HttpRequestHandler< * @returns The same handler, now considering the specified restriction. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerwithrestriction `handler.with()` API reference} */ - - with: ( - restriction: HttpRequestHandlerRestriction, - ) => HttpRequestHandler; + with: (restriction: HttpRequestHandlerRestriction) => this; /** * Declares a response to return for matched intercepted requests. @@ -189,11 +221,11 @@ export interface HttpRequestHandler< * status code. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()` API reference} */ - respond: ['response']>>>( + respond: ['response']>>>( declaration: - | HttpRequestHandlerResponseDeclaration, StatusCode> - | HttpRequestHandlerResponseDeclarationFactory, StatusCode>, - ) => HttpRequestHandler; + | HttpRequestHandlerResponseDeclaration, NewStatusCode> + | HttpRequestHandlerResponseDeclarationFactory, NewStatusCode>, + ) => LocalHttpRequestHandler; /** * Clears any response declared with @@ -211,7 +243,7 @@ export interface HttpRequestHandler< * @returns The same handler, now without a declared responses. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass `handler.bypass()` API reference} */ - bypass: () => HttpRequestHandler; + bypass: () => this; /** * Clears any response declared with @@ -232,7 +264,7 @@ export interface HttpRequestHandler< * @returns The same handler, now cleared of any declared responses, restrictions, and intercepted requests. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear `handler.clear()` API reference} */ - clear: () => HttpRequestHandler; + clear: () => this; /** * Returns the intercepted requests that matched this handler, along with the responses returned to each of them. This @@ -246,47 +278,6 @@ export interface HttpRequestHandler< * @throws {DisabledRequestSavingError} If the interceptor was not created with `saveRequests: true`. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrequests `handler.requests()` API reference} */ - requests: - | (() => readonly TrackedHttpInterceptorRequest, StatusCode>[]) - | (() => Promise, StatusCode>[]>); -} - -/** - * A local HTTP request handler to declare responses for intercepted requests. In a local handler, the mocking - * operations are synchronous and are executed in the same process where it was created. - * - * When multiple handlers of the same interceptor match the same method and path, the _last_ handler created with - * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)`} - * will be used. - * - * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler` API reference} - */ -export interface LocalHttpRequestHandler< - Schema extends HttpSchema, - Method extends HttpSchemaMethod, - Path extends HttpSchemaPath, - StatusCode extends IfAny< - Schema, - any, // eslint-disable-line @typescript-eslint/no-explicit-any - HttpResponseSchemaStatusCode['response']>> - > = never, -> extends HttpRequestHandler { - readonly type: 'local'; - - with: ( - restriction: HttpRequestHandlerRestriction, - ) => LocalHttpRequestHandler; - - respond: ['response']>>>( - declaration: - | HttpRequestHandlerResponseDeclaration, StatusCode> - | HttpRequestHandlerResponseDeclarationFactory, StatusCode>, - ) => LocalHttpRequestHandler; - - bypass: () => LocalHttpRequestHandler; - - clear: () => LocalHttpRequestHandler; - requests: () => readonly TrackedHttpInterceptorRequest, StatusCode>[]; } @@ -301,22 +292,98 @@ export interface SyncedRemoteHttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, -> extends HttpRequestHandler { + StatusCode extends HttpStatusCode = never, +> extends HttpRequestHandler { + readonly type: 'remote'; + + /** + * Declares a restriction to intercepted request matches. `headers`, `searchParams`, and `body` are supported to limit + * which requests will match the handler and receive the mock response. If multiple restrictions are declared, either + * in a single object or with multiple calls to `handler.with()`, all of them must be met, essentially creating an AND + * condition. + * + * By default, restrictions use `exact: false`, meaning that any request **containing** the declared restrictions will + * match the handler, regardless of having more properties or values. If you want to match only requests with the + * exact values declared, you can use `exact: true`. + * + * A function is also supported to declare restrictions, in case they are dynamic. + * + * @param restriction The restriction to match intercepted requests. + * @returns The same handler, now considering the specified restriction. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerwithrestriction `handler.with()` API reference} + */ with: ( restriction: HttpRequestHandlerRestriction, ) => PendingRemoteHttpRequestHandler; - respond: ['response']>>>( + /** + * Declares a response to return for matched intercepted requests. + * + * When the handler matches a request, it will respond with the given declaration. The response type is statically + * validated against the schema of the interceptor. + * + * @param declaration The response declaration or a factory to create it. + * @returns The same handler, now including type information about the response declaration based on the specified + * status code. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()` API reference} + */ + respond: ['response']>>>( declaration: - | HttpRequestHandlerResponseDeclaration, StatusCode> - | HttpRequestHandlerResponseDeclarationFactory, StatusCode>, - ) => PendingRemoteHttpRequestHandler; + | HttpRequestHandlerResponseDeclaration, NewStatusCode> + | HttpRequestHandlerResponseDeclarationFactory, NewStatusCode>, + ) => PendingRemoteHttpRequestHandler; + /** + * Clears any response declared with + * [`handler.respond(declaration)`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerresponddeclaration), + * making the handler stop matching requests. The next handler, created before this one, that matches the same method + * and path will be used if present. If not, the requests of the method and path will not be intercepted. + * + * To make the handler match requests again, register a new response with + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()`}. + * + * This method is useful to skip a handler. It is more gentle than + * [`handler.clear()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear), as it only + * removed the response, keeping restrictions and intercepted requests. + * + * @returns The same handler, now without a declared responses. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass `handler.bypass()` API reference} + */ bypass: () => PendingRemoteHttpRequestHandler; + /** + * Clears any response declared with + * [`handler.respond(declaration)`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerresponddeclaration), + * restrictions declared with + * [`handler.with(restriction)`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerwithrestriction), + * and intercepted requests, making the handler stop matching requests. The next handler, created before this one, + * that matches the same method and path will be used if present. If not, the requests of the method and path will not + * be intercepted. + * + * To make the handler match requests again, register a new response with + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()`}. + * + * This method is useful to reset handlers to a clean state between tests. It is more aggressive than + * [`handler.bypass()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass), as it + * also clears restrictions and intercepted requests. + * + * @returns The same handler, now cleared of any declared responses, restrictions, and intercepted requests. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear `handler.clear()` API reference} + */ clear: () => PendingRemoteHttpRequestHandler; + /** + * Returns the intercepted requests that matched this handler, along with the responses returned to each of them. This + * is useful for testing that the correct requests were made by your application. + * + * **IMPORTANT**: This method can only be used if `saveRequests` was set to `true` when creating the interceptor. See + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#saving-requests Saving intercepted requests} + * for more information. + * + * @returns The intercepted requests. + * @throws {DisabledRequestSavingError} If the interceptor was not created with `saveRequests: true`. + * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrequests `handler.requests()` API reference} + */ requests: () => Promise, StatusCode>[]>; } @@ -334,7 +401,7 @@ export interface PendingRemoteHttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends HttpResponseSchemaStatusCode['response']>> = never, + StatusCode extends HttpStatusCode = never, > extends SyncedRemoteHttpRequestHandler { /** * Waits for the remote handler to be synced with the connected @@ -377,15 +444,9 @@ export interface PendingRemoteHttpRequestHandler< * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler` API reference} */ -export interface RemoteHttpRequestHandler< +export type RemoteHttpRequestHandler< Schema extends HttpSchema, Method extends HttpSchemaMethod, Path extends HttpSchemaPath, - StatusCode extends IfAny< - Schema, - any, // eslint-disable-line @typescript-eslint/no-explicit-any - HttpResponseSchemaStatusCode['response']>> - > = never, -> extends PendingRemoteHttpRequestHandler { - readonly type: 'remote'; -} + StatusCode extends HttpStatusCode = never, +> = PendingRemoteHttpRequestHandler; diff --git a/packages/zimic/src/interceptor/http/requestHandler/types/requests.ts b/packages/zimic/src/interceptor/http/requestHandler/types/requests.ts index 52e2ab858..b3532079c 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/types/requests.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/types/requests.ts @@ -2,12 +2,7 @@ import HttpHeaders from '@/http/headers/HttpHeaders'; import { HttpHeadersInit } from '@/http/headers/types'; import HttpSearchParams from '@/http/searchParams/HttpSearchParams'; import { HttpRequest, HttpResponse } from '@/http/types/requests'; -import { - HttpMethodSchema, - HttpResponseSchema, - HttpResponseSchemaStatusCode, - InferPathParams, -} from '@/http/types/schema'; +import { HttpMethodSchema, HttpResponseSchema, HttpStatusCode, InferPathParams } from '@/http/types/schema'; import { Default, DefaultNoExclude, IfNever, PossiblePromise, ReplaceBy } from '@/types/utils'; export type HttpRequestHandlerResponseBodyAttribute = @@ -20,8 +15,8 @@ export type HttpRequestHandlerResponseHeadersAttribute>, + MethodSchema extends HttpMethodSchema = HttpMethodSchema, + StatusCode extends HttpStatusCode = HttpStatusCode, > = StatusCode extends StatusCode ? { status: StatusCode; @@ -38,17 +33,17 @@ export type HttpRequestHandlerResponseDeclaration< export type HttpRequestHandlerResponseDeclarationFactory< Path extends string, MethodSchema extends HttpMethodSchema, - StatusCode extends HttpResponseSchemaStatusCode>, + StatusCode extends HttpStatusCode = HttpStatusCode, > = ( request: Omit, 'response'>, ) => PossiblePromise>; export type HttpRequestHeadersSchema = Default< - DefaultNoExclude['headers']> + Default['headers'] >; export type HttpRequestSearchParamsSchema = Default< - DefaultNoExclude['searchParams']> + Default['searchParams'] >; export type HttpRequestBodySchema = ReplaceBy< @@ -77,12 +72,12 @@ export interface HttpInterceptorRequest>, + StatusCode extends HttpStatusCode, > = IfNever[StatusCode]>['headers']>>, {}>; export type HttpResponseBodySchema< MethodSchema extends HttpMethodSchema, - StatusCode extends HttpResponseSchemaStatusCode>, + StatusCode extends HttpStatusCode, > = ReplaceBy< ReplaceBy< IfNever[StatusCode]>['body']>, null>, @@ -97,10 +92,8 @@ export type HttpResponseBodySchema< * A strict representation of an intercepted HTTP response. The body, search params and path params are already parsed * by default. */ -export interface HttpInterceptorResponse< - MethodSchema extends HttpMethodSchema, - StatusCode extends HttpResponseSchemaStatusCode>, -> extends Omit { +export interface HttpInterceptorResponse + extends Omit { /** The headers of the response. */ headers: HttpHeaders>; /** The status code of the response. */ @@ -136,7 +129,7 @@ export const HTTP_INTERCEPTOR_RESPONSE_HIDDEN_PROPERTIES = Object.freeze( export interface TrackedHttpInterceptorRequest< Path extends string, MethodSchema extends HttpMethodSchema, - StatusCode extends HttpResponseSchemaStatusCode> = never, + StatusCode extends HttpStatusCode = never, > extends HttpInterceptorRequest { /** The response that was returned for the intercepted request. */ response: StatusCode extends [never] ? never : HttpInterceptorResponse; diff --git a/packages/zimic/src/types/utils.ts b/packages/zimic/src/types/utils.ts index c2ca38503..8e898f7ee 100644 --- a/packages/zimic/src/types/utils.ts +++ b/packages/zimic/src/types/utils.ts @@ -31,12 +31,6 @@ export type Prettify = { [Key in keyof Type]: Type[Key]; }; -export type NonUndefined = Exclude; - -export type NonNullable = Exclude; - -export type Defined = NonNullable>; - export type ArrayItemIfArray = Type extends (infer Item)[] ? Item : Type; type PickArrayProperties = { diff --git a/packages/zimic/src/utils/data.ts b/packages/zimic/src/utils/data.ts index 1f7ea7f56..f719fba84 100644 --- a/packages/zimic/src/utils/data.ts +++ b/packages/zimic/src/utils/data.ts @@ -1,5 +1,3 @@ -import { Defined } from '@/types/utils'; - import { isClientSide } from './environment'; export async function blobEquals(blob: Blob, otherBlob: Blob) { @@ -33,10 +31,10 @@ export function convertBase64ToArrayBuffer(base64Value: string) { } } -export function isDefined(value: Value): value is Defined { +export function isDefined(value: Value): value is NonNullable { return value !== undefined && value !== null; } -export function isNonEmpty(value: Value): value is Exclude, ''> { +export function isNonEmpty(value: Value): value is Exclude, ''> { return isDefined(value) && value !== ''; } From e19d0dfdb3c73cea052720890c18f983e7ae98cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:00:36 +0000 Subject: [PATCH 14/16] chore(release): zimic@0.11.0-canary.6 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3dc612b1b..5bcec27fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.5", + "version": "0.11.0-canary.6", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 12ad19fb9..7cdfdc93f 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.5", + "version": "0.11.0-canary.6", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git", From 5fa6969d4a2640bd2d01a56a7a64a38ac90685cf Mon Sep 17 00:00:00 2001 From: Diego Aquino Date: Sun, 8 Dec 2024 11:00:07 -0300 Subject: [PATCH 15/16] chore(#zimic): deprecate `handler.bypass()` (#498) (#505) Closes #498. --- .../interceptor/thirdParty/shared/default.ts | 1 + ...342\200\220interceptor\342\200\220http.md" | 33 +++++++++++++------ .../http/interceptor/HttpInterceptorClient.ts | 10 +++--- .../interceptor/__tests__/shared/bypass.ts | 2 ++ .../interceptor/__tests__/shared/clear.ts | 2 +- .../interceptor/__tests__/shared/lifeCycle.ts | 8 ++--- .../__tests__/shared/pathParams.ts | 1 + .../shared/unhandledRequests.logging.ts | 4 +++ .../http/interceptor/types/public.ts | 11 ++++--- .../HttpRequestHandlerClient.ts | 12 +++---- .../requestHandler/LocalHttpRequestHandler.ts | 2 ++ .../RemoteHttpRequestHandler.ts | 2 ++ .../__tests__/shared/default.ts | 4 +++ .../http/requestHandler/types/public.ts | 16 ++++----- 14 files changed, 70 insertions(+), 38 deletions(-) diff --git a/apps/zimic-test-client/tests/interceptor/thirdParty/shared/default.ts b/apps/zimic-test-client/tests/interceptor/thirdParty/shared/default.ts index cc70e8ea5..bf012da1c 100644 --- a/apps/zimic-test-client/tests/interceptor/thirdParty/shared/default.ts +++ b/apps/zimic-test-client/tests/interceptor/thirdParty/shared/default.ts @@ -683,6 +683,7 @@ async function declareDefaultClientTests(options: ClientTestOptionsByWorkerType) expectTypeOf(listRequests[0].response.raw.json).toEqualTypeOf<() => Promise>(); expect(await listRequests[0].response.raw.json()).toEqual([notification]); + // eslint-disable-next-line @typescript-eslint/no-deprecated await listHandler.bypass(); response = await listNotifications(notification.userId); diff --git "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" index 7f884621e..4c6539ec2 100644 --- "a/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" +++ "b/docs/wiki/api\342\200\220zimic\342\200\220interceptor\342\200\220http.md" @@ -413,7 +413,10 @@ When targeting a browser environment with a local interceptor, make sure to foll ### HTTP `interceptor.stop()` -Stops the interceptor. Stopping an interceptor will also clear its registered handlers and responses. +Stops the interceptor, preventing it from intercepting HTTP requests. Stopped interceptors are automatically cleared, +exactly as if +[`interceptor.clear()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorclear) had been +called. ```ts await interceptor.stop(); @@ -446,14 +449,20 @@ const platform = interceptor.platform(); ### HTTP `interceptor.(path)` Creates an [`HttpRequestHandler`](#httprequesthandler) for the given method and path. The path and method must be -declared in the interceptor schema. - -The supported methods are: `get`, `post`, `put`, `patch`, `delete`, `head`, and `options`. +declared in the interceptor schema. The supported methods are: `get`, `post`, `put`, `patch`, `delete`, `head`, and +`options`. When using a [remote interceptor](getting‐started#remote-http-interceptors), creating a handler is an asynchronous operation, so you need to `await` it. You can also chain any number of operations and apply them by awaiting the handler. +To decide which handler to use when intercepting a request, Zimic finds a handler that matches the request considering +the interceptor base URL, method, path, and [restrictions](#http-handlerwithrestriction). The handlers are checked from +the **last** created to the first, so new handlers have preference over old ones. This allows you to declare generic and +specific handlers based on their order of creation. For example, a generic handler for `GET /users` can return an empty +list, while a specific handler in a test case can return a list with some users. In this case, the specific handler will +be considered first as long as it is created **after** the generic one. +
Using a local interceptor ```ts @@ -570,9 +579,9 @@ await fetch('http://localhost:3000/users/1', { method: 'PUT' }); ### HTTP `interceptor.clear()` -Clears all of the [`HttpRequestHandler`](#httprequesthandler) instances created by this interceptor, including their -registered responses and intercepted requests. After calling this method, the interceptor will no longer intercept any -requests until new mock responses are registered. +Clears the interceptor and all of its [`HttpRequestHandler`](#httprequesthandler) instances, including their registered +responses and intercepted requests. After calling this method, the interceptor will no longer intercept any requests +until new mock responses are registered. This method is useful to reset the interceptor mocks between tests. @@ -1431,6 +1440,13 @@ To make the handler match requests again, register a new response with This method is useful to skip a handler. It is more gentle than [`handler.clear()`](#http-handlerclear), as it only removed the response, keeping restrictions and intercepted requests. +> [!IMPORTANT] +> +> This method is deprecated and will be removed soon. You can achieve an equivalent behavior by controlling the order in +> which handlers are created. Since new handlers are always considered before old ones, you can replace `bypass()` calls +> with new handler declarations describing your new responses. Learn more at the +> [`interceptor.(path)` API reference](#http-interceptormethodpath). +
Using a local interceptor ```ts @@ -1476,9 +1492,6 @@ present. If not, the requests of the method and path will not be intercepted. To make the handler match requests again, register a new response with `handler.respond()`. -This method is useful to reset handlers to a clean state between tests. It is more aggressive than -[`handler.bypass()`](#http-handlerbypass), as it also clears restrictions and intercepted requests. -
Using a local interceptor ```ts diff --git a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts index 751f24e7b..2441bdf82 100644 --- a/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts +++ b/packages/zimic/src/interceptor/http/interceptor/HttpInterceptorClient.ts @@ -256,7 +256,7 @@ class HttpInterceptorClient< const clearResults: PossiblePromise[] = []; for (const method of HTTP_METHODS) { - clearResults.push(...this.bypassMethodHandlers(method)); + clearResults.push(...this.clearMethodHandlers(method)); this.handlerClientsByMethod[method].clear(); } @@ -268,16 +268,16 @@ class HttpInterceptorClient< } } - private bypassMethodHandlers(method: HttpMethod) { - const bypassResults: PossiblePromise[] = []; + private clearMethodHandlers(method: HttpMethod) { + const clearResults: PossiblePromise[] = []; for (const handlers of this.handlerClientsByMethod[method].values()) { for (const handler of handlers) { - bypassResults.push(handler.bypass()); + clearResults.push(handler.clear()); } } - return bypassResults; + return clearResults; } } diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts index d526c99fa..9ed5af7d7 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/bypass.ts @@ -60,6 +60,7 @@ export function declareBypassHttpInterceptorTests(options: RuntimeSharedHttpInte status: 200, headers: DEFAULT_ACCESS_CONTROL_HEADERS, }) + // eslint-disable-next-line @typescript-eslint/no-deprecated .bypass(), interceptor, ); @@ -139,6 +140,7 @@ export function declareBypassHttpInterceptorTests(options: RuntimeSharedHttpInte expectTypeOf(withMessageRequest.response.body).toEqualTypeOf(); expect(withMessageRequest.response.body).toBe(null); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(noContentHandler.bypass(), interceptor); response = await fetch(joinURL(baseURL, '/users'), { method }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts index c237f1f19..52303ad94 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/clear.ts @@ -79,7 +79,7 @@ export function declareClearHttpInterceptorTests(options: RuntimeSharedHttpInter } requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + expect(requests).toHaveLength(0); }); }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts index 2d75beace..3f8cee36d 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/lifeCycle.ts @@ -92,7 +92,7 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI } requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + expect(requests).toHaveLength(0); await interceptor.start(); expect(interceptor.isRunning()).toBe(true); @@ -106,7 +106,7 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI } requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + expect(requests).toHaveLength(0); }); }); @@ -160,7 +160,7 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI } requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + expect(requests).toHaveLength(0); await interceptor.start(); expect(interceptor.isRunning()).toBe(true); @@ -175,7 +175,7 @@ export function declareLifeCycleHttpInterceptorTests(options: RuntimeSharedHttpI } requests = await promiseIfRemote(handler.requests(), interceptor); - expect(requests).toHaveLength(numberOfRequestsIncludingPreflight); + expect(requests).toHaveLength(0); }); }); }); diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts index 2c525cebe..a6bd583bc 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/pathParams.ts @@ -97,6 +97,7 @@ export async function declarePathParamsHttpInterceptorTests(options: RuntimeShar expectTypeOf(genericRequest.response.body).toEqualTypeOf(); expect(genericRequest.response.body).toBe(null); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(genericHandler.bypass(), interceptor); const specificHandler = await promiseIfRemote( diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts index a226f964b..8cd599e97 100644 --- a/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts +++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/shared/unhandledRequests.logging.ts @@ -174,6 +174,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.warn).toHaveBeenCalledTimes(0); expect(spies.error).toHaveBeenCalledTimes(0); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); const request = new Request(joinURL(baseURL, '/users'), { method }); @@ -233,6 +234,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.warn).toHaveBeenCalledTimes(0); expect(spies.error).toHaveBeenCalledTimes(0); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); const request = new Request(joinURL(baseURL, '/users'), { @@ -453,6 +455,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.warn).toHaveBeenCalledTimes(0); expect(spies.error).toHaveBeenCalledTimes(0); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); const request = new Request(joinURL(baseURL, '/users'), { @@ -515,6 +518,7 @@ export async function declareUnhandledRequestLoggingHttpInterceptorTests( expect(spies.warn).toHaveBeenCalledTimes(0); expect(spies.error).toHaveBeenCalledTimes(0); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); const request = new Request(joinURL(baseURL, '/users'), { diff --git a/packages/zimic/src/interceptor/http/interceptor/types/public.ts b/packages/zimic/src/interceptor/http/interceptor/types/public.ts index a99213a86..9c6c6837c 100644 --- a/packages/zimic/src/interceptor/http/interceptor/types/public.ts +++ b/packages/zimic/src/interceptor/http/interceptor/types/public.ts @@ -43,17 +43,20 @@ export interface HttpInterceptor<_Schema extends HttpSchema> { start: () => Promise; /** - * Stops the interceptor, preventing it from intercepting HTTP requests. + * Stops the interceptor, preventing it from intercepting HTTP requests. Stopped interceptors are automatically + * cleared, exactly as if + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorclear `interceptor.clear()`} + * had been called. * * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptorstop `interceptor.stop()` API reference} */ stop: () => Promise; /** - * Clears all of the + * Clears the interceptor and all of its * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#httprequesthandler `HttpRequestHandler`} - * instances created by this interceptor. After calling this method, the interceptor will no longer intercept any - * requests until new mock responses are registered. + * instances, including their registered responses and intercepted requests. After calling this method, the + * interceptor will no longer intercept any requests until new mock responses are registered. * * This method is useful to reset the interceptor mocks between tests. * diff --git a/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts b/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts index 319405000..1428043cd 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/HttpRequestHandlerClient.ts @@ -54,9 +54,7 @@ class HttpRequestHandlerClient< return this._path; } - with( - restriction: HttpRequestHandlerRestriction, - ): HttpRequestHandlerClient { + with(restriction: HttpRequestHandlerRestriction): this { this.restrictions.push(restriction); return this; } @@ -86,15 +84,17 @@ class HttpRequestHandlerClient< return typeof declaration === 'function'; } - bypass(): HttpRequestHandlerClient { + /** @deprecated */ + bypass(): this { this.createResponseDeclaration = undefined; return this; } - clear(): HttpRequestHandlerClient { + clear(): this { this.restrictions = []; this.interceptedRequests = []; - return this.bypass(); + this.createResponseDeclaration = undefined; + return this; } async matchesRequest(request: HttpInterceptorRequest>): Promise { diff --git a/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts b/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts index f989ce017..c87b20585 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/LocalHttpRequestHandler.ts @@ -55,7 +55,9 @@ class LocalHttpRequestHandler< return newThis; } + /** @deprecated */ bypass(): this { + // eslint-disable-next-line @typescript-eslint/no-deprecated this._client.bypass(); return this; } diff --git a/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts b/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts index 889dc04da..37e1ed739 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/RemoteHttpRequestHandler.ts @@ -89,7 +89,9 @@ class RemoteHttpRequestHandler< return newUnsyncedThis; } + /** @deprecated */ bypass(): this { + // eslint-disable-next-line @typescript-eslint/no-deprecated this._client.bypass(); return this.unsynced; } diff --git a/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/default.ts b/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/default.ts index b2db28977..d4400bcbf 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/default.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/__tests__/shared/default.ts @@ -98,6 +98,7 @@ export function declareDefaultHttpRequestHandlerTests( const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); expect(await handler.matchesRequest(parsedRequest)).toBe(false); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); expect(await handler.matchesRequest(parsedRequest)).toBe(false); @@ -110,6 +111,7 @@ export function declareDefaultHttpRequestHandlerTests( ); expect(await handler.matchesRequest(parsedRequest)).toBe(true); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); expect(await handler.matchesRequest(parsedRequest)).toBe(false); @@ -420,6 +422,7 @@ export function declareDefaultHttpRequestHandlerTests( const parsedRequest = await HttpInterceptorWorker.parseRawRequest<'/users', MethodSchema>(request); expect(await handler.matchesRequest(parsedRequest)).toBe(false); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); expect(await handler.matchesRequest(parsedRequest)).toBe(false); @@ -434,6 +437,7 @@ export function declareDefaultHttpRequestHandlerTests( ); expect(await handler.matchesRequest(parsedRequest)).toBe(false); + // eslint-disable-next-line @typescript-eslint/no-deprecated await promiseIfRemote(handler.bypass(), interceptor); expect(await handler.matchesRequest(parsedRequest)).toBe(false); diff --git a/packages/zimic/src/interceptor/http/requestHandler/types/public.ts b/packages/zimic/src/interceptor/http/requestHandler/types/public.ts index 1734ee96d..e3d62a13c 100644 --- a/packages/zimic/src/interceptor/http/requestHandler/types/public.ts +++ b/packages/zimic/src/interceptor/http/requestHandler/types/public.ts @@ -240,6 +240,10 @@ export interface LocalHttpRequestHandler< * [`handler.clear()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear), as it only * removed the response, keeping restrictions and intercepted requests. * + * @deprecated This method is deprecated and will be removed soon. You can achieve an equivalent behavior by + * controlling the order in which handlers are created. Since new handlers are always considered before old ones, + * you can replace `bypass()` calls with new handler declarations describing your new responses. Learn more at the + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference}. * @returns The same handler, now without a declared responses. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass `handler.bypass()` API reference} */ @@ -257,10 +261,6 @@ export interface LocalHttpRequestHandler< * To make the handler match requests again, register a new response with * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()`}. * - * This method is useful to reset handlers to a clean state between tests. It is more aggressive than - * [`handler.bypass()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass), as it - * also clears restrictions and intercepted requests. - * * @returns The same handler, now cleared of any declared responses, restrictions, and intercepted requests. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear `handler.clear()` API reference} */ @@ -346,6 +346,10 @@ export interface SyncedRemoteHttpRequestHandler< * [`handler.clear()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear), as it only * removed the response, keeping restrictions and intercepted requests. * + * @deprecated This method is deprecated and will be removed soon. You can achieve an equivalent behavior by + * controlling the order in which handlers are created. Since new handlers are always considered before old ones, + * you can replace `bypass()` calls with new handler declarations describing your new responses. Learn more at the + * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-interceptormethodpath `interceptor.(path)` API reference}. * @returns The same handler, now without a declared responses. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass `handler.bypass()` API reference} */ @@ -363,10 +367,6 @@ export interface SyncedRemoteHttpRequestHandler< * To make the handler match requests again, register a new response with * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerrespond `handler.respond()`}. * - * This method is useful to reset handlers to a clean state between tests. It is more aggressive than - * [`handler.bypass()`](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerbypass), as it - * also clears restrictions and intercepted requests. - * * @returns The same handler, now cleared of any declared responses, restrictions, and intercepted requests. * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http#http-handlerclear `handler.clear()` API reference} */ From 5ea45c25eca8a7feb57c4d9a35e629e948a50a88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:06:38 +0000 Subject: [PATCH 16/16] chore(release): zimic@0.11.0-canary.7 --- package.json | 2 +- packages/zimic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5bcec27fc..4b95b5be4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zimic-root", "description": "TypeScript-first, statically inferred HTTP mocks", - "version": "0.11.0-canary.6", + "version": "0.11.0-canary.7", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git" diff --git a/packages/zimic/package.json b/packages/zimic/package.json index 7cdfdc93f..1375c93ad 100644 --- a/packages/zimic/package.json +++ b/packages/zimic/package.json @@ -10,7 +10,7 @@ "mock", "static" ], - "version": "0.11.0-canary.6", + "version": "0.11.0-canary.7", "repository": { "type": "git", "url": "https://github.com/zimicjs/zimic.git",