@jaredwray/mockhttp 0.6.1 → 0.7.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/dist/index.js CHANGED
@@ -6,152 +6,13 @@ import process from "process";
6
6
 
7
7
  // src/mock-http.ts
8
8
  import path from "path";
9
- import fastifyStatic from "@fastify/static";
9
+ import fastifyCookie from "@fastify/cookie";
10
10
  import fastifyHelmet from "@fastify/helmet";
11
+ import fastifyStatic from "@fastify/static";
11
12
  import { fastifySwagger } from "@fastify/swagger";
12
- import fastifyCookie from "@fastify/cookie";
13
+ import { detect } from "detect-port";
13
14
  import Fastify from "fastify";
14
15
  import { Hookified } from "hookified";
15
- import { detect } from "detect-port";
16
-
17
- // src/swagger.ts
18
- import { fastifySwaggerUi } from "@fastify/swagger-ui";
19
-
20
- // package.json
21
- var package_default = {
22
- name: "@jaredwray/mockhttp",
23
- version: "0.6.1",
24
- description: "Mock Http - Easy way to mock http with httpbin replacement",
25
- type: "module",
26
- main: "dist/index.js",
27
- directories: {
28
- test: "test"
29
- },
30
- scripts: {
31
- test: "xo --fix && vitest run --coverage",
32
- dev: "xo --fix && tsx src/index.ts --watch",
33
- build: "rimraf ./dist && tsup src/index.ts --format esm --dts --clean",
34
- "docker:build": "docker build -t jaredwray/mockhttp .",
35
- "docker:run": "docker run -p 3000:3000 jaredwray/mockhttp",
36
- "docker:push": "docker push jaredwray/mockhttp",
37
- "docker:stop": 'docker stop $(docker ps -a -q --filter ancestor=jaredwray/mockhttp --format="{{.ID}}")',
38
- "docker:readme": "tsx ./scripts/create-docker-readme.ts",
39
- "docker:compose:up": "docker-compose up -d",
40
- "docker:compose:down": "docker-compose down",
41
- prepare: "pnpm run build",
42
- start: "node dist/index.js"
43
- },
44
- repository: {
45
- type: "git",
46
- url: "git+https://github.com/jaredwray/mockhttp.git"
47
- },
48
- keywords: [
49
- "mock",
50
- "http",
51
- "httpbin",
52
- "test"
53
- ],
54
- author: "Jared Wray <me@jaredwray.com>",
55
- license: "MIT",
56
- bugs: {
57
- url: "https://github.com/jaredwray/mockhttp/issues"
58
- },
59
- homepage: "https://github.com/jaredwray/mockhttp#readme",
60
- devDependencies: {
61
- "@swc/core": "^1.13.0",
62
- "@types/html-escaper": "^3.0.4",
63
- "@vitest/coverage-v8": "^3.2.4",
64
- rimraf: "^6.0.1",
65
- tsup: "^8.5.0",
66
- tsx: "^4.20.3",
67
- typescript: "^5.8.3",
68
- vitest: "^3.2.4",
69
- xo: "^1.2.1"
70
- },
71
- dependencies: {
72
- "@fastify/cookie": "^11.0.2",
73
- "@fastify/helmet": "^13.0.1",
74
- "@fastify/static": "^8.2.0",
75
- "@fastify/swagger": "^9.5.1",
76
- "@fastify/swagger-ui": "^5.2.3",
77
- "@scalar/api-reference": "^1.32.9",
78
- "detect-port": "^2.1.0",
79
- fastify: "^5.4.0",
80
- hookified: "^1.10.0",
81
- "html-escaper": "^3.0.3",
82
- pino: "^9.7.0",
83
- "pino-pretty": "^13.0.0"
84
- },
85
- files: [
86
- "dist",
87
- "public",
88
- "LICENCE"
89
- ],
90
- pnpm: {
91
- onlyBuiltDependencies: [
92
- "@swc/core",
93
- "esbuild",
94
- "unrs-resolver",
95
- "vue-demi"
96
- ]
97
- },
98
- xo: {
99
- rules: {
100
- "@typescript-eslint/no-unsafe-call": "off",
101
- "@typescript-eslint/no-unsafe-assignment": "off"
102
- }
103
- }
104
- };
105
-
106
- // src/swagger.ts
107
- var description = `
108
- A simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using \`nodejs\` and \`fastify\` with the idea of running it via https://mockhttp.org, via docker \`jaredwray/mockhttp\`, or nodejs \`npm install jaredwray/mockhttp\`.
109
-
110
- * [GitHub Repository](https://github.com/jaredwray/mockhttp)
111
- * [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)
112
- * [NPM Package](https://www.npmjs.com/package/jaredwray/mockhttp)
113
-
114
- # About mockhttp.org
115
-
116
- [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. 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.
117
- `;
118
- var fastifySwaggerConfig = {
119
- openapi: {
120
- info: {
121
- title: "Mock HTTP API",
122
- description,
123
- version: package_default.version
124
- },
125
- consumes: [
126
- "application/json"
127
- ],
128
- produces: [
129
- "application/json"
130
- ]
131
- }
132
- };
133
- var registerSwaggerUi = /* @__PURE__ */ __name(async (fastify) => {
134
- await fastify.register(fastifySwaggerUi, {
135
- routePrefix: "/docs",
136
- uiConfig: {
137
- docExpansion: "none",
138
- deepLinking: false
139
- },
140
- uiHooks: {
141
- /* c8 ignore next 6 */
142
- onRequest(_request, _reply, next) {
143
- next();
144
- },
145
- preHandler(_request, _reply, next) {
146
- next();
147
- }
148
- },
149
- // eslint-disable-next-line @typescript-eslint/naming-convention
150
- staticCSP: true,
151
- transformSpecification: /* @__PURE__ */ __name((swaggerObject, _request, _reply) => swaggerObject, "transformSpecification"),
152
- transformSpecificationClone: true
153
- });
154
- }, "registerSwaggerUi");
155
16
 
156
17
  // src/fastify-config.ts
157
18
  var fastifyConfig = {
@@ -168,949 +29,1017 @@ var fastifyConfig = {
168
29
  }
169
30
  };
170
31
 
171
- // src/routes/index.ts
172
- var indexRoute = /* @__PURE__ */ __name((fastify) => {
173
- fastify.get("/", {
174
- schema: {
175
- hide: true
176
- }
177
- }, async (_request, reply) => {
178
- const redocHtml = `
179
- <!doctype html>
180
- <html>
181
- <head>
182
- <title>{ mockhttp }</title>
183
- <meta charset="utf-8" />
184
- <meta
185
- name="viewport"
186
- content="width=device-width, initial-scale=1" />
187
- <style>
188
- :root {
189
- --scalar-custom-header-height: 70px;
190
- }
191
- .custom-header {
192
- height: var(--scalar-custom-header-height);
193
- background-color: var(--scalar-background-1);
194
- box-shadow: inset 0 -1px 0 var(--scalar-border-color);
195
- color: var(--scalar-color-1);
196
- font-size: var(--scalar-font-size-2);
197
- padding: 0 18px;
198
- position: sticky;
199
- justify-content: space-between;
200
- top: 0;
201
- z-index: 100;
202
- }
203
- .custom-header,
204
- .custom-header nav {
205
- display: flex;
206
- align-items: center;
207
- gap: 18px;
208
- }
209
-
210
- .custom-header img {
211
- height: var(--scalar-custom-header-height);
212
- object-fit: contain;
213
- }
214
-
215
- .custom-header a:hover {
216
- color: var(--scalar-color-2);
217
- }
218
-
219
- .github-corner {
220
- fill: var(--scalar-border-color) !important;
221
- }
222
-
223
- </style>
224
- </head>
225
- <body>
226
- <header class="custom-header scalar-app">
227
- <a href="/"><img src="/logo.svg" alt="{ mockhttp }" /></a>
228
- <a href="https://github.com/jaredwray/mockhttp" class="github-corner" aria-label="View source on GitHub">
229
- <svg width="70" height="70" viewBox="0 0 250 250" style="color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
230
- <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
231
- <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
232
- <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
233
- </header>
234
-
235
- <script
236
- id="api-reference"
237
- data-url="/docs/json"></script>
238
-
239
- <script src="/scalar/browser/standalone.js"></script>
240
- </body>
241
- </html>
242
- `;
243
- await reply.type("text/html").send(redocHtml);
244
- });
245
- }, "indexRoute");
246
-
247
- // src/routes/sitemap.ts
248
- var sitemapRoute = /* @__PURE__ */ __name((fastify) => {
249
- fastify.get("/sitemap.xml", {
250
- schema: {
251
- hide: true
252
- }
253
- }, async (_request, reply) => {
254
- try {
255
- const host = _request.headers.host ?? "mockhttp.org";
256
- const isSecure = _request.headers["x-forwarded-proto"]?.includes("https");
257
- const protocol = isSecure ? "https" : "http";
258
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
259
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
260
- <url>
261
- <loc>${protocol}://${host}/</loc>
262
- <changefreq>monthly</changefreq>
263
- <priority>1.0</priority>
264
- </url>
265
- </urlset>`;
266
- await reply.type("text/xml").send(xml);
267
- } catch (error) {
268
- fastify.log.error(error);
269
- await reply.status(500).send({
270
- error: "Internal Server Error"
271
- });
272
- }
273
- });
274
- }, "sitemapRoute");
275
-
276
- // src/routes/http-methods/get.ts
277
- var getSchema = {
278
- description: "Returns request information for GET requests",
32
+ // src/routes/anything/index.ts
33
+ var anythingSchema = {
34
+ description: "Will return anything that you send it",
279
35
  tags: [
280
- "HTTP Methods"
36
+ "Anything"
281
37
  ],
282
- querystring: {
283
- type: "object",
284
- additionalProperties: true,
285
- properties: {},
286
- description: "Query parameters sent in the request"
287
- },
288
- headers: {
289
- type: "object",
290
- additionalProperties: true,
291
- properties: {},
292
- description: "Headers sent in the request"
293
- },
294
38
  response: {
295
- // eslint-disable-next-line @typescript-eslint/naming-convention
39
+ // eslint-disable-next-line @typescript-eslint/naming-convention
296
40
  200: {
297
41
  type: "object",
298
42
  properties: {
299
- method: {
300
- type: "string"
43
+ // Echo back all your params under `args`
44
+ args: {
45
+ type: "object",
46
+ description: "Route parameters",
47
+ additionalProperties: true
301
48
  },
302
- headers: {
49
+ // Echo back whatever was in the request.body
50
+ data: {
303
51
  type: "object",
304
- additionalProperties: {
305
- type: "string"
306
- }
52
+ description: "Request body data",
53
+ additionalProperties: true
307
54
  },
308
- queryParams: {
55
+ // If you’re using fastify-multipart, you’ll get
56
+ // files via request.files(); this just allows
57
+ // you to mirror them back
58
+ files: {
309
59
  type: "object",
310
- additionalProperties: {
311
- type: "string"
312
- }
313
- }
314
- },
315
- required: [
316
- "method",
317
- "headers",
318
- "queryParams"
319
- ]
320
- }
321
- }
322
- };
323
- var getRoute = /* @__PURE__ */ __name((fastify) => {
324
- fastify.get("/get", {
325
- schema: getSchema
326
- }, async (request, _reply) => ({
327
- method: "GET",
328
- headers: request.headers,
329
- queryParams: request.query
330
- }));
331
- }, "getRoute");
332
-
333
- // src/routes/http-methods/post.ts
334
- var postSchema = {
335
- description: "Handles a POST request and returns request information",
336
- tags: [
337
- "HTTP Methods"
338
- ],
339
- body: {
340
- type: "object",
341
- additionalProperties: true,
342
- description: "The body of the POST request"
343
- },
344
- response: {
345
- // eslint-disable-next-line @typescript-eslint/naming-convention
346
- 200: {
347
- type: "object",
348
- properties: {
349
- method: {
350
- type: "string"
60
+ description: "Uploaded files",
61
+ additionalProperties: true
62
+ },
63
+ // If you parse application/x-www-form-urlencoded
64
+ // these fields land in request.body too, but you can
65
+ // mirror them separately if you like
66
+ form: {
67
+ type: "object",
68
+ description: "Form data",
69
+ additionalProperties: true
351
70
  },
352
71
  headers: {
353
72
  type: "object",
354
- additionalProperties: {
355
- type: "string"
356
- }
73
+ description: "Request headers",
74
+ additionalProperties: true
357
75
  },
358
- body: {
76
+ json: {
359
77
  type: "object",
78
+ description: "Parsed JSON body",
360
79
  additionalProperties: true
80
+ },
81
+ method: {
82
+ type: "string",
83
+ description: "HTTP method used"
84
+ },
85
+ origin: {
86
+ type: "string",
87
+ description: "Origin of the request"
88
+ },
89
+ url: {
90
+ type: "string",
91
+ description: "Request URL"
361
92
  }
362
93
  },
363
- required: [
364
- "method",
365
- "headers",
366
- "body"
367
- ]
94
+ // Don’t allow any other top-level keys in your response
95
+ additionalProperties: false
368
96
  }
369
97
  }
370
98
  };
371
- var postRoute = /* @__PURE__ */ __name((fastify) => {
372
- fastify.post("/post", {
373
- schema: postSchema
374
- }, async (request) => ({
375
- method: "POST",
376
- headers: request.headers,
377
- body: request.body
378
- }));
379
- }, "postRoute");
99
+ var anythingRoute = /* @__PURE__ */ __name((fastify) => {
100
+ const handler = /* @__PURE__ */ __name(async (request, reply) => {
101
+ const response = {
102
+ args: request.query,
103
+ data: request.body ?? {},
104
+ // biome-ignore lint/suspicious/noExplicitAny: expected
105
+ files: request.raw?.files ?? {},
106
+ form: request.body ?? {},
107
+ headers: request.headers,
108
+ json: request.body ?? {},
109
+ method: request.method,
110
+ origin: request.headers.origin ?? request.headers.host ?? "",
111
+ url: request.url
112
+ };
113
+ await reply.send(response);
114
+ }, "handler");
115
+ const noValidation = {
116
+ schema: anythingSchema,
117
+ // Skip AJV on *this* route
118
+ validatorCompiler: /* @__PURE__ */ __name(() => () => true, "validatorCompiler")
119
+ };
120
+ fastify.get("/anything", noValidation, handler);
121
+ fastify.post("/anything", noValidation, handler);
122
+ fastify.put("/anything", noValidation, handler);
123
+ fastify.patch("/anything", noValidation, handler);
124
+ fastify.delete("/anything", noValidation, handler);
125
+ }, "anythingRoute");
380
126
 
381
- // src/routes/http-methods/delete.ts
382
- var deleteSchema = {
383
- description: "Handles a DELETE request and returns request information",
127
+ // src/routes/auth/basic.ts
128
+ import { Buffer as Buffer2 } from "buffer";
129
+ import { timingSafeEqual } from "crypto";
130
+ var parseBasic = /* @__PURE__ */ __name((header) => {
131
+ if (!header || typeof header !== "string") {
132
+ return void 0;
133
+ }
134
+ const spaceIndex = header.indexOf(" ");
135
+ if (spaceIndex === -1) {
136
+ return void 0;
137
+ }
138
+ const scheme = header.slice(0, spaceIndex);
139
+ const value = header.slice(spaceIndex + 1);
140
+ if (scheme.toLowerCase() !== "basic" || !value) {
141
+ return void 0;
142
+ }
143
+ try {
144
+ const decoded = Buffer2.from(value, "base64").toString("utf8");
145
+ const idx = decoded.indexOf(":");
146
+ if (idx === -1) {
147
+ return void 0;
148
+ }
149
+ const username = decoded.slice(0, idx);
150
+ const password = decoded.slice(idx + 1);
151
+ if (username === "") {
152
+ return void 0;
153
+ }
154
+ return {
155
+ username,
156
+ password
157
+ };
158
+ } catch {
159
+ return void 0;
160
+ }
161
+ }, "parseBasic");
162
+ var safeCompare = /* @__PURE__ */ __name((a, b) => {
163
+ if (a.length !== b.length) {
164
+ return false;
165
+ }
166
+ try {
167
+ return timingSafeEqual(Buffer2.from(a, "utf8"), Buffer2.from(b, "utf8"));
168
+ } catch {
169
+ return false;
170
+ }
171
+ }, "safeCompare");
172
+ var basicAuthSchema = {
173
+ description: "HTTP Basic authentication. Succeeds only if the user/pass provided in the path matches the Basic Authorization header.",
384
174
  tags: [
385
- "HTTP Methods"
175
+ "Auth"
386
176
  ],
387
- body: {
177
+ params: {
388
178
  type: "object",
389
- additionalProperties: true,
390
- description: "The body of the DELETE request"
179
+ properties: {
180
+ user: {
181
+ type: "string"
182
+ },
183
+ passwd: {
184
+ type: "string"
185
+ }
186
+ },
187
+ required: [
188
+ "user",
189
+ "passwd"
190
+ ]
391
191
  },
392
192
  response: {
393
193
  // eslint-disable-next-line @typescript-eslint/naming-convention
394
194
  200: {
395
195
  type: "object",
396
196
  properties: {
397
- method: {
398
- type: "string"
399
- },
400
- headers: {
401
- type: "object",
402
- additionalProperties: {
403
- type: "string"
404
- }
197
+ authenticated: {
198
+ type: "boolean"
405
199
  },
406
- body: {
407
- type: "object",
408
- additionalProperties: true
200
+ user: {
201
+ type: "string"
409
202
  }
410
203
  },
411
204
  required: [
412
- "method",
413
- "headers",
414
- "body"
205
+ "authenticated",
206
+ "user"
415
207
  ]
416
- }
417
- }
418
- };
419
- var deleteRoute = /* @__PURE__ */ __name((fastify) => {
420
- fastify.delete("/delete", {
421
- schema: deleteSchema
422
- }, async (request) => ({
423
- method: "DELETE",
424
- headers: request.headers,
425
- body: request.body
426
- }));
427
- }, "deleteRoute");
428
-
429
- // src/routes/http-methods/put.ts
430
- var putSchema = {
431
- description: "Handles a PUT request and returns request information",
432
- tags: [
433
- "HTTP Methods"
434
- ],
435
- body: {
436
- type: "object",
437
- additionalProperties: true,
438
- description: "The body of the PUT request"
439
- },
440
- response: {
208
+ },
441
209
  // eslint-disable-next-line @typescript-eslint/naming-convention
442
- 200: {
210
+ 401: {
443
211
  type: "object",
444
212
  properties: {
445
- method: {
213
+ message: {
446
214
  type: "string"
447
- },
448
- headers: {
449
- type: "object",
450
- additionalProperties: {
451
- type: "string"
452
- }
453
- },
454
- body: {
455
- type: "object",
456
- additionalProperties: true
457
215
  }
458
216
  },
459
217
  required: [
460
- "method",
461
- "headers",
462
- "body"
218
+ "message"
463
219
  ]
464
220
  }
465
221
  }
466
222
  };
467
- var putRoute = /* @__PURE__ */ __name((fastify) => {
468
- fastify.put("/put", {
469
- schema: putSchema
470
- }, async (request) => ({
471
- method: "PUT",
472
- headers: request.headers,
473
- body: request.body
474
- }));
475
- }, "putRoute");
223
+ var basicAuthRoute = /* @__PURE__ */ __name((fastify) => {
224
+ fastify.get("/basic-auth/:user/:passwd", {
225
+ schema: basicAuthSchema
226
+ }, async (request, reply) => {
227
+ const { user, passwd } = request.params;
228
+ if (!user || typeof user !== "string" || typeof passwd !== "string") {
229
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
230
+ return reply.status(401).send({
231
+ message: "Unauthorized"
232
+ });
233
+ }
234
+ const parsed = parseBasic(request.headers.authorization);
235
+ if (!parsed || !safeCompare(parsed.username, user) || !safeCompare(parsed.password, passwd)) {
236
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
237
+ return reply.status(401).send({
238
+ message: "Unauthorized"
239
+ });
240
+ }
241
+ return reply.send({
242
+ authenticated: true,
243
+ user
244
+ });
245
+ });
246
+ }, "basicAuthRoute");
476
247
 
477
- // src/routes/http-methods/patch.ts
478
- var patchSchema = {
479
- description: "Handles a PATCH request and returns request information",
248
+ // src/routes/auth/bearer.ts
249
+ var parseBearer = /* @__PURE__ */ __name((header) => {
250
+ if (!header) {
251
+ return void 0;
252
+ }
253
+ const [scheme, token] = header.split(" ");
254
+ if (!scheme || scheme.toLowerCase() !== "bearer" || !token) {
255
+ return void 0;
256
+ }
257
+ return token;
258
+ }, "parseBearer");
259
+ var bearerAuthSchema = {
260
+ description: "HTTP Bearer authentication. Succeeds if any Bearer token is present unless ?required=true is provided.",
480
261
  tags: [
481
- "HTTP Methods"
262
+ "Auth"
482
263
  ],
483
- body: {
264
+ querystring: {
484
265
  type: "object",
485
- additionalProperties: true,
486
- description: "The body of the PATCH request"
266
+ properties: {
267
+ required: {
268
+ type: "boolean",
269
+ default: true
270
+ }
271
+ }
487
272
  },
488
273
  response: {
489
274
  // eslint-disable-next-line @typescript-eslint/naming-convention
490
275
  200: {
491
276
  type: "object",
492
277
  properties: {
493
- method: {
494
- type: "string"
495
- },
496
- headers: {
497
- type: "object",
498
- additionalProperties: {
499
- type: "string"
500
- }
278
+ authenticated: {
279
+ type: "boolean"
501
280
  },
502
- body: {
503
- type: "object",
504
- additionalProperties: true
281
+ token: {
282
+ type: "string"
505
283
  }
506
284
  },
507
285
  required: [
508
- "method",
509
- "headers",
510
- "body"
286
+ "authenticated",
287
+ "token"
288
+ ]
289
+ },
290
+ // eslint-disable-next-line @typescript-eslint/naming-convention
291
+ 401: {
292
+ type: "object",
293
+ properties: {
294
+ message: {
295
+ type: "string"
296
+ }
297
+ },
298
+ required: [
299
+ "message"
511
300
  ]
512
301
  }
513
302
  }
514
303
  };
515
- var patchRoute = /* @__PURE__ */ __name((fastify) => {
516
- fastify.patch("/patch", {
517
- schema: patchSchema
518
- }, async (request) => ({
519
- method: "PATCH",
520
- headers: request.headers,
521
- body: request.body
522
- }));
523
- }, "patchRoute");
304
+ var bearerAuthRoute = /* @__PURE__ */ __name((fastify) => {
305
+ fastify.get("/bearer", {
306
+ schema: bearerAuthSchema
307
+ }, async (request, reply) => {
308
+ const token = parseBearer(request.headers.authorization);
309
+ const qs = request.query;
310
+ const required = qs?.required ?? true;
311
+ if (!token && required) {
312
+ void reply.header("WWW-Authenticate", "Bearer");
313
+ return reply.status(401).send({
314
+ message: "Unauthorized"
315
+ });
316
+ }
317
+ if (!token) {
318
+ return reply.send({
319
+ authenticated: false,
320
+ token: ""
321
+ });
322
+ }
323
+ return reply.send({
324
+ authenticated: true,
325
+ token
326
+ });
327
+ });
328
+ }, "bearerAuthRoute");
524
329
 
525
- // src/routes/status-codes/index.ts
526
- import { randomInt } from "crypto";
527
- var statusCodes = [
528
- {
529
- code: 100,
530
- message: "Continue"
531
- },
532
- {
533
- code: 101,
534
- message: "Switching Protocols"
535
- },
536
- {
537
- code: 102,
538
- message: "Processing"
539
- },
540
- {
541
- code: 103,
542
- message: "Early Hints"
543
- },
544
- {
545
- code: 200,
546
- message: "OK"
547
- },
548
- {
549
- code: 201,
550
- message: "Created"
551
- },
552
- {
553
- code: 202,
554
- message: "Accepted"
555
- },
556
- {
557
- code: 203,
558
- message: "Non-Authoritative Information"
559
- },
560
- {
561
- code: 204,
562
- message: "No Content"
563
- },
564
- {
565
- code: 205,
566
- message: "Reset Content"
567
- },
568
- {
569
- code: 206,
570
- message: "Partial Content"
571
- },
572
- {
573
- code: 207,
574
- message: "Multi-Status"
330
+ // src/routes/auth/digest.ts
331
+ import crypto from "crypto";
332
+ var makeNonce = /* @__PURE__ */ __name(() => crypto.randomBytes(16).toString("hex"), "makeNonce");
333
+ var algorithms = /* @__PURE__ */ new Set([
334
+ "MD5",
335
+ "md5",
336
+ "SHA-256",
337
+ "sha-256",
338
+ "SHA-512",
339
+ "sha-512"
340
+ ]);
341
+ var hashAlg = /* @__PURE__ */ __name((alg) => {
342
+ if (!alg) {
343
+ return "md5";
344
+ }
345
+ const a = alg.toLowerCase();
346
+ if (a === "md5") {
347
+ return "md5";
348
+ }
349
+ if (a === "sha-256") {
350
+ return "sha256";
351
+ }
352
+ if (a === "sha-512") {
353
+ return "sha512";
354
+ }
355
+ return "md5";
356
+ }, "hashAlg");
357
+ var h = /* @__PURE__ */ __name((algo, data) => crypto.createHash(algo).update(data).digest("hex"), "h");
358
+ var parseDigest = /* @__PURE__ */ __name((header) => {
359
+ if (!header) {
360
+ return void 0;
361
+ }
362
+ const spaceIndex = header.indexOf(" ");
363
+ if (spaceIndex === -1) {
364
+ return void 0;
365
+ }
366
+ const scheme = header.slice(0, spaceIndex);
367
+ const rest = header.slice(spaceIndex + 1);
368
+ if (!scheme || scheme.toLowerCase() !== "digest" || !rest) {
369
+ return void 0;
370
+ }
371
+ const out = {};
372
+ const parts = [];
373
+ let current = "";
374
+ let inQuotes = false;
375
+ for (const char of rest) {
376
+ if (char === '"') {
377
+ inQuotes = !inQuotes;
378
+ current += char;
379
+ } else if (char === "," && !inQuotes) {
380
+ if (current.trim()) {
381
+ parts.push(current.trim());
382
+ }
383
+ current = "";
384
+ } else {
385
+ current += char;
386
+ }
387
+ }
388
+ if (current.trim()) {
389
+ parts.push(current.trim());
390
+ }
391
+ if (parts.length === 0) {
392
+ return out;
393
+ }
394
+ for (const p of parts) {
395
+ const equalIndex = p.indexOf("=");
396
+ if (equalIndex === -1) {
397
+ continue;
398
+ }
399
+ const k = p.slice(0, equalIndex).trim();
400
+ const v = p.slice(equalIndex + 1).trim();
401
+ let value = v;
402
+ if (value.startsWith('"') && value.endsWith('"')) {
403
+ value = value.slice(1, -1);
404
+ }
405
+ out[k] = value;
406
+ }
407
+ return out;
408
+ }, "parseDigest");
409
+ var digestAuthSchema = {
410
+ description: "HTTP Digest authentication. Mirrors httpbin behavior for testing clients.",
411
+ tags: [
412
+ "Auth"
413
+ ],
414
+ params: {
415
+ type: "object",
416
+ properties: {
417
+ qop: {
418
+ type: "string"
419
+ },
420
+ user: {
421
+ type: "string"
422
+ },
423
+ passwd: {
424
+ type: "string"
425
+ },
426
+ algorithm: {
427
+ type: "string"
428
+ },
429
+ // eslint-disable-next-line @typescript-eslint/naming-convention
430
+ stale_after: {
431
+ type: "string"
432
+ }
433
+ },
434
+ required: [
435
+ "qop",
436
+ "user",
437
+ "passwd"
438
+ ]
439
+ }
440
+ };
441
+ var digestAuthRoute = /* @__PURE__ */ __name((fastify) => {
442
+ fastify.get("/digest-auth/:qop/:user/:passwd", {
443
+ schema: digestAuthSchema
444
+ }, async (request, reply) => {
445
+ const { user, passwd } = request.params;
446
+ const realm = "mockhttp";
447
+ const nonce = makeNonce();
448
+ const auth = parseDigest(request.headers.authorization);
449
+ if (!auth) {
450
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
451
+ return reply.status(401).send({
452
+ message: "Unauthorized"
453
+ });
454
+ }
455
+ if (auth.username !== user) {
456
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
457
+ return reply.status(401).send({
458
+ message: "Unauthorized"
459
+ });
460
+ }
461
+ const method = "GET";
462
+ const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
463
+ const algo = hashAlg(auth.algorithm);
464
+ const ha1 = h(algo, `${user}:${realm}:${passwd}`);
465
+ const ha2 = h(algo, `${method}:${uri}`);
466
+ let expected = "";
467
+ expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
468
+ if (!auth.response || auth.response !== expected) {
469
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
470
+ return reply.status(401).send({
471
+ message: "Unauthorized"
472
+ });
473
+ }
474
+ return reply.send({
475
+ authenticated: true,
476
+ user
477
+ });
478
+ });
479
+ fastify.get("/digest-auth/:qop/:user/:passwd/:algorithm", {
480
+ schema: digestAuthSchema
481
+ }, async (request, reply) => {
482
+ const { user, passwd, algorithm } = request.params;
483
+ const realm = "mockhttp";
484
+ const algoHeader = algorithms.has(String(algorithm)) ? String(algorithm) : "MD5";
485
+ const nonce = makeNonce();
486
+ const auth = parseDigest(request.headers.authorization);
487
+ if (!auth) {
488
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
489
+ return reply.status(401).send({
490
+ message: "Unauthorized"
491
+ });
492
+ }
493
+ if (auth.username !== user) {
494
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
495
+ return reply.status(401).send({
496
+ message: "Unauthorized"
497
+ });
498
+ }
499
+ const method = "GET";
500
+ const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
501
+ const algo = hashAlg(auth.algorithm ?? algoHeader);
502
+ const ha1 = h(algo, `${user}:${realm}:${passwd}`);
503
+ const ha2 = h(algo, `${method}:${uri}`);
504
+ const expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
505
+ if (!auth.response || auth.response !== expected) {
506
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
507
+ return reply.status(401).send({
508
+ message: "Unauthorized"
509
+ });
510
+ }
511
+ return reply.send({
512
+ authenticated: true,
513
+ user
514
+ });
515
+ });
516
+ }, "digestAuthRoute");
517
+
518
+ // src/routes/auth/hidden-basic.ts
519
+ import { Buffer as Buffer3 } from "buffer";
520
+ var parseBasic2 = /* @__PURE__ */ __name((header) => {
521
+ if (!header) {
522
+ return void 0;
523
+ }
524
+ const [scheme, value] = header.split(" ");
525
+ if (!scheme || scheme.toLowerCase() !== "basic" || !value) {
526
+ return void 0;
527
+ }
528
+ try {
529
+ const decoded = Buffer3.from(value, "base64").toString("utf8");
530
+ const idx = decoded.indexOf(":");
531
+ if (idx === -1) {
532
+ return void 0;
533
+ }
534
+ return {
535
+ username: decoded.slice(0, idx),
536
+ password: decoded.slice(idx + 1)
537
+ };
538
+ } catch {
539
+ return void 0;
540
+ }
541
+ }, "parseBasic");
542
+ var hiddenBasicAuthSchema = {
543
+ description: "HTTP Basic authentication with docs hidden. Mirrors /basic-auth but hides from schema.",
544
+ tags: [
545
+ "Auth"
546
+ ],
547
+ hide: true,
548
+ params: {
549
+ type: "object",
550
+ properties: {
551
+ user: {
552
+ type: "string"
553
+ },
554
+ passwd: {
555
+ type: "string"
556
+ }
557
+ },
558
+ required: [
559
+ "user",
560
+ "passwd"
561
+ ]
562
+ }
563
+ };
564
+ var hiddenBasicAuthRoute = /* @__PURE__ */ __name((fastify) => {
565
+ fastify.get("/hidden-basic-auth/:user/:passwd", {
566
+ schema: hiddenBasicAuthSchema
567
+ }, async (request, reply) => {
568
+ const { user, passwd } = request.params;
569
+ const parsed = parseBasic2(request.headers.authorization);
570
+ if (!parsed || parsed.username !== user || parsed.password !== passwd) {
571
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
572
+ return reply.status(401).send({
573
+ message: "Unauthorized"
574
+ });
575
+ }
576
+ return reply.send({
577
+ authenticated: true,
578
+ user
579
+ });
580
+ });
581
+ }, "hiddenBasicAuthRoute");
582
+
583
+ // src/routes/cookies/delete.ts
584
+ var cookiesDeleteSchema = {
585
+ description: "Delete a cookie",
586
+ tags: [
587
+ "Cookies"
588
+ ],
589
+ querystring: {
590
+ type: "object",
591
+ properties: {
592
+ name: {
593
+ type: "string"
594
+ }
595
+ },
596
+ required: [
597
+ "name"
598
+ ],
599
+ additionalProperties: false
575
600
  },
576
- {
577
- code: 208,
578
- message: "Already Reported"
601
+ response: {
602
+ // eslint-disable-next-line @typescript-eslint/naming-convention
603
+ 204: {
604
+ type: "null"
605
+ }
606
+ }
607
+ };
608
+ var deleteCookieRoute = /* @__PURE__ */ __name((fastify) => {
609
+ fastify.delete("/cookies", {
610
+ schema: cookiesDeleteSchema
611
+ }, async (request, reply) => {
612
+ const { name } = request.query;
613
+ reply.clearCookie(name, {
614
+ path: "/"
615
+ });
616
+ return reply.status(204).send();
617
+ });
618
+ }, "deleteCookieRoute");
619
+
620
+ // src/routes/cookies/get.ts
621
+ var getCookiesRouteSchema = {
622
+ description: "Return cookies from the request",
623
+ tags: [
624
+ "Cookies"
625
+ ],
626
+ response: {
627
+ // eslint-disable-next-line @typescript-eslint/naming-convention
628
+ 200: {
629
+ type: "object",
630
+ properties: {
631
+ cookies: {
632
+ type: "object",
633
+ additionalProperties: {
634
+ type: "string"
635
+ }
636
+ }
637
+ },
638
+ required: [
639
+ "cookies"
640
+ ]
641
+ }
642
+ }
643
+ };
644
+ var getCookiesRoute = /* @__PURE__ */ __name((fastify) => {
645
+ fastify.get("/cookies", {
646
+ schema: getCookiesRouteSchema
647
+ }, async (request, reply) => {
648
+ reply.type("application/json");
649
+ await reply.send({
650
+ cookies: request.cookies
651
+ });
652
+ });
653
+ }, "getCookiesRoute");
654
+
655
+ // src/routes/cookies/post.ts
656
+ var postCookieRouteSchema = {
657
+ description: "Set a cookie",
658
+ tags: [
659
+ "Cookies"
660
+ ],
661
+ body: {
662
+ type: "object",
663
+ properties: {
664
+ name: {
665
+ type: "string"
666
+ },
667
+ value: {
668
+ type: "string"
669
+ },
670
+ expires: {
671
+ type: "string",
672
+ format: "date-time"
673
+ }
674
+ },
675
+ required: [
676
+ "name",
677
+ "value"
678
+ ],
679
+ additionalProperties: false
579
680
  },
580
- {
581
- code: 226,
582
- message: "IM Used"
583
- },
584
- {
585
- code: 300,
586
- message: "Multiple Choices"
587
- },
588
- {
589
- code: 301,
590
- message: "Moved Permanently"
591
- },
592
- {
593
- code: 302,
594
- message: "Found"
595
- },
596
- {
597
- code: 303,
598
- message: "See Other"
599
- },
600
- {
601
- code: 304,
602
- message: "Not Modified"
603
- },
604
- {
605
- code: 305,
606
- message: "Use Proxy"
607
- },
608
- {
609
- code: 306,
610
- message: "Switch Proxy"
611
- },
612
- {
613
- code: 307,
614
- message: "Temporary Redirect"
615
- },
616
- {
617
- code: 308,
618
- message: "Permanent Redirect"
619
- },
620
- {
621
- code: 400,
622
- message: "Bad Request"
623
- },
624
- {
625
- code: 401,
626
- message: "Unauthorized"
627
- },
628
- {
629
- code: 402,
630
- message: "Payment Required"
631
- },
632
- {
633
- code: 403,
634
- message: "Forbidden"
635
- },
636
- {
637
- code: 404,
638
- message: "Not Found"
639
- },
640
- {
641
- code: 405,
642
- message: "Method Not Allowed"
643
- },
644
- {
645
- code: 406,
646
- message: "Not Acceptable"
647
- },
648
- {
649
- code: 407,
650
- message: "Proxy Authentication Required"
651
- },
652
- {
653
- code: 408,
654
- message: "Request Timeout"
655
- },
656
- {
657
- code: 409,
658
- message: "Conflict"
659
- },
660
- {
661
- code: 410,
662
- message: "Gone"
663
- },
664
- {
665
- code: 411,
666
- message: "Length Required"
667
- },
668
- {
669
- code: 412,
670
- message: "Precondition Failed"
671
- },
672
- {
673
- code: 413,
674
- message: "Payload Too Large"
675
- },
676
- {
677
- code: 414,
678
- message: "URI Too Long"
679
- },
680
- {
681
- code: 415,
682
- message: "Unsupported Media Type"
683
- },
684
- {
685
- code: 416,
686
- message: "Range Not Satisfiable"
687
- },
688
- {
689
- code: 417,
690
- message: "Expectation Failed"
691
- },
692
- {
693
- code: 418,
694
- message: "I'm a teapot"
695
- },
696
- {
697
- code: 421,
698
- message: "Misdirected Request"
699
- },
700
- {
701
- code: 422,
702
- message: "Unprocessable Entity"
703
- },
704
- {
705
- code: 423,
706
- message: "Locked"
707
- },
708
- {
709
- code: 424,
710
- message: "Failed Dependency"
711
- },
712
- {
713
- code: 425,
714
- message: "Too Early"
715
- },
716
- {
717
- code: 426,
718
- message: "Upgrade Required"
719
- },
720
- {
721
- code: 428,
722
- message: "Precondition Required"
723
- },
724
- {
725
- code: 429,
726
- message: "Too Many Requests"
727
- },
728
- {
729
- code: 431,
730
- message: "Request Header Fields Too Large"
731
- },
732
- {
733
- code: 451,
734
- message: "Unavailable For Legal Reasons"
735
- },
736
- {
737
- code: 500,
738
- message: "Internal Server Error"
739
- },
740
- {
741
- code: 501,
742
- message: "Not Implemented"
743
- },
744
- {
745
- code: 502,
746
- message: "Bad Gateway"
747
- },
748
- {
749
- code: 503,
750
- message: "Service Unavailable"
751
- },
752
- {
753
- code: 504,
754
- message: "Gateway Timeout"
755
- },
756
- {
757
- code: 505,
758
- message: "HTTP Version Not Supported"
759
- },
760
- {
761
- code: 506,
762
- message: "Variant Also Negotiates"
763
- },
764
- {
765
- code: 507,
766
- message: "Insufficient Storage"
767
- },
768
- {
769
- code: 508,
770
- message: "Loop Detected"
771
- },
772
- {
773
- code: 510,
774
- message: "Not Extended"
775
- },
776
- {
777
- code: 511,
778
- message: "Network Authentication Required"
681
+ response: {
682
+ // eslint-disable-next-line @typescript-eslint/naming-convention
683
+ 200: {
684
+ type: "object",
685
+ properties: {
686
+ cookies: {
687
+ type: "object",
688
+ additionalProperties: {
689
+ type: "string"
690
+ }
691
+ }
692
+ },
693
+ required: [
694
+ "cookies"
695
+ ]
696
+ }
779
697
  }
780
- ];
781
- var statusSchema = {
782
- description: "Return status code or random status code if more than one are given ",
698
+ };
699
+ var postCookieRoute = /* @__PURE__ */ __name((fastify) => {
700
+ fastify.post("/cookies", {
701
+ schema: postCookieRouteSchema
702
+ }, async (request, reply) => {
703
+ const { name, value, expires } = request.body;
704
+ if (expires) {
705
+ const date = new Date(expires);
706
+ reply.setCookie(name, value, {
707
+ path: "/",
708
+ expires: date
709
+ });
710
+ } else {
711
+ reply.setCookie(name, value, {
712
+ path: "/"
713
+ });
714
+ }
715
+ return reply.status(200).send();
716
+ });
717
+ }, "postCookieRoute");
718
+
719
+ // src/routes/http-methods/delete.ts
720
+ var deleteSchema = {
721
+ description: "Handles a DELETE request and returns request information",
783
722
  tags: [
784
- "Status Codes"
723
+ "HTTP Methods"
785
724
  ],
786
- params: {
725
+ body: {
787
726
  type: "object",
788
- properties: {
789
- code: {
790
- type: "string",
791
- description: "HTTP status code to be returned"
792
- }
793
- }
727
+ additionalProperties: true,
728
+ description: "The body of the DELETE request"
794
729
  },
795
730
  response: {
796
731
  // eslint-disable-next-line @typescript-eslint/naming-convention
797
732
  200: {
798
733
  type: "object",
799
734
  properties: {
800
- status: {
735
+ method: {
801
736
  type: "string"
737
+ },
738
+ headers: {
739
+ type: "object",
740
+ additionalProperties: {
741
+ type: "string"
742
+ }
743
+ },
744
+ body: {
745
+ type: "object",
746
+ additionalProperties: true
802
747
  }
803
- }
748
+ },
749
+ required: [
750
+ "method",
751
+ "headers",
752
+ "body"
753
+ ]
804
754
  }
805
755
  }
806
756
  };
807
- var statusCodeRoute = /* @__PURE__ */ __name((fastify) => {
808
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
809
- let { code } = request.params;
810
- if (!code || !statusCodes.map((status) => status.code).includes(Number(code))) {
811
- code = statusCodes[randomInt(0, statusCodes.length)].code.toString();
812
- }
813
- await reply.code(Number(code)).send({
814
- status: `Response with status code ${code}`
815
- });
816
- }, "handler");
817
- fastify.get("/status/:code?", {
818
- schema: statusSchema
819
- }, handler);
820
- fastify.post("/status/:code?", {
821
- schema: statusSchema
822
- }, handler);
823
- fastify.put("/status/:code?", {
824
- schema: statusSchema
825
- }, handler);
826
- fastify.patch("/status/:code?", {
827
- schema: statusSchema
828
- }, handler);
829
- fastify.delete("/status/:code?", {
830
- schema: statusSchema
831
- }, handler);
832
- }, "statusCodeRoute");
757
+ var deleteRoute = /* @__PURE__ */ __name((fastify) => {
758
+ fastify.delete("/delete", {
759
+ schema: deleteSchema
760
+ }, async (request) => ({
761
+ method: "DELETE",
762
+ headers: request.headers,
763
+ body: request.body
764
+ }));
765
+ }, "deleteRoute");
833
766
 
834
- // src/routes/request-inspection/ip.ts
835
- var ipSchema = {
836
- description: "Returns the IP address of the client",
767
+ // src/routes/http-methods/get.ts
768
+ var getSchema = {
769
+ description: "Returns request information for GET requests",
837
770
  tags: [
838
- "Request Inspection"
771
+ "HTTP Methods"
839
772
  ],
773
+ querystring: {
774
+ type: "object",
775
+ additionalProperties: true,
776
+ properties: {},
777
+ description: "Query parameters sent in the request"
778
+ },
779
+ headers: {
780
+ type: "object",
781
+ additionalProperties: true,
782
+ properties: {},
783
+ description: "Headers sent in the request"
784
+ },
840
785
  response: {
841
786
  // eslint-disable-next-line @typescript-eslint/naming-convention
842
787
  200: {
843
788
  type: "object",
844
789
  properties: {
845
- ip: {
846
- type: "string",
847
- description: "The IP address of the client"
790
+ method: {
791
+ type: "string"
792
+ },
793
+ headers: {
794
+ type: "object",
795
+ additionalProperties: {
796
+ type: "string"
797
+ }
798
+ },
799
+ queryParams: {
800
+ type: "object",
801
+ additionalProperties: {
802
+ type: "string"
803
+ }
848
804
  }
849
805
  },
850
806
  required: [
851
- "ip"
807
+ "method",
808
+ "headers",
809
+ "queryParams"
852
810
  ]
853
811
  }
854
812
  }
855
813
  };
856
- var ipRoute = /* @__PURE__ */ __name((fastify) => {
857
- fastify.get("/ip", {
858
- schema: ipSchema
859
- }, async (request) => {
860
- const cloudflareIp = request.headers["cf-connecting-ip"];
861
- const forwardedIp = request.headers["x-forwarded-for"];
862
- const remoteIp = request.ip;
863
- const ip = cloudflareIp ?? (typeof forwardedIp === "string" ? forwardedIp.split(",")[0].trim() : remoteIp);
864
- return {
865
- ip
866
- };
867
- });
868
- }, "ipRoute");
814
+ var getRoute = /* @__PURE__ */ __name((fastify) => {
815
+ fastify.get("/get", {
816
+ schema: getSchema
817
+ }, async (request, _reply) => ({
818
+ method: "GET",
819
+ headers: request.headers,
820
+ queryParams: request.query
821
+ }));
822
+ }, "getRoute");
869
823
 
870
- // src/routes/request-inspection/headers.ts
871
- var headersSchema = {
872
- description: "Returns all headers of the client request",
824
+ // src/routes/http-methods/patch.ts
825
+ var patchSchema = {
826
+ description: "Handles a PATCH request and returns request information",
873
827
  tags: [
874
- "Request Inspection"
828
+ "HTTP Methods"
875
829
  ],
830
+ body: {
831
+ type: "object",
832
+ additionalProperties: true,
833
+ description: "The body of the PATCH request"
834
+ },
876
835
  response: {
877
836
  // eslint-disable-next-line @typescript-eslint/naming-convention
878
837
  200: {
879
838
  type: "object",
880
839
  properties: {
840
+ method: {
841
+ type: "string"
842
+ },
881
843
  headers: {
882
844
  type: "object",
883
845
  additionalProperties: {
884
846
  type: "string"
885
- },
886
- description: "All headers of the client request"
847
+ }
848
+ },
849
+ body: {
850
+ type: "object",
851
+ additionalProperties: true
887
852
  }
888
853
  },
889
854
  required: [
890
- "headers"
855
+ "method",
856
+ "headers",
857
+ "body"
891
858
  ]
892
859
  }
893
860
  }
894
861
  };
895
- var headersRoute = /* @__PURE__ */ __name((fastify) => {
896
- fastify.get("/headers", {
897
- schema: headersSchema
898
- }, async (request) => {
899
- const { headers } = request;
900
- return {
901
- headers
902
- };
903
- });
904
- }, "headersRoute");
862
+ var patchRoute = /* @__PURE__ */ __name((fastify) => {
863
+ fastify.patch("/patch", {
864
+ schema: patchSchema
865
+ }, async (request) => ({
866
+ method: "PATCH",
867
+ headers: request.headers,
868
+ body: request.body
869
+ }));
870
+ }, "patchRoute");
905
871
 
906
- // src/routes/request-inspection/user-agent.ts
907
- var userAgentSchema = {
908
- description: "Returns the User-Agent header of the client request",
872
+ // src/routes/http-methods/post.ts
873
+ var postSchema = {
874
+ description: "Handles a POST request and returns request information",
909
875
  tags: [
910
- "Request Inspection"
876
+ "HTTP Methods"
911
877
  ],
878
+ body: {
879
+ type: "object",
880
+ additionalProperties: true,
881
+ description: "The body of the POST request"
882
+ },
912
883
  response: {
913
884
  // eslint-disable-next-line @typescript-eslint/naming-convention
914
885
  200: {
915
886
  type: "object",
916
887
  properties: {
917
- userAgent: {
918
- type: "string",
919
- description: "The User-Agent header of the client request"
888
+ method: {
889
+ type: "string"
890
+ },
891
+ headers: {
892
+ type: "object",
893
+ additionalProperties: {
894
+ type: "string"
895
+ }
896
+ },
897
+ body: {
898
+ type: "object",
899
+ additionalProperties: true
920
900
  }
921
901
  },
922
902
  required: [
923
- "userAgent"
903
+ "method",
904
+ "headers",
905
+ "body"
924
906
  ]
925
907
  }
926
908
  }
927
909
  };
928
- var userAgentRoute = /* @__PURE__ */ __name((fastify) => {
929
- fastify.get("/user-agent", {
930
- schema: userAgentSchema
931
- }, async (request) => {
932
- const userAgent = request.headers["user-agent"];
933
- return {
934
- userAgent
935
- };
936
- });
937
- }, "userAgentRoute");
910
+ var postRoute = /* @__PURE__ */ __name((fastify) => {
911
+ fastify.post("/post", {
912
+ schema: postSchema
913
+ }, async (request) => ({
914
+ method: "POST",
915
+ headers: request.headers,
916
+ body: request.body
917
+ }));
918
+ }, "postRoute");
938
919
 
939
- // src/routes/response-inspection/cache.ts
940
- var cacheGetSchema = {
941
- description: "Handles cache validation and retrieval.",
920
+ // src/routes/http-methods/put.ts
921
+ var putSchema = {
922
+ description: "Handles a PUT request and returns request information",
942
923
  tags: [
943
- "Response Inspection"
924
+ "HTTP Methods"
944
925
  ],
926
+ body: {
927
+ type: "object",
928
+ additionalProperties: true,
929
+ description: "The body of the PUT request"
930
+ },
945
931
  response: {
946
- // eslint-disable-next-line @typescript-eslint/naming-convention
932
+ // eslint-disable-next-line @typescript-eslint/naming-convention
947
933
  200: {
948
- type: "string",
949
- description: "The cached content or resource."
950
- },
951
- // eslint-disable-next-line @typescript-eslint/naming-convention
952
- 304: {
953
- type: "null",
954
- description: "Indicates the resource has not been modified."
934
+ type: "object",
935
+ properties: {
936
+ method: {
937
+ type: "string"
938
+ },
939
+ headers: {
940
+ type: "object",
941
+ additionalProperties: {
942
+ type: "string"
943
+ }
944
+ },
945
+ body: {
946
+ type: "object",
947
+ additionalProperties: true
948
+ }
949
+ },
950
+ required: [
951
+ "method",
952
+ "headers",
953
+ "body"
954
+ ]
955
955
  }
956
956
  }
957
957
  };
958
- var cacheSetSchema = {
959
- description: "Sets a Cache-Control header for n seconds.",
960
- tags: [
961
- "Response Inspection"
962
- ],
963
- params: {
964
- type: "object",
965
- properties: {
966
- value: {
967
- type: "integer",
968
- description: "Number of seconds to cache the resource."
969
- }
970
- },
971
- required: [
972
- "value"
973
- ]
974
- },
975
- response: {
976
- // eslint-disable-next-line @typescript-eslint/naming-convention
977
- 200: {
978
- type: "string",
979
- description: "Confirmation message for setting cache."
980
- }
981
- }
982
- };
983
- var cacheRoutes = /* @__PURE__ */ __name((fastify) => {
984
- fastify.get("/cache", {
985
- schema: cacheGetSchema
986
- }, async (request, reply) => {
987
- const ifModifiedSince = request.headers["if-modified-since"];
988
- const ifNoneMatch = request.headers["if-none-match"];
989
- if (ifModifiedSince ?? ifNoneMatch) {
990
- await reply.status(304).send();
991
- return;
992
- }
993
- await reply.send("Cache content or resource response here.");
994
- });
995
- fastify.get("/cache/:value", {
996
- schema: cacheSetSchema
997
- }, async (request, reply) => {
998
- const { value } = request.params;
999
- await reply.header("Cache-Control", `max-age=${value}`).send(`Cache-Control set for ${value} seconds.`);
1000
- });
1001
- }, "cacheRoutes");
958
+ var putRoute = /* @__PURE__ */ __name((fastify) => {
959
+ fastify.put("/put", {
960
+ schema: putSchema
961
+ }, async (request) => ({
962
+ method: "PUT",
963
+ headers: request.headers,
964
+ body: request.body
965
+ }));
966
+ }, "putRoute");
1002
967
 
1003
- // src/routes/response-inspection/etag.ts
1004
- var etagSchema = {
1005
- description: "Handles ETag-based conditional requests.",
1006
- tags: [
1007
- "Response Inspection"
1008
- ],
1009
- params: {
1010
- type: "object",
1011
- properties: {
1012
- etag: {
1013
- type: "string",
1014
- description: "The ETag value for the resource."
1015
- }
1016
- },
1017
- required: [
1018
- "etag"
1019
- ]
1020
- },
1021
- response: {
1022
- // eslint-disable-next-line @typescript-eslint/naming-convention
1023
- 200: {
1024
- type: "string",
1025
- description: "Resource content for matching ETag."
1026
- },
1027
- // eslint-disable-next-line @typescript-eslint/naming-convention
1028
- 304: {
1029
- type: "null",
1030
- description: "Indicates the resource has not been modified."
1031
- },
1032
- // eslint-disable-next-line @typescript-eslint/naming-convention
1033
- 412: {
1034
- type: "null",
1035
- description: "Indicates a precondition failed due to mismatched ETag."
1036
- }
1037
- }
1038
- };
1039
- var etagRoutes = /* @__PURE__ */ __name((fastify) => {
1040
- fastify.get("/etag/:etag", {
1041
- schema: etagSchema
1042
- }, async (request, reply) => {
1043
- const { etag } = request.params;
1044
- const ifNoneMatch = request.headers["if-none-match"];
1045
- const ifMatch = request.headers["if-match"];
1046
- if (ifNoneMatch === etag) {
1047
- await reply.status(304).send();
1048
- return;
1049
- }
1050
- if (ifMatch && ifMatch !== etag) {
1051
- await reply.status(412).send();
1052
- return;
968
+ // src/routes/index.ts
969
+ var indexRoute = /* @__PURE__ */ __name((fastify) => {
970
+ fastify.get("/", {
971
+ schema: {
972
+ hide: true
1053
973
  }
1054
- await reply.header("ETag", etag).send("Resource content for matching ETag.");
1055
- });
1056
- }, "etagRoutes");
974
+ }, async (_request, reply) => {
975
+ const redocHtml = `
976
+ <!doctype html>
977
+ <html>
978
+ <head>
979
+ <title>{ mockhttp }</title>
980
+ <meta charset="utf-8" />
981
+ <meta
982
+ name="viewport"
983
+ content="width=device-width, initial-scale=1" />
984
+ <style>
985
+ :root {
986
+ --scalar-custom-header-height: 70px;
987
+ }
988
+ .custom-header {
989
+ height: var(--scalar-custom-header-height);
990
+ background-color: var(--scalar-background-1);
991
+ box-shadow: inset 0 -1px 0 var(--scalar-border-color);
992
+ color: var(--scalar-color-1);
993
+ font-size: var(--scalar-font-size-2);
994
+ padding: 0 18px;
995
+ position: sticky;
996
+ justify-content: space-between;
997
+ top: 0;
998
+ z-index: 100;
999
+ }
1000
+ .custom-header,
1001
+ .custom-header nav {
1002
+ display: flex;
1003
+ align-items: center;
1004
+ gap: 18px;
1005
+ }
1057
1006
 
1058
- // src/routes/response-inspection/response-headers.ts
1059
- import { escape } from "html-escaper";
1060
- var responseHeadersSchema = {
1061
- description: "Returns a set of response headers based on the query string.",
1062
- tags: [
1063
- "Response Inspection"
1064
- ],
1065
- querystring: {
1066
- type: "object",
1067
- additionalProperties: true,
1068
- description: "Freeform query string parameters to be added as response headers."
1069
- },
1070
- response: {
1071
- // eslint-disable-next-line @typescript-eslint/naming-convention
1072
- 200: {
1073
- type: "object",
1074
- additionalProperties: true,
1075
- description: "Response headers as per query string parameters."
1076
- }
1077
- }
1078
- };
1079
- var responseHeadersRoutes = /* @__PURE__ */ __name((fastify) => {
1080
- fastify.get("/response-headers", {
1081
- schema: responseHeadersSchema
1082
- }, async (request, reply) => {
1083
- const queryParameters = request.query;
1084
- for (const [key, value] of Object.entries(queryParameters)) {
1085
- reply.header(key, escape(value));
1086
- }
1087
- const cleanedResponse = Object.fromEntries(Object.entries(queryParameters).map(([key, value]) => [
1088
- key,
1089
- escape(value)
1090
- ]));
1091
- await reply.send(cleanedResponse);
1092
- });
1093
- fastify.post("/response-headers", {
1094
- schema: responseHeadersSchema
1095
- }, async (request, reply) => {
1096
- const queryParameters = request.query ?? {};
1097
- const body = request.body ?? {};
1098
- for (const [key, value] of Object.entries(queryParameters)) {
1099
- reply.header(key, escape(value));
1100
- }
1101
- for (const [key, value] of Object.entries(body)) {
1102
- reply.header(key, escape(value));
1103
- }
1104
- const cleanedResponse = Object.fromEntries(Object.entries({
1105
- ...queryParameters,
1106
- ...body
1107
- }).map(([key, value]) => [
1108
- key,
1109
- escape(value)
1110
- ]));
1111
- await reply.send(cleanedResponse);
1007
+ .custom-header img {
1008
+ height: var(--scalar-custom-header-height);
1009
+ object-fit: contain;
1010
+ }
1011
+
1012
+ .custom-header a:hover {
1013
+ color: var(--scalar-color-2);
1014
+ }
1015
+
1016
+ .github-corner {
1017
+ fill: var(--scalar-border-color) !important;
1018
+ }
1019
+
1020
+ </style>
1021
+ </head>
1022
+ <body>
1023
+ <header class="custom-header scalar-app">
1024
+ <a href="/"><img src="/logo.svg" alt="{ mockhttp }" /></a>
1025
+ <a href="https://github.com/jaredwray/mockhttp" class="github-corner" aria-label="View source on GitHub">
1026
+ <svg width="70" height="70" viewBox="0 0 250 250" style="color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
1027
+ <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
1028
+ <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
1029
+ <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
1030
+ </header>
1031
+
1032
+ <script
1033
+ id="api-reference"
1034
+ data-url="/docs/json"></script>
1035
+
1036
+ <script src="/scalar/browser/standalone.js"></script>
1037
+ </body>
1038
+ </html>
1039
+ `;
1040
+ await reply.type("text/html").send(redocHtml);
1112
1041
  });
1113
- }, "responseHeadersRoutes");
1042
+ }, "indexRoute");
1114
1043
 
1115
1044
  // src/routes/redirects/absolute-redirect.ts
1116
1045
  var absoluteRedirectSchema = {
@@ -1173,30 +1102,80 @@ var absoluteRedirectRoute = /* @__PURE__ */ __name((fastify) => {
1173
1102
  });
1174
1103
  }, "absoluteRedirectRoute");
1175
1104
 
1176
- // src/routes/redirects/relative-redirect.ts
1177
- var relativeRedirectSchema = {
1105
+ // src/routes/redirects/redirect-to.ts
1106
+ var redirectToSchema = {
1178
1107
  tags: [
1179
1108
  "Redirects"
1180
1109
  ],
1181
- description: "Redirects the request to the target URL using an relative URL",
1182
- params: {
1110
+ description: "Redirects the request to the target URL",
1111
+ querystring: {
1183
1112
  type: "object",
1184
1113
  properties: {
1185
- value: {
1186
- type: "integer",
1187
- minimum: 1
1114
+ url: {
1115
+ type: "string",
1116
+ format: "uri",
1117
+ description: "The URL to redirect to"
1118
+ },
1119
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1120
+ status_code: {
1121
+ type: "string",
1122
+ pattern: "^[1-5][0-9][0-9]$",
1123
+ description: "The HTTP status code to use for redirection"
1188
1124
  }
1189
1125
  },
1190
1126
  required: [
1191
- "value"
1192
- ]
1193
- },
1194
- headers: {
1195
- type: "object",
1196
- properties: {
1197
- host: {
1198
- type: "string"
1199
- }
1127
+ "url"
1128
+ ],
1129
+ additionalProperties: false
1130
+ }
1131
+ };
1132
+ var redirectToRoute = /* @__PURE__ */ __name((fastify) => {
1133
+ const handler = /* @__PURE__ */ __name(async (request, reply) => {
1134
+ const { url, status_code } = request.query;
1135
+ const statusCode = status_code ? Number(status_code) : 302;
1136
+ await reply.redirect(url, statusCode);
1137
+ }, "handler");
1138
+ fastify.get("/redirect-to", {
1139
+ schema: redirectToSchema
1140
+ }, handler);
1141
+ fastify.post("/redirect-to", {
1142
+ schema: redirectToSchema
1143
+ }, handler);
1144
+ fastify.put("/redirect-to", {
1145
+ schema: redirectToSchema
1146
+ }, handler);
1147
+ fastify.patch("/redirect-to", {
1148
+ schema: redirectToSchema
1149
+ }, handler);
1150
+ fastify.delete("/redirect-to", {
1151
+ schema: redirectToSchema
1152
+ }, handler);
1153
+ }, "redirectToRoute");
1154
+
1155
+ // src/routes/redirects/relative-redirect.ts
1156
+ var relativeRedirectSchema = {
1157
+ tags: [
1158
+ "Redirects"
1159
+ ],
1160
+ description: "Redirects the request to the target URL using an relative URL",
1161
+ params: {
1162
+ type: "object",
1163
+ properties: {
1164
+ value: {
1165
+ type: "integer",
1166
+ minimum: 1
1167
+ }
1168
+ },
1169
+ required: [
1170
+ "value"
1171
+ ]
1172
+ },
1173
+ headers: {
1174
+ type: "object",
1175
+ properties: {
1176
+ host: {
1177
+ type: "string"
1178
+ }
1200
1179
  },
1201
1180
  required: [
1202
1181
  "host"
@@ -1232,285 +1211,758 @@ var relativeRedirectRoute = /* @__PURE__ */ __name((fastify) => {
1232
1211
  });
1233
1212
  }, "relativeRedirectRoute");
1234
1213
 
1235
- // src/routes/redirects/redirect-to.ts
1236
- var redirectToSchema = {
1237
- tags: [
1238
- "Redirects"
1239
- ],
1240
- description: "Redirects the request to the target URL",
1241
- querystring: {
1242
- type: "object",
1243
- properties: {
1244
- url: {
1245
- type: "string",
1246
- format: "uri",
1247
- description: "The URL to redirect to"
1248
- },
1249
- // eslint-disable-next-line @typescript-eslint/naming-convention
1250
- status_code: {
1251
- type: "string",
1252
- pattern: "^[1-5][0-9][0-9]$",
1253
- description: "The HTTP status code to use for redirection"
1254
- }
1255
- },
1256
- required: [
1257
- "url"
1258
- ],
1259
- additionalProperties: false
1260
- }
1261
- };
1262
- var redirectToRoute = /* @__PURE__ */ __name((fastify) => {
1263
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
1264
- const { url, status_code } = request.query;
1265
- const statusCode = status_code ? Number(status_code) : 302;
1266
- await reply.redirect(url, statusCode);
1267
- }, "handler");
1268
- fastify.get("/redirect-to", {
1269
- schema: redirectToSchema
1270
- }, handler);
1271
- fastify.post("/redirect-to", {
1272
- schema: redirectToSchema
1273
- }, handler);
1274
- fastify.put("/redirect-to", {
1275
- schema: redirectToSchema
1276
- }, handler);
1277
- fastify.patch("/redirect-to", {
1278
- schema: redirectToSchema
1279
- }, handler);
1280
- fastify.delete("/redirect-to", {
1281
- schema: redirectToSchema
1282
- }, handler);
1283
- }, "redirectToRoute");
1284
-
1285
- // src/routes/cookies/get.ts
1286
- var getCookiesRouteSchema = {
1287
- description: "Return cookies from the request",
1214
+ // src/routes/request-inspection/headers.ts
1215
+ var headersSchema = {
1216
+ description: "Returns all headers of the client request",
1288
1217
  tags: [
1289
- "Cookies"
1218
+ "Request Inspection"
1290
1219
  ],
1291
1220
  response: {
1292
1221
  // eslint-disable-next-line @typescript-eslint/naming-convention
1293
1222
  200: {
1294
1223
  type: "object",
1295
1224
  properties: {
1296
- cookies: {
1225
+ headers: {
1297
1226
  type: "object",
1298
1227
  additionalProperties: {
1299
1228
  type: "string"
1300
- }
1229
+ },
1230
+ description: "All headers of the client request"
1301
1231
  }
1302
1232
  },
1303
1233
  required: [
1304
- "cookies"
1234
+ "headers"
1305
1235
  ]
1306
1236
  }
1307
1237
  }
1308
1238
  };
1309
- var getCookiesRoute = /* @__PURE__ */ __name((fastify) => {
1310
- fastify.get("/cookies", {
1311
- schema: getCookiesRouteSchema
1312
- }, async (request, reply) => {
1313
- reply.type("application/json");
1314
- await reply.send({
1315
- cookies: request.cookies
1316
- });
1239
+ var headersRoute = /* @__PURE__ */ __name((fastify) => {
1240
+ fastify.get("/headers", {
1241
+ schema: headersSchema
1242
+ }, async (request) => {
1243
+ const { headers } = request;
1244
+ return {
1245
+ headers
1246
+ };
1317
1247
  });
1318
- }, "getCookiesRoute");
1248
+ }, "headersRoute");
1319
1249
 
1320
- // src/routes/cookies/post.ts
1321
- var postCookieRouteSchema = {
1322
- description: "Set a cookie",
1250
+ // src/routes/request-inspection/ip.ts
1251
+ var ipSchema = {
1252
+ description: "Returns the IP address of the client",
1323
1253
  tags: [
1324
- "Cookies"
1254
+ "Request Inspection"
1325
1255
  ],
1326
- body: {
1327
- type: "object",
1328
- properties: {
1329
- name: {
1330
- type: "string"
1331
- },
1332
- value: {
1333
- type: "string"
1334
- },
1335
- expires: {
1336
- type: "string",
1337
- format: "date-time"
1338
- }
1339
- },
1340
- required: [
1341
- "name",
1342
- "value"
1343
- ],
1344
- additionalProperties: false
1345
- },
1346
1256
  response: {
1347
1257
  // eslint-disable-next-line @typescript-eslint/naming-convention
1348
1258
  200: {
1349
1259
  type: "object",
1350
1260
  properties: {
1351
- cookies: {
1352
- type: "object",
1353
- additionalProperties: {
1354
- type: "string"
1355
- }
1261
+ ip: {
1262
+ type: "string",
1263
+ description: "The IP address of the client"
1356
1264
  }
1357
1265
  },
1358
1266
  required: [
1359
- "cookies"
1267
+ "ip"
1360
1268
  ]
1361
1269
  }
1362
1270
  }
1363
1271
  };
1364
- var postCookieRoute = /* @__PURE__ */ __name((fastify) => {
1365
- fastify.post("/cookies", {
1366
- schema: postCookieRouteSchema
1367
- }, async (request, reply) => {
1368
- const { name, value, expires } = request.body;
1369
- if (expires) {
1370
- const date = new Date(expires);
1371
- reply.setCookie(name, value, {
1372
- path: "/",
1373
- expires: date
1374
- });
1375
- } else {
1376
- reply.setCookie(name, value, {
1377
- path: "/"
1378
- });
1379
- }
1380
- return reply.status(200).send();
1272
+ var ipRoute = /* @__PURE__ */ __name((fastify) => {
1273
+ fastify.get("/ip", {
1274
+ schema: ipSchema
1275
+ }, async (request) => {
1276
+ const cloudflareIp = request.headers["cf-connecting-ip"];
1277
+ const forwardedIp = request.headers["x-forwarded-for"];
1278
+ const remoteIp = request.ip;
1279
+ const ip = cloudflareIp ?? (typeof forwardedIp === "string" ? forwardedIp.split(",")[0].trim() : remoteIp);
1280
+ return {
1281
+ ip
1282
+ };
1381
1283
  });
1382
- }, "postCookieRoute");
1284
+ }, "ipRoute");
1383
1285
 
1384
- // src/routes/cookies/delete.ts
1385
- var cookiesDeleteSchema = {
1386
- description: "Delete a cookie",
1286
+ // src/routes/request-inspection/user-agent.ts
1287
+ var userAgentSchema = {
1288
+ description: "Returns the User-Agent header of the client request",
1387
1289
  tags: [
1388
- "Cookies"
1290
+ "Request Inspection"
1389
1291
  ],
1390
- querystring: {
1391
- type: "object",
1392
- properties: {
1393
- name: {
1394
- type: "string"
1395
- }
1396
- },
1397
- required: [
1398
- "name"
1399
- ],
1400
- additionalProperties: false
1401
- },
1402
1292
  response: {
1403
1293
  // eslint-disable-next-line @typescript-eslint/naming-convention
1404
- 204: {
1405
- type: "null"
1294
+ 200: {
1295
+ type: "object",
1296
+ properties: {
1297
+ userAgent: {
1298
+ type: "string",
1299
+ description: "The User-Agent header of the client request"
1300
+ }
1301
+ },
1302
+ required: [
1303
+ "userAgent"
1304
+ ]
1406
1305
  }
1407
1306
  }
1408
1307
  };
1409
- var deleteCookieRoute = /* @__PURE__ */ __name((fastify) => {
1410
- fastify.delete("/cookies", {
1411
- schema: cookiesDeleteSchema
1412
- }, async (request, reply) => {
1413
- const { name } = request.query;
1414
- reply.clearCookie(name, {
1415
- path: "/"
1416
- });
1417
- return reply.status(204).send();
1308
+ var userAgentRoute = /* @__PURE__ */ __name((fastify) => {
1309
+ fastify.get("/user-agent", {
1310
+ schema: userAgentSchema
1311
+ }, async (request) => {
1312
+ const userAgent = request.headers["user-agent"];
1313
+ return {
1314
+ userAgent
1315
+ };
1418
1316
  });
1419
- }, "deleteCookieRoute");
1317
+ }, "userAgentRoute");
1420
1318
 
1421
- // src/routes/anything/index.ts
1422
- var anythingSchema = {
1423
- description: "Will return anything that you send it",
1319
+ // src/routes/response-inspection/cache.ts
1320
+ var cacheGetSchema = {
1321
+ description: "Handles cache validation and retrieval.",
1322
+ tags: [
1323
+ "Response Inspection"
1324
+ ],
1325
+ response: {
1326
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1327
+ 200: {
1328
+ type: "string",
1329
+ description: "The cached content or resource."
1330
+ },
1331
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1332
+ 304: {
1333
+ type: "null",
1334
+ description: "Indicates the resource has not been modified."
1335
+ }
1336
+ }
1337
+ };
1338
+ var cacheSetSchema = {
1339
+ description: "Sets a Cache-Control header for n seconds.",
1340
+ tags: [
1341
+ "Response Inspection"
1342
+ ],
1343
+ params: {
1344
+ type: "object",
1345
+ properties: {
1346
+ value: {
1347
+ type: "integer",
1348
+ description: "Number of seconds to cache the resource."
1349
+ }
1350
+ },
1351
+ required: [
1352
+ "value"
1353
+ ]
1354
+ },
1355
+ response: {
1356
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1357
+ 200: {
1358
+ type: "string",
1359
+ description: "Confirmation message for setting cache."
1360
+ }
1361
+ }
1362
+ };
1363
+ var cacheRoutes = /* @__PURE__ */ __name((fastify) => {
1364
+ fastify.get("/cache", {
1365
+ schema: cacheGetSchema
1366
+ }, async (request, reply) => {
1367
+ const ifModifiedSince = request.headers["if-modified-since"];
1368
+ const ifNoneMatch = request.headers["if-none-match"];
1369
+ if (ifModifiedSince ?? ifNoneMatch) {
1370
+ await reply.status(304).send();
1371
+ return;
1372
+ }
1373
+ await reply.send("Cache content or resource response here.");
1374
+ });
1375
+ fastify.get("/cache/:value", {
1376
+ schema: cacheSetSchema
1377
+ }, async (request, reply) => {
1378
+ const { value } = request.params;
1379
+ await reply.header("Cache-Control", `max-age=${value}`).send(`Cache-Control set for ${value} seconds.`);
1380
+ });
1381
+ }, "cacheRoutes");
1382
+
1383
+ // src/routes/response-inspection/etag.ts
1384
+ var etagSchema = {
1385
+ description: "Handles ETag-based conditional requests.",
1386
+ tags: [
1387
+ "Response Inspection"
1388
+ ],
1389
+ params: {
1390
+ type: "object",
1391
+ properties: {
1392
+ etag: {
1393
+ type: "string",
1394
+ description: "The ETag value for the resource."
1395
+ }
1396
+ },
1397
+ required: [
1398
+ "etag"
1399
+ ]
1400
+ },
1401
+ response: {
1402
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1403
+ 200: {
1404
+ type: "string",
1405
+ description: "Resource content for matching ETag."
1406
+ },
1407
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1408
+ 304: {
1409
+ type: "null",
1410
+ description: "Indicates the resource has not been modified."
1411
+ },
1412
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1413
+ 412: {
1414
+ type: "null",
1415
+ description: "Indicates a precondition failed due to mismatched ETag."
1416
+ }
1417
+ }
1418
+ };
1419
+ var etagRoutes = /* @__PURE__ */ __name((fastify) => {
1420
+ fastify.get("/etag/:etag", {
1421
+ schema: etagSchema
1422
+ }, async (request, reply) => {
1423
+ const { etag } = request.params;
1424
+ const ifNoneMatch = request.headers["if-none-match"];
1425
+ const ifMatch = request.headers["if-match"];
1426
+ if (ifNoneMatch === etag) {
1427
+ await reply.status(304).send();
1428
+ return;
1429
+ }
1430
+ if (ifMatch && ifMatch !== etag) {
1431
+ await reply.status(412).send();
1432
+ return;
1433
+ }
1434
+ await reply.header("ETag", etag).send("Resource content for matching ETag.");
1435
+ });
1436
+ }, "etagRoutes");
1437
+
1438
+ // src/routes/response-inspection/response-headers.ts
1439
+ import { escape as escapeHtml } from "html-escaper";
1440
+ var responseHeadersSchema = {
1441
+ description: "Returns a set of response headers based on the query string.",
1442
+ tags: [
1443
+ "Response Inspection"
1444
+ ],
1445
+ querystring: {
1446
+ type: "object",
1447
+ additionalProperties: true,
1448
+ description: "Freeform query string parameters to be added as response headers."
1449
+ },
1450
+ response: {
1451
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1452
+ 200: {
1453
+ type: "object",
1454
+ additionalProperties: true,
1455
+ description: "Response headers as per query string parameters."
1456
+ }
1457
+ }
1458
+ };
1459
+ var responseHeadersRoutes = /* @__PURE__ */ __name((fastify) => {
1460
+ fastify.get("/response-headers", {
1461
+ schema: responseHeadersSchema
1462
+ }, async (request, reply) => {
1463
+ const queryParameters = request.query;
1464
+ for (const [key, value] of Object.entries(queryParameters)) {
1465
+ reply.header(key, escapeHtml(value));
1466
+ }
1467
+ const cleanedResponse = Object.fromEntries(Object.entries(queryParameters).map(([key, value]) => [
1468
+ key,
1469
+ escapeHtml(value)
1470
+ ]));
1471
+ await reply.send(cleanedResponse);
1472
+ });
1473
+ fastify.post("/response-headers", {
1474
+ schema: responseHeadersSchema
1475
+ }, async (request, reply) => {
1476
+ const queryParameters = request.query ?? {};
1477
+ const body = request.body ?? {};
1478
+ for (const [key, value] of Object.entries(queryParameters)) {
1479
+ reply.header(key, escapeHtml(value));
1480
+ }
1481
+ for (const [key, value] of Object.entries(body)) {
1482
+ reply.header(key, escapeHtml(value));
1483
+ }
1484
+ const cleanedResponse = Object.fromEntries(Object.entries({
1485
+ ...queryParameters,
1486
+ ...body
1487
+ }).map(([key, value]) => [
1488
+ key,
1489
+ escapeHtml(value)
1490
+ ]));
1491
+ await reply.send(cleanedResponse);
1492
+ });
1493
+ }, "responseHeadersRoutes");
1494
+
1495
+ // src/routes/sitemap.ts
1496
+ var sitemapRoute = /* @__PURE__ */ __name((fastify) => {
1497
+ fastify.get("/sitemap.xml", {
1498
+ schema: {
1499
+ hide: true
1500
+ }
1501
+ }, async (_request, reply) => {
1502
+ try {
1503
+ const host = _request.headers.host ?? "mockhttp.org";
1504
+ const isSecure = _request.headers["x-forwarded-proto"]?.includes("https");
1505
+ const protocol = isSecure ? "https" : "http";
1506
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
1507
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1508
+ <url>
1509
+ <loc>${protocol}://${host}/</loc>
1510
+ <changefreq>monthly</changefreq>
1511
+ <priority>1.0</priority>
1512
+ </url>
1513
+ </urlset>`;
1514
+ await reply.type("text/xml").send(xml);
1515
+ } catch (error) {
1516
+ fastify.log.error(error);
1517
+ await reply.status(500).send({
1518
+ error: "Internal Server Error"
1519
+ });
1520
+ }
1521
+ });
1522
+ }, "sitemapRoute");
1523
+
1524
+ // src/routes/status-codes/index.ts
1525
+ import { randomInt } from "crypto";
1526
+ var statusCodes = [
1527
+ {
1528
+ code: 100,
1529
+ message: "Continue"
1530
+ },
1531
+ {
1532
+ code: 101,
1533
+ message: "Switching Protocols"
1534
+ },
1535
+ {
1536
+ code: 102,
1537
+ message: "Processing"
1538
+ },
1539
+ {
1540
+ code: 103,
1541
+ message: "Early Hints"
1542
+ },
1543
+ {
1544
+ code: 200,
1545
+ message: "OK"
1546
+ },
1547
+ {
1548
+ code: 201,
1549
+ message: "Created"
1550
+ },
1551
+ {
1552
+ code: 202,
1553
+ message: "Accepted"
1554
+ },
1555
+ {
1556
+ code: 203,
1557
+ message: "Non-Authoritative Information"
1558
+ },
1559
+ {
1560
+ code: 204,
1561
+ message: "No Content"
1562
+ },
1563
+ {
1564
+ code: 205,
1565
+ message: "Reset Content"
1566
+ },
1567
+ {
1568
+ code: 206,
1569
+ message: "Partial Content"
1570
+ },
1571
+ {
1572
+ code: 207,
1573
+ message: "Multi-Status"
1574
+ },
1575
+ {
1576
+ code: 208,
1577
+ message: "Already Reported"
1578
+ },
1579
+ {
1580
+ code: 226,
1581
+ message: "IM Used"
1582
+ },
1583
+ {
1584
+ code: 300,
1585
+ message: "Multiple Choices"
1586
+ },
1587
+ {
1588
+ code: 301,
1589
+ message: "Moved Permanently"
1590
+ },
1591
+ {
1592
+ code: 302,
1593
+ message: "Found"
1594
+ },
1595
+ {
1596
+ code: 303,
1597
+ message: "See Other"
1598
+ },
1599
+ {
1600
+ code: 304,
1601
+ message: "Not Modified"
1602
+ },
1603
+ {
1604
+ code: 305,
1605
+ message: "Use Proxy"
1606
+ },
1607
+ {
1608
+ code: 306,
1609
+ message: "Switch Proxy"
1610
+ },
1611
+ {
1612
+ code: 307,
1613
+ message: "Temporary Redirect"
1614
+ },
1615
+ {
1616
+ code: 308,
1617
+ message: "Permanent Redirect"
1618
+ },
1619
+ {
1620
+ code: 400,
1621
+ message: "Bad Request"
1622
+ },
1623
+ {
1624
+ code: 401,
1625
+ message: "Unauthorized"
1626
+ },
1627
+ {
1628
+ code: 402,
1629
+ message: "Payment Required"
1630
+ },
1631
+ {
1632
+ code: 403,
1633
+ message: "Forbidden"
1634
+ },
1635
+ {
1636
+ code: 404,
1637
+ message: "Not Found"
1638
+ },
1639
+ {
1640
+ code: 405,
1641
+ message: "Method Not Allowed"
1642
+ },
1643
+ {
1644
+ code: 406,
1645
+ message: "Not Acceptable"
1646
+ },
1647
+ {
1648
+ code: 407,
1649
+ message: "Proxy Authentication Required"
1650
+ },
1651
+ {
1652
+ code: 408,
1653
+ message: "Request Timeout"
1654
+ },
1655
+ {
1656
+ code: 409,
1657
+ message: "Conflict"
1658
+ },
1659
+ {
1660
+ code: 410,
1661
+ message: "Gone"
1662
+ },
1663
+ {
1664
+ code: 411,
1665
+ message: "Length Required"
1666
+ },
1667
+ {
1668
+ code: 412,
1669
+ message: "Precondition Failed"
1670
+ },
1671
+ {
1672
+ code: 413,
1673
+ message: "Payload Too Large"
1674
+ },
1675
+ {
1676
+ code: 414,
1677
+ message: "URI Too Long"
1678
+ },
1679
+ {
1680
+ code: 415,
1681
+ message: "Unsupported Media Type"
1682
+ },
1683
+ {
1684
+ code: 416,
1685
+ message: "Range Not Satisfiable"
1686
+ },
1687
+ {
1688
+ code: 417,
1689
+ message: "Expectation Failed"
1690
+ },
1691
+ {
1692
+ code: 418,
1693
+ message: "I'm a teapot"
1694
+ },
1695
+ {
1696
+ code: 421,
1697
+ message: "Misdirected Request"
1698
+ },
1699
+ {
1700
+ code: 422,
1701
+ message: "Unprocessable Entity"
1702
+ },
1703
+ {
1704
+ code: 423,
1705
+ message: "Locked"
1706
+ },
1707
+ {
1708
+ code: 424,
1709
+ message: "Failed Dependency"
1710
+ },
1711
+ {
1712
+ code: 425,
1713
+ message: "Too Early"
1714
+ },
1715
+ {
1716
+ code: 426,
1717
+ message: "Upgrade Required"
1718
+ },
1719
+ {
1720
+ code: 428,
1721
+ message: "Precondition Required"
1722
+ },
1723
+ {
1724
+ code: 429,
1725
+ message: "Too Many Requests"
1726
+ },
1727
+ {
1728
+ code: 431,
1729
+ message: "Request Header Fields Too Large"
1730
+ },
1731
+ {
1732
+ code: 451,
1733
+ message: "Unavailable For Legal Reasons"
1734
+ },
1735
+ {
1736
+ code: 500,
1737
+ message: "Internal Server Error"
1738
+ },
1739
+ {
1740
+ code: 501,
1741
+ message: "Not Implemented"
1742
+ },
1743
+ {
1744
+ code: 502,
1745
+ message: "Bad Gateway"
1746
+ },
1747
+ {
1748
+ code: 503,
1749
+ message: "Service Unavailable"
1750
+ },
1751
+ {
1752
+ code: 504,
1753
+ message: "Gateway Timeout"
1754
+ },
1755
+ {
1756
+ code: 505,
1757
+ message: "HTTP Version Not Supported"
1758
+ },
1759
+ {
1760
+ code: 506,
1761
+ message: "Variant Also Negotiates"
1762
+ },
1763
+ {
1764
+ code: 507,
1765
+ message: "Insufficient Storage"
1766
+ },
1767
+ {
1768
+ code: 508,
1769
+ message: "Loop Detected"
1770
+ },
1771
+ {
1772
+ code: 510,
1773
+ message: "Not Extended"
1774
+ },
1775
+ {
1776
+ code: 511,
1777
+ message: "Network Authentication Required"
1778
+ }
1779
+ ];
1780
+ var statusSchema = {
1781
+ description: "Return status code or random status code if more than one are given ",
1424
1782
  tags: [
1425
- "Anything"
1783
+ "Status Codes"
1426
1784
  ],
1785
+ params: {
1786
+ type: "object",
1787
+ properties: {
1788
+ code: {
1789
+ type: "string",
1790
+ description: "HTTP status code to be returned"
1791
+ }
1792
+ }
1793
+ },
1427
1794
  response: {
1428
- // eslint-disable-next-line @typescript-eslint/naming-convention
1795
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1429
1796
  200: {
1430
1797
  type: "object",
1431
1798
  properties: {
1432
- // Echo back all your params under `args`
1433
- args: {
1434
- type: "object",
1435
- description: "Route parameters",
1436
- additionalProperties: true
1437
- },
1438
- // Echo back whatever was in the request.body
1439
- data: {
1440
- type: "object",
1441
- description: "Request body data",
1442
- additionalProperties: true
1443
- },
1444
- // If you’re using fastify-multipart, you’ll get
1445
- // files via request.files(); this just allows
1446
- // you to mirror them back
1447
- files: {
1448
- type: "object",
1449
- description: "Uploaded files",
1450
- additionalProperties: true
1451
- },
1452
- // If you parse application/x-www-form-urlencoded
1453
- // these fields land in request.body too, but you can
1454
- // mirror them separately if you like
1455
- form: {
1456
- type: "object",
1457
- description: "Form data",
1458
- additionalProperties: true
1459
- },
1460
- headers: {
1461
- type: "object",
1462
- description: "Request headers",
1463
- additionalProperties: true
1464
- },
1465
- json: {
1466
- type: "object",
1467
- description: "Parsed JSON body",
1468
- additionalProperties: true
1469
- },
1470
- method: {
1471
- type: "string",
1472
- description: "HTTP method used"
1473
- },
1474
- origin: {
1475
- type: "string",
1476
- description: "Origin of the request"
1477
- },
1478
- url: {
1479
- type: "string",
1480
- description: "Request URL"
1799
+ status: {
1800
+ type: "string"
1481
1801
  }
1482
- },
1483
- // Don’t allow any other top-level keys in your response
1484
- additionalProperties: false
1802
+ }
1485
1803
  }
1486
1804
  }
1487
1805
  };
1488
- var anythingRoute = /* @__PURE__ */ __name((fastify) => {
1806
+ var statusCodeRoute = /* @__PURE__ */ __name((fastify) => {
1489
1807
  const handler = /* @__PURE__ */ __name(async (request, reply) => {
1490
- const response = {
1491
- args: request.query,
1492
- data: request.body ?? {},
1493
- files: request.raw?.files ?? {},
1494
- form: request.body ?? {},
1495
- headers: request.headers,
1496
- json: request.body ?? {},
1497
- method: request.method,
1498
- origin: request.headers.origin ?? request.headers.host ?? "",
1499
- url: request.url
1500
- };
1501
- await reply.send(response);
1808
+ let { code } = request.params;
1809
+ if (!code || !statusCodes.map((status) => status.code).includes(Number(code))) {
1810
+ code = statusCodes[randomInt(0, statusCodes.length)].code.toString();
1811
+ }
1812
+ await reply.code(Number(code)).send({
1813
+ status: `Response with status code ${code}`
1814
+ });
1502
1815
  }, "handler");
1503
- const noValidation = {
1504
- schema: anythingSchema,
1505
- // Skip AJV on *this* route
1506
- validatorCompiler: /* @__PURE__ */ __name(() => () => true, "validatorCompiler")
1507
- };
1508
- fastify.get("/anything", noValidation, handler);
1509
- fastify.post("/anything", noValidation, handler);
1510
- fastify.put("/anything", noValidation, handler);
1511
- fastify.patch("/anything", noValidation, handler);
1512
- fastify.delete("/anything", noValidation, handler);
1513
- }, "anythingRoute");
1816
+ fastify.get("/status/:code?", {
1817
+ schema: statusSchema
1818
+ }, handler);
1819
+ fastify.post("/status/:code?", {
1820
+ schema: statusSchema
1821
+ }, handler);
1822
+ fastify.put("/status/:code?", {
1823
+ schema: statusSchema
1824
+ }, handler);
1825
+ fastify.patch("/status/:code?", {
1826
+ schema: statusSchema
1827
+ }, handler);
1828
+ fastify.delete("/status/:code?", {
1829
+ schema: statusSchema
1830
+ }, handler);
1831
+ }, "statusCodeRoute");
1832
+
1833
+ // src/swagger.ts
1834
+ import { fastifySwaggerUi } from "@fastify/swagger-ui";
1835
+
1836
+ // package.json
1837
+ var package_default = {
1838
+ name: "@jaredwray/mockhttp",
1839
+ version: "0.7.0",
1840
+ description: "Mock Http - Easy way to mock http with httpbin replacement",
1841
+ type: "module",
1842
+ main: "dist/index.js",
1843
+ directories: {
1844
+ test: "test"
1845
+ },
1846
+ scripts: {
1847
+ lint: "biome check --write --error-on-warnings",
1848
+ test: "pnpm lint && vitest run --coverage",
1849
+ "test:ci": "biome check --error-on-warnings && vitest run --coverage",
1850
+ dev: "pnpm lint && tsx src/index.ts --watch",
1851
+ build: "rimraf ./dist && tsup src/index.ts --format esm --dts --clean",
1852
+ "docker:build": "docker build -t jaredwray/mockhttp .",
1853
+ "docker:run": "docker run -p 3000:3000 jaredwray/mockhttp",
1854
+ "docker:push": "docker push jaredwray/mockhttp",
1855
+ "docker:stop": 'docker stop $(docker ps -a -q --filter ancestor=jaredwray/mockhttp --format="{{.ID}}")',
1856
+ "docker:readme": "tsx ./scripts/create-docker-readme.ts",
1857
+ "docker:compose:up": "docker-compose up -d",
1858
+ "docker:compose:down": "docker-compose down",
1859
+ prepare: "pnpm run build",
1860
+ start: "node dist/index.js"
1861
+ },
1862
+ repository: {
1863
+ type: "git",
1864
+ url: "git+https://github.com/jaredwray/mockhttp.git"
1865
+ },
1866
+ keywords: [
1867
+ "mock",
1868
+ "http",
1869
+ "httpbin",
1870
+ "test"
1871
+ ],
1872
+ author: "Jared Wray <me@jaredwray.com>",
1873
+ license: "MIT",
1874
+ bugs: {
1875
+ url: "https://github.com/jaredwray/mockhttp/issues"
1876
+ },
1877
+ homepage: "https://github.com/jaredwray/mockhttp#readme",
1878
+ devDependencies: {
1879
+ "@biomejs/biome": "^2.2.2",
1880
+ "@swc/core": "^1.13.4",
1881
+ "@types/html-escaper": "^3.0.4",
1882
+ "@vitest/coverage-v8": "^3.2.4",
1883
+ rimraf: "^6.0.1",
1884
+ tsup: "^8.5.0",
1885
+ tsx: "^4.20.4",
1886
+ typescript: "^5.9.2",
1887
+ vitest: "^3.2.4"
1888
+ },
1889
+ dependencies: {
1890
+ "@fastify/cookie": "^11.0.2",
1891
+ "@fastify/helmet": "^13.0.1",
1892
+ "@fastify/static": "^8.2.0",
1893
+ "@fastify/swagger": "^9.5.1",
1894
+ "@fastify/swagger-ui": "^5.2.3",
1895
+ "@scalar/api-reference": "^1.34.4",
1896
+ "detect-port": "^2.1.0",
1897
+ fastify: "^5.5.0",
1898
+ hookified: "^1.12.0",
1899
+ "html-escaper": "^3.0.3",
1900
+ pino: "^9.9.0",
1901
+ "pino-pretty": "^13.1.1"
1902
+ },
1903
+ files: [
1904
+ "dist",
1905
+ "public",
1906
+ "LICENCE"
1907
+ ],
1908
+ pnpm: {
1909
+ onlyBuiltDependencies: [
1910
+ "@swc/core",
1911
+ "unrs-resolver",
1912
+ "vue-demi"
1913
+ ]
1914
+ }
1915
+ };
1916
+
1917
+ // src/swagger.ts
1918
+ var description = `
1919
+ A simple HTTP server that can be used to mock HTTP responses for testing purposes. Inspired by [httpbin](https://httpbin.org/) and built using \`nodejs\` and \`fastify\` with the idea of running it via https://mockhttp.org, via docker \`jaredwray/mockhttp\`, or nodejs \`npm install jaredwray/mockhttp\`.
1920
+
1921
+ * [GitHub Repository](https://github.com/jaredwray/mockhttp)
1922
+ * [Docker Image](https://hub.docker.com/r/jaredwray/mockhttp)
1923
+ * [NPM Package](https://www.npmjs.com/package/jaredwray/mockhttp)
1924
+
1925
+ # About mockhttp.org
1926
+
1927
+ [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. 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.
1928
+ `;
1929
+ var fastifySwaggerConfig = {
1930
+ openapi: {
1931
+ info: {
1932
+ title: "Mock HTTP API",
1933
+ description,
1934
+ version: package_default.version
1935
+ },
1936
+ consumes: [
1937
+ "application/json"
1938
+ ],
1939
+ produces: [
1940
+ "application/json"
1941
+ ]
1942
+ }
1943
+ };
1944
+ var registerSwaggerUi = /* @__PURE__ */ __name(async (fastify) => {
1945
+ await fastify.register(fastifySwaggerUi, {
1946
+ routePrefix: "/docs",
1947
+ uiConfig: {
1948
+ docExpansion: "none",
1949
+ deepLinking: false
1950
+ },
1951
+ uiHooks: {
1952
+ /* c8 ignore next 6 */
1953
+ onRequest(_request, _reply, next) {
1954
+ next();
1955
+ },
1956
+ preHandler(_request, _reply, next) {
1957
+ next();
1958
+ }
1959
+ },
1960
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1961
+ staticCSP: true,
1962
+ transformSpecification: /* @__PURE__ */ __name((swaggerObject, _request, _reply) => swaggerObject, "transformSpecification"),
1963
+ transformSpecificationClone: true
1964
+ });
1965
+ }, "registerSwaggerUi");
1514
1966
 
1515
1967
  // src/mock-http.ts
1516
1968
  var MockHttp = class extends Hookified {
@@ -1529,7 +1981,8 @@ var MockHttp = class extends Hookified {
1529
1981
  responseInspection: true,
1530
1982
  statusCodes: true,
1531
1983
  cookies: true,
1532
- anything: true
1984
+ anything: true,
1985
+ auth: true
1533
1986
  };
1534
1987
  // eslint-disable-next-line new-cap
1535
1988
  _server = Fastify();
@@ -1669,7 +2122,7 @@ var MockHttp = class extends Hookified {
1669
2122
  if (this._apiDocs) {
1670
2123
  await this.registerApiDocs();
1671
2124
  }
1672
- const { httpMethods, redirects, requestInspection, responseInspection, statusCodes: statusCodes2, cookies, anything } = this._httpBin;
2125
+ const { httpMethods, redirects, requestInspection, responseInspection, statusCodes: statusCodes2, cookies, anything, auth } = this._httpBin;
1673
2126
  if (httpMethods) {
1674
2127
  await this.registerHttpMethods();
1675
2128
  }
@@ -1691,6 +2144,9 @@ var MockHttp = class extends Hookified {
1691
2144
  if (anything) {
1692
2145
  await this.registerAnythingRoutes();
1693
2146
  }
2147
+ if (auth) {
2148
+ await this.registerAuthRoutes();
2149
+ }
1694
2150
  if (this._autoDetectPort) {
1695
2151
  const originalPort = this._port;
1696
2152
  this._port = await this.detectPort();
@@ -1799,6 +2255,17 @@ var MockHttp = class extends Hookified {
1799
2255
  const fastify = fastifyInstance ?? this._server;
1800
2256
  await fastify.register(anythingRoute);
1801
2257
  }
2258
+ /**
2259
+ * Register the auth routes.
2260
+ * @param fastifyInstance - the server instance to register the routes on.
2261
+ */
2262
+ async registerAuthRoutes(fastifyInstance) {
2263
+ const fastify = fastifyInstance ?? this._server;
2264
+ await fastify.register(basicAuthRoute);
2265
+ await fastify.register(hiddenBasicAuthRoute);
2266
+ await fastify.register(bearerAuthRoute);
2267
+ await fastify.register(digestAuthRoute);
2268
+ }
1802
2269
  };
1803
2270
 
1804
2271
  // src/index.ts
@@ -1813,7 +2280,9 @@ var start = /* @__PURE__ */ __name(async () => {
1813
2280
  await mockHttp.start();
1814
2281
  return mockHttp;
1815
2282
  }, "start");
1816
- await start();
2283
+ if (import.meta.url === `file://${process.argv[1]}`) {
2284
+ await start();
2285
+ }
1817
2286
  export {
1818
2287
  MockHttp as default,
1819
2288
  MockHttp as mockhttp,