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