diff --git a/README.md b/README.md
index 3d31c8ef..990a3234 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Zimic provides a flexible and type-safe way to mock HTTP requests.
- [`zimic browser init`](#zimic-browser-init)
- [`zimic server`](#zimic-server)
- [`zimic server start`](#zimic-server-start)
+ - [Programmatic usage](#programmatic-usage)
- [Changelog](#changelog)
## Getting started
@@ -1807,13 +1808,21 @@ Positionals:
[string]
Options:
- -h, --hostname The hostname to start the server on.
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -h, --hostname The hostname to start the server on.
[string] [default: "localhost"]
- -p, --port The port to start the server on. [number]
- -e, --ephemeral Whether the server should stop automatically after the
- on-ready command finishes. If no on-ready command is provided
- and ephemeral is true, the server will stop immediately after
- starting. [boolean] [default: false]
+ -p, --port The port to start the server on. [number]
+ -e, --ephemeral Whether the server should stop automatically
+ after the on-ready command finishes. If no
+ on-ready command is provided and ephemeral is
+ true, the server will stop immediately after
+ starting. [boolean] [default: false]
+ -l, --log-unhandled-requests Whether to log a warning when no interceptors
+ were found for the base URL of a request. If an
+ interceptor was matched, the logging behavior
+ for that base URL is configured in the
+ interceptor itself. [boolean]
```
You can use this command to start an independent server:
@@ -1831,6 +1840,28 @@ zimic server start --port 4000 --ephemeral -- npm run test
The command after `--` will be executed when the server is ready. The flag `--ephemeral` indicates that the server
should automatically stop after the command finishes.
+#### Programmatic usage
+
+The module `zimic/server` exports resources for managing interceptor servers programmatically. Even though we recommend
+using the CLI, this module is a valid alternative for more advanced use cases.
+
+```ts
+import { createInterceptorServer, runCommand } from 'zimic/server';
+
+const server = createInterceptorServer({ hostname: 'localhost', port: 3000 });
+await server.start();
+
+// Run a command when the server is ready
+const [command, ...commandArguments] = process.argv.slice(3);
+await runCommand(command, commandArguments);
+
+await server.stop();
+```
+
+The helper function `runCommand` is useful to run a shell command in server scripts. The
+[Next.js App Router](./examples/README.md#nextjs) and the [Playwright](./examples/README.md#playwright) examples use
+this function to run the application after the interceptor server is ready and all mocks are set up.
+
---
## Changelog
diff --git a/apps/zimic-test-client/tests/v0/interceptor/exports/exports.node.test.ts b/apps/zimic-test-client/tests/v0/interceptor/exports/exports.node.test.ts
new file mode 100644
index 00000000..36c30e93
--- /dev/null
+++ b/apps/zimic-test-client/tests/v0/interceptor/exports/exports.node.test.ts
@@ -0,0 +1,27 @@
+import { describe, expect, expectTypeOf, it } from 'vitest';
+import {
+ createInterceptorServer,
+ InterceptorServer,
+ InterceptorServerOptions,
+ NotStartedInterceptorServerError,
+ runCommand,
+ CommandError,
+ DEFAULT_ACCESS_CONTROL_HEADERS,
+ DEFAULT_PREFLIGHT_STATUS_CODE,
+} from 'zimic0/server';
+
+describe('Exports (Node.js)', () => {
+ it('should export all expected resources', () => {
+ expect(typeof createInterceptorServer).toBe('function');
+ expectTypeOf().not.toBeAny();
+ expectTypeOf().not.toBeAny();
+ expectTypeOf().not.toBeAny();
+ expect(typeof NotStartedInterceptorServerError).toBe('function');
+ expect(typeof runCommand).toBe('function');
+ expectTypeOf().not.toBeAny();
+ expect(typeof CommandError).toBe('function');
+
+ expect(DEFAULT_ACCESS_CONTROL_HEADERS).toEqual(expect.any(Object));
+ expect(DEFAULT_PREFLIGHT_STATUS_CODE).toEqual(expect.any(Number));
+ });
+});
diff --git a/examples/with-next-js-app/.env.development b/examples/with-next-js-app/.env.development
index 937666de..a0b79abb 100644
--- a/examples/with-next-js-app/.env.development
+++ b/examples/with-next-js-app/.env.development
@@ -1,4 +1,3 @@
NODE_ENV=development
-ZIMIC_SERVER_URL=http://localhost:3005
-NEXT_PUBLIC_GITHUB_API_BASE_URL=$ZIMIC_SERVER_URL/github
+GITHUB_API_BASE_URL=https://api.github.com
diff --git a/examples/with-next-js-app/.env.test b/examples/with-next-js-app/.env.test
index 2c87534c..062bada0 100644
--- a/examples/with-next-js-app/.env.test
+++ b/examples/with-next-js-app/.env.test
@@ -1 +1,4 @@
NODE_ENV=test
+
+ZIMIC_SERVER_URL=http://localhost:3005
+GITHUB_API_BASE_URL=$ZIMIC_SERVER_URL/github
diff --git a/examples/with-next-js-app/README.md b/examples/with-next-js-app/README.md
index 00c16bbe..7d16a676 100644
--- a/examples/with-next-js-app/README.md
+++ b/examples/with-next-js-app/README.md
@@ -2,24 +2,21 @@
Zimic + Next.js App Router
-This example uses Zimic with [Next.js](https://nextjs.org). The application is verified with end-to-end tests using
-[Playwright](https://playwright.dev).
+This example uses Zimic with [Next.js](https://nextjs.org).
## Application
-The application is a simple [Next.js](https://nextjs.org) application using the
-[App Router](https://nextjs.org/docs/app). It fetches repositories from the
-[GitHub API](https://docs.github.com/en/rest).
+The application is a simple [Next.js](https://nextjs.org) project using the [App Router](https://nextjs.org/docs/app).
+It fetches repositories from the [GitHub API](https://docs.github.com/en/rest).
-- Application: [`src/app/app/page.page.tsx`](./src/app/page.tsx)
-- Interceptor provider:
- [`src/providers/interceptors/InterceptorProvider.tsx`](./src/providers/interceptors/InterceptorProvider.tsx)
- - This provider is used to apply Zimic mocks when the application is started in development.
+- Application: [`src/app/page.tsx`](./src/app/page.tsx)
- GitHub fetch: [`src/services/github.ts`](./src/services/github.ts)
- - Before fetching resources, it is necessary to wait for the interceptors and fixtures to be loaded. This is done via
- `await waitForLoadedInterceptors();`.
-A `postinstall` in [`package.json`](./package.json) script is used to install Playwright's browsers.
+A `postinstall` script in [`package.json`](./package.json) is used to install Playwright's browsers.
+
+The script [`tests/interceptors/scripts/load.ts`](./tests/interceptors/scripts/load.ts) loads the interceptors and mocks
+before the application is started in development. It is used by the command `dev:mock` in
+[`package.json`](./package.json).
## Testing
@@ -28,8 +25,7 @@ GitHub API and simulate a test case where the repository is found and another wh
### Zimic
-- GitHub interceptor: [`tests/interceptors/github/interceptor.ts`](./tests/interceptors/github/interceptor.ts)
- - Fixtures: [`tests/interceptors/github/fixtures.ts`](./tests/interceptors/github/fixtures.ts)
+- GitHub interceptor and mocks: [`tests/interceptors/github.ts`](./tests/interceptors/github.ts)
### Test
@@ -62,7 +58,7 @@ GitHub API and simulate a test case where the repository is found and another wh
1. Start the application:
```bash
- pnpm run dev
+ pnpm run dev:mock
```
After started, the application will be available at [http://localhost:3004](http://localhost:3004).
diff --git a/examples/with-next-js-app/package.json b/examples/with-next-js-app/package.json
index cdff4192..e7f63288 100644
--- a/examples/with-next-js-app/package.json
+++ b/examples/with-next-js-app/package.json
@@ -3,7 +3,9 @@
"version": "0.0.0",
"private": false,
"scripts": {
- "dev": "dotenv -c development -- zimic server start --port 3005 --ephemeral -- next dev --turbo --port 3004",
+ "dev": "dotenv -c development -- next dev --turbo --port 3004",
+ "dev:mock": "dotenv -c test -- zimic server start --port 3005 --ephemeral -- pnpm dev:load-interceptors -- pnpm dev",
+ "dev:load-interceptors": "tsx ./tests/interceptors/scripts/load.ts",
"test": "dotenv -c test -- dotenv -c development -- playwright test",
"test:turbo": "pnpm run test",
"types:check": "tsc --noEmit",
@@ -25,6 +27,7 @@
"@types/react-dom": "^18.3.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.1",
+ "tsx": "^4.7.0",
"typescript": "^5.4.3",
"zimic": "latest"
}
diff --git a/examples/with-next-js-app/playwright.config.ts b/examples/with-next-js-app/playwright.config.ts
index 0b20d779..4d1022bc 100644
--- a/examples/with-next-js-app/playwright.config.ts
+++ b/examples/with-next-js-app/playwright.config.ts
@@ -33,7 +33,7 @@ export default defineConfig({
],
webServer: {
- command: 'pnpm run dev',
+ command: 'pnpm run dev:mock',
port: 3004,
stdout: 'pipe',
stderr: 'pipe',
diff --git a/examples/with-next-js-app/src/__tests__/HomePage.e2e.test.ts b/examples/with-next-js-app/src/__tests__/HomePage.e2e.test.ts
index c71e1a47..4e731636 100644
--- a/examples/with-next-js-app/src/__tests__/HomePage.e2e.test.ts
+++ b/examples/with-next-js-app/src/__tests__/HomePage.e2e.test.ts
@@ -1,6 +1,6 @@
import test, { expect } from '@playwright/test';
-import { githubFixtures } from '../../tests/interceptors/github/fixtures';
+import { githubFixtures } from '../../tests/interceptors/github';
test.describe('Home page', () => {
const { repository } = githubFixtures;
diff --git a/examples/with-next-js-app/src/app/layout.tsx b/examples/with-next-js-app/src/app/layout.tsx
index 58f9a5c1..1e6a44d1 100644
--- a/examples/with-next-js-app/src/app/layout.tsx
+++ b/examples/with-next-js-app/src/app/layout.tsx
@@ -3,9 +3,6 @@ import { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { PropsWithChildren } from 'react';
-import { loadInterceptors } from '../../tests/interceptors';
-import InterceptorProvider from '../providers/interceptors/InterceptorProvider';
-
import '../styles/global.css';
const inter = Inter({ subsets: ['latin'] });
@@ -15,13 +12,11 @@ export const metadata: Metadata = {
description: 'Generated by create-next-app',
};
-async function RootLayout({ children }: PropsWithChildren) {
- await loadInterceptors();
-
+function RootLayout({ children }: PropsWithChildren) {
return (
- {children}
+ {children}
);
diff --git a/examples/with-next-js-app/src/app/page.tsx b/examples/with-next-js-app/src/app/page.tsx
index f92e8c85..8d4ed0d7 100644
--- a/examples/with-next-js-app/src/app/page.tsx
+++ b/examples/with-next-js-app/src/app/page.tsx
@@ -22,7 +22,7 @@ function HomePage({ searchParams }: Props) {
{shouldFetchRepository && (
- Loading...
}>
+ Loading...}>
)}
diff --git a/examples/with-next-js-app/src/config/environment.ts b/examples/with-next-js-app/src/config/environment.ts
index 329f426c..a1dfc20b 100644
--- a/examples/with-next-js-app/src/config/environment.ts
+++ b/examples/with-next-js-app/src/config/environment.ts
@@ -1,5 +1,5 @@
const environment = {
- GITHUB_API_BASE_URL: process.env.NEXT_PUBLIC_GITHUB_API_BASE_URL ?? '',
+ GITHUB_API_BASE_URL: process.env.GITHUB_API_BASE_URL ?? '',
};
export default environment;
diff --git a/examples/with-next-js-app/src/providers/interceptors/InterceptorProvider.tsx b/examples/with-next-js-app/src/providers/interceptors/InterceptorProvider.tsx
deleted file mode 100644
index 257f4df8..00000000
--- a/examples/with-next-js-app/src/providers/interceptors/InterceptorProvider.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-'use client';
-
-import { PropsWithChildren, useEffect } from 'react';
-
-import { loadInterceptors } from '../../../tests/interceptors';
-
-type Props = PropsWithChildren;
-
-function InterceptorProvider({ children }: Props) {
- useEffect(() => {
- void loadInterceptors();
- }, []);
-
- return children;
-}
-
-export default InterceptorProvider;
diff --git a/examples/with-next-js-app/src/services/github.ts b/examples/with-next-js-app/src/services/github.ts
index ea1d5b24..31a5ea89 100644
--- a/examples/with-next-js-app/src/services/github.ts
+++ b/examples/with-next-js-app/src/services/github.ts
@@ -1,9 +1,8 @@
import type { JSONValue } from 'zimic';
-import { waitForLoadedInterceptors } from '../../tests/interceptors';
import environment from '../config/environment';
-const BASE_URL = environment.GITHUB_API_BASE_URL;
+const GITHUB_API_BASE_URL = environment.GITHUB_API_BASE_URL;
const CACHE_STRATEGY = process.env.NODE_ENV === 'production' ? 'default' : 'no-store';
export type GitHubRepository = JSONValue<{
@@ -15,13 +14,11 @@ export type GitHubRepository = JSONValue<{
}>;
export async function fetchGitHubRepository(ownerName: string, repositoryName: string) {
- await waitForLoadedInterceptors();
-
try {
const sanitizedOwnerName = encodeURIComponent(ownerName);
const sanitizedRepositoryName = encodeURIComponent(repositoryName);
- const response = await fetch(`${BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
+ const response = await fetch(`${GITHUB_API_BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
cache: CACHE_STRATEGY,
});
diff --git a/examples/with-next-js-app/tests/interceptors/github.ts b/examples/with-next-js-app/tests/interceptors/github.ts
new file mode 100644
index 00000000..aa554a87
--- /dev/null
+++ b/examples/with-next-js-app/tests/interceptors/github.ts
@@ -0,0 +1,43 @@
+import { http } from 'zimic/interceptor';
+
+import environment from '../../src/config/environment';
+import { GitHubRepository } from '../../src/services/github';
+
+const githubInterceptor = http.createInterceptor<{
+ '/repos/:owner/:name': {
+ GET: {
+ response: {
+ 200: { body: GitHubRepository };
+ 404: { body: { message: string } };
+ 500: { body: { message: string } };
+ };
+ };
+ };
+}>({
+ type: 'remote',
+ baseURL: environment.GITHUB_API_BASE_URL,
+});
+
+export const githubFixtures = {
+ repository: {
+ id: 1,
+ name: 'example',
+ full_name: 'owner/example',
+ html_url: 'https://github.com/owner/example',
+ owner: { login: 'owner' },
+ } satisfies GitHubRepository,
+
+ async apply() {
+ await githubInterceptor.get('/repos/:owner/:name').respond({
+ status: 404,
+ body: { message: 'Not Found' },
+ });
+
+ await githubInterceptor.get(`/repos/${this.repository.owner.login}/${this.repository.name}`).respond({
+ status: 200,
+ body: this.repository,
+ });
+ },
+};
+
+export default githubInterceptor;
diff --git a/examples/with-next-js-app/tests/interceptors/github/fixtures.ts b/examples/with-next-js-app/tests/interceptors/github/fixtures.ts
deleted file mode 100644
index 66893fb7..00000000
--- a/examples/with-next-js-app/tests/interceptors/github/fixtures.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { GitHubRepository } from '../../../src/services/github';
-import githubInterceptor from './interceptor';
-
-export const githubFixtures = {
- repository: {
- id: 1,
- name: 'example',
- full_name: 'owner/example',
- html_url: 'https://github.com/owner/example',
- owner: { login: 'owner' },
- },
-} satisfies Record;
-
-export async function applyGitHubFixtures() {
- const { repository } = githubFixtures;
-
- await githubInterceptor.get('/repos/:owner/:name').respond({
- status: 404,
- body: { message: 'Not Found' },
- });
-
- await githubInterceptor.get(`/repos/${repository.owner.login}/${repository.name}`).respond({
- status: 200,
- body: repository,
- });
-}
diff --git a/examples/with-next-js-app/tests/interceptors/github/interceptor.ts b/examples/with-next-js-app/tests/interceptors/github/interceptor.ts
deleted file mode 100644
index 7e7cc41f..00000000
--- a/examples/with-next-js-app/tests/interceptors/github/interceptor.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { http } from 'zimic/interceptor';
-
-import environment from '../../../src/config/environment';
-import { GitHubRepository } from '../../../src/services/github';
-
-const githubInterceptor = http.createInterceptor<{
- '/repos/:owner/:name': {
- GET: {
- response: {
- 200: { body: GitHubRepository };
- 404: { body: { message: string } };
- 500: { body: { message: string } };
- };
- };
- };
-}>({
- type: 'remote',
- baseURL: environment.GITHUB_API_BASE_URL,
-});
-
-export default githubInterceptor;
diff --git a/examples/with-next-js-app/tests/interceptors/index.ts b/examples/with-next-js-app/tests/interceptors/index.ts
deleted file mode 100644
index 5a4d3d31..00000000
--- a/examples/with-next-js-app/tests/interceptors/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { applyGitHubFixtures } from './github/fixtures';
-import githubInterceptor from './github/interceptor';
-
-export let markInterceptorsAsLoaded: (() => void) | undefined;
-let areInterceptorsLoaded = false;
-
-const loadInterceptorsPromise = new Promise((resolve) => {
- markInterceptorsAsLoaded = () => {
- areInterceptorsLoaded = true;
- resolve();
- };
-});
-
-export async function waitForLoadedInterceptors() {
- if (process.env.NODE_ENV === 'production' || areInterceptorsLoaded) {
- return;
- }
-
- await loadInterceptorsPromise;
-}
-
-export async function loadInterceptors() {
- if (process.env.NODE_ENV === 'production' || areInterceptorsLoaded) {
- return;
- }
-
- await githubInterceptor.start();
- await applyGitHubFixtures();
-
- markInterceptorsAsLoaded?.();
-}
-
-export async function stopInterceptors() {
- if (process.env.NODE_ENV === 'production') {
- return;
- }
-
- await githubInterceptor.stop();
-}
diff --git a/examples/with-next-js-app/tests/interceptors/scripts/load.ts b/examples/with-next-js-app/tests/interceptors/scripts/load.ts
new file mode 100644
index 00000000..519ca364
--- /dev/null
+++ b/examples/with-next-js-app/tests/interceptors/scripts/load.ts
@@ -0,0 +1,23 @@
+import { runCommand } from 'zimic/server';
+
+import githubInterceptor, { githubFixtures } from '../github';
+
+async function runOnReadyCommand() {
+ const commandDivisorIndex = process.argv.indexOf('--');
+ if (commandDivisorIndex !== -1) {
+ const [command, ...commandArguments] = process.argv.slice(commandDivisorIndex + 1);
+ await runCommand(command, commandArguments);
+ }
+}
+
+async function loadInterceptors() {
+ await githubInterceptor.start();
+ await githubFixtures.apply();
+ console.log('Interceptors loaded.');
+
+ await runOnReadyCommand();
+
+ await githubInterceptor.stop();
+}
+
+void loadInterceptors();
diff --git a/examples/with-next-js-pages/README.md b/examples/with-next-js-pages/README.md
index fa59aa8d..51123109 100644
--- a/examples/with-next-js-pages/README.md
+++ b/examples/with-next-js-pages/README.md
@@ -2,26 +2,19 @@
Zimic + Next.js Pages Router
-This example uses Zimic with [Next.js](https://nextjs.org). The application is verified with end-to-end tests using
-[Playwright](https://playwright.dev).
+This example uses Zimic with [Next.js](https://nextjs.org).
## Application
-The application is a simple [Next.js](https://nextjs.org) application using the
+The application is a simple [Next.js](https://nextjs.org) project using the
[Pages Router](https://nextjs.org/docs/pages). It fetches repositories from the
[GitHub API](https://docs.github.com/en/rest).
- Application: [`src/pages/index.page.tsx`](./src/pages/index.page.tsx)
-- Interceptor provider:
- [`src/providers/interceptors/InterceptorProvider.tsx`](./src/providers/interceptors/InterceptorProvider.tsx)
- - This provider is used to apply Zimic mocks when the application is started in development.
- GitHub fetch: [`src/services/github.ts`](./src/services/github.ts)
- - Before fetching resources, it is necessary to wait for the interceptors and fixtures to be loaded. This is done via
- `await waitForLoadedInterceptors();`.
-A `postinstall` in [`package.json`](./package.json) script is used to install Playwright's browsers and initialize
-Zimic's mock service worker to the `./public` directory. The mock service worker at `./public/mockServiceWorker.js` is
-ignored in the [`.gitignore`](./.gitignore) file.
+The file [`_app.page.tsx`](./src/pages/_app.page.tsx) loads the interceptors and mocks before the rest of the
+application is rendered in development.
## Testing
@@ -30,8 +23,7 @@ GitHub API and simulate a test case where the repository is found and another wh
### Zimic
-- GitHub interceptor: [`tests/interceptors/github/interceptor.ts`](./tests/interceptors/github/interceptor.ts)
- - Fixtures: [`tests/interceptors/github/fixtures.ts`](./tests/interceptors/github/fixtures.ts)
+- GitHub interceptor and mocks: [`tests/interceptors/github.ts`](./tests/interceptors/github.ts)
### Test
diff --git a/examples/with-next-js-pages/src/__tests__/HomePage.e2e.test.ts b/examples/with-next-js-pages/src/__tests__/HomePage.e2e.test.ts
index c71e1a47..4e731636 100644
--- a/examples/with-next-js-pages/src/__tests__/HomePage.e2e.test.ts
+++ b/examples/with-next-js-pages/src/__tests__/HomePage.e2e.test.ts
@@ -1,6 +1,6 @@
import test, { expect } from '@playwright/test';
-import { githubFixtures } from '../../tests/interceptors/github/fixtures';
+import { githubFixtures } from '../../tests/interceptors/github';
test.describe('Home page', () => {
const { repository } = githubFixtures;
diff --git a/examples/with-next-js-pages/src/pages/_app.page.tsx b/examples/with-next-js-pages/src/pages/_app.page.tsx
index a51fa8ce..7dc2d8a9 100644
--- a/examples/with-next-js-pages/src/pages/_app.page.tsx
+++ b/examples/with-next-js-pages/src/pages/_app.page.tsx
@@ -2,24 +2,33 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import clsx from 'clsx';
import type { AppProps } from 'next/app';
import { Inter } from 'next/font/google';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
-import InterceptorProvider from '../providers/interceptors/InterceptorProvider';
+import { loadInterceptors } from '../../tests/interceptors/utils';
import '../styles/global.css';
const inter = Inter({ subsets: ['latin'] });
+const SHOULD_LOAD_INTERCEPTORS = process.env.NODE_ENV !== 'production';
function App({ Component, pageProps }: AppProps) {
const [queryClient] = useState(() => new QueryClient());
+ const [isLoading, setIsLoading] = useState(SHOULD_LOAD_INTERCEPTORS);
+
+ useEffect(() => {
+ if (SHOULD_LOAD_INTERCEPTORS) {
+ void (async () => {
+ await loadInterceptors();
+ setIsLoading(false);
+ })();
+ }
+ }, []);
return (
-
-
-
-
-
+
+ {!isLoading && }
+
);
}
diff --git a/examples/with-next-js-pages/src/providers/interceptors/InterceptorProvider.tsx b/examples/with-next-js-pages/src/providers/interceptors/InterceptorProvider.tsx
deleted file mode 100644
index 82cbebda..00000000
--- a/examples/with-next-js-pages/src/providers/interceptors/InterceptorProvider.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { PropsWithChildren, useEffect } from 'react';
-
-import { loadInterceptors } from '../../../tests/interceptors';
-
-type Props = PropsWithChildren;
-
-function InterceptorProvider({ children }: Props) {
- useEffect(() => {
- void loadInterceptors();
- }, []);
-
- return children;
-}
-
-export default InterceptorProvider;
diff --git a/examples/with-next-js-pages/src/services/github.ts b/examples/with-next-js-pages/src/services/github.ts
index ea1d5b24..31a5ea89 100644
--- a/examples/with-next-js-pages/src/services/github.ts
+++ b/examples/with-next-js-pages/src/services/github.ts
@@ -1,9 +1,8 @@
import type { JSONValue } from 'zimic';
-import { waitForLoadedInterceptors } from '../../tests/interceptors';
import environment from '../config/environment';
-const BASE_URL = environment.GITHUB_API_BASE_URL;
+const GITHUB_API_BASE_URL = environment.GITHUB_API_BASE_URL;
const CACHE_STRATEGY = process.env.NODE_ENV === 'production' ? 'default' : 'no-store';
export type GitHubRepository = JSONValue<{
@@ -15,13 +14,11 @@ export type GitHubRepository = JSONValue<{
}>;
export async function fetchGitHubRepository(ownerName: string, repositoryName: string) {
- await waitForLoadedInterceptors();
-
try {
const sanitizedOwnerName = encodeURIComponent(ownerName);
const sanitizedRepositoryName = encodeURIComponent(repositoryName);
- const response = await fetch(`${BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
+ const response = await fetch(`${GITHUB_API_BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
cache: CACHE_STRATEGY,
});
diff --git a/examples/with-next-js-pages/tests/interceptors/github.ts b/examples/with-next-js-pages/tests/interceptors/github.ts
new file mode 100644
index 00000000..16da1a90
--- /dev/null
+++ b/examples/with-next-js-pages/tests/interceptors/github.ts
@@ -0,0 +1,43 @@
+import { http } from 'zimic/interceptor';
+
+import environment from '../../src/config/environment';
+import { GitHubRepository } from '../../src/services/github';
+
+const githubInterceptor = http.createInterceptor<{
+ '/repos/:owner/:name': {
+ GET: {
+ response: {
+ 200: { body: GitHubRepository };
+ 404: { body: { message: string } };
+ 500: { body: { message: string } };
+ };
+ };
+ };
+}>({
+ type: 'local',
+ baseURL: environment.GITHUB_API_BASE_URL,
+});
+
+export const githubFixtures = {
+ repository: {
+ id: 1,
+ name: 'example',
+ full_name: 'owner/example',
+ html_url: 'https://github.com/owner/example',
+ owner: { login: 'owner' },
+ } satisfies GitHubRepository,
+
+ apply() {
+ githubInterceptor.get('/repos/:owner/:name').respond({
+ status: 404,
+ body: { message: 'Not Found' },
+ });
+
+ githubInterceptor.get(`/repos/${this.repository.owner.login}/${this.repository.name}`).respond({
+ status: 200,
+ body: this.repository,
+ });
+ },
+};
+
+export default githubInterceptor;
diff --git a/examples/with-next-js-pages/tests/interceptors/github/fixtures.ts b/examples/with-next-js-pages/tests/interceptors/github/fixtures.ts
deleted file mode 100644
index 6332e3ec..00000000
--- a/examples/with-next-js-pages/tests/interceptors/github/fixtures.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { GitHubRepository } from '../../../src/services/github';
-import githubInterceptor from './interceptor';
-
-export const githubFixtures = {
- repository: {
- id: 1,
- name: 'example',
- full_name: 'owner/example',
- html_url: 'https://github.com/owner/example',
- owner: { login: 'owner' },
- },
-} satisfies Record;
-
-export function applyGitHubFixtures() {
- const { repository } = githubFixtures;
-
- githubInterceptor.get('/repos/:owner/:name').respond({
- status: 404,
- body: { message: 'Not Found' },
- });
-
- githubInterceptor.get(`/repos/${repository.owner.login}/${repository.name}`).respond({
- status: 200,
- body: repository,
- });
-}
diff --git a/examples/with-next-js-pages/tests/interceptors/github/interceptor.ts b/examples/with-next-js-pages/tests/interceptors/github/interceptor.ts
deleted file mode 100644
index a7c6ebce..00000000
--- a/examples/with-next-js-pages/tests/interceptors/github/interceptor.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { http } from 'zimic/interceptor';
-
-import environment from '../../../src/config/environment';
-import { GitHubRepository } from '../../../src/services/github';
-
-const githubInterceptor = http.createInterceptor<{
- '/repos/:owner/:name': {
- GET: {
- response: {
- 200: { body: GitHubRepository };
- 404: { body: { message: string } };
- 500: { body: { message: string } };
- };
- };
- };
-}>({
- type: 'local',
- baseURL: environment.GITHUB_API_BASE_URL,
-});
-
-export default githubInterceptor;
diff --git a/examples/with-next-js-pages/tests/interceptors/index.ts b/examples/with-next-js-pages/tests/interceptors/index.ts
deleted file mode 100644
index ab60ec74..00000000
--- a/examples/with-next-js-pages/tests/interceptors/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { applyGitHubFixtures } from './github/fixtures';
-import githubInterceptor from './github/interceptor';
-
-export let markInterceptorsAsLoaded: (() => void) | undefined;
-let areInterceptorsLoaded = false;
-
-const loadInterceptorsPromise = new Promise((resolve) => {
- markInterceptorsAsLoaded = () => {
- areInterceptorsLoaded = true;
- resolve();
- };
-});
-
-export async function waitForLoadedInterceptors() {
- if (process.env.NODE_ENV === 'production' || areInterceptorsLoaded) {
- return;
- }
-
- await loadInterceptorsPromise;
-}
-
-export async function loadInterceptors() {
- if (process.env.NODE_ENV === 'production' || areInterceptorsLoaded) {
- return;
- }
-
- await githubInterceptor.start();
- applyGitHubFixtures();
-
- markInterceptorsAsLoaded?.();
-}
-
-export async function stopInterceptors() {
- if (process.env.NODE_ENV === 'production') {
- return;
- }
-
- await githubInterceptor.stop();
-}
diff --git a/examples/with-next-js-pages/tests/interceptors/utils.ts b/examples/with-next-js-pages/tests/interceptors/utils.ts
new file mode 100644
index 00000000..5d6d2d5d
--- /dev/null
+++ b/examples/with-next-js-pages/tests/interceptors/utils.ts
@@ -0,0 +1,6 @@
+import githubInterceptor, { githubFixtures } from './github';
+
+export async function loadInterceptors() {
+ await githubInterceptor.start();
+ githubFixtures.apply();
+}
diff --git a/examples/with-playwright/.env.development b/examples/with-playwright/.env.development
index 5973dabb..a0b79abb 100644
--- a/examples/with-playwright/.env.development
+++ b/examples/with-playwright/.env.development
@@ -1,3 +1,3 @@
NODE_ENV=development
-NEXT_PUBLIC_GITHUB_API_BASE_URL=https://api.github.com
+GITHUB_API_BASE_URL=https://api.github.com
diff --git a/examples/with-playwright/.env.test b/examples/with-playwright/.env.test
index 1031b27b..fa1d900b 100644
--- a/examples/with-playwright/.env.test
+++ b/examples/with-playwright/.env.test
@@ -1,4 +1,4 @@
NODE_ENV=test
ZIMIC_SERVER_URL=http://localhost:3003
-NEXT_PUBLIC_GITHUB_API_BASE_URL=$ZIMIC_SERVER_URL/github
+GITHUB_API_BASE_URL=$ZIMIC_SERVER_URL/github
diff --git a/examples/with-playwright/README.md b/examples/with-playwright/README.md
index 2c2b5e3c..aff92ef1 100644
--- a/examples/with-playwright/README.md
+++ b/examples/with-playwright/README.md
@@ -6,13 +6,32 @@ This example uses Zimic with [Playwright](https://playwright.dev) in end-to-end
## Application
-The tested application is a simple [Next.js](https://nextjs.org) application, fetching repositories from the
+The tested application is a simple [Next.js](https://nextjs.org) project, fetching repositories from the
[GitHub API](https://docs.github.com/en/rest).
- Application: [`src/app/page.tsx`](./src/app/page.tsx)
- GitHub fetch: [`src/services/github.ts`](./src/services/github.ts)
-A `postinstall` in [`package.json`](./package.json#L12) script is used to install Playwright's browsers.
+A `postinstall` script in [`package.json`](./package.json) is used to install Playwright's browsers.
+
+The script [`tests/interceptors/scripts/load.ts`](./tests/interceptors/scripts/load.ts) loads the interceptors and mocks
+before the application is started in development. It is used by the command `dev:mock` in
+[`package.json`](./package.json).
+
+> [!NOTE]
+>
+> **Preventing racing conditions**
+>
+> The mocks are loaded before starting the application to prevent racing conditions in tests. This example uses a single
+> interceptor server, so we would need to reduce the number of workers to 1 if the mocks were applied inside the tests
+> or `beforeEach`/`beforeAll`/`afterEach`/`afterAll` hooks. That would make the tests significantly slower in large
+> applications, which is a trade-off to consider.
+>
+> If using a single test worker is not a problem for your project, applying the mocks inside your tests or hooks is
+> perfectly possible. On the other hand, if you need parallelism, you can still simulate dynamic behavior by creating
+> all of the mocks you need beforehand in a load script like in this example. Using
+> [restrictions](https://github.com/zimicjs/zimic#http-handlerwithrestriction) is a good way to narrow down the scope of
+> those mocks.
## Testing
@@ -21,21 +40,13 @@ GitHub API and simulate a test case where the repository is found and another wh
### Zimic
-- GitHub interceptor: [`tests/interceptors/github/interceptor.ts`](./tests/interceptors/github/interceptor.ts)
- - Fixtures: [`tests/interceptors/github/fixtures.ts`](./tests/interceptors/github/fixtures.ts)
+- GitHub interceptor and mocks: [`tests/interceptors/github.ts`](./tests/interceptors/github.ts)
### Test
- Test suite: [`src/app/__tests__/HomePage.e2e.test.ts`](./src/app/__tests__/HomePage.e2e.test.ts)
-- Test setup file: [`tests/setup.ts`](./tests/setup.ts)
- - This file is responsible for starting the Zimic interceptors before each test. It also applies default mock
- responses based on the [fixtures](./tests/interceptors/github/interceptor.ts).
- Playwright configuration: [`playwright.config.ts`](./playwright.config.ts)
-> [!IMPORTANT]
->
-> The setup file must be imported from each test file to apply the global `test.beforeAll` and `test.afterAll`.
-
### Running
1. Clone this example:
@@ -62,7 +73,7 @@ GitHub API and simulate a test case where the repository is found and another wh
1. Start the application:
```bash
- pnpm run dev:test
+ pnpm run dev:mock
```
After started, it will be available at [http://localhost:3002](http://localhost:3002).
diff --git a/examples/with-playwright/package.json b/examples/with-playwright/package.json
index 83af907d..3f7e1df9 100644
--- a/examples/with-playwright/package.json
+++ b/examples/with-playwright/package.json
@@ -3,9 +3,10 @@
"version": "0.0.0",
"private": false,
"scripts": {
- "dev": "dotenv -c development -- next dev --port 3002",
- "dev:test": "dotenv -c test -- pnpm dev",
- "test": "dotenv -c test -- dotenv -c development -- zimic server start --port 3003 --ephemeral -- playwright test",
+ "dev": "dotenv -c development -- next dev --turbo --port 3002",
+ "dev:mock": "dotenv -c test -- zimic server start --port 3003 --ephemeral -- pnpm dev:load-interceptors -- pnpm dev",
+ "dev:load-interceptors": "tsx ./tests/interceptors/scripts/load.ts",
+ "test": "dotenv -c test -- dotenv -c development -- playwright test",
"test:turbo": "pnpm run test",
"types:check": "tsc --noEmit",
"deps:install-playwright": "pnpm playwright install chromium",
@@ -25,6 +26,7 @@
"@types/react-dom": "^18.3.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.1",
+ "tsx": "^4.7.0",
"typescript": "^5.4.3",
"zimic": "latest"
}
diff --git a/examples/with-playwright/playwright.config.ts b/examples/with-playwright/playwright.config.ts
index 6975de5c..43814990 100644
--- a/examples/with-playwright/playwright.config.ts
+++ b/examples/with-playwright/playwright.config.ts
@@ -34,7 +34,7 @@ export default defineConfig({
],
webServer: {
- command: 'pnpm run dev:test',
+ command: 'pnpm run dev:mock',
url: 'http://localhost:3002',
stdout: 'pipe',
stderr: 'pipe',
diff --git a/examples/with-playwright/src/app/__tests__/HomePage.e2e.test.ts b/examples/with-playwright/src/app/__tests__/HomePage.e2e.test.ts
index 65b7e273..4d2b2b51 100644
--- a/examples/with-playwright/src/app/__tests__/HomePage.e2e.test.ts
+++ b/examples/with-playwright/src/app/__tests__/HomePage.e2e.test.ts
@@ -1,8 +1,6 @@
import test, { expect } from '@playwright/test';
-import { githubFixtures } from '../../../tests/interceptors/github/fixtures';
-
-import '../../../tests/setup';
+import { githubFixtures } from '../../../tests/interceptors/github';
test.describe('Home page', () => {
const { repository } = githubFixtures;
diff --git a/examples/with-playwright/src/app/page.tsx b/examples/with-playwright/src/app/page.tsx
index 1fa7b3d8..610adc15 100644
--- a/examples/with-playwright/src/app/page.tsx
+++ b/examples/with-playwright/src/app/page.tsx
@@ -19,7 +19,7 @@ function HomePage({ searchParams }: Props) {
{shouldFetchRepository && (
- Loading...}>
+ Loading...}>
)}
diff --git a/examples/with-playwright/src/config/environment.ts b/examples/with-playwright/src/config/environment.ts
index 329f426c..a1dfc20b 100644
--- a/examples/with-playwright/src/config/environment.ts
+++ b/examples/with-playwright/src/config/environment.ts
@@ -1,5 +1,5 @@
const environment = {
- GITHUB_API_BASE_URL: process.env.NEXT_PUBLIC_GITHUB_API_BASE_URL ?? '',
+ GITHUB_API_BASE_URL: process.env.GITHUB_API_BASE_URL ?? '',
};
export default environment;
diff --git a/examples/with-playwright/src/services/github.ts b/examples/with-playwright/src/services/github.ts
index 025f7af9..f8a77170 100644
--- a/examples/with-playwright/src/services/github.ts
+++ b/examples/with-playwright/src/services/github.ts
@@ -3,7 +3,7 @@ import type { JSONValue } from 'zimic';
import environment from '../config/environment';
-const BASE_URL = environment.GITHUB_API_BASE_URL;
+const GITHUB_API_BASE_URL = environment.GITHUB_API_BASE_URL;
const CACHE_STRATEGY = process.env.NODE_ENV === 'production' ? 'default' : 'no-store';
export type GitHubRepository = JSONValue<{
@@ -19,7 +19,7 @@ export const fetchGitHubRepository = cache(async (ownerName: string, repositoryN
const sanitizedOwnerName = encodeURIComponent(ownerName);
const sanitizedRepositoryName = encodeURIComponent(repositoryName);
- const response = await fetch(`${BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
+ const response = await fetch(`${GITHUB_API_BASE_URL}/repos/${sanitizedOwnerName}/${sanitizedRepositoryName}`, {
cache: CACHE_STRATEGY,
});
diff --git a/examples/with-playwright/tests/interceptors/github.ts b/examples/with-playwright/tests/interceptors/github.ts
new file mode 100644
index 00000000..aa554a87
--- /dev/null
+++ b/examples/with-playwright/tests/interceptors/github.ts
@@ -0,0 +1,43 @@
+import { http } from 'zimic/interceptor';
+
+import environment from '../../src/config/environment';
+import { GitHubRepository } from '../../src/services/github';
+
+const githubInterceptor = http.createInterceptor<{
+ '/repos/:owner/:name': {
+ GET: {
+ response: {
+ 200: { body: GitHubRepository };
+ 404: { body: { message: string } };
+ 500: { body: { message: string } };
+ };
+ };
+ };
+}>({
+ type: 'remote',
+ baseURL: environment.GITHUB_API_BASE_URL,
+});
+
+export const githubFixtures = {
+ repository: {
+ id: 1,
+ name: 'example',
+ full_name: 'owner/example',
+ html_url: 'https://github.com/owner/example',
+ owner: { login: 'owner' },
+ } satisfies GitHubRepository,
+
+ async apply() {
+ await githubInterceptor.get('/repos/:owner/:name').respond({
+ status: 404,
+ body: { message: 'Not Found' },
+ });
+
+ await githubInterceptor.get(`/repos/${this.repository.owner.login}/${this.repository.name}`).respond({
+ status: 200,
+ body: this.repository,
+ });
+ },
+};
+
+export default githubInterceptor;
diff --git a/examples/with-playwright/tests/interceptors/github/fixtures.ts b/examples/with-playwright/tests/interceptors/github/fixtures.ts
deleted file mode 100644
index 66893fb7..00000000
--- a/examples/with-playwright/tests/interceptors/github/fixtures.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { GitHubRepository } from '../../../src/services/github';
-import githubInterceptor from './interceptor';
-
-export const githubFixtures = {
- repository: {
- id: 1,
- name: 'example',
- full_name: 'owner/example',
- html_url: 'https://github.com/owner/example',
- owner: { login: 'owner' },
- },
-} satisfies Record;
-
-export async function applyGitHubFixtures() {
- const { repository } = githubFixtures;
-
- await githubInterceptor.get('/repos/:owner/:name').respond({
- status: 404,
- body: { message: 'Not Found' },
- });
-
- await githubInterceptor.get(`/repos/${repository.owner.login}/${repository.name}`).respond({
- status: 200,
- body: repository,
- });
-}
diff --git a/examples/with-playwright/tests/interceptors/github/interceptor.ts b/examples/with-playwright/tests/interceptors/github/interceptor.ts
deleted file mode 100644
index 7e7cc41f..00000000
--- a/examples/with-playwright/tests/interceptors/github/interceptor.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { http } from 'zimic/interceptor';
-
-import environment from '../../../src/config/environment';
-import { GitHubRepository } from '../../../src/services/github';
-
-const githubInterceptor = http.createInterceptor<{
- '/repos/:owner/:name': {
- GET: {
- response: {
- 200: { body: GitHubRepository };
- 404: { body: { message: string } };
- 500: { body: { message: string } };
- };
- };
- };
-}>({
- type: 'remote',
- baseURL: environment.GITHUB_API_BASE_URL,
-});
-
-export default githubInterceptor;
diff --git a/examples/with-playwright/tests/interceptors/scripts/load.ts b/examples/with-playwright/tests/interceptors/scripts/load.ts
new file mode 100644
index 00000000..519ca364
--- /dev/null
+++ b/examples/with-playwright/tests/interceptors/scripts/load.ts
@@ -0,0 +1,23 @@
+import { runCommand } from 'zimic/server';
+
+import githubInterceptor, { githubFixtures } from '../github';
+
+async function runOnReadyCommand() {
+ const commandDivisorIndex = process.argv.indexOf('--');
+ if (commandDivisorIndex !== -1) {
+ const [command, ...commandArguments] = process.argv.slice(commandDivisorIndex + 1);
+ await runCommand(command, commandArguments);
+ }
+}
+
+async function loadInterceptors() {
+ await githubInterceptor.start();
+ await githubFixtures.apply();
+ console.log('Interceptors loaded.');
+
+ await runOnReadyCommand();
+
+ await githubInterceptor.stop();
+}
+
+void loadInterceptors();
diff --git a/examples/with-playwright/tests/setup.ts b/examples/with-playwright/tests/setup.ts
deleted file mode 100644
index b85441e1..00000000
--- a/examples/with-playwright/tests/setup.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import test from '@playwright/test';
-
-import { applyGitHubFixtures } from './interceptors/github/fixtures';
-import githubInterceptor from './interceptors/github/interceptor';
-
-test.beforeAll(async () => {
- await githubInterceptor.start();
- await applyGitHubFixtures();
-});
-
-test.afterAll(async () => {
- await githubInterceptor.stop();
-});
diff --git a/examples/with-vitest-browser/README.md b/examples/with-vitest-browser/README.md
index 60571b13..4c35012d 100644
--- a/examples/with-vitest-browser/README.md
+++ b/examples/with-vitest-browser/README.md
@@ -13,7 +13,7 @@ The application is a simple HTML layout rendered by vanilla JavaScript, fetching
- Application: [`src/app.ts`](./src/app.ts)
-A `postinstall` in [`package.json`](./package.json) script is used to install Playwright's browsers and initialize
+A `postinstall` script in [`package.json`](./package.json) is used to install Playwright's browsers and initialize
Zimic's mock service worker to the `./public` directory. The mock service worker at `./public/mockServiceWorker.js` is
ignored in the [`.gitignore`](./.gitignore) file.
diff --git a/packages/zimic/package.json b/packages/zimic/package.json
index b14dceb4..5b58ca36 100644
--- a/packages/zimic/package.json
+++ b/packages/zimic/package.json
@@ -52,6 +52,12 @@
"default": "./dist/interceptor.js",
"types": "./dist/interceptor.d.ts"
},
+ "./server": {
+ "import": "./dist/server.mjs",
+ "require": "./dist/server.js",
+ "default": "./dist/server.js",
+ "types": "./dist/server.d.ts"
+ },
"./package.json": "./package.json"
},
"scripts": {
diff --git a/packages/zimic/server.d.ts b/packages/zimic/server.d.ts
new file mode 100644
index 00000000..699fd532
--- /dev/null
+++ b/packages/zimic/server.d.ts
@@ -0,0 +1 @@
+export * from './dist/server';
diff --git a/packages/zimic/src/cli/__tests__/server.cli.node.test.ts b/packages/zimic/src/cli/__tests__/server.cli.node.test.ts
index 890cc255..4e607b82 100644
--- a/packages/zimic/src/cli/__tests__/server.cli.node.test.ts
+++ b/packages/zimic/src/cli/__tests__/server.cli.node.test.ts
@@ -373,7 +373,7 @@ describe('CLI (server)', async () => {
await usingIgnoredConsole(['error', 'log'], async (spies) => {
const error = new CommandError('node', exitCode, null);
await expect(runCLI()).rejects.toThrowError(error);
- expect(error.message).toBe(`[zimic] Command 'node' exited with code ${exitCode}.`);
+ expect(error.message).toBe(`Command 'node' exited with code ${exitCode}.`);
expect(spies.error).toHaveBeenCalledTimes(1);
expect(spies.error).toHaveBeenCalledWith(error);
@@ -398,7 +398,7 @@ describe('CLI (server)', async () => {
await usingIgnoredConsole(['error', 'log'], async (spies) => {
const error = new CommandError('node', null, signal);
await expect(runCLI()).rejects.toThrowError(error);
- expect(error.message).toBe(`[zimic] Command 'node' exited after signal ${signal}.`);
+ expect(error.message).toBe(`Command 'node' exited after signal ${signal}.`);
expect(spies.error).toHaveBeenCalledTimes(1);
expect(spies.error).toHaveBeenCalledWith(error);
diff --git a/packages/zimic/src/cli/cli.ts b/packages/zimic/src/cli/cli.ts
index be9e5c82..7ab2f1e1 100644
--- a/packages/zimic/src/cli/cli.ts
+++ b/packages/zimic/src/cli/cli.ts
@@ -39,7 +39,6 @@ async function runCLI() {
.positional('onReady', {
description: 'A command to run when the server is ready to accept connections.',
type: 'string',
- array: true,
})
.option('hostname', {
type: 'string',
diff --git a/packages/zimic/src/cli/server/start.ts b/packages/zimic/src/cli/server/start.ts
index d6cf0e7b..584057e2 100644
--- a/packages/zimic/src/cli/server/start.ts
+++ b/packages/zimic/src/cli/server/start.ts
@@ -1,7 +1,8 @@
+import { createInterceptorServer } from '@/interceptor/server';
+import { InterceptorServerOptions } from '@/interceptor/server/types/options';
+import { InterceptorServer } from '@/interceptor/server/types/public';
import { logWithPrefix } from '@/utils/console';
-import { runCommand, PROCESS_EXIT_EVENTS } from '@/utils/processes';
-
-import InterceptorServer, { InterceptorServerOptions } from '../../interceptor/server/InterceptorServer';
+import { runCommand } from '@/utils/processes';
interface InterceptorServerStartOptions extends InterceptorServerOptions {
ephemeral: boolean;
@@ -20,7 +21,7 @@ async function startInterceptorServer({
onUnhandledRequest,
onReady,
}: InterceptorServerStartOptions) {
- const server = new InterceptorServer({
+ const server = createInterceptorServer({
hostname,
port,
onUnhandledRequest,
@@ -28,12 +29,6 @@ async function startInterceptorServer({
singletonServer = server;
- for (const exitEvent of PROCESS_EXIT_EVENTS) {
- process.on(exitEvent, async () => {
- await server.stop();
- });
- }
-
await server.start();
logWithPrefix(`${ephemeral ? 'Ephemeral s' : 'S'}erver is running on ${server.httpURL()}`);
@@ -45,10 +40,6 @@ async function startInterceptorServer({
if (ephemeral) {
await server.stop();
}
-
- for (const exitEvent of PROCESS_EXIT_EVENTS) {
- process.removeAllListeners(exitEvent);
- }
}
export default startInterceptorServer;
diff --git a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.node.test.ts b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.node.test.ts
index a49980a5..a4e5e827 100644
--- a/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.node.test.ts
+++ b/packages/zimic/src/interceptor/http/interceptor/__tests__/HttpInterceptor.node.test.ts
@@ -1,12 +1,12 @@
import { describe } from 'vitest';
-import InterceptorServer from '@/interceptor/server/InterceptorServer';
import { getNodeBaseURL } from '@tests/utils/interceptors';
+import { createInternalInterceptorServer } from '@tests/utils/interceptorServers';
import { declareSharedHttpInterceptorTests } from './shared/interceptorTests';
describe('HttpInterceptor (Node.js)', () => {
- const server = new InterceptorServer();
+ const server = createInternalInterceptorServer();
declareSharedHttpInterceptorTests({
platform: 'node',
diff --git a/packages/zimic/src/interceptor/http/interceptor/errors/NotStartedHttpInterceptorError.ts b/packages/zimic/src/interceptor/http/interceptor/errors/NotStartedHttpInterceptorError.ts
index c11b9316..08aa8972 100644
--- a/packages/zimic/src/interceptor/http/interceptor/errors/NotStartedHttpInterceptorError.ts
+++ b/packages/zimic/src/interceptor/http/interceptor/errors/NotStartedHttpInterceptorError.ts
@@ -7,7 +7,7 @@
*/
class NotStartedHttpInterceptorError extends Error {
constructor() {
- super('[zimic] Interceptor is not running. Did you forget to call `await interceptor.start()`?');
+ super('Interceptor is not running. Did you forget to call `await interceptor.start()`?');
this.name = 'NotStartedHttpInterceptorError';
}
}
diff --git a/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorPlatform.ts b/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorPlatform.ts
index fa4c6eb6..2901cfc9 100644
--- a/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorPlatform.ts
+++ b/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorPlatform.ts
@@ -8,7 +8,7 @@ class UnknownHttpInterceptorPlatform extends Error {
/* istanbul ignore next -- @preserve
* Ignoring because checking unknown platforms is currently not possible in our Vitest setup */
constructor() {
- super('[zimic] Unknown interceptor platform.');
+ super('Unknown interceptor platform.');
this.name = 'UnknownHttpInterceptorPlatform';
}
}
diff --git a/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts b/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts
index 9e3a3ae6..2aa73a9d 100644
--- a/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts
+++ b/packages/zimic/src/interceptor/http/interceptor/errors/UnknownHttpInterceptorTypeError.ts
@@ -3,7 +3,7 @@ import { HttpInterceptorType } from '../types/options';
class UnknownHttpInterceptorTypeError extends TypeError {
constructor(unknownType: unknown) {
super(
- `[zimic] Unknown HTTP interceptor type: ${unknownType}. The available options are ` +
+ `Unknown HTTP interceptor type: ${unknownType}. The available options are ` +
`'${'local' satisfies HttpInterceptorType}' and ` +
`'${'remote' satisfies HttpInterceptorType}'.`,
);
diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/HttpinterceptorWorker.node.test.ts b/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/HttpinterceptorWorker.node.test.ts
index 66084cb1..4ed19c74 100644
--- a/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/HttpinterceptorWorker.node.test.ts
+++ b/packages/zimic/src/interceptor/http/interceptorWorker/__tests__/HttpinterceptorWorker.node.test.ts
@@ -1,12 +1,12 @@
import { describe } from 'vitest';
-import InterceptorServer from '@/interceptor/server/InterceptorServer';
import { getNodeBaseURL } from '@tests/utils/interceptors';
+import { createInternalInterceptorServer } from '@tests/utils/interceptorServers';
import { declareSharedHttpInterceptorWorkerTests } from './shared/workerTests';
describe('HttpInterceptorWorker (Node.js)', () => {
- const server = new InterceptorServer();
+ const server = createInternalInterceptorServer();
declareSharedHttpInterceptorWorkerTests({
platform: 'node',
diff --git a/packages/zimic/src/interceptor/http/interceptorWorker/errors/UnregisteredServiceWorkerError.ts b/packages/zimic/src/interceptor/http/interceptorWorker/errors/UnregisteredServiceWorkerError.ts
index 1d98f1e9..052bd96a 100644
--- a/packages/zimic/src/interceptor/http/interceptorWorker/errors/UnregisteredServiceWorkerError.ts
+++ b/packages/zimic/src/interceptor/http/interceptorWorker/errors/UnregisteredServiceWorkerError.ts
@@ -8,7 +8,7 @@ import { SERVICE_WORKER_FILE_NAME } from '@/cli/browser/shared/constants';
class UnregisteredServiceWorkerError extends Error {
constructor() {
super(
- `[zimic] Failed to register the browser service worker: ` +
+ `Failed to register the browser service worker: ` +
`script '${window.location.origin}/${SERVICE_WORKER_FILE_NAME}' not found.\n\n` +
'Did you forget to run "npx zimic browser init "?\n\n' +
'Learn more at https://github.com/zimicjs/zimic#browser-post-install.',
diff --git a/packages/zimic/src/interceptor/http/requestHandler/__tests__/HttpRequestHandler.node.test.ts b/packages/zimic/src/interceptor/http/requestHandler/__tests__/HttpRequestHandler.node.test.ts
index c14d457a..d9676feb 100644
--- a/packages/zimic/src/interceptor/http/requestHandler/__tests__/HttpRequestHandler.node.test.ts
+++ b/packages/zimic/src/interceptor/http/requestHandler/__tests__/HttpRequestHandler.node.test.ts
@@ -1,12 +1,12 @@
import { describe } from 'vitest';
-import InterceptorServer from '@/interceptor/server/InterceptorServer';
import { getNodeBaseURL } from '@tests/utils/interceptors';
+import { createInternalInterceptorServer } from '@tests/utils/interceptorServers';
import { declareSharedHttpRequestHandlerTests } from './shared/requestHandlerTests';
describe('HttpRequestHandler (Node.js)', () => {
- const server = new InterceptorServer();
+ const server = createInternalInterceptorServer();
declareSharedHttpRequestHandlerTests({
platform: 'node',
diff --git a/packages/zimic/src/interceptor/http/requestHandler/errors/NoResponseDefinitionError.ts b/packages/zimic/src/interceptor/http/requestHandler/errors/NoResponseDefinitionError.ts
index 53f20c53..74aedf4b 100644
--- a/packages/zimic/src/interceptor/http/requestHandler/errors/NoResponseDefinitionError.ts
+++ b/packages/zimic/src/interceptor/http/requestHandler/errors/NoResponseDefinitionError.ts
@@ -1,6 +1,6 @@
class NoResponseDefinitionError extends TypeError {
constructor() {
- super('[zimic] Cannot generate a response without a definition. Use .respond() to set a response.');
+ super('Cannot generate a response without a definition. Use .respond() to set a response.');
this.name = 'NoResponseDefinitionError';
}
}
diff --git a/packages/zimic/src/interceptor/server/InterceptorServer.ts b/packages/zimic/src/interceptor/server/InterceptorServer.ts
index d2f06db2..0c9b7840 100644
--- a/packages/zimic/src/interceptor/server/InterceptorServer.ts
+++ b/packages/zimic/src/interceptor/server/InterceptorServer.ts
@@ -8,23 +8,17 @@ import HttpInterceptorWorker from '@/interceptor/http/interceptorWorker/HttpInte
import HttpInterceptorWorkerStore from '@/interceptor/http/interceptorWorker/HttpInterceptorWorkerStore';
import { deserializeResponse, serializeRequest } from '@/utils/fetch';
import { getHttpServerPort, startHttpServer, stopHttpServer } from '@/utils/http';
+import { PROCESS_EXIT_EVENTS } from '@/utils/processes';
import { createRegexFromURL, createURL, excludeNonPathParams } from '@/utils/urls';
import { WebSocket } from '@/webSocket/types';
import WebSocketServer from '@/webSocket/WebSocketServer';
import { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE } from './constants';
import NotStartedInterceptorServerError from './errors/NotStartedInterceptorServerError';
+import { InterceptorServerOptions } from './types/options';
import { InterceptorServer as PublicInterceptorServer } from './types/public';
import { HttpHandlerCommit, InterceptorServerWebSocketSchema } from './types/schema';
-export interface InterceptorServerOptions {
- hostname?: string;
- port?: number;
- onUnhandledRequest?: {
- log?: boolean;
- };
-}
-
interface HttpHandler {
id: string;
url: { regex: RegExp };
@@ -55,7 +49,7 @@ class InterceptorServer implements PublicInterceptorServer {
private knownWorkerSockets = new Set();
- constructor(options: InterceptorServerOptions = {}) {
+ constructor(options: InterceptorServerOptions) {
this._hostname = options.hostname ?? 'localhost';
this._port = options.port;
this.onUnhandledRequest = options.onUnhandledRequest;
@@ -103,6 +97,10 @@ class InterceptorServer implements PublicInterceptorServer {
return;
}
+ for (const exitEvent of PROCESS_EXIT_EVENTS) {
+ process.on(exitEvent, this.stop);
+ }
+
this._httpServer = createServer({
keepAlive: true,
joinDuplicateHeaders: true,
@@ -192,14 +190,18 @@ class InterceptorServer implements PublicInterceptorServer {
}
}
- async stop() {
+ stop = async () => {
if (!this.isRunning()) {
return;
}
+ for (const exitEvent of PROCESS_EXIT_EVENTS) {
+ process.removeListener(exitEvent, this.stop);
+ }
+
await this.stopWebSocketServer();
await this.stopHttpServer();
- }
+ };
private async stopHttpServer() {
const httpServer = this.httpServer();
diff --git a/packages/zimic/src/interceptor/server/__tests__/InterceptorServer.node.test.ts b/packages/zimic/src/interceptor/server/__tests__/InterceptorServer.node.test.ts
index 01f092c1..b15184e9 100644
--- a/packages/zimic/src/interceptor/server/__tests__/InterceptorServer.node.test.ts
+++ b/packages/zimic/src/interceptor/server/__tests__/InterceptorServer.node.test.ts
@@ -1,5 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest';
+import { createInternalInterceptorServer } from '@tests/utils/interceptorServers';
+
import InterceptorServer from '../InterceptorServer';
// These are integration tests for the server. Only features not easily reproducible by the CLI and the remote
@@ -13,7 +15,7 @@ describe('Interceptor server', () => {
});
it('should start correctly with a defined port', async () => {
- server = new InterceptorServer({ hostname: 'localhost', port: 8080 });
+ server = createInternalInterceptorServer({ hostname: 'localhost', port: 8080 });
expect(server.isRunning()).toBe(false);
expect(server.hostname()).toBe('localhost');
@@ -29,7 +31,7 @@ describe('Interceptor server', () => {
});
it('should start correctly with an undefined port', async () => {
- server = new InterceptorServer({ hostname: 'localhost' });
+ server = createInternalInterceptorServer({ hostname: 'localhost' });
expect(server.isRunning()).toBe(false);
expect(server.hostname()).toBe('localhost');
@@ -45,7 +47,7 @@ describe('Interceptor server', () => {
});
it('should not throw an error is started multiple times', async () => {
- server = new InterceptorServer({ hostname: 'localhost' });
+ server = createInternalInterceptorServer({ hostname: 'localhost' });
expect(server.isRunning()).toBe(false);
@@ -60,7 +62,7 @@ describe('Interceptor server', () => {
});
it('should not throw an error if stopped multiple times', async () => {
- server = new InterceptorServer({ hostname: 'localhost' });
+ server = createInternalInterceptorServer({ hostname: 'localhost' });
expect(server.isRunning()).toBe(false);
diff --git a/packages/zimic/src/interceptor/server/constants.ts b/packages/zimic/src/interceptor/server/constants.ts
index 1e00c52d..99b77375 100644
--- a/packages/zimic/src/interceptor/server/constants.ts
+++ b/packages/zimic/src/interceptor/server/constants.ts
@@ -15,6 +15,7 @@ export type AccessControlHeaders = HttpSchema.Headers<{
'access-control-max-age'?: string;
}>;
+/** The default access control headers for the server. */
export const DEFAULT_ACCESS_CONTROL_HEADERS = Object.freeze({
'access-control-allow-origin': '*',
'access-control-allow-methods': ALLOWED_ACCESS_CONTROL_HTTP_METHODS,
@@ -23,4 +24,5 @@ export const DEFAULT_ACCESS_CONTROL_HEADERS = Object.freeze({
'access-control-max-age': process.env.SERVER_ACCESS_CONTROL_MAX_AGE,
}) satisfies AccessControlHeaders;
+/** The default status code for the preflight request. */
export const DEFAULT_PREFLIGHT_STATUS_CODE = 204;
diff --git a/packages/zimic/src/interceptor/server/errors/NotStartedInterceptorServerError.ts b/packages/zimic/src/interceptor/server/errors/NotStartedInterceptorServerError.ts
index d3a3e7c8..578939f8 100644
--- a/packages/zimic/src/interceptor/server/errors/NotStartedInterceptorServerError.ts
+++ b/packages/zimic/src/interceptor/server/errors/NotStartedInterceptorServerError.ts
@@ -1,10 +1,8 @@
-/* istanbul ignore next -- @preserve
- * This error is a fallback to prevent doing operations without a started server. It should not happen in normal
- * conditions.
- */
+/* istanbul ignore next -- @preserve */
+/** An error thrown when the interceptor server is not running. */
class NotStartedInterceptorServerError extends Error {
constructor() {
- super('[zimic] The interceptor server is not running.');
+ super('The interceptor server is not running.');
this.name = 'NotStartedInterceptorServerError';
}
}
diff --git a/packages/zimic/src/interceptor/server/factory.ts b/packages/zimic/src/interceptor/server/factory.ts
new file mode 100644
index 00000000..9196e11e
--- /dev/null
+++ b/packages/zimic/src/interceptor/server/factory.ts
@@ -0,0 +1,15 @@
+import InterceptorServer from './InterceptorServer';
+import { InterceptorServerOptions } from './types/options';
+import { InterceptorServer as PublicInterceptorServer } from './types/public';
+
+/**
+ * Creates an {@link https://github.com/zimicjs/zimic#zimic-server interceptor server}.
+ *
+ * @param options The options to create the server.
+ * @returns The created server.
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ * @see {@link https://github.com/zimicjs/zimic#remote-http-interceptors Remote HTTP Interceptors} .
+ */
+export function createInterceptorServer(options: InterceptorServerOptions = {}): PublicInterceptorServer {
+ return new InterceptorServer(options);
+}
diff --git a/packages/zimic/src/interceptor/server/index.ts b/packages/zimic/src/interceptor/server/index.ts
new file mode 100644
index 00000000..e4ee9834
--- /dev/null
+++ b/packages/zimic/src/interceptor/server/index.ts
@@ -0,0 +1,11 @@
+import NotStartedInterceptorServerError from './errors/NotStartedInterceptorServerError';
+
+export type { InterceptorServerOptions } from './types/options';
+export type { InterceptorServer } from './types/public';
+
+export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE } from './constants';
+
+export { createInterceptorServer } from './factory';
+export { NotStartedInterceptorServerError };
+
+export { runCommand, CommandError } from '@/utils/processes';
diff --git a/packages/zimic/src/interceptor/server/types/options.ts b/packages/zimic/src/interceptor/server/types/options.ts
new file mode 100644
index 00000000..f87eea0c
--- /dev/null
+++ b/packages/zimic/src/interceptor/server/types/options.ts
@@ -0,0 +1,26 @@
+/**
+ * The options to create an {@link https://github.com/zimicjs/zimic#zimic-server interceptor server}.
+ *
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
+export interface InterceptorServerOptions {
+ /**
+ * The hostname to start the server on.
+ *
+ * @default localhost
+ */
+ hostname?: string;
+
+ /** The port to start the server on. If no port is provided, a random one is chosen. */
+ port?: number;
+
+ /** The strategy to handle unhandled requests. */
+ onUnhandledRequest?: {
+ /**
+ * Whether to log unhandled requests.
+ *
+ * @default true
+ */
+ log?: boolean;
+ };
+}
diff --git a/packages/zimic/src/interceptor/server/types/public.ts b/packages/zimic/src/interceptor/server/types/public.ts
index 59fe529d..cc9ee36b 100644
--- a/packages/zimic/src/interceptor/server/types/public.ts
+++ b/packages/zimic/src/interceptor/server/types/public.ts
@@ -1,9 +1,48 @@
+/**
+ * A server to intercept and handle requests. It is used in combination with
+ * {@link https://github.com/zimicjs/zimic#remote-http-interceptors remote interceptors}.
+ *
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
export interface InterceptorServer {
+ /**
+ * @returns The hostname of the server.
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
hostname: () => string;
+
+ /**
+ * @returns The port of the server.
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
port: () => number | undefined;
+
+ /**
+ * @returns The HTTP URL of the server.
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
httpURL: () => string | undefined;
+
+ /**
+ * @returns Whether the server is running.
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
isRunning: () => boolean;
+ /**
+ * Starts the server.
+ *
+ * The server is automatically stopped if a process exit event is detected, such as SIGINT, SIGTERM, or an uncaught
+ * exception.
+ *
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
start: () => Promise;
+
+ /**
+ * Stops the server.
+ *
+ * @see {@link https://github.com/zimicjs/zimic#zimic-server `zimic server` API reference}
+ */
stop: () => Promise;
}
diff --git a/packages/zimic/src/utils/http.ts b/packages/zimic/src/utils/http.ts
index 07670e89..6fe21cd6 100644
--- a/packages/zimic/src/utils/http.ts
+++ b/packages/zimic/src/utils/http.ts
@@ -4,14 +4,14 @@ class HttpServerTimeoutError extends Error {}
export class HttpServerStartTimeoutError extends HttpServerTimeoutError {
constructor(reachedTimeout: number) {
- super(`[zimic] HTTP server start timed out after ${reachedTimeout}ms.`);
+ super(`HTTP server start timed out after ${reachedTimeout}ms.`);
this.name = 'HttpServerStartTimeout';
}
}
export class HttpServerStopTimeoutError extends HttpServerTimeoutError {
constructor(reachedTimeout: number) {
- super(`[zimic] HTTP server stop timed out after ${reachedTimeout}ms.`);
+ super(`HTTP server stop timed out after ${reachedTimeout}ms.`);
this.name = 'HttpServerStopTimeout';
}
}
diff --git a/packages/zimic/src/utils/processes.ts b/packages/zimic/src/utils/processes.ts
index c2e190af..960edc27 100644
--- a/packages/zimic/src/utils/processes.ts
+++ b/packages/zimic/src/utils/processes.ts
@@ -1,3 +1,4 @@
+import { SpawnOptions } from 'child_process';
import { spawn } from 'cross-spawn';
export const PROCESS_EXIT_EVENTS = Object.freeze([
@@ -9,20 +10,27 @@ export const PROCESS_EXIT_EVENTS = Object.freeze([
'SIGBREAK',
] as const);
+/** An error thrown when a command exits with a non-zero code. */
export class CommandError extends Error {
constructor(command: string, exitCode: number | null, signal: NodeJS.Signals | null) {
- super(
- `[zimic] Command '${command}' exited ` +
- `${exitCode === null ? `after signal ${signal}` : `with code ${exitCode}`}.`,
- );
+ super(`Command '${command}' exited ${exitCode === null ? `after signal ${signal}` : `with code ${exitCode}`}.`);
this.name = 'CommandError';
}
}
-export async function runCommand(command: string, commandArguments: string[]) {
+/**
+ * Runs a command with the given arguments.
+ *
+ * @param command The command to run.
+ * @param commandArguments The arguments to pass to the command.
+ * @param options The options to pass to the spawn function. By default, stdio is set to 'inherit'.
+ * @throws {CommandError} When the command exits with a non-zero code.
+ */
+export async function runCommand(command: string, commandArguments: string[], options: SpawnOptions = {}) {
await new Promise((resolve, reject) => {
const childProcess = spawn(command, commandArguments, {
stdio: 'inherit',
+ ...options,
});
childProcess.once('error', (error) => {
diff --git a/packages/zimic/src/utils/urls.ts b/packages/zimic/src/utils/urls.ts
index b981794a..00e978e1 100644
--- a/packages/zimic/src/utils/urls.ts
+++ b/packages/zimic/src/utils/urls.ts
@@ -1,6 +1,6 @@
export class InvalidURLError extends TypeError {
constructor(url: unknown) {
- super(`[zimic] Invalid URL: '${url}'`);
+ super(`Invalid URL: '${url}'`);
this.name = 'InvalidURL';
}
}
@@ -8,7 +8,7 @@ export class InvalidURLError extends TypeError {
export class UnsupportedURLProtocolError extends TypeError {
constructor(protocol: string, availableProtocols: string[] | readonly string[]) {
super(
- `[zimic] Unsupported URL protocol: '${protocol}'. ` +
+ `Unsupported URL protocol: '${protocol}'. ` +
`The available options are ${availableProtocols.map((protocol) => `'${protocol}'`).join(', ')}`,
);
this.name = 'UnsupportedURLProtocolError';
@@ -62,7 +62,7 @@ export function excludeNonPathParams(url: URL) {
export class DuplicatedPathParamError extends Error {
constructor(url: string, paramName: string) {
super(
- `[zimic] The path parameter '${paramName}' appears more than once in the URL '${url}'. This is not supported. ` +
+ `The path parameter '${paramName}' appears more than once in the URL '${url}'. This is not supported. ` +
'Please make sure that each parameter is unique.',
);
this.name = 'DuplicatedPathParamError';
diff --git a/packages/zimic/src/utils/webSocket.ts b/packages/zimic/src/utils/webSocket.ts
index a9ed4069..8e9a4476 100644
--- a/packages/zimic/src/utils/webSocket.ts
+++ b/packages/zimic/src/utils/webSocket.ts
@@ -6,21 +6,21 @@ class WebSocketTimeoutError extends Error {}
export class WebSocketOpenTimeoutError extends WebSocketTimeoutError {
constructor(reachedTimeout: number) {
- super(`[zimic] Web socket open timed out after ${reachedTimeout}ms.`);
+ super(`Web socket open timed out after ${reachedTimeout}ms.`);
this.name = 'WebSocketOpenTimeout';
}
}
export class WebSocketMessageTimeoutError extends WebSocketTimeoutError {
constructor(reachedTimeout: number) {
- super(`[zimic] Web socket message timed out after ${reachedTimeout}ms.`);
+ super(`Web socket message timed out after ${reachedTimeout}ms.`);
this.name = 'WebSocketMessageTimeout';
}
}
export class WebSocketCloseTimeoutError extends WebSocketTimeoutError {
constructor(reachedTimeout: number) {
- super(`[zimic] Web socket close timed out after ${reachedTimeout}ms.`);
+ super(`Web socket close timed out after ${reachedTimeout}ms.`);
this.name = 'WebSocketCloseTimeout';
}
}
diff --git a/packages/zimic/src/webSocket/errors/InvalidWebSocketMessage.ts b/packages/zimic/src/webSocket/errors/InvalidWebSocketMessage.ts
index 57b01222..07837ac0 100644
--- a/packages/zimic/src/webSocket/errors/InvalidWebSocketMessage.ts
+++ b/packages/zimic/src/webSocket/errors/InvalidWebSocketMessage.ts
@@ -1,6 +1,6 @@
class InvalidWebSocketMessage extends Error {
constructor(message: unknown) {
- super(`[zimic] Web socket message is invalid and could not be parsed: ${message}`);
+ super(`Web socket message is invalid and could not be parsed: ${message}`);
this.name = 'InvalidWebSocketMessage';
}
}
diff --git a/packages/zimic/src/webSocket/errors/NotStartedWebSocketHandlerError.ts b/packages/zimic/src/webSocket/errors/NotStartedWebSocketHandlerError.ts
index d8b31a69..fd346228 100644
--- a/packages/zimic/src/webSocket/errors/NotStartedWebSocketHandlerError.ts
+++ b/packages/zimic/src/webSocket/errors/NotStartedWebSocketHandlerError.ts
@@ -1,6 +1,6 @@
class NotStartedWebSocketHandlerError extends Error {
constructor() {
- super('[zimic] Web socket handler is not running.');
+ super('Web socket handler is not running.');
this.name = 'NotStartedWebSocketHandlerError';
}
}
diff --git a/packages/zimic/tests/utils/interceptorServers.ts b/packages/zimic/tests/utils/interceptorServers.ts
new file mode 100644
index 00000000..15156fc3
--- /dev/null
+++ b/packages/zimic/tests/utils/interceptorServers.ts
@@ -0,0 +1,7 @@
+import { InterceptorServerOptions, createInterceptorServer } from '@/interceptor/server';
+import InterceptorServer from '@/interceptor/server/InterceptorServer';
+import { InterceptorServer as PublicInterceptorServer } from '@/interceptor/server/types/public';
+
+export function createInternalInterceptorServer(options?: InterceptorServerOptions) {
+ return createInterceptorServer(options) satisfies PublicInterceptorServer as InterceptorServer;
+}
diff --git a/packages/zimic/tsup.config.ts b/packages/zimic/tsup.config.ts
index aafba563..b969fad9 100644
--- a/packages/zimic/tsup.config.ts
+++ b/packages/zimic/tsup.config.ts
@@ -22,6 +22,7 @@ export default defineConfig([
entry: {
index: 'src/index.ts',
interceptor: 'src/interceptor/index.ts',
+ server: 'src/interceptor/server/index.ts',
},
},
{
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 647133ce..3fffd33a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -207,6 +207,9 @@ importers:
tailwindcss:
specifier: ^3.4.1
version: 3.4.3
+ tsx:
+ specifier: ^4.7.0
+ version: 4.7.0
typescript:
specifier: ^5.4.3
version: 5.4.5
@@ -296,6 +299,9 @@ importers:
tailwindcss:
specifier: ^3.4.1
version: 3.4.3
+ tsx:
+ specifier: ^4.7.0
+ version: 4.7.0
typescript:
specifier: ^5.4.3
version: 5.4.3
@@ -3071,9 +3077,6 @@ packages:
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
engines: {node: '>= 0.4'}
- get-tsconfig@4.7.2:
- resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
-
get-tsconfig@4.7.3:
resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==}
@@ -8086,10 +8089,6 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.2.4
- get-tsconfig@4.7.2:
- dependencies:
- resolve-pkg-maps: 1.0.0
-
get-tsconfig@4.7.3:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -10271,7 +10270,7 @@ snapshots:
tsx@4.7.0:
dependencies:
esbuild: 0.19.11
- get-tsconfig: 4.7.2
+ get-tsconfig: 4.7.3
optionalDependencies:
fsevents: 2.3.3