@jaredwray/mockhttp 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,7 @@ A simple HTTP server that can be used to mock HTTP responses for testing purpose
12
12
 
13
13
  # Features
14
14
  * All the features of [httpbin](https://httpbin.org/)
15
+ * Taps - Inject custom responses for testing and develepment
15
16
  * `@fastify/helmet` built in by default
16
17
  * Built with `nodejs`, `typescript`, and `fastify`
17
18
  * Deploy via `docker` or `nodejs`
@@ -63,6 +64,328 @@ console.log(reaponse);
63
64
  await mockhttp.stop(); // stop the server
64
65
  ```
65
66
 
67
+ # Response Injection (Tap Feature)
68
+
69
+ The injection/tap feature allows you to "tap into" the request flow and inject custom responses for specific requests. This is particularly useful for:
70
+ - **Offline testing** - Mock external API responses without network access
71
+ - **Testing edge cases** - Simulate errors, timeouts, or specific response scenarios
72
+ - **Development** - Work on your application without depending on external services
73
+
74
+ ## What is a "Tap"?
75
+
76
+ A "tap" is a reference to an injected response, similar to "wiretapping" - you're intercepting requests and returning predefined responses. Each tap can be removed when you're done with it, restoring normal server behavior.
77
+
78
+ ## Basic Usage
79
+
80
+ ```javascript
81
+ import { mockhttp } from '@jaredwray/mockhttp';
82
+
83
+ const mock = new mockhttp();
84
+ await mock.start();
85
+
86
+ // Inject a simple response
87
+ const tap = mock.taps.inject(
88
+ {
89
+ response: "Hello, World!",
90
+ statusCode: 200,
91
+ headers: { "Content-Type": "text/plain" }
92
+ },
93
+ {
94
+ url: "/api/greeting",
95
+ method: "GET"
96
+ }
97
+ );
98
+
99
+ // Make requests - they will get the injected response
100
+ const response = await fetch('http://localhost:3000/api/greeting');
101
+ console.log(await response.text()); // "Hello, World!"
102
+
103
+ // Remove the injection when done
104
+ mock.taps.removeInjection(tap);
105
+
106
+ await mock.close();
107
+ ```
108
+
109
+ ## Advanced Examples
110
+
111
+ ### Inject JSON Response
112
+
113
+ ```javascript
114
+ const tap = mock.taps.inject(
115
+ {
116
+ response: { message: "Success", data: { id: 123 } },
117
+ statusCode: 200
118
+ },
119
+ { url: "/api/users/123" }
120
+ );
121
+ ```
122
+
123
+ ### Wildcard URL Matching
124
+
125
+ ```javascript
126
+ // Match all requests under /api/
127
+ const tap = mock.taps.inject(
128
+ {
129
+ response: "API is mocked",
130
+ statusCode: 503
131
+ },
132
+ { url: "/api/*" }
133
+ );
134
+ ```
135
+
136
+ ### Multiple Injections
137
+
138
+ ```javascript
139
+ const tap1 = mock.taps.inject(
140
+ { response: "Users data" },
141
+ { url: "/api/users" }
142
+ );
143
+
144
+ const tap2 = mock.taps.inject(
145
+ { response: "Posts data" },
146
+ { url: "/api/posts" }
147
+ );
148
+
149
+ // View all active injections
150
+ console.log(mock.taps.injections); // Map of all active taps
151
+
152
+ // Remove specific injections
153
+ mock.taps.removeInjection(tap1);
154
+ mock.taps.removeInjection(tap2);
155
+ ```
156
+
157
+ ### Match by HTTP Method
158
+
159
+ ```javascript
160
+ // Only intercept POST requests
161
+ const tap = mock.taps.inject(
162
+ { response: "Created", statusCode: 201 },
163
+ { url: "/api/users", method: "POST" }
164
+ );
165
+ ```
166
+
167
+ ### Match by Headers
168
+
169
+ ```javascript
170
+ const tap = mock.taps.inject(
171
+ { response: "Authenticated response" },
172
+ {
173
+ url: "/api/secure",
174
+ headers: {
175
+ "authorization": "Bearer token123"
176
+ }
177
+ }
178
+ );
179
+ ```
180
+
181
+ ### Catch-All Injection
182
+
183
+ ```javascript
184
+ // Match ALL requests (no matcher specified)
185
+ const tap = mock.taps.inject({
186
+ response: "Server is in maintenance mode",
187
+ statusCode: 503
188
+ });
189
+ ```
190
+
191
+ ### Dynamic Function Response
192
+
193
+ You can provide a function that dynamically generates the response based on the incoming request:
194
+
195
+ ```javascript
196
+ // Function response with access to the request object
197
+ const tap = mock.taps.inject(
198
+ (request) => {
199
+ return {
200
+ response: {
201
+ message: `You requested ${request.url}`,
202
+ method: request.method,
203
+ timestamp: new Date().toISOString()
204
+ },
205
+ statusCode: 200,
206
+ headers: {
207
+ "X-Request-Path": request.url
208
+ }
209
+ };
210
+ },
211
+ { url: "/api/*" }
212
+ );
213
+ ```
214
+
215
+ ```javascript
216
+ // Conditional responses based on request
217
+ const tap = mock.taps.inject((request) => {
218
+ // Return error for URLs containing 'error'
219
+ if (request.url.includes('error')) {
220
+ return {
221
+ response: { error: "Something went wrong" },
222
+ statusCode: 500
223
+ };
224
+ }
225
+
226
+ // Return success for everything else
227
+ return {
228
+ response: { status: "success" },
229
+ statusCode: 200
230
+ };
231
+ });
232
+ ```
233
+
234
+ ```javascript
235
+ // Dynamic headers based on request
236
+ const tap = mock.taps.inject(
237
+ (request) => ({
238
+ response: "OK",
239
+ statusCode: 200,
240
+ headers: {
241
+ "X-Original-Method": request.method,
242
+ "X-Original-URL": request.url,
243
+ "X-Original-Host": request.hostname
244
+ }
245
+ }),
246
+ { url: "/api/mirror" }
247
+ );
248
+ ```
249
+
250
+ # API Reference
251
+
252
+ ## MockHttp Class
253
+
254
+ ### Constructor
255
+
256
+ ```javascript
257
+ new MockHttp(options?)
258
+ ```
259
+
260
+ **Parameters:**
261
+ - `options?` (MockHttpOptions):
262
+ - `port?`: number - The port to listen on (default: 3000)
263
+ - `host?`: string - The host to listen on (default: '0.0.0.0')
264
+ - `autoDetectPort?`: boolean - Auto-detect next available port if in use (default: true)
265
+ - `helmet?`: boolean - Use Helmet for security headers (default: true)
266
+ - `apiDocs?`: boolean - Enable Swagger API documentation (default: true)
267
+ - `httpBin?`: HttpBinOptions - Configure which httpbin routes to enable
268
+ - `hookOptions?`: HookifiedOptions - Hookified options
269
+
270
+ ### Properties
271
+
272
+ - `port`: number - Get/set the server port
273
+ - `host`: string - Get/set the server host
274
+ - `autoDetectPort`: boolean - Get/set auto-detect port behavior
275
+ - `helmet`: boolean - Get/set Helmet security headers
276
+ - `apiDocs`: boolean - Get/set API documentation
277
+ - `httpBin`: HttpBinOptions - Get/set httpbin route options
278
+ - `server`: FastifyInstance - Get/set the Fastify server instance
279
+ - `taps`: TapManager - Get/set the TapManager instance
280
+
281
+ ### Methods
282
+
283
+ #### `async start()`
284
+
285
+ Start the Fastify server. If already running, it will be closed and restarted.
286
+
287
+ #### `async close()`
288
+
289
+ Stop the Fastify server.
290
+
291
+ #### `async detectPort()`
292
+
293
+ Detect the next available port.
294
+
295
+ **Returns:** number - The available port
296
+
297
+ #### `async registerApiDocs(fastifyInstance?)`
298
+
299
+ Register Swagger API documentation routes.
300
+
301
+ #### `async registerHttpMethods(fastifyInstance?)`
302
+
303
+ Register HTTP method routes (GET, POST, PUT, PATCH, DELETE).
304
+
305
+ #### `async registerStatusCodeRoutes(fastifyInstance?)`
306
+
307
+ Register status code routes.
308
+
309
+ #### `async registerRequestInspectionRoutes(fastifyInstance?)`
310
+
311
+ Register request inspection routes (headers, ip, user-agent).
312
+
313
+ #### `async registerResponseInspectionRoutes(fastifyInstance?)`
314
+
315
+ Register response inspection routes (cache, etag, response-headers).
316
+
317
+ #### `async registerResponseFormatRoutes(fastifyInstance?)`
318
+
319
+ Register response format routes (json, xml, html, etc.).
320
+
321
+ #### `async registerRedirectRoutes(fastifyInstance?)`
322
+
323
+ Register redirect routes (absolute, relative, redirect-to).
324
+
325
+ #### `async registerCookieRoutes(fastifyInstance?)`
326
+
327
+ Register cookie routes (get, set, delete).
328
+
329
+ #### `async registerAnythingRoutes(fastifyInstance?)`
330
+
331
+ Register "anything" catch-all routes.
332
+
333
+ #### `async registerAuthRoutes(fastifyInstance?)`
334
+
335
+ Register authentication routes (basic, bearer, digest, hidden-basic).
336
+
337
+ ## Taps (Response Injection)
338
+
339
+ Access the TapManager via `mockHttp.taps` to inject custom responses.
340
+
341
+ ### `taps.inject(response, matcher?)`
342
+
343
+ Injects a custom response for requests matching the criteria.
344
+
345
+ **Parameters:**
346
+ - `response` (InjectionResponse | InjectionResponseFunction):
347
+ - **Static Response** (InjectionResponse):
348
+ - `response`: string | object | Buffer - The response body
349
+ - `statusCode?`: number - HTTP status code (default: 200)
350
+ - `headers?`: object - Response headers
351
+ - **Function Response** (InjectionResponseFunction):
352
+ - A function that receives the Fastify request object and returns an InjectionResponse
353
+ - `(request: FastifyRequest) => InjectionResponse`
354
+ - Allows dynamic response generation based on request properties (url, method, headers, etc.)
355
+
356
+ - `matcher?` (InjectionMatcher) - Optional matching criteria:
357
+ - `url?`: string - URL path (supports wildcards with `*`)
358
+ - `method?`: string - HTTP method (GET, POST, etc.)
359
+ - `hostname?`: string - Hostname to match
360
+ - `headers?`: object - Headers that must be present
361
+
362
+ **Returns:** `InjectionTap` - A tap object with a unique `id` that can be used to remove the injection
363
+
364
+ ## `taps.removeInjection(tapOrId)`
365
+
366
+ Removes an injection.
367
+
368
+ **Parameters:**
369
+ - `tapOrId`: InjectionTap | string - The tap object or tap ID to remove
370
+
371
+ **Returns:** boolean - `true` if removed, `false` if not found
372
+
373
+ ### `taps.injections`
374
+
375
+ A getter that returns a Map of all active injection taps.
376
+
377
+ **Returns:** `Map<string, InjectionTap>` - Map of all active injections with tap IDs as keys
378
+
379
+ ## `taps.clear()`
380
+
381
+ Removes all injections.
382
+
383
+ ## `taps.hasInjections`
384
+
385
+ A getter that returns whether there are any active injections.
386
+
387
+ **Returns:** boolean - `true` if there are active injections, `false` otherwise
388
+
66
389
  # About mockhttp.org
67
390
 
68
391
  [mockhttp.org](https://mockhttp.org) is a free service that runs this codebase and allows you to use it for testing purposes. It is a simple way to mock HTTP responses for testing purposes. It is globally available has some limitations on it to prevent abuse such as requests per second. It is ran via [Cloudflare](https://cloudflare.com) and [Google Cloud Run](https://cloud.google.com/run/) across 7 regions globally and can do millions of requests per second.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,125 @@
1
- import { FastifyInstance } from 'fastify';
1
+ import { FastifyRequest, FastifyInstance } from 'fastify';
2
2
  import { Hookified, HookifiedOptions } from 'hookified';
3
3
 
4
+ /**
5
+ * Configuration for the injected response
6
+ */
7
+ type InjectionResponse = {
8
+ /**
9
+ * The response body (can be string, object, or buffer)
10
+ */
11
+ response: string | Record<string, unknown> | Buffer;
12
+ /**
13
+ * HTTP status code for the response
14
+ * @default 200
15
+ */
16
+ statusCode?: number;
17
+ /**
18
+ * Response headers
19
+ */
20
+ headers?: Record<string, string | string[]>;
21
+ };
22
+ /**
23
+ * Criteria for matching requests to inject responses
24
+ */
25
+ type InjectionMatcher = {
26
+ /**
27
+ * URL path to match (supports wildcards with *)
28
+ * @example "/api/users" - exact match
29
+ * @example "/api/*" - wildcard match
30
+ */
31
+ url?: string;
32
+ /**
33
+ * Hostname to match
34
+ * @example "localhost"
35
+ * @example "api.example.com"
36
+ */
37
+ hostname?: string;
38
+ /**
39
+ * HTTP method to match
40
+ * @example "GET"
41
+ * @example "POST"
42
+ */
43
+ method?: string;
44
+ /**
45
+ * Request headers to match (all specified headers must match)
46
+ */
47
+ headers?: Record<string, string>;
48
+ };
49
+ /**
50
+ * Function that returns an injection response
51
+ */
52
+ type InjectionResponseFunction = (request: FastifyRequest) => InjectionResponse;
53
+ /**
54
+ * A tap represents an active injection that can be removed later
55
+ */
56
+ type InjectionTap = {
57
+ /**
58
+ * Unique identifier for this injection
59
+ */
60
+ id: string;
61
+ /**
62
+ * The response configuration or a function that returns it
63
+ */
64
+ response: InjectionResponse | InjectionResponseFunction;
65
+ /**
66
+ * The request matcher configuration
67
+ */
68
+ matcher?: InjectionMatcher;
69
+ };
70
+ /**
71
+ * Manages HTTP response injections for testing and mocking
72
+ */
73
+ declare class TapManager {
74
+ private _injections;
75
+ constructor();
76
+ /**
77
+ * Get all active injection taps
78
+ */
79
+ get injections(): Map<string, InjectionTap>;
80
+ /**
81
+ * Check if there are any active injections
82
+ */
83
+ get hasInjections(): boolean;
84
+ /**
85
+ * Inject a custom response for requests matching the given criteria
86
+ * @param response - The response configuration or a function that returns it
87
+ * @param matcher - Optional criteria to match requests
88
+ * @returns A tap object that can be used to remove the injection
89
+ */
90
+ inject(response: InjectionResponse | InjectionResponseFunction, matcher?: InjectionMatcher): InjectionTap;
91
+ /**
92
+ * Remove an injection by its tap object or ID
93
+ * @param tapOrId - The tap object or tap ID to remove
94
+ * @returns true if the injection was removed, false if it wasn't found
95
+ */
96
+ removeInjection(tapOrId: InjectionTap | string): boolean;
97
+ /**
98
+ * Find the first injection that matches the given request
99
+ * @param request - The Fastify request object
100
+ * @returns The matching injection tap, or undefined if no match
101
+ */
102
+ matchRequest(request: FastifyRequest): InjectionTap | undefined;
103
+ /**
104
+ * Clear all injections
105
+ */
106
+ clear(): void;
107
+ /**
108
+ * Check if a request matches the given matcher criteria
109
+ * @param request - The Fastify request object
110
+ * @param matcher - The matcher criteria (undefined matches all requests)
111
+ * @returns true if the request matches
112
+ */
113
+ private requestMatches;
114
+ /**
115
+ * Check if a URL matches a pattern (supports wildcards with *)
116
+ * @param url - The URL to test
117
+ * @param pattern - The pattern to match against
118
+ * @returns true if the URL matches the pattern
119
+ */
120
+ private urlMatches;
121
+ }
122
+
4
123
  type HttpBinOptions = {
5
124
  httpMethods?: boolean;
6
125
  redirects?: boolean;
@@ -51,6 +170,7 @@ declare class MockHttp extends Hookified {
51
170
  private _apiDocs;
52
171
  private _httpBin;
53
172
  private _server;
173
+ private _taps;
54
174
  constructor(options?: MockHttpOptions);
55
175
  /**
56
176
  * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
@@ -118,6 +238,14 @@ declare class MockHttp extends Hookified {
118
238
  * The Fastify server instance.
119
239
  */
120
240
  set server(server: FastifyInstance);
241
+ /**
242
+ * The TapManager instance for managing injection taps.
243
+ */
244
+ get taps(): TapManager;
245
+ /**
246
+ * The TapManager instance for managing injection taps.
247
+ */
248
+ set taps(taps: TapManager);
121
249
  /**
122
250
  * Start the Fastify server. If the server is already running, it will be closed and restarted.
123
251
  */
@@ -181,4 +309,4 @@ declare class MockHttp extends Hookified {
181
309
 
182
310
  declare const start: () => Promise<MockHttp>;
183
311
 
184
- export { MockHttp as default, MockHttp as mockhttp, start };
312
+ export { type InjectionMatcher, type InjectionResponse, type InjectionTap, MockHttp as default, MockHttp as mockhttp, start };