@jaredwray/mockhttp 0.6.0 → 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.0",
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.12.1",
62
- "@types/html-escaper": "^3.0.4",
63
- "@vitest/coverage-v8": "^3.2.3",
64
- rimraf: "^6.0.1",
65
- tsup: "^8.5.0",
66
- tsx: "^4.20.3",
67
- typescript: "^5.8.3",
68
- vitest: "^3.2.3",
69
- xo: "^1.1.0"
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.31.11",
78
- "detect-port": "^2.1.0",
79
- fastify: "^5.4.0",
80
- hookified: "^1.9.1",
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"
301
- },
302
- headers: {
43
+ // Echo back all your params under `args`
44
+ args: {
303
45
  type: "object",
304
- additionalProperties: {
305
- type: "string"
306
- }
46
+ description: "Route parameters",
47
+ additionalProperties: true
307
48
  },
308
- queryParams: {
49
+ // Echo back whatever was in the request.body
50
+ data: {
309
51
  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"
52
+ description: "Request body data",
53
+ additionalProperties: true
54
+ },
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: {
59
+ type: "object",
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"
197
+ authenticated: {
198
+ type: "boolean"
399
199
  },
400
- headers: {
401
- type: "object",
402
- additionalProperties: {
403
- type: "string"
404
- }
405
- },
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"
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
571
600
  },
572
- {
573
- code: 207,
574
- message: "Multi-Status"
575
- },
576
- {
577
- code: 208,
578
- message: "Already Reported"
579
- },
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"
779
- }
780
- ];
781
- var statusSchema = {
782
- description: "Return status code or random status code if more than one are given ",
783
- tags: [
784
- "Status Codes"
785
- ],
786
- params: {
787
- type: "object",
788
- properties: {
789
- code: {
790
- type: "string",
791
- description: "HTTP status code to be returned"
792
- }
793
- }
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
794
680
  },
795
681
  response: {
796
682
  // eslint-disable-next-line @typescript-eslint/naming-convention
797
683
  200: {
798
684
  type: "object",
799
685
  properties: {
800
- status: {
801
- type: "string"
686
+ cookies: {
687
+ type: "object",
688
+ additionalProperties: {
689
+ type: "string"
690
+ }
802
691
  }
803
- }
692
+ },
693
+ required: [
694
+ "cookies"
695
+ ]
804
696
  }
805
697
  }
806
698
  };
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();
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
+ });
812
714
  }
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");
715
+ return reply.status(200).send();
716
+ });
717
+ }, "postCookieRoute");
833
718
 
834
- // src/routes/request-inspection/ip.ts
835
- var ipSchema = {
836
- description: "Returns the IP address of the client",
719
+ // src/routes/http-methods/delete.ts
720
+ var deleteSchema = {
721
+ description: "Handles a DELETE request and returns request information",
837
722
  tags: [
838
- "Request Inspection"
723
+ "HTTP Methods"
839
724
  ],
725
+ body: {
726
+ type: "object",
727
+ additionalProperties: true,
728
+ description: "The body of the DELETE request"
729
+ },
840
730
  response: {
841
731
  // eslint-disable-next-line @typescript-eslint/naming-convention
842
732
  200: {
843
733
  type: "object",
844
734
  properties: {
845
- ip: {
846
- type: "string",
847
- description: "The IP address of the client"
735
+ method: {
736
+ type: "string"
737
+ },
738
+ headers: {
739
+ type: "object",
740
+ additionalProperties: {
741
+ type: "string"
742
+ }
743
+ },
744
+ body: {
745
+ type: "object",
746
+ additionalProperties: true
848
747
  }
849
748
  },
850
749
  required: [
851
- "ip"
750
+ "method",
751
+ "headers",
752
+ "body"
852
753
  ]
853
754
  }
854
755
  }
855
756
  };
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");
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");
869
766
 
870
- // src/routes/request-inspection/headers.ts
871
- var headersSchema = {
872
- description: "Returns all headers of the client request",
767
+ // src/routes/http-methods/get.ts
768
+ var getSchema = {
769
+ description: "Returns request information for GET requests",
873
770
  tags: [
874
- "Request Inspection"
771
+ "HTTP Methods"
875
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
+ },
876
785
  response: {
877
786
  // eslint-disable-next-line @typescript-eslint/naming-convention
878
787
  200: {
879
788
  type: "object",
880
789
  properties: {
790
+ method: {
791
+ type: "string"
792
+ },
881
793
  headers: {
882
794
  type: "object",
883
795
  additionalProperties: {
884
796
  type: "string"
885
- },
886
- description: "All headers of the client request"
797
+ }
798
+ },
799
+ queryParams: {
800
+ type: "object",
801
+ additionalProperties: {
802
+ type: "string"
803
+ }
887
804
  }
888
805
  },
889
806
  required: [
890
- "headers"
807
+ "method",
808
+ "headers",
809
+ "queryParams"
891
810
  ]
892
811
  }
893
812
  }
894
813
  };
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");
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");
905
823
 
906
- // src/routes/request-inspection/user-agent.ts
907
- var userAgentSchema = {
908
- description: "Returns the User-Agent header of the client request",
824
+ // src/routes/http-methods/patch.ts
825
+ var patchSchema = {
826
+ description: "Handles a PATCH request and returns request information",
909
827
  tags: [
910
- "Request Inspection"
828
+ "HTTP Methods"
911
829
  ],
830
+ body: {
831
+ type: "object",
832
+ additionalProperties: true,
833
+ description: "The body of the PATCH request"
834
+ },
912
835
  response: {
913
836
  // eslint-disable-next-line @typescript-eslint/naming-convention
914
837
  200: {
915
838
  type: "object",
916
839
  properties: {
917
- userAgent: {
918
- type: "string",
919
- description: "The User-Agent header of the client request"
840
+ method: {
841
+ type: "string"
842
+ },
843
+ headers: {
844
+ type: "object",
845
+ additionalProperties: {
846
+ type: "string"
847
+ }
848
+ },
849
+ body: {
850
+ type: "object",
851
+ additionalProperties: true
920
852
  }
921
853
  },
922
854
  required: [
923
- "userAgent"
855
+ "method",
856
+ "headers",
857
+ "body"
924
858
  ]
925
859
  }
926
860
  }
927
861
  };
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");
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");
938
871
 
939
- // src/routes/response-inspection/cache.ts
940
- var cacheGetSchema = {
941
- description: "Handles cache validation and retrieval.",
872
+ // src/routes/http-methods/post.ts
873
+ var postSchema = {
874
+ description: "Handles a POST request and returns request information",
942
875
  tags: [
943
- "Response Inspection"
876
+ "HTTP Methods"
944
877
  ],
878
+ body: {
879
+ type: "object",
880
+ additionalProperties: true,
881
+ description: "The body of the POST request"
882
+ },
945
883
  response: {
946
- // eslint-disable-next-line @typescript-eslint/naming-convention
884
+ // eslint-disable-next-line @typescript-eslint/naming-convention
947
885
  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."
886
+ type: "object",
887
+ properties: {
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
900
+ }
901
+ },
902
+ required: [
903
+ "method",
904
+ "headers",
905
+ "body"
906
+ ]
955
907
  }
956
908
  }
957
909
  };
958
- var cacheSetSchema = {
959
- description: "Sets a Cache-Control header for n seconds.",
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");
919
+
920
+ // src/routes/http-methods/put.ts
921
+ var putSchema = {
922
+ description: "Handles a PUT request and returns request information",
960
923
  tags: [
961
- "Response Inspection"
924
+ "HTTP Methods"
962
925
  ],
963
- params: {
926
+ body: {
964
927
  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
- ]
928
+ additionalProperties: true,
929
+ description: "The body of the PUT request"
974
930
  },
975
931
  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");
1002
-
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;
1053
- }
1054
- await reply.header("ETag", etag).send("Resource content for matching ETag.");
1055
- });
1056
- }, "etagRoutes");
1057
-
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
932
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1072
933
  200: {
1073
934
  type: "object",
1074
- additionalProperties: true,
1075
- description: "Response headers as per query string parameters."
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
+ ]
1076
955
  }
1077
956
  }
1078
957
  };
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));
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");
967
+
968
+ // src/routes/index.ts
969
+ var indexRoute = /* @__PURE__ */ __name((fastify) => {
970
+ fastify.get("/", {
971
+ schema: {
972
+ hide: true
1103
973
  }
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);
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
+ }
1006
+
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,35 +1102,85 @@ 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: {
1183
- type: "object",
1184
- properties: {
1185
- value: {
1186
- type: "integer",
1187
- minimum: 1
1188
- }
1189
- },
1190
- required: [
1191
- "value"
1192
- ]
1193
- },
1194
- headers: {
1110
+ description: "Redirects the request to the target URL",
1111
+ querystring: {
1195
1112
  type: "object",
1196
1113
  properties: {
1197
- host: {
1198
- type: "string"
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"
1199
1124
  }
1200
1125
  },
1201
1126
  required: [
1202
- "host"
1203
- ]
1204
- },
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
+ }
1179
+ },
1180
+ required: [
1181
+ "host"
1182
+ ]
1183
+ },
1205
1184
  response: {
1206
1185
  // eslint-disable-next-line @typescript-eslint/naming-convention
1207
1186
  302: {
@@ -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();
1418
- });
1419
- }, "deleteCookieRoute");
1420
-
1421
- // src/routes/anything/index.ts
1422
- var anythingSchema = {
1423
- description: "Will return anything that you send it",
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
+ };
1316
+ });
1317
+ }, "userAgentRoute");
1318
+
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,