@jaredwray/mockhttp 1.3.0 → 1.5.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 +181 -3
- package/dist/index.d.ts +131 -1
- package/dist/index.js +451 -20
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ A simple HTTP server that can be used to mock HTTP responses for testing purpose
|
|
|
25
25
|
- [Deploy via Docker](#deploy-via-docker)
|
|
26
26
|
- [Deploy via Docker Compose](#deploy-via-docker-compose)
|
|
27
27
|
- [Deploy via NodeJS](#deploy-via-nodejs)
|
|
28
|
+
- [HTTPS Support](#https-support)
|
|
29
|
+
- [HTTP/2 Support](#http2-support)
|
|
28
30
|
- [Response Injection (Tap Feature)](#response-injection-tap-feature)
|
|
29
31
|
- [Rate Limiting](#rate-limiting)
|
|
30
32
|
- [Logging](#logging)
|
|
@@ -69,11 +71,180 @@ npm install @jaredwray/mockhttp --save
|
|
|
69
71
|
then run `mockhttp` in your code.
|
|
70
72
|
|
|
71
73
|
```javascript
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
+
import { MockHttp } from '@jaredwray/mockhttp';
|
|
75
|
+
const mock = new MockHttp();
|
|
76
|
+
await mock.start(); // start the server
|
|
74
77
|
const response = await fetch('http://localhost:3000/get');
|
|
75
78
|
console.log(response);
|
|
76
|
-
await
|
|
79
|
+
await mock.close(); // stop the server
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
# HTTPS Support
|
|
83
|
+
|
|
84
|
+
MockHttp supports HTTPS with auto-generated self-signed certificates or your own custom certificates. No external dependencies are required — certificate generation uses only Node.js built-in `crypto`.
|
|
85
|
+
|
|
86
|
+
## Auto-Generated Certificate
|
|
87
|
+
|
|
88
|
+
The simplest way to enable HTTPS is to pass `https: true`. A self-signed certificate for `localhost` is generated automatically:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { MockHttp } from '@jaredwray/mockhttp';
|
|
92
|
+
|
|
93
|
+
const mock = new MockHttp({ https: true });
|
|
94
|
+
await mock.start();
|
|
95
|
+
|
|
96
|
+
console.log(mock.isHttps); // true
|
|
97
|
+
|
|
98
|
+
// Use Fastify's built-in inject() for testing (no TLS setup needed)
|
|
99
|
+
const response = await mock.server.inject({ method: 'GET', url: '/get' });
|
|
100
|
+
console.log(response.statusCode); // 200
|
|
101
|
+
|
|
102
|
+
await mock.close();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> **Note:** Self-signed certificates are not trusted by default. When making real HTTPS requests (e.g. with `fetch`), set `NODE_TLS_REJECT_UNAUTHORIZED=0` in your test environment or use a custom HTTPS agent.
|
|
106
|
+
|
|
107
|
+
## Custom Certificate Options
|
|
108
|
+
|
|
109
|
+
You can customize the auto-generated certificate by passing `certificateOptions`:
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const mock = new MockHttp({
|
|
113
|
+
https: {
|
|
114
|
+
certificateOptions: {
|
|
115
|
+
commonName: 'my-test-server',
|
|
116
|
+
validityDays: 30,
|
|
117
|
+
keySize: 4096,
|
|
118
|
+
altNames: [
|
|
119
|
+
{ type: 'dns', value: 'example.local' },
|
|
120
|
+
{ type: 'dns', value: '*.example.local' },
|
|
121
|
+
{ type: 'ip', value: '192.168.1.100' },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await mock.start();
|
|
128
|
+
// Make requests...
|
|
129
|
+
await mock.close();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Provide Your Own Certificate
|
|
133
|
+
|
|
134
|
+
You can supply your own PEM-encoded certificate and key, either as strings or file paths:
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// Using PEM strings
|
|
138
|
+
const mock = new MockHttp({
|
|
139
|
+
https: {
|
|
140
|
+
cert: '-----BEGIN CERTIFICATE-----\n...',
|
|
141
|
+
key: '-----BEGIN PRIVATE KEY-----\n...',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
await mock.start();
|
|
145
|
+
// Make requests...
|
|
146
|
+
await mock.close();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
// Using file paths
|
|
151
|
+
const mock = new MockHttp({
|
|
152
|
+
https: {
|
|
153
|
+
cert: '/path/to/cert.pem',
|
|
154
|
+
key: '/path/to/key.pem',
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
await mock.start();
|
|
158
|
+
// Make requests...
|
|
159
|
+
await mock.close();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Standalone Certificate Generation
|
|
163
|
+
|
|
164
|
+
You can also generate certificates independently using the exported utility functions:
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
import { generateCertificate, generateCertificateFiles } from '@jaredwray/mockhttp';
|
|
168
|
+
|
|
169
|
+
// Generate in-memory PEM strings
|
|
170
|
+
const { cert, key } = generateCertificate({
|
|
171
|
+
commonName: 'my-app',
|
|
172
|
+
validityDays: 90,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Generate and write to disk
|
|
176
|
+
const result = await generateCertificateFiles({
|
|
177
|
+
certPath: './certs/cert.pem',
|
|
178
|
+
keyPath: './certs/key.pem',
|
|
179
|
+
commonName: 'my-app',
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## HTTPS Options Reference
|
|
184
|
+
|
|
185
|
+
| Option | Type | Default | Description |
|
|
186
|
+
|--------|------|---------|-------------|
|
|
187
|
+
| `cert` | string | — | PEM-encoded certificate string or file path |
|
|
188
|
+
| `key` | string | — | PEM-encoded private key string or file path |
|
|
189
|
+
| `autoGenerate` | boolean | `true` | Auto-generate a self-signed certificate when cert/key are not provided |
|
|
190
|
+
| `certificateOptions` | CertificateOptions | — | Options for the auto-generated certificate |
|
|
191
|
+
|
|
192
|
+
### Certificate Options
|
|
193
|
+
|
|
194
|
+
| Option | Type | Default | Description |
|
|
195
|
+
|--------|------|---------|-------------|
|
|
196
|
+
| `commonName` | string | `'localhost'` | Certificate subject Common Name (CN) |
|
|
197
|
+
| `altNames` | Array\<{ type, value }\> | `[dns:localhost, ip:127.0.0.1, ip:::1]` | Subject Alternative Names with type `'dns'` or `'ip'` |
|
|
198
|
+
| `validityDays` | number | `365` | Certificate validity period in days |
|
|
199
|
+
| `keySize` | number | `2048` | RSA key size in bits |
|
|
200
|
+
|
|
201
|
+
# HTTP/2 Support
|
|
202
|
+
|
|
203
|
+
MockHttp supports HTTP/2 in two modes:
|
|
204
|
+
- **h2** — HTTP/2 over TLS (used by browsers), enabled with both `http2: true` and `https: true`
|
|
205
|
+
- **h2c** — HTTP/2 cleartext (no TLS), enabled with just `http2: true`, useful for service-to-service testing
|
|
206
|
+
|
|
207
|
+
## HTTP/2 over TLS (h2)
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
import { MockHttp } from '@jaredwray/mockhttp';
|
|
211
|
+
|
|
212
|
+
const mock = new MockHttp({ http2: true, https: true });
|
|
213
|
+
await mock.start();
|
|
214
|
+
|
|
215
|
+
console.log(mock.http2); // true
|
|
216
|
+
console.log(mock.isHttps); // true
|
|
217
|
+
|
|
218
|
+
const response = await mock.server.inject({ method: 'GET', url: '/get' });
|
|
219
|
+
console.log(response.statusCode); // 200
|
|
220
|
+
|
|
221
|
+
await mock.close();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
By default, HTTP/1.1 clients can still connect via ALPN negotiation (`http1` defaults to `true`). To disable HTTP/1.1 fallback:
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
const mock = new MockHttp({ http2: true, https: true, http1: false });
|
|
228
|
+
await mock.start();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## HTTP/2 Cleartext (h2c)
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
const mock = new MockHttp({ http2: true });
|
|
235
|
+
await mock.start();
|
|
236
|
+
|
|
237
|
+
console.log(mock.http2); // true
|
|
238
|
+
|
|
239
|
+
await mock.close();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
> **Note:** Browsers do not support h2c. This mode is useful for testing gRPC or service-to-service communication.
|
|
243
|
+
|
|
244
|
+
## HTTP/2 via Environment Variable
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
HTTP2=true node your-app.js
|
|
77
248
|
```
|
|
78
249
|
|
|
79
250
|
# Response Injection (Tap Feature)
|
|
@@ -467,6 +638,9 @@ new MockHttp(options?)
|
|
|
467
638
|
- `anything?`: boolean - Enable anything routes (default: true)
|
|
468
639
|
- `auth?`: boolean - Enable authentication routes (default: true)
|
|
469
640
|
- `images?`: boolean - Enable image routes (default: true)
|
|
641
|
+
- `https?`: boolean | HttpsOptions - Enable HTTPS with auto-generated or custom certificates (default: undefined/disabled)
|
|
642
|
+
- `http2?`: boolean - Enable HTTP/2 support (default: false)
|
|
643
|
+
- `http1?`: boolean - Allow HTTP/1.1 fallback when using HTTP/2 with HTTPS (default: true)
|
|
470
644
|
- `hookOptions?`: HookifiedOptions - Hookified options
|
|
471
645
|
|
|
472
646
|
### Properties
|
|
@@ -479,6 +653,10 @@ new MockHttp(options?)
|
|
|
479
653
|
- `logging`: boolean - Get/set logging enabled state
|
|
480
654
|
- `rateLimit`: RateLimitPluginOptions | undefined - Get/set rate limiting options
|
|
481
655
|
- `httpBin`: HttpBinOptions - Get/set httpbin route options
|
|
656
|
+
- `https`: HttpsOptions | undefined - Get/set HTTPS configuration
|
|
657
|
+
- `isHttps`: boolean - Whether the server is running with HTTPS
|
|
658
|
+
- `http2`: boolean - Get/set HTTP/2 support
|
|
659
|
+
- `http1`: boolean - Get/set HTTP/1.1 fallback for HTTP/2 with HTTPS
|
|
482
660
|
- `server`: FastifyInstance - Get/set the Fastify server instance
|
|
483
661
|
- `taps`: TapManager - Get/set the TapManager instance
|
|
484
662
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,61 @@ import { RateLimitPluginOptions } from '@fastify/rate-limit';
|
|
|
2
2
|
import { FastifyRequest, FastifyInstance } from 'fastify';
|
|
3
3
|
import { Hookified, HookifiedOptions } from 'hookified';
|
|
4
4
|
|
|
5
|
+
type CertificateOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Common Name for the certificate subject. Defaults to 'localhost'.
|
|
8
|
+
*/
|
|
9
|
+
commonName?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Subject Alternative Names. Defaults to [dns:localhost, ip:127.0.0.1, ip:::1].
|
|
12
|
+
*/
|
|
13
|
+
altNames?: Array<{
|
|
14
|
+
type: "dns";
|
|
15
|
+
value: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "ip";
|
|
18
|
+
value: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Number of days the certificate is valid. Defaults to 365.
|
|
22
|
+
*/
|
|
23
|
+
validityDays?: number;
|
|
24
|
+
/**
|
|
25
|
+
* RSA key size in bits. Defaults to 2048.
|
|
26
|
+
*/
|
|
27
|
+
keySize?: number;
|
|
28
|
+
};
|
|
29
|
+
type CertificateResult = {
|
|
30
|
+
/**
|
|
31
|
+
* PEM-encoded X.509 certificate.
|
|
32
|
+
*/
|
|
33
|
+
cert: string;
|
|
34
|
+
/**
|
|
35
|
+
* PEM-encoded PKCS#8 private key.
|
|
36
|
+
*/
|
|
37
|
+
key: string;
|
|
38
|
+
};
|
|
39
|
+
type CertificateFileOptions = CertificateOptions & {
|
|
40
|
+
/**
|
|
41
|
+
* File path to write the certificate PEM.
|
|
42
|
+
*/
|
|
43
|
+
certPath: string;
|
|
44
|
+
/**
|
|
45
|
+
* File path to write the private key PEM.
|
|
46
|
+
*/
|
|
47
|
+
keyPath: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Generate a self-signed certificate using only Node.js built-in crypto.
|
|
51
|
+
* Returns PEM-encoded certificate and private key strings.
|
|
52
|
+
*/
|
|
53
|
+
declare function generateCertificate(options?: CertificateOptions): CertificateResult;
|
|
54
|
+
/**
|
|
55
|
+
* Generate a self-signed certificate and write PEM files to disk.
|
|
56
|
+
* Returns the same CertificateResult as generateCertificate().
|
|
57
|
+
*/
|
|
58
|
+
declare function generateCertificateFiles(options: CertificateFileOptions): Promise<CertificateResult>;
|
|
59
|
+
|
|
5
60
|
/**
|
|
6
61
|
* Configuration for the injected response
|
|
7
62
|
*/
|
|
@@ -134,6 +189,24 @@ type HttpBinOptions = {
|
|
|
134
189
|
images?: boolean;
|
|
135
190
|
dynamicData?: boolean;
|
|
136
191
|
};
|
|
192
|
+
type HttpsOptions = {
|
|
193
|
+
/**
|
|
194
|
+
* PEM-encoded certificate string, or file path to a certificate PEM file.
|
|
195
|
+
*/
|
|
196
|
+
cert?: string;
|
|
197
|
+
/**
|
|
198
|
+
* PEM-encoded private key string, or file path to a private key PEM file.
|
|
199
|
+
*/
|
|
200
|
+
key?: string;
|
|
201
|
+
/**
|
|
202
|
+
* If true, auto-generate a self-signed certificate. Defaults to true when no cert/key provided.
|
|
203
|
+
*/
|
|
204
|
+
autoGenerate?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Options for auto-generating a self-signed certificate. Only used when auto-generating.
|
|
207
|
+
*/
|
|
208
|
+
certificateOptions?: CertificateOptions;
|
|
209
|
+
};
|
|
137
210
|
type MockHttpOptions = {
|
|
138
211
|
/**
|
|
139
212
|
* The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
|
|
@@ -173,6 +246,23 @@ type MockHttpOptions = {
|
|
|
173
246
|
* Whether to enable logging. Defaults to true.
|
|
174
247
|
*/
|
|
175
248
|
logging?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
251
|
+
* or provide an object with cert/key strings/paths or auto-generation options.
|
|
252
|
+
*/
|
|
253
|
+
https?: boolean | HttpsOptions;
|
|
254
|
+
/**
|
|
255
|
+
* Whether to enable HTTP/2 support. When used with `https`, enables h2 (HTTP/2 over TLS).
|
|
256
|
+
* When used without `https`, enables h2c (HTTP/2 cleartext).
|
|
257
|
+
* Defaults to false.
|
|
258
|
+
*/
|
|
259
|
+
http2?: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS (ALPN negotiation).
|
|
262
|
+
* Only applies when both `http2` and `https` are enabled.
|
|
263
|
+
* Defaults to true.
|
|
264
|
+
*/
|
|
265
|
+
http1?: boolean;
|
|
176
266
|
};
|
|
177
267
|
declare class MockHttp extends Hookified {
|
|
178
268
|
private _port;
|
|
@@ -183,6 +273,10 @@ declare class MockHttp extends Hookified {
|
|
|
183
273
|
private _logging;
|
|
184
274
|
private _httpBin;
|
|
185
275
|
private _rateLimit?;
|
|
276
|
+
private _http2;
|
|
277
|
+
private _http1;
|
|
278
|
+
private _https;
|
|
279
|
+
private _httpsCredentials;
|
|
186
280
|
private _server;
|
|
187
281
|
private _taps;
|
|
188
282
|
constructor(options?: MockHttpOptions);
|
|
@@ -246,6 +340,40 @@ declare class MockHttp extends Hookified {
|
|
|
246
340
|
* @default true
|
|
247
341
|
*/
|
|
248
342
|
set logging(logging: boolean);
|
|
343
|
+
/**
|
|
344
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
345
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
346
|
+
*/
|
|
347
|
+
get https(): HttpsOptions | undefined;
|
|
348
|
+
/**
|
|
349
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
350
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
351
|
+
*/
|
|
352
|
+
set https(value: boolean | HttpsOptions | undefined);
|
|
353
|
+
/**
|
|
354
|
+
* Whether the server is running in HTTPS mode.
|
|
355
|
+
*/
|
|
356
|
+
get isHttps(): boolean;
|
|
357
|
+
/**
|
|
358
|
+
* Whether HTTP/2 support is enabled.
|
|
359
|
+
* @default false
|
|
360
|
+
*/
|
|
361
|
+
get http2(): boolean;
|
|
362
|
+
/**
|
|
363
|
+
* Whether HTTP/2 support is enabled.
|
|
364
|
+
* @default false
|
|
365
|
+
*/
|
|
366
|
+
set http2(http2: boolean);
|
|
367
|
+
/**
|
|
368
|
+
* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
|
|
369
|
+
* @default true
|
|
370
|
+
*/
|
|
371
|
+
get http1(): boolean;
|
|
372
|
+
/**
|
|
373
|
+
* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
|
|
374
|
+
* @default true
|
|
375
|
+
*/
|
|
376
|
+
set http1(http1: boolean);
|
|
249
377
|
/**
|
|
250
378
|
* HTTP Bin options. Defaults to all enabled.
|
|
251
379
|
*/
|
|
@@ -353,8 +481,10 @@ declare class MockHttp extends Hookified {
|
|
|
353
481
|
* @param fastifyInstance - the server instance to register the routes on.
|
|
354
482
|
*/
|
|
355
483
|
registerDynamicDataRoutes(fastifyInstance?: FastifyInstance): Promise<void>;
|
|
484
|
+
private resolveHttpsCredentials;
|
|
485
|
+
private loadPemValue;
|
|
356
486
|
}
|
|
357
487
|
|
|
358
488
|
declare const start: () => Promise<MockHttp>;
|
|
359
489
|
|
|
360
|
-
export { type InjectionMatcher, type InjectionResponse, type InjectionTap, MockHttp as default, MockHttp as mockhttp, start };
|
|
490
|
+
export { type CertificateFileOptions, type CertificateOptions, type CertificateResult, type HttpsOptions, type InjectionMatcher, type InjectionResponse, type InjectionTap, MockHttp as default, generateCertificate, generateCertificateFiles, MockHttp as mockhttp, start };
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
import process2 from "process";
|
|
6
6
|
|
|
7
7
|
// src/mock-http.ts
|
|
8
|
+
import * as fsPromises2 from "fs/promises";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import fastifyCookie from "@fastify/cookie";
|
|
10
11
|
import fastifyHelmet from "@fastify/helmet";
|
|
@@ -15,6 +16,307 @@ import { detect } from "detect-port";
|
|
|
15
16
|
import Fastify from "fastify";
|
|
16
17
|
import { Hookified } from "hookified";
|
|
17
18
|
|
|
19
|
+
// src/certificate.ts
|
|
20
|
+
import * as crypto from "crypto";
|
|
21
|
+
import * as fsPromises from "fs/promises";
|
|
22
|
+
function encodeLength(length) {
|
|
23
|
+
if (length < 128) {
|
|
24
|
+
return Buffer.from([
|
|
25
|
+
length
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
const bytes = [];
|
|
29
|
+
let temp = length;
|
|
30
|
+
while (temp > 0) {
|
|
31
|
+
bytes.unshift(temp & 255);
|
|
32
|
+
temp >>= 8;
|
|
33
|
+
}
|
|
34
|
+
return Buffer.from([
|
|
35
|
+
128 | bytes.length,
|
|
36
|
+
...bytes
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
__name(encodeLength, "encodeLength");
|
|
40
|
+
function encodeTlv(tag, value) {
|
|
41
|
+
return Buffer.concat([
|
|
42
|
+
Buffer.from([
|
|
43
|
+
tag
|
|
44
|
+
]),
|
|
45
|
+
encodeLength(value.length),
|
|
46
|
+
value
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
__name(encodeTlv, "encodeTlv");
|
|
50
|
+
function encodeSequence(...elements) {
|
|
51
|
+
const content = Buffer.concat(elements);
|
|
52
|
+
return encodeTlv(48, content);
|
|
53
|
+
}
|
|
54
|
+
__name(encodeSequence, "encodeSequence");
|
|
55
|
+
function encodeSet(...elements) {
|
|
56
|
+
const content = Buffer.concat(elements);
|
|
57
|
+
return encodeTlv(49, content);
|
|
58
|
+
}
|
|
59
|
+
__name(encodeSet, "encodeSet");
|
|
60
|
+
function encodeInteger(value) {
|
|
61
|
+
let buf;
|
|
62
|
+
if (typeof value === "number") {
|
|
63
|
+
if (value === 0) {
|
|
64
|
+
buf = Buffer.from([
|
|
65
|
+
0
|
|
66
|
+
]);
|
|
67
|
+
} else {
|
|
68
|
+
const bytes = [];
|
|
69
|
+
let temp = value;
|
|
70
|
+
while (temp > 0) {
|
|
71
|
+
bytes.unshift(temp & 255);
|
|
72
|
+
temp >>= 8;
|
|
73
|
+
}
|
|
74
|
+
if (bytes[0] & 128) {
|
|
75
|
+
bytes.unshift(0);
|
|
76
|
+
}
|
|
77
|
+
buf = Buffer.from(bytes);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
if (value.length > 0 && value[0] & 128) {
|
|
81
|
+
buf = Buffer.concat([
|
|
82
|
+
Buffer.from([
|
|
83
|
+
0
|
|
84
|
+
]),
|
|
85
|
+
value
|
|
86
|
+
]);
|
|
87
|
+
} else {
|
|
88
|
+
buf = value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return encodeTlv(2, buf);
|
|
92
|
+
}
|
|
93
|
+
__name(encodeInteger, "encodeInteger");
|
|
94
|
+
function encodeBitString(value) {
|
|
95
|
+
const content = Buffer.concat([
|
|
96
|
+
Buffer.from([
|
|
97
|
+
0
|
|
98
|
+
]),
|
|
99
|
+
value
|
|
100
|
+
]);
|
|
101
|
+
return encodeTlv(3, content);
|
|
102
|
+
}
|
|
103
|
+
__name(encodeBitString, "encodeBitString");
|
|
104
|
+
function encodeOctetString(value) {
|
|
105
|
+
return encodeTlv(4, value);
|
|
106
|
+
}
|
|
107
|
+
__name(encodeOctetString, "encodeOctetString");
|
|
108
|
+
function encodeOid(oid) {
|
|
109
|
+
const bytes = [];
|
|
110
|
+
bytes.push(40 * oid[0] + oid[1]);
|
|
111
|
+
for (let i = 2; i < oid.length; i++) {
|
|
112
|
+
let component = oid[i];
|
|
113
|
+
if (component < 128) {
|
|
114
|
+
bytes.push(component);
|
|
115
|
+
} else {
|
|
116
|
+
const encoded = [];
|
|
117
|
+
encoded.push(component & 127);
|
|
118
|
+
component >>= 7;
|
|
119
|
+
while (component > 0) {
|
|
120
|
+
encoded.push(component & 127 | 128);
|
|
121
|
+
component >>= 7;
|
|
122
|
+
}
|
|
123
|
+
encoded.reverse();
|
|
124
|
+
bytes.push(...encoded);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return encodeTlv(6, Buffer.from(bytes));
|
|
128
|
+
}
|
|
129
|
+
__name(encodeOid, "encodeOid");
|
|
130
|
+
function encodeUtf8String(value) {
|
|
131
|
+
return encodeTlv(12, Buffer.from(value, "utf8"));
|
|
132
|
+
}
|
|
133
|
+
__name(encodeUtf8String, "encodeUtf8String");
|
|
134
|
+
function encodeTime(date) {
|
|
135
|
+
const fullYear = date.getUTCFullYear();
|
|
136
|
+
const timePart = `${pad2(date.getUTCMonth() + 1)}${pad2(date.getUTCDate())}${pad2(date.getUTCHours())}${pad2(date.getUTCMinutes())}${pad2(date.getUTCSeconds())}Z`;
|
|
137
|
+
if (fullYear >= 2050) {
|
|
138
|
+
const str2 = `${fullYear}${timePart}`;
|
|
139
|
+
return encodeTlv(24, Buffer.from(str2, "ascii"));
|
|
140
|
+
}
|
|
141
|
+
const str = `${pad2(fullYear % 100)}${timePart}`;
|
|
142
|
+
return encodeTlv(23, Buffer.from(str, "ascii"));
|
|
143
|
+
}
|
|
144
|
+
__name(encodeTime, "encodeTime");
|
|
145
|
+
function encodeContextSpecific(tag, value) {
|
|
146
|
+
return encodeTlv(160 | tag, value);
|
|
147
|
+
}
|
|
148
|
+
__name(encodeContextSpecific, "encodeContextSpecific");
|
|
149
|
+
function pad2(n) {
|
|
150
|
+
return n.toString().padStart(2, "0");
|
|
151
|
+
}
|
|
152
|
+
__name(pad2, "pad2");
|
|
153
|
+
var OID_SHA256_WITH_RSA = [
|
|
154
|
+
1,
|
|
155
|
+
2,
|
|
156
|
+
840,
|
|
157
|
+
113549,
|
|
158
|
+
1,
|
|
159
|
+
1,
|
|
160
|
+
11
|
|
161
|
+
];
|
|
162
|
+
var OID_COMMON_NAME = [
|
|
163
|
+
2,
|
|
164
|
+
5,
|
|
165
|
+
4,
|
|
166
|
+
3
|
|
167
|
+
];
|
|
168
|
+
var OID_SUBJECT_ALT_NAME = [
|
|
169
|
+
2,
|
|
170
|
+
5,
|
|
171
|
+
29,
|
|
172
|
+
17
|
|
173
|
+
];
|
|
174
|
+
function buildAlgorithmIdentifier() {
|
|
175
|
+
return encodeSequence(encodeOid(OID_SHA256_WITH_RSA), encodeTlv(5, Buffer.alloc(0)));
|
|
176
|
+
}
|
|
177
|
+
__name(buildAlgorithmIdentifier, "buildAlgorithmIdentifier");
|
|
178
|
+
function buildName(cn) {
|
|
179
|
+
const rdn = encodeSet(encodeSequence(encodeOid(OID_COMMON_NAME), encodeUtf8String(cn)));
|
|
180
|
+
return encodeSequence(rdn);
|
|
181
|
+
}
|
|
182
|
+
__name(buildName, "buildName");
|
|
183
|
+
function buildValidity(notBefore, notAfter) {
|
|
184
|
+
return encodeSequence(encodeTime(notBefore), encodeTime(notAfter));
|
|
185
|
+
}
|
|
186
|
+
__name(buildValidity, "buildValidity");
|
|
187
|
+
function encodeIpAddress(ip) {
|
|
188
|
+
if (ip.includes(".")) {
|
|
189
|
+
const parts = ip.split(".").map(Number);
|
|
190
|
+
return Buffer.from(parts);
|
|
191
|
+
}
|
|
192
|
+
const expanded = expandIpv6(ip);
|
|
193
|
+
const buf = Buffer.alloc(16);
|
|
194
|
+
const groups = expanded.split(":");
|
|
195
|
+
for (let i = 0; i < 8; i++) {
|
|
196
|
+
const val = Number.parseInt(groups[i], 16);
|
|
197
|
+
buf.writeUInt16BE(val, i * 2);
|
|
198
|
+
}
|
|
199
|
+
return buf;
|
|
200
|
+
}
|
|
201
|
+
__name(encodeIpAddress, "encodeIpAddress");
|
|
202
|
+
function expandIpv6(ip) {
|
|
203
|
+
if (ip.includes("::")) {
|
|
204
|
+
const [left, right] = ip.split("::");
|
|
205
|
+
const leftGroups = left ? left.split(":") : [];
|
|
206
|
+
const rightGroups = right ? right.split(":") : [];
|
|
207
|
+
const missing = 8 - leftGroups.length - rightGroups.length;
|
|
208
|
+
const middle = Array.from({
|
|
209
|
+
length: missing
|
|
210
|
+
}).fill("0000");
|
|
211
|
+
const allGroups = [
|
|
212
|
+
...leftGroups,
|
|
213
|
+
...middle,
|
|
214
|
+
...rightGroups
|
|
215
|
+
];
|
|
216
|
+
return allGroups.map((g) => g.padStart(4, "0")).join(":");
|
|
217
|
+
}
|
|
218
|
+
return ip.split(":").map((g) => g.padStart(4, "0")).join(":");
|
|
219
|
+
}
|
|
220
|
+
__name(expandIpv6, "expandIpv6");
|
|
221
|
+
function buildSubjectAltNameExtension(altNames) {
|
|
222
|
+
const names = [];
|
|
223
|
+
for (const alt of altNames) {
|
|
224
|
+
if (alt.type === "dns") {
|
|
225
|
+
names.push(encodeTlv(130, Buffer.from(alt.value, "ascii")));
|
|
226
|
+
} else {
|
|
227
|
+
names.push(encodeTlv(135, encodeIpAddress(alt.value)));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const sanValue = encodeSequence(...names);
|
|
231
|
+
return encodeSequence(encodeOid(OID_SUBJECT_ALT_NAME), encodeOctetString(sanValue));
|
|
232
|
+
}
|
|
233
|
+
__name(buildSubjectAltNameExtension, "buildSubjectAltNameExtension");
|
|
234
|
+
function buildExtensions(altNames) {
|
|
235
|
+
const extensions = encodeSequence(buildSubjectAltNameExtension(altNames));
|
|
236
|
+
return encodeContextSpecific(3, extensions);
|
|
237
|
+
}
|
|
238
|
+
__name(buildExtensions, "buildExtensions");
|
|
239
|
+
function buildTbsCertificate(serialNumber, issuerCn, notBefore, notAfter, subjectCn, publicKeyDer, altNames) {
|
|
240
|
+
const version = encodeContextSpecific(0, encodeInteger(2));
|
|
241
|
+
const serial = encodeInteger(serialNumber);
|
|
242
|
+
const signatureAlgorithm = buildAlgorithmIdentifier();
|
|
243
|
+
const issuer = buildName(issuerCn);
|
|
244
|
+
const validity = buildValidity(notBefore, notAfter);
|
|
245
|
+
const subject = buildName(subjectCn);
|
|
246
|
+
const extensions = buildExtensions(altNames);
|
|
247
|
+
return encodeSequence(version, serial, signatureAlgorithm, issuer, validity, subject, publicKeyDer, extensions);
|
|
248
|
+
}
|
|
249
|
+
__name(buildTbsCertificate, "buildTbsCertificate");
|
|
250
|
+
function derToPem(der, label) {
|
|
251
|
+
const base64 = der.toString("base64");
|
|
252
|
+
const lines = [];
|
|
253
|
+
for (let i = 0; i < base64.length; i += 64) {
|
|
254
|
+
lines.push(base64.slice(i, i + 64));
|
|
255
|
+
}
|
|
256
|
+
return `-----BEGIN ${label}-----
|
|
257
|
+
${lines.join("\n")}
|
|
258
|
+
-----END ${label}-----
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
__name(derToPem, "derToPem");
|
|
262
|
+
function generateCertificate(options) {
|
|
263
|
+
const commonName = options?.commonName ?? "localhost";
|
|
264
|
+
const validityDays = options?.validityDays ?? 365;
|
|
265
|
+
const keySize = options?.keySize ?? 2048;
|
|
266
|
+
const altNames = options?.altNames ?? [
|
|
267
|
+
{
|
|
268
|
+
type: "dns",
|
|
269
|
+
value: "localhost"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
type: "ip",
|
|
273
|
+
value: "127.0.0.1"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
type: "ip",
|
|
277
|
+
value: "::1"
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
281
|
+
modulusLength: keySize,
|
|
282
|
+
publicKeyEncoding: {
|
|
283
|
+
type: "spki",
|
|
284
|
+
format: "der"
|
|
285
|
+
},
|
|
286
|
+
privateKeyEncoding: {
|
|
287
|
+
type: "pkcs8",
|
|
288
|
+
format: "pem"
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
const notBefore = /* @__PURE__ */ new Date();
|
|
292
|
+
const notAfter = /* @__PURE__ */ new Date();
|
|
293
|
+
notAfter.setDate(notAfter.getDate() + validityDays);
|
|
294
|
+
const serialBytes = crypto.randomBytes(16);
|
|
295
|
+
serialBytes[0] &= 127;
|
|
296
|
+
let start2 = 0;
|
|
297
|
+
while (start2 < serialBytes.length - 1 && serialBytes[start2] === 0) {
|
|
298
|
+
start2++;
|
|
299
|
+
}
|
|
300
|
+
const serialNumber = serialBytes.subarray(start2);
|
|
301
|
+
const tbsCertificate = buildTbsCertificate(serialNumber, commonName, notBefore, notAfter, commonName, publicKey, altNames);
|
|
302
|
+
const signer = crypto.createSign("SHA256");
|
|
303
|
+
signer.update(tbsCertificate);
|
|
304
|
+
const signature = signer.sign(privateKey);
|
|
305
|
+
const certificate = encodeSequence(tbsCertificate, buildAlgorithmIdentifier(), encodeBitString(signature));
|
|
306
|
+
return {
|
|
307
|
+
cert: derToPem(certificate, "CERTIFICATE"),
|
|
308
|
+
key: privateKey
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
__name(generateCertificate, "generateCertificate");
|
|
312
|
+
async function generateCertificateFiles(options) {
|
|
313
|
+
const result = generateCertificate(options);
|
|
314
|
+
await fsPromises.writeFile(options.certPath, result.cert, "utf8");
|
|
315
|
+
await fsPromises.writeFile(options.keyPath, result.key, "utf8");
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
__name(generateCertificateFiles, "generateCertificateFiles");
|
|
319
|
+
|
|
18
320
|
// src/fastify-config.ts
|
|
19
321
|
function getFastifyConfig(logging = true) {
|
|
20
322
|
return {
|
|
@@ -328,8 +630,8 @@ var bearerAuthRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
328
630
|
}, "bearerAuthRoute");
|
|
329
631
|
|
|
330
632
|
// src/routes/auth/digest.ts
|
|
331
|
-
import
|
|
332
|
-
var makeNonce = /* @__PURE__ */ __name(() =>
|
|
633
|
+
import crypto2 from "crypto";
|
|
634
|
+
var makeNonce = /* @__PURE__ */ __name(() => crypto2.randomBytes(16).toString("hex"), "makeNonce");
|
|
333
635
|
var algorithms = /* @__PURE__ */ new Set([
|
|
334
636
|
"MD5",
|
|
335
637
|
"md5",
|
|
@@ -354,7 +656,7 @@ var hashAlg = /* @__PURE__ */ __name((alg) => {
|
|
|
354
656
|
}
|
|
355
657
|
return "md5";
|
|
356
658
|
}, "hashAlg");
|
|
357
|
-
var h = /* @__PURE__ */ __name((algo, data) =>
|
|
659
|
+
var h = /* @__PURE__ */ __name((algo, data) => crypto2.createHash(algo).update(data).digest("hex"), "h");
|
|
358
660
|
var parseDigest = /* @__PURE__ */ __name((header) => {
|
|
359
661
|
if (!header) {
|
|
360
662
|
return void 0;
|
|
@@ -771,7 +1073,7 @@ var base64Route = /* @__PURE__ */ __name((fastify) => {
|
|
|
771
1073
|
}, "base64Route");
|
|
772
1074
|
|
|
773
1075
|
// src/routes/dynamic-data/bytes.ts
|
|
774
|
-
import { randomBytes } from "crypto";
|
|
1076
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
775
1077
|
var bytesSchema = {
|
|
776
1078
|
description: "Returns n random bytes generated with given seed",
|
|
777
1079
|
tags: [
|
|
@@ -853,7 +1155,7 @@ var bytesRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
853
1155
|
}
|
|
854
1156
|
bytes = generateSeededBytes(limitedN, seed);
|
|
855
1157
|
} else {
|
|
856
|
-
bytes =
|
|
1158
|
+
bytes = randomBytes2(limitedN);
|
|
857
1159
|
}
|
|
858
1160
|
return reply.header("Content-Type", "application/octet-stream").header("Content-Length", bytes.length).send(bytes);
|
|
859
1161
|
});
|
|
@@ -1375,7 +1677,7 @@ var streamRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
1375
1677
|
}, "streamRoute");
|
|
1376
1678
|
|
|
1377
1679
|
// src/routes/dynamic-data/stream-bytes.ts
|
|
1378
|
-
import { randomBytes as
|
|
1680
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1379
1681
|
var streamBytesSchema = {
|
|
1380
1682
|
description: "Streams n random bytes generated with given seed, in chunks of chunk_size per packet",
|
|
1381
1683
|
tags: [
|
|
@@ -1468,7 +1770,7 @@ var streamBytesRoute = /* @__PURE__ */ __name((fastify) => {
|
|
|
1468
1770
|
}
|
|
1469
1771
|
allBytes = generateSeededBytes2(limitedN, seed);
|
|
1470
1772
|
} else {
|
|
1471
|
-
allBytes =
|
|
1773
|
+
allBytes = randomBytes3(limitedN);
|
|
1472
1774
|
}
|
|
1473
1775
|
reply.raw.writeHead(200, {
|
|
1474
1776
|
"Content-Type": "application/octet-stream",
|
|
@@ -3530,7 +3832,7 @@ import { fastifySwaggerUi } from "@fastify/swagger-ui";
|
|
|
3530
3832
|
// package.json
|
|
3531
3833
|
var package_default = {
|
|
3532
3834
|
name: "@jaredwray/mockhttp",
|
|
3533
|
-
version: "1.
|
|
3835
|
+
version: "1.5.0",
|
|
3534
3836
|
description: "Mock Http - Easy way to mock http with httpbin replacement",
|
|
3535
3837
|
type: "module",
|
|
3536
3838
|
main: "dist/index.js",
|
|
@@ -3581,29 +3883,29 @@ var package_default = {
|
|
|
3581
3883
|
},
|
|
3582
3884
|
homepage: "https://github.com/jaredwray/mockhttp#readme",
|
|
3583
3885
|
devDependencies: {
|
|
3584
|
-
"@biomejs/biome": "^2.
|
|
3585
|
-
"@swc/core": "^1.15.
|
|
3886
|
+
"@biomejs/biome": "^2.4.7",
|
|
3887
|
+
"@swc/core": "^1.15.18",
|
|
3586
3888
|
"@types/html-escaper": "^3.0.4",
|
|
3587
|
-
"@vitest/coverage-v8": "^4.0
|
|
3588
|
-
rimraf: "^6.1.
|
|
3889
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
3890
|
+
rimraf: "^6.1.3",
|
|
3589
3891
|
tsup: "^8.5.1",
|
|
3590
3892
|
tsx: "^4.21.0",
|
|
3591
3893
|
typescript: "^5.9.3",
|
|
3592
|
-
vitest: "^4.0
|
|
3894
|
+
vitest: "^4.1.0"
|
|
3593
3895
|
},
|
|
3594
3896
|
dependencies: {
|
|
3595
3897
|
"@fastify/cookie": "^11.0.2",
|
|
3596
3898
|
"@fastify/helmet": "^13.0.2",
|
|
3597
3899
|
"@fastify/rate-limit": "^10.3.0",
|
|
3598
3900
|
"@fastify/static": "^9.0.0",
|
|
3599
|
-
"@fastify/swagger": "^9.
|
|
3600
|
-
"@fastify/swagger-ui": "^5.2.
|
|
3601
|
-
"@scalar/api-reference": "^1.
|
|
3901
|
+
"@fastify/swagger": "^9.7.0",
|
|
3902
|
+
"@fastify/swagger-ui": "^5.2.5",
|
|
3903
|
+
"@scalar/api-reference": "^1.48.7",
|
|
3602
3904
|
"detect-port": "^2.1.0",
|
|
3603
|
-
fastify: "^5.
|
|
3604
|
-
hookified: "^1.
|
|
3905
|
+
fastify: "^5.8.2",
|
|
3906
|
+
hookified: "^2.1.0",
|
|
3605
3907
|
"html-escaper": "^3.0.3",
|
|
3606
|
-
pino: "^10.
|
|
3908
|
+
pino: "^10.3.1",
|
|
3607
3909
|
"pino-pretty": "^13.1.3"
|
|
3608
3910
|
},
|
|
3609
3911
|
files: [
|
|
@@ -3822,6 +4124,10 @@ var MockHttp = class extends Hookified {
|
|
|
3822
4124
|
"::1"
|
|
3823
4125
|
]
|
|
3824
4126
|
};
|
|
4127
|
+
_http2 = false;
|
|
4128
|
+
_http1 = true;
|
|
4129
|
+
_https;
|
|
4130
|
+
_httpsCredentials;
|
|
3825
4131
|
_server = Fastify();
|
|
3826
4132
|
_taps = new TapManager();
|
|
3827
4133
|
constructor(options) {
|
|
@@ -3851,6 +4157,23 @@ var MockHttp = class extends Hookified {
|
|
|
3851
4157
|
if (options?.logging !== void 0) {
|
|
3852
4158
|
this._logging = options.logging;
|
|
3853
4159
|
}
|
|
4160
|
+
if (options?.http2 !== void 0) {
|
|
4161
|
+
this._http2 = options.http2;
|
|
4162
|
+
}
|
|
4163
|
+
if (options?.http1 !== void 0) {
|
|
4164
|
+
this._http1 = options.http1;
|
|
4165
|
+
}
|
|
4166
|
+
if (options?.https !== void 0) {
|
|
4167
|
+
if (options.https === true) {
|
|
4168
|
+
this._https = {
|
|
4169
|
+
autoGenerate: true
|
|
4170
|
+
};
|
|
4171
|
+
} else if (options.https === false) {
|
|
4172
|
+
this._https = void 0;
|
|
4173
|
+
} else {
|
|
4174
|
+
this._https = options.https;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
3854
4177
|
}
|
|
3855
4178
|
/**
|
|
3856
4179
|
* The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
|
|
@@ -3937,6 +4260,62 @@ var MockHttp = class extends Hookified {
|
|
|
3937
4260
|
this._logging = logging;
|
|
3938
4261
|
}
|
|
3939
4262
|
/**
|
|
4263
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
4264
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
4265
|
+
*/
|
|
4266
|
+
get https() {
|
|
4267
|
+
return this._https;
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* HTTPS configuration. Set to true to auto-generate a self-signed certificate,
|
|
4271
|
+
* or provide an HttpsOptions object with cert/key or auto-generation options.
|
|
4272
|
+
*/
|
|
4273
|
+
set https(value) {
|
|
4274
|
+
if (value === true) {
|
|
4275
|
+
this._https = {
|
|
4276
|
+
autoGenerate: true
|
|
4277
|
+
};
|
|
4278
|
+
} else if (value === false || value === void 0) {
|
|
4279
|
+
this._https = void 0;
|
|
4280
|
+
} else {
|
|
4281
|
+
this._https = value;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
/**
|
|
4285
|
+
* Whether the server is running in HTTPS mode.
|
|
4286
|
+
*/
|
|
4287
|
+
get isHttps() {
|
|
4288
|
+
return this._httpsCredentials !== void 0;
|
|
4289
|
+
}
|
|
4290
|
+
/**
|
|
4291
|
+
* Whether HTTP/2 support is enabled.
|
|
4292
|
+
* @default false
|
|
4293
|
+
*/
|
|
4294
|
+
get http2() {
|
|
4295
|
+
return this._http2;
|
|
4296
|
+
}
|
|
4297
|
+
/**
|
|
4298
|
+
* Whether HTTP/2 support is enabled.
|
|
4299
|
+
* @default false
|
|
4300
|
+
*/
|
|
4301
|
+
set http2(http2) {
|
|
4302
|
+
this._http2 = http2;
|
|
4303
|
+
}
|
|
4304
|
+
/**
|
|
4305
|
+
* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
|
|
4306
|
+
* @default true
|
|
4307
|
+
*/
|
|
4308
|
+
get http1() {
|
|
4309
|
+
return this._http1;
|
|
4310
|
+
}
|
|
4311
|
+
/**
|
|
4312
|
+
* Whether to allow HTTP/1.1 fallback when using HTTP/2 with HTTPS.
|
|
4313
|
+
* @default true
|
|
4314
|
+
*/
|
|
4315
|
+
set http1(http1) {
|
|
4316
|
+
this._http1 = http1;
|
|
4317
|
+
}
|
|
4318
|
+
/**
|
|
3940
4319
|
* HTTP Bin options. Defaults to all enabled.
|
|
3941
4320
|
*/
|
|
3942
4321
|
get httpBin() {
|
|
@@ -3994,11 +4373,31 @@ var MockHttp = class extends Hookified {
|
|
|
3994
4373
|
* Start the Fastify server. If the server is already running, it will be closed and restarted.
|
|
3995
4374
|
*/
|
|
3996
4375
|
async start() {
|
|
4376
|
+
this._httpsCredentials = void 0;
|
|
4377
|
+
if (this._https) {
|
|
4378
|
+
this._httpsCredentials = await this.resolveHttpsCredentials(this._https);
|
|
4379
|
+
}
|
|
3997
4380
|
try {
|
|
3998
4381
|
if (this._server) {
|
|
3999
4382
|
await this._server.close();
|
|
4000
4383
|
}
|
|
4001
|
-
|
|
4384
|
+
const fastifyOptions = {
|
|
4385
|
+
...getFastifyConfig(this._logging)
|
|
4386
|
+
};
|
|
4387
|
+
if (this._http2) {
|
|
4388
|
+
fastifyOptions.http2 = true;
|
|
4389
|
+
}
|
|
4390
|
+
if (this._httpsCredentials) {
|
|
4391
|
+
const httpsOptions = {
|
|
4392
|
+
key: this._httpsCredentials.key,
|
|
4393
|
+
cert: this._httpsCredentials.cert
|
|
4394
|
+
};
|
|
4395
|
+
if (this._http2) {
|
|
4396
|
+
httpsOptions.allowHTTP1 = this._http1;
|
|
4397
|
+
}
|
|
4398
|
+
fastifyOptions.https = httpsOptions;
|
|
4399
|
+
}
|
|
4400
|
+
this._server = Fastify(fastifyOptions);
|
|
4002
4401
|
this._server.addHook("onRequest", async (request, reply) => {
|
|
4003
4402
|
const matchedTap = this._taps.matchRequest(request);
|
|
4004
4403
|
if (matchedTap) {
|
|
@@ -4248,6 +4647,33 @@ var MockHttp = class extends Hookified {
|
|
|
4248
4647
|
await fastify.register(dripRoute);
|
|
4249
4648
|
await fastify.register(linksRoute);
|
|
4250
4649
|
}
|
|
4650
|
+
async resolveHttpsCredentials(options) {
|
|
4651
|
+
if (options.cert && options.key) {
|
|
4652
|
+
const cert = await this.loadPemValue(options.cert);
|
|
4653
|
+
const key = await this.loadPemValue(options.key);
|
|
4654
|
+
return {
|
|
4655
|
+
cert,
|
|
4656
|
+
key
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
if ((options.cert || options.key) && options.autoGenerate !== true) {
|
|
4660
|
+
throw new Error("HTTPS options must include both 'cert' and 'key'. Only one was provided.");
|
|
4661
|
+
}
|
|
4662
|
+
if (options.autoGenerate !== false) {
|
|
4663
|
+
const result = generateCertificate(options.certificateOptions);
|
|
4664
|
+
return {
|
|
4665
|
+
cert: result.cert,
|
|
4666
|
+
key: result.key
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4669
|
+
throw new Error("HTTPS is enabled but no certificate was provided and autoGenerate is false.");
|
|
4670
|
+
}
|
|
4671
|
+
async loadPemValue(value) {
|
|
4672
|
+
if (value.startsWith("-----")) {
|
|
4673
|
+
return value;
|
|
4674
|
+
}
|
|
4675
|
+
return fsPromises2.readFile(value, "utf8");
|
|
4676
|
+
}
|
|
4251
4677
|
};
|
|
4252
4678
|
|
|
4253
4679
|
// src/index.ts
|
|
@@ -4262,6 +4688,9 @@ var start = /* @__PURE__ */ __name(async () => {
|
|
|
4262
4688
|
if (process2.env.LOGGING === "false") {
|
|
4263
4689
|
mockHttp.logging = false;
|
|
4264
4690
|
}
|
|
4691
|
+
if (process2.env.HTTP2 === "true") {
|
|
4692
|
+
mockHttp.http2 = true;
|
|
4693
|
+
}
|
|
4265
4694
|
await mockHttp.start();
|
|
4266
4695
|
return mockHttp;
|
|
4267
4696
|
}, "start");
|
|
@@ -4270,6 +4699,8 @@ if (import.meta.url === `file://${process2.argv[1]}`) {
|
|
|
4270
4699
|
}
|
|
4271
4700
|
export {
|
|
4272
4701
|
MockHttp as default,
|
|
4702
|
+
generateCertificate,
|
|
4703
|
+
generateCertificateFiles,
|
|
4273
4704
|
MockHttp as mockhttp,
|
|
4274
4705
|
start
|
|
4275
4706
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaredwray/mockhttp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Mock Http - Easy way to mock http with httpbin replacement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -51,29 +51,29 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/jaredwray/mockhttp#readme",
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@biomejs/biome": "^2.
|
|
55
|
-
"@swc/core": "^1.15.
|
|
54
|
+
"@biomejs/biome": "^2.4.7",
|
|
55
|
+
"@swc/core": "^1.15.18",
|
|
56
56
|
"@types/html-escaper": "^3.0.4",
|
|
57
|
-
"@vitest/coverage-v8": "^4.0
|
|
58
|
-
"rimraf": "^6.1.
|
|
57
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
58
|
+
"rimraf": "^6.1.3",
|
|
59
59
|
"tsup": "^8.5.1",
|
|
60
60
|
"tsx": "^4.21.0",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
|
-
"vitest": "^4.0
|
|
62
|
+
"vitest": "^4.1.0"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@fastify/cookie": "^11.0.2",
|
|
66
66
|
"@fastify/helmet": "^13.0.2",
|
|
67
67
|
"@fastify/rate-limit": "^10.3.0",
|
|
68
68
|
"@fastify/static": "^9.0.0",
|
|
69
|
-
"@fastify/swagger": "^9.
|
|
70
|
-
"@fastify/swagger-ui": "^5.2.
|
|
71
|
-
"@scalar/api-reference": "^1.
|
|
69
|
+
"@fastify/swagger": "^9.7.0",
|
|
70
|
+
"@fastify/swagger-ui": "^5.2.5",
|
|
71
|
+
"@scalar/api-reference": "^1.48.7",
|
|
72
72
|
"detect-port": "^2.1.0",
|
|
73
|
-
"fastify": "^5.
|
|
74
|
-
"hookified": "^1.
|
|
73
|
+
"fastify": "^5.8.2",
|
|
74
|
+
"hookified": "^2.1.0",
|
|
75
75
|
"html-escaper": "^3.0.3",
|
|
76
|
-
"pino": "^10.
|
|
76
|
+
"pino": "^10.3.1",
|
|
77
77
|
"pino-pretty": "^13.1.3"
|
|
78
78
|
},
|
|
79
79
|
"files": [
|