@jaredwray/mockhttp 0.8.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,964 +1,4 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
-
4
- // src/index.ts
5
- import process from "process";
6
-
7
- // src/mock-http.ts
8
- import path from "path";
9
- import fastifyCookie from "@fastify/cookie";
10
- import fastifyHelmet from "@fastify/helmet";
11
- import fastifyStatic from "@fastify/static";
12
- import { fastifySwagger } from "@fastify/swagger";
13
- import { detect } from "detect-port";
14
- import Fastify from "fastify";
15
- import { Hookified } from "hookified";
16
-
17
- // src/fastify-config.ts
18
- var fastifyConfig = {
19
- logger: {
20
- transport: {
21
- target: "pino-pretty",
22
- options: {
23
- colorize: true,
24
- translateTime: true,
25
- ignore: "pid,hostname",
26
- singleLine: true
27
- }
28
- }
29
- }
30
- };
31
-
32
- // src/routes/anything/index.ts
33
- var anythingSchema = {
34
- description: "Will return anything that you send it",
35
- tags: [
36
- "Anything"
37
- ],
38
- response: {
39
- 200: {
40
- type: "object",
41
- properties: {
42
- // Echo back all your params under `args`
43
- args: {
44
- type: "object",
45
- description: "Route parameters",
46
- additionalProperties: true
47
- },
48
- // Echo back whatever was in the request.body
49
- data: {
50
- type: "object",
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
69
- },
70
- headers: {
71
- type: "object",
72
- description: "Request headers",
73
- additionalProperties: true
74
- },
75
- json: {
76
- type: "object",
77
- description: "Parsed JSON body",
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"
91
- }
92
- },
93
- // Don’t allow any other top-level keys in your response
94
- additionalProperties: false
95
- }
96
- }
97
- };
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");
125
-
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.",
173
- tags: [
174
- "Auth"
175
- ],
176
- params: {
177
- type: "object",
178
- properties: {
179
- user: {
180
- type: "string"
181
- },
182
- passwd: {
183
- type: "string"
184
- }
185
- },
186
- required: [
187
- "user",
188
- "passwd"
189
- ]
190
- },
191
- response: {
192
- 200: {
193
- type: "object",
194
- properties: {
195
- authenticated: {
196
- type: "boolean"
197
- },
198
- user: {
199
- type: "string"
200
- }
201
- },
202
- required: [
203
- "authenticated",
204
- "user"
205
- ]
206
- },
207
- 401: {
208
- type: "object",
209
- properties: {
210
- message: {
211
- type: "string"
212
- }
213
- },
214
- required: [
215
- "message"
216
- ]
217
- }
218
- }
219
- };
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");
244
-
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.",
258
- tags: [
259
- "Auth"
260
- ],
261
- querystring: {
262
- type: "object",
263
- properties: {
264
- required: {
265
- type: "boolean",
266
- default: true
267
- }
268
- }
269
- },
270
- response: {
271
- 200: {
272
- type: "object",
273
- properties: {
274
- authenticated: {
275
- type: "boolean"
276
- },
277
- token: {
278
- type: "string"
279
- }
280
- },
281
- required: [
282
- "authenticated",
283
- "token"
284
- ]
285
- },
286
- 401: {
287
- type: "object",
288
- properties: {
289
- message: {
290
- type: "string"
291
- }
292
- },
293
- required: [
294
- "message"
295
- ]
296
- }
297
- }
298
- };
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");
324
-
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
594
- },
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
672
- },
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
- }
688
- }
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",
713
- tags: [
714
- "HTTP Methods"
715
- ],
716
- body: {
717
- type: "object",
718
- additionalProperties: true,
719
- description: "The body of the DELETE request"
720
- },
721
- response: {
722
- 200: {
723
- type: "object",
724
- properties: {
725
- method: {
726
- type: "string"
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
- ]
744
- }
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");
756
-
757
- // src/routes/http-methods/get.ts
758
- var getSchema = {
759
- description: "Returns request information for GET requests",
760
- tags: [
761
- "HTTP Methods"
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
- },
775
- response: {
776
- 200: {
777
- type: "object",
778
- properties: {
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
- }
793
- }
794
- },
795
- required: [
796
- "method",
797
- "headers",
798
- "queryParams"
799
- ]
800
- }
801
- }
802
- };
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");
812
-
813
- // src/routes/http-methods/patch.ts
814
- var patchSchema = {
815
- description: "Handles a PATCH request and returns request information",
816
- tags: [
817
- "HTTP Methods"
818
- ],
819
- body: {
820
- type: "object",
821
- additionalProperties: true,
822
- description: "The body of the PATCH request"
823
- },
824
- response: {
825
- 200: {
826
- type: "object",
827
- properties: {
828
- method: {
829
- type: "string"
830
- },
831
- headers: {
832
- type: "object",
833
- additionalProperties: {
834
- type: "string"
835
- }
836
- },
837
- body: {
838
- type: "object",
839
- additionalProperties: true
840
- }
841
- },
842
- required: [
843
- "method",
844
- "headers",
845
- "body"
846
- ]
847
- }
848
- }
849
- };
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");
859
-
860
- // src/routes/http-methods/post.ts
861
- var postSchema = {
862
- description: "Handles a POST request and returns request information",
863
- tags: [
864
- "HTTP Methods"
865
- ],
866
- body: {
867
- type: "object",
868
- additionalProperties: true,
869
- description: "The body of the POST request"
870
- },
871
- response: {
872
- 200: {
873
- type: "object",
874
- properties: {
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
887
- }
888
- },
889
- required: [
890
- "method",
891
- "headers",
892
- "body"
893
- ]
894
- }
895
- }
896
- };
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");
906
-
907
- // src/routes/http-methods/put.ts
908
- var putSchema = {
909
- description: "Handles a PUT request and returns request information",
910
- tags: [
911
- "HTTP Methods"
912
- ],
913
- body: {
914
- type: "object",
915
- additionalProperties: true,
916
- description: "The body of the PUT request"
917
- },
918
- response: {
919
- 200: {
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
- ]
941
- }
942
- }
943
- };
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");
953
-
954
- // src/routes/index.ts
955
- var indexRoute = /* @__PURE__ */ __name((fastify) => {
956
- fastify.get("/", {
957
- schema: {
958
- hide: true
959
- }
960
- }, async (_request, reply) => {
961
- const redocHtml = `
1
+ var De=Object.defineProperty;var r=(s,e)=>De(s,"name",{value:e,configurable:!0});import I from"process";import qe from"path";import pt from"@fastify/cookie";import ht from"@fastify/helmet";import _e from"@fastify/static";import{fastifySwagger as mt}from"@fastify/swagger";import{detect as ut}from"detect-port";import He from"fastify";import{Hookified as lt}from"hookified";var ce={logger:{transport:{target:"pino-pretty",options:{colorize:!0,translateTime:!0,ignore:"pid,hostname",singleLine:!0}}}};var $e={description:"Will return anything that you send it",tags:["Anything"],response:{200:{type:"object",properties:{args:{type:"object",description:"Route parameters",additionalProperties:!0},data:{type:"object",description:"Request body data",additionalProperties:!0},files:{type:"object",description:"Uploaded files",additionalProperties:!0},form:{type:"object",description:"Form data",additionalProperties:!0},headers:{type:"object",description:"Request headers",additionalProperties:!0},json:{type:"object",description:"Parsed JSON body",additionalProperties:!0},method:{type:"string",description:"HTTP method used"},origin:{type:"string",description:"Origin of the request"},url:{type:"string",description:"Request URL"}},additionalProperties:!1}}},de=r(s=>{let e=r(async(o,a)=>{let i={args:o.query,data:o.body??{},files:o.raw?.files??{},form:o.body??{},headers:o.headers,json:o.body??{},method:o.method,origin:o.headers.origin??o.headers.host??"",url:o.url};await a.send(i)},"handler"),t={schema:$e,validatorCompiler:r(()=>()=>!0,"validatorCompiler")};s.get("/anything",t,e),s.post("/anything",t,e),s.put("/anything",t,e),s.patch("/anything",t,e),s.delete("/anything",t,e)},"anythingRoute");import{Buffer as z}from"buffer";import{timingSafeEqual as Me}from"crypto";var Ae=r(s=>{if(!s||typeof s!="string")return;let e=s.indexOf(" ");if(e===-1)return;let t=s.slice(0,e),o=s.slice(e+1);if(!(t.toLowerCase()!=="basic"||!o))try{let a=z.from(o,"base64").toString("utf8"),i=a.indexOf(":");if(i===-1)return;let n=a.slice(0,i),d=a.slice(i+1);return n===""?void 0:{username:n,password:d}}catch{return}},"parseBasic"),pe=r((s,e)=>{if(s.length!==e.length)return!1;try{return Me(z.from(s,"utf8"),z.from(e,"utf8"))}catch{return!1}},"safeCompare"),Ie={description:"HTTP Basic authentication. Succeeds only if the user/pass provided in the path matches the Basic Authorization header.",tags:["Auth"],params:{type:"object",properties:{user:{type:"string"},passwd:{type:"string"}},required:["user","passwd"]},response:{200:{type:"object",properties:{authenticated:{type:"boolean"},user:{type:"string"}},required:["authenticated","user"]},401:{type:"object",properties:{message:{type:"string"}},required:["message"]}}},U=r(s=>{s.get("/basic-auth/:user/:passwd",{schema:Ie},async(e,t)=>{let{user:o,passwd:a}=e.params;if(!o||typeof o!="string"||typeof a!="string")return t.header("WWW-Authenticate",'Basic realm="mockhttp"'),t.status(401).send({message:"Unauthorized"});let i=Ae(e.headers.authorization);return!i||!pe(i.username,o)||!pe(i.password,a)?(t.header("WWW-Authenticate",'Basic realm="mockhttp"'),t.status(401).send({message:"Unauthorized"})):t.send({authenticated:!0,user:o})})},"basicAuthRoute");var Ee=r(s=>{if(!s)return;let[e,t]=s.split(" ");if(!(!e||e.toLowerCase()!=="bearer"||!t))return t},"parseBearer"),Le={description:"HTTP Bearer authentication. Succeeds if any Bearer token is present unless ?required=true is provided.",tags:["Auth"],querystring:{type:"object",properties:{required:{type:"boolean",default:!0}}},response:{200:{type:"object",properties:{authenticated:{type:"boolean"},token:{type:"string"}},required:["authenticated","token"]},401:{type:"object",properties:{message:{type:"string"}},required:["message"]}}},O=r(s=>{s.get("/bearer",{schema:Le},async(e,t)=>{let o=Ee(e.headers.authorization),i=e.query?.required??!0;return!o&&i?(t.header("WWW-Authenticate","Bearer"),t.status(401).send({message:"Unauthorized"})):o?t.send({authenticated:!0,token:o}):t.send({authenticated:!1,token:""})})},"bearerAuthRoute");import le from"crypto";var w=r(()=>le.randomBytes(16).toString("hex"),"makeNonce"),ze=new Set(["MD5","md5","SHA-256","sha-256","SHA-512","sha-512"]),he=r(s=>{if(!s)return"md5";let e=s.toLowerCase();return e==="md5"?"md5":e==="sha-256"?"sha256":e==="sha-512"?"sha512":"md5"},"hashAlg"),x=r((s,e)=>le.createHash(s).update(e).digest("hex"),"h"),me=r(s=>{if(!s)return;let e=s.indexOf(" ");if(e===-1)return;let t=s.slice(0,e),o=s.slice(e+1);if(!t||t.toLowerCase()!=="digest"||!o)return;let a={},i=[],n="",d=!1;for(let g of o)g==='"'?(d=!d,n+=g):g===","&&!d?(n.trim()&&i.push(n.trim()),n=""):n+=g;if(n.trim()&&i.push(n.trim()),i.length===0)return a;for(let g of i){let h=g.indexOf("=");if(h===-1)continue;let l=g.slice(0,h).trim(),p=g.slice(h+1).trim();p.startsWith('"')&&p.endsWith('"')&&(p=p.slice(1,-1)),a[l]=p}return a},"parseDigest"),ue={description:"HTTP Digest authentication. Mirrors httpbin behavior for testing clients.",tags:["Auth"],params:{type:"object",properties:{qop:{type:"string"},user:{type:"string"},passwd:{type:"string"},algorithm:{type:"string"},stale_after:{type:"string"}},required:["qop","user","passwd"]}},B=r(s=>{s.get("/digest-auth/:qop/:user/:passwd",{schema:ue},async(e,t)=>{let{user:o,passwd:a}=e.params,i="mockhttp",n=w(),d=me(e.headers.authorization);if(!d)return t.header("WWW-Authenticate",`Digest realm="${i}", qop="auth,auth-int", nonce="${n}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"});if(d.username!==o)return t.header("WWW-Authenticate",`Digest realm="${i}", qop="auth,auth-int", nonce="${n}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"});let g="GET",h=d.uri??e.url.replace(/^https?:\/\/[^/]+/,""),l=he(d.algorithm),c=x(l,`${o}:${i}:${a}`),p=x(l,`${g}:${h}`),u="";return u=d.qop?x(l,`${c}:${d.nonce}:${d.nc}:${d.cnonce}:${d.qop}:${p}`):x(l,`${c}:${d.nonce}:${p}`),!d.response||d.response!==u?(t.header("WWW-Authenticate",`Digest realm="${i}", qop="auth,auth-int", nonce="${n}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"})):t.send({authenticated:!0,user:o})}),s.get("/digest-auth/:qop/:user/:passwd/:algorithm",{schema:ue},async(e,t)=>{let{user:o,passwd:a,algorithm:i}=e.params,n="mockhttp",d=ze.has(String(i))?String(i):"MD5",g=w(),h=me(e.headers.authorization);if(!h)return t.header("WWW-Authenticate",`Digest realm="${n}", qop="auth,auth-int", algorithm=${d}, nonce="${g}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"});if(h.username!==o)return t.header("WWW-Authenticate",`Digest realm="${n}", qop="auth,auth-int", algorithm=${d}, nonce="${g}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"});let l="GET",c=h.uri??e.url.replace(/^https?:\/\/[^/]+/,""),p=he(h.algorithm??d),u=x(p,`${o}:${n}:${a}`),m=x(p,`${l}:${c}`),y=h.qop?x(p,`${u}:${h.nonce}:${h.nc}:${h.cnonce}:${h.qop}:${m}`):x(p,`${u}:${h.nonce}:${m}`);return!h.response||h.response!==y?(t.header("WWW-Authenticate",`Digest realm="${n}", qop="auth,auth-int", algorithm=${d}, nonce="${g}", opaque="${w()}"`),t.status(401).send({message:"Unauthorized"})):t.send({authenticated:!0,user:o})})},"digestAuthRoute");import{Buffer as Ue}from"buffer";var Oe=r(s=>{if(!s)return;let[e,t]=s.split(" ");if(!(!e||e.toLowerCase()!=="basic"||!t))try{let o=Ue.from(t,"base64").toString("utf8"),a=o.indexOf(":");return a===-1?void 0:{username:o.slice(0,a),password:o.slice(a+1)}}catch{return}},"parseBasic"),Be={description:"HTTP Basic authentication with docs hidden. Mirrors /basic-auth but hides from schema.",tags:["Auth"],hide:!0,params:{type:"object",properties:{user:{type:"string"},passwd:{type:"string"}},required:["user","passwd"]}},F=r(s=>{s.get("/hidden-basic-auth/:user/:passwd",{schema:Be},async(e,t)=>{let{user:o,passwd:a}=e.params,i=Oe(e.headers.authorization);return!i||i.username!==o||i.password!==a?(t.header("WWW-Authenticate",'Basic realm="mockhttp"'),t.status(401).send({message:"Unauthorized"})):t.send({authenticated:!0,user:o})})},"hiddenBasicAuthRoute");var Fe={description:"Delete a cookie",tags:["Cookies"],querystring:{type:"object",properties:{name:{type:"string"}},required:["name"],additionalProperties:!1},response:{204:{type:"null"}}},W=r(s=>{s.delete("/cookies",{schema:Fe},async(e,t)=>{let{name:o}=e.query;return t.clearCookie(o,{path:"/"}),t.status(204).send()})},"deleteCookieRoute");var We={description:"Return cookies from the request",tags:["Cookies"],response:{200:{type:"object",properties:{cookies:{type:"object",additionalProperties:{type:"string"}}},required:["cookies"]}}},N=r(s=>{s.get("/cookies",{schema:We},async(e,t)=>{t.type("application/json"),await t.send({cookies:e.cookies})})},"getCookiesRoute");var Ne={description:"Set a cookie",tags:["Cookies"],body:{type:"object",properties:{name:{type:"string"},value:{type:"string"},expires:{type:"string",format:"date-time"}},required:["name","value"],additionalProperties:!1},response:{200:{type:"object",properties:{cookies:{type:"object",additionalProperties:{type:"string"}}},required:["cookies"]}}},J=r(s=>{s.post("/cookies",{schema:Ne},async(e,t)=>{let{name:o,value:a,expires:i}=e.body;if(i){let n=new Date(i);t.setCookie(o,a,{path:"/",expires:n})}else t.setCookie(o,a,{path:"/"});return t.status(200).send()})},"postCookieRoute");var Je={description:"Handles a DELETE request and returns request information",tags:["HTTP Methods"],body:{oneOf:[{type:"object",additionalProperties:!0,description:"JSON body of the DELETE request"},{type:"string",description:"String body of the DELETE request"}]},response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},body:{oneOf:[{type:"object",additionalProperties:!0},{type:"string"}]}},required:["method","headers","body"]}}},G=r(s=>{s.delete("/delete",{schema:Je},async e=>({method:"DELETE",headers:e.headers,body:e.body}))},"deleteRoute");var Ge={description:"Returns request information for GET requests",tags:["HTTP Methods"],querystring:{type:"object",additionalProperties:!0,properties:{},description:"Query parameters sent in the request"},headers:{type:"object",additionalProperties:!0,properties:{},description:"Headers sent in the request"},response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},queryParams:{type:"object",additionalProperties:{type:"string"}}},required:["method","headers","queryParams"]}}},Y=r(s=>{s.get("/get",{schema:Ge},async(e,t)=>({method:"GET",headers:e.headers,queryParams:e.query}))},"getRoute");var Ye={description:"Handles a PATCH request and returns request information",tags:["HTTP Methods"],body:{type:"object",additionalProperties:!0,description:"The body of the PATCH request"},response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},body:{type:"object",additionalProperties:!0}},required:["method","headers","body"]}}},Z=r(s=>{s.patch("/patch",{schema:Ye},async e=>({method:"PATCH",headers:e.headers,body:e.body}))},"patchRoute");var Ze={description:"Handles a POST request and returns request information",tags:["HTTP Methods"],body:{type:"object",additionalProperties:!0,description:"The body of the POST request"},response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},body:{type:"object",additionalProperties:!0}},required:["method","headers","body"]}}},V=r(s=>{s.post("/post",{schema:Ze},async e=>({method:"POST",headers:e.headers,body:e.body}))},"postRoute");var Ve={description:"Handles a PUT request and returns request information",tags:["HTTP Methods"],body:{type:"object",additionalProperties:!0,description:"The body of the PUT request"},response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},body:{type:"object",additionalProperties:!0}},required:["method","headers","body"]}}},X=r(s=>{s.put("/put",{schema:Ve},async e=>({method:"PUT",headers:e.headers,body:e.body}))},"putRoute");var ge=r(s=>{s.get("/",{schema:{hide:!0}},async(e,t)=>{await t.type("text/html").send(`
962
2
  <!doctype html>
963
3
  <html>
964
4
  <head>
@@ -1022,447 +62,23 @@ var indexRoute = /* @__PURE__ */ __name((fastify) => {
1022
62
  <script src="/scalar/browser/standalone.js"></script>
1023
63
  </body>
1024
64
  </html>
1025
- `;
1026
- await reply.type("text/html").send(redocHtml);
1027
- });
1028
- }, "indexRoute");
1029
-
1030
- // src/routes/redirects/absolute-redirect.ts
1031
- var absoluteRedirectSchema = {
1032
- tags: [
1033
- "Redirects"
1034
- ],
1035
- description: "Redirects the request to the target URL using an absolute URL",
1036
- params: {
1037
- type: "object",
1038
- properties: {
1039
- value: {
1040
- type: "integer",
1041
- minimum: 1
1042
- }
1043
- },
1044
- required: [
1045
- "value"
1046
- ]
1047
- },
1048
- headers: {
1049
- type: "object",
1050
- properties: {
1051
- host: {
1052
- type: "string"
1053
- }
1054
- },
1055
- required: [
1056
- "host"
1057
- ]
1058
- },
1059
- response: {
1060
- 302: {
1061
- type: "string"
1062
- }
1063
- }
1064
- };
1065
- var absoluteRedirectRoute = /* @__PURE__ */ __name((fastify) => {
1066
- fastify.get("/absolute-redirect/:value", {
1067
- schema: absoluteRedirectSchema
1068
- }, async (request, reply) => {
1069
- const { value } = request.params;
1070
- const { host } = request.headers;
1071
- const { protocol } = request;
1072
- let url = `${protocol}://${host}/get`;
1073
- let html = `
65
+ `)})},"indexRoute");var Xe={tags:["Redirects"],description:"Redirects the request to the target URL using an absolute URL",params:{type:"object",properties:{value:{type:"integer",minimum:1}},required:["value"]},headers:{type:"object",properties:{host:{type:"string"}},required:["host"]},response:{302:{type:"string"}}},K=r(s=>{s.get("/absolute-redirect/:value",{schema:Xe},async(e,t)=>{let{value:o}=e.params,{host:a}=e.headers,{protocol:i}=e,n=`${i}://${a}/get`,d=`
1074
66
  <title>Redirecting...</title>
1075
67
  <h1>Redirecting...</h1>
1076
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1077
- `;
1078
- if (value > 1) {
1079
- url = `${protocol}://${host}/absolute-redirect/${value - 1}`;
1080
- html = `
68
+ <p>You should be redirected automatically to target URL: <a href="${n}">${n}</a>. If not click the link.</p>
69
+ `;o>1&&(n=`${i}://${a}/absolute-redirect/${o-1}`,d=`
1081
70
  <title>Redirecting...</title>
1082
71
  <h1>Redirecting...</h1>
1083
- <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1084
- `;
1085
- }
1086
- await reply.header("Location", url).status(302).type("text/html").send(html);
1087
- });
1088
- }, "absoluteRedirectRoute");
1089
-
1090
- // src/routes/redirects/redirect-to.ts
1091
- var redirectToSchema = {
1092
- tags: [
1093
- "Redirects"
1094
- ],
1095
- description: "Redirects the request to the target URL",
1096
- querystring: {
1097
- type: "object",
1098
- properties: {
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"
1108
- }
1109
- },
1110
- required: [
1111
- "url"
1112
- ],
1113
- additionalProperties: false
1114
- }
1115
- };
1116
- var redirectToRoute = /* @__PURE__ */ __name((fastify) => {
1117
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
1118
- const { url, status_code } = request.query;
1119
- const statusCode = status_code ? Number(status_code) : 302;
1120
- await reply.redirect(url, statusCode);
1121
- }, "handler");
1122
- fastify.get("/redirect-to", {
1123
- schema: redirectToSchema
1124
- }, handler);
1125
- fastify.post("/redirect-to", {
1126
- schema: redirectToSchema
1127
- }, handler);
1128
- fastify.put("/redirect-to", {
1129
- schema: redirectToSchema
1130
- }, handler);
1131
- fastify.patch("/redirect-to", {
1132
- schema: redirectToSchema
1133
- }, handler);
1134
- fastify.delete("/redirect-to", {
1135
- schema: redirectToSchema
1136
- }, handler);
1137
- }, "redirectToRoute");
1138
-
1139
- // src/routes/redirects/relative-redirect.ts
1140
- var relativeRedirectSchema = {
1141
- tags: [
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 = `
72
+ <p>You should be redirected automatically to target URL: <a href="${n}">${n}</a>. If not click the link.</p>
73
+ `),await t.header("Location",n).status(302).type("text/html").send(d)})},"absoluteRedirectRoute");var T={tags:["Redirects"],description:"Redirects the request to the target URL",querystring:{type:"object",properties:{url:{type:"string",format:"uri",description:"The URL to redirect to"},status_code:{type:"string",pattern:"^[1-5][0-9][0-9]$",description:"The HTTP status code to use for redirection"}},required:["url"],additionalProperties:!1}},Q=r(s=>{let e=r(async(t,o)=>{let{url:a,status_code:i}=t.query,n=i?Number(i):302;await o.redirect(a,n)},"handler");s.get("/redirect-to",{schema:T},e),s.post("/redirect-to",{schema:T},e),s.put("/redirect-to",{schema:T},e),s.patch("/redirect-to",{schema:T},e),s.delete("/redirect-to",{schema:T},e)},"redirectToRoute");var Ke={tags:["Redirects"],description:"Redirects the request to the target URL using an relative URL",params:{type:"object",properties:{value:{type:"integer",minimum:1}},required:["value"]},headers:{type:"object",properties:{host:{type:"string"}},required:["host"]},response:{302:{type:"string"}}},ee=r(s=>{s.get("/relative-redirect/:value",{schema:Ke},async(e,t)=>{let{value:o}=e.params,a="/get",i=`
1181
74
  <title>Redirecting...</title>
1182
75
  <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 = `
76
+ <p>You should be redirected automatically to target URL: <a href="${a}">${a}</a>. If not click the link.</p>
77
+ `;o>1&&(a=`/relative-redirect/${o-1}`,i=`
1188
78
  <title>Redirecting...</title>
1189
79
  <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"
1202
- ],
1203
- response: {
1204
- 200: {
1205
- type: "object",
1206
- properties: {
1207
- headers: {
1208
- type: "object",
1209
- additionalProperties: {
1210
- type: "string"
1211
- },
1212
- description: "All headers of the client request"
1213
- }
1214
- },
1215
- required: [
1216
- "headers"
1217
- ]
1218
- }
1219
- }
1220
- };
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
- };
1229
- });
1230
- }, "headersRoute");
1231
-
1232
- // src/routes/request-inspection/ip.ts
1233
- var ipSchema = {
1234
- description: "Returns the IP address of the client",
1235
- tags: [
1236
- "Request Inspection"
1237
- ],
1238
- response: {
1239
- 200: {
1240
- type: "object",
1241
- properties: {
1242
- ip: {
1243
- type: "string",
1244
- description: "The IP address of the client"
1245
- }
1246
- },
1247
- required: [
1248
- "ip"
1249
- ]
1250
- }
1251
- }
1252
- };
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
- };
1264
- });
1265
- }, "ipRoute");
1266
-
1267
- // src/routes/request-inspection/user-agent.ts
1268
- var userAgentSchema = {
1269
- description: "Returns the User-Agent header of the client request",
1270
- tags: [
1271
- "Request Inspection"
1272
- ],
1273
- response: {
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
- ]
1285
- }
1286
- }
1287
- };
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>
80
+ <p>You should be redirected automatically to target URL: <a href="${a}">${a}</a>. If not click the link.</p>
81
+ `),await t.header("Location",a).status(302).type("text/html").send(i)})},"relativeRedirectRoute");var Qe={description:"Returns all headers of the client request",tags:["Request Inspection"],response:{200:{type:"object",properties:{headers:{type:"object",additionalProperties:{type:"string"},description:"All headers of the client request"}},required:["headers"]}}},te=r(s=>{s.get("/headers",{schema:Qe},async e=>{let{headers:t}=e;return{headers:t}})},"headersRoute");var et={description:"Returns the IP address of the client",tags:["Request Inspection"],response:{200:{type:"object",properties:{ip:{type:"string",description:"The IP address of the client"}},required:["ip"]}}},se=r(s=>{s.get("/ip",{schema:et},async e=>{let t=e.headers["cf-connecting-ip"],o=e.headers["x-forwarded-for"],a=e.ip;return{ip:t??(typeof o=="string"?o.split(",")[0].trim():a)}})},"ipRoute");var tt={description:"Returns the User-Agent header of the client request",tags:["Request Inspection"],response:{200:{type:"object",properties:{userAgent:{type:"string",description:"The User-Agent header of the client request"}},required:["userAgent"]}}},oe=r(s=>{s.get("/user-agent",{schema:tt},async e=>({userAgent:e.headers["user-agent"]}))},"userAgentRoute");import{brotliCompressSync as fe,deflateSync as ye,gzipSync as be}from"zlib";var S={description:"Returns random plain text content",tags:["Response Formats"],response:{200:{type:"string",description:"Random plain text content"}}},j={description:"Returns random text content",tags:["Response Formats"],response:{200:{type:"string",description:"Random text content"}}},C={description:"Returns random HTML content",tags:["Response Formats"],response:{200:{type:"string",description:"Random HTML content"}}},P={description:"Returns random XML content",tags:["Response Formats"],response:{200:{type:"string",description:"Random XML content"}}},q={description:"Returns random JSON content",tags:["Response Formats"],response:{200:{type:"object",description:"Random JSON content",additionalProperties:!0}}},_={description:"Returns page denied by robots.txt rules",tags:["Response Formats"],response:{403:{type:"string",description:"Page denied by robots.txt rules"}}},H={description:"Returns GZip-encoded data",tags:["Response Formats"],response:{200:{type:"string",description:"GZip-encoded content"}}},D={description:"Returns Deflate-encoded data",tags:["Response Formats"],response:{200:{type:"string",description:"Deflate-encoded content"}}},$={description:"Returns Brotli-encoded data",tags:["Response Formats"],response:{200:{type:"string",description:"Brotli-encoded content"}}},E=["The quick brown fox jumps over the lazy dog.","Lorem ipsum dolor sit amet, consectetur adipiscing elit.","To be or not to be, that is the question.","All work and no play makes Jack a dull boy.","The only way to do great work is to love what you do.","In the beginning was the Word, and the Word was with God.","Once upon a time in a galaxy far, far away...","It was the best of times, it was the worst of times.","The journey of a thousand miles begins with a single step.","Knowledge is power. France is bacon."],Re=[`<!DOCTYPE html>
1466
82
  <html lang="en">
1467
83
  <head>
1468
84
  <title>Sample Page</title>
@@ -1471,8 +87,7 @@ var randomHtmlContent = [
1471
87
  <h1>Welcome to MockHTTP</h1>
1472
88
  <p>This is a sample HTML page with random content.</p>
1473
89
  </body>
1474
- </html>`,
1475
- `<!DOCTYPE html>
90
+ </html>`,`<!DOCTYPE html>
1476
91
  <html>
1477
92
  <head>
1478
93
  <meta charset="UTF-8">
@@ -1493,8 +108,7 @@ var randomHtmlContent = [
1493
108
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
1494
109
  </main>
1495
110
  </body>
1496
- </html>`,
1497
- `<!DOCTYPE html>
111
+ </html>`,`<!DOCTYPE html>
1498
112
  <html lang="en">
1499
113
  <head>
1500
114
  <title>Article Page</title>
@@ -1512,8 +126,7 @@ var randomHtmlContent = [
1512
126
  </blockquote>
1513
127
  </article>
1514
128
  </body>
1515
- </html>`,
1516
- `<!DOCTYPE html>
129
+ </html>`,`<!DOCTYPE html>
1517
130
  <html>
1518
131
  <head>
1519
132
  <title>Form Example</title>
@@ -1530,8 +143,7 @@ var randomHtmlContent = [
1530
143
  <button type="submit">Submit</button>
1531
144
  </form>
1532
145
  </body>
1533
- </html>`,
1534
- `<!DOCTYPE html>
146
+ </html>`,`<!DOCTYPE html>
1535
147
  <html lang="en">
1536
148
  <head>
1537
149
  <meta charset="UTF-8">
@@ -1545,529 +157,89 @@ var randomHtmlContent = [
1545
157
  <img src="placeholder.jpg" alt="Placeholder" style="max-width: 100%; height: auto;">
1546
158
  </div>
1547
159
  </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"?>
160
+ </html>`],we=[`<?xml version="1.0" encoding="UTF-8"?>
161
+ <root>
162
+ <message>Hello from MockHTTP</message>
163
+ <timestamp>${new Date().toISOString()}</timestamp>
164
+ <status>success</status>
165
+ </root>`,`<?xml version="1.0" encoding="UTF-8"?>
166
+ <book>
167
+ <title>The Great Gatsby</title>
168
+ <author>F. Scott Fitzgerald</author>
169
+ <year>1925</year>
170
+ <publisher>Charles Scribner's Sons</publisher>
171
+ <isbn>978-0-7432-7356-5</isbn>
172
+ </book>`,`<?xml version="1.0" encoding="UTF-8"?>
173
+ <users>
174
+ <user id="1">
175
+ <name>John Doe</name>
176
+ <email>john@example.com</email>
177
+ <role>admin</role>
178
+ </user>
179
+ <user id="2">
180
+ <name>Jane Smith</name>
181
+ <email>jane@example.com</email>
182
+ <role>user</role>
183
+ </user>
184
+ </users>`,`<?xml version="1.0" encoding="UTF-8"?>
185
+ <product>
186
+ <id>12345</id>
187
+ <name>Wireless Mouse</name>
188
+ <description>Ergonomic wireless mouse with 3-year battery life</description>
189
+ <price currency="USD">29.99</price>
190
+ <stock>150</stock>
191
+ <category>Electronics</category>
192
+ </product>`,`<?xml version="1.0" encoding="UTF-8"?>
193
+ <rss version="2.0">
194
+ <channel>
195
+ <title>Mock RSS Feed</title>
196
+ <link>http://example.com/rss</link>
197
+ <description>Sample RSS feed from MockHTTP</description>
198
+ <item>
199
+ <title>First Article</title>
200
+ <link>http://example.com/article1</link>
201
+ <description>This is the first article in our feed</description>
202
+ <pubDate>${new Date().toUTCString()}</pubDate>
203
+ </item>
204
+ </channel>
205
+ </rss>`],xe=[()=>({message:"Hello from MockHTTP",timestamp:new Date().toISOString(),status:"success",code:200}),()=>({user:{id:1,name:"John Doe",email:"john@example.com",role:"admin",createdAt:"2024-01-15T10:30:00Z"},permissions:["read","write","delete"]}),()=>({products:[{id:101,name:"Laptop",price:999.99,stock:50},{id:102,name:"Mouse",price:29.99,stock:200}],total:2,page:1}),()=>({config:{theme:"dark",language:"en",notifications:{email:!0,push:!1,sms:!1},privacy:{profile:"public",activity:"friends"}}}),()=>({metrics:{cpu:45.2,memory:78.5,disk:62.3,network:{inbound:"1.2MB/s",outbound:"0.8MB/s"},uptime:"15 days",services:[{name:"api",status:"healthy"},{name:"database",status:"healthy"},{name:"cache",status:"warning"}]}})],ke=r(s=>{let e=r(async(l,c)=>{let p=Math.floor(Math.random()*E.length),u=E[p];return c.type("text/plain"),u},"plainHandler");s.get("/plain",{schema:S},e),s.post("/plain",{schema:S},e),s.put("/plain",{schema:S},e),s.patch("/plain",{schema:S},e),s.delete("/plain",{schema:S},e);let t=r(async(l,c)=>{let p=Math.floor(Math.random()*E.length),u=E[p];return c.type("text/plain"),u},"textHandler");s.get("/text",{schema:j},t),s.post("/text",{schema:j},t),s.put("/text",{schema:j},t),s.patch("/text",{schema:j},t),s.delete("/text",{schema:j},t);let o=r(async(l,c)=>{let p=Math.floor(Math.random()*Re.length),u=Re[p];return c.type("text/html"),u},"htmlHandler");s.get("/html",{schema:C},o),s.post("/html",{schema:C},o),s.put("/html",{schema:C},o),s.patch("/html",{schema:C},o),s.delete("/html",{schema:C},o);let a=r(async(l,c)=>{let p=Math.floor(Math.random()*we.length),u=we[p];return c.type("application/xml"),u},"xmlHandler");s.get("/xml",{schema:P},a),s.post("/xml",{schema:P},a),s.put("/xml",{schema:P},a),s.patch("/xml",{schema:P},a),s.delete("/xml",{schema:P},a);let i=r(async(l,c)=>{let p=Math.floor(Math.random()*xe.length),u=xe[p],m=u();return c.type("application/json"),m},"jsonHandler");s.get("/json",{schema:q},i),s.post("/json",{schema:q},i),s.put("/json",{schema:q},i),s.patch("/json",{schema:q},i),s.delete("/json",{schema:q},i);let n=r(async(l,c)=>{let p=[`<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
206
+ <html><head>
207
+ <title>403 Forbidden</title>
208
+ </head><body>
209
+ <h1>Forbidden</h1>
210
+ <p>Access denied by robots.txt rules.</p>
211
+ <p>The requested resource is disallowed by robots.txt.</p>
212
+ </body></html>`,`<!DOCTYPE html>
213
+ <html lang="en">
214
+ <head>
215
+ <meta charset="UTF-8">
216
+ <title>403 - Access Denied</title>
217
+ </head>
218
+ <body>
219
+ <h1>403 Forbidden</h1>
220
+ <p>This page is blocked by robots.txt rules.</p>
221
+ <p>User-agent: *<br>Disallow: ${l.url}</p>
222
+ </body>
223
+ </html>`,`<!DOCTYPE html>
224
+ <html>
225
+ <head>
226
+ <title>403 - Access Denied</title>
227
+ <style>body { font-family: Arial, sans-serif; margin: 40px; }</style>
228
+ </head>
229
+ <body>
230
+ <h2>403 - Access Denied by robots.txt</h2>
231
+ <p>The robots.txt file prevents access to this resource.</p>
232
+ <hr>
233
+ <small>MockHTTP Server</small>
234
+ </body>
235
+ </html>`],u=Math.floor(Math.random()*p.length),m=p[u];return c.code(403),c.type("text/html"),m},"denyHandler");s.get("/deny",{schema:_},n),s.post("/deny",{schema:_},n),s.put("/deny",{schema:_},n),s.patch("/deny",{schema:_},n),s.delete("/deny",{schema:_},n);let d=r(async(l,c)=>{let p=[{message:"This is GZip compressed data from MockHTTP",timestamp:new Date().toISOString(),compression:"gzip",originalSize:0,compressedSize:0},{title:"Sample Document",content:"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50),metadata:{author:"MockHTTP Server",format:"gzip",version:"1.0"}},{data:Array.from({length:100},(R,f)=>({id:f+1,value:`Item ${f+1}`,description:`This is item number ${f+1} in the dataset`})),info:"Large dataset compressed with GZip"}],u=Math.floor(Math.random()*p.length),m=p[u],y=JSON.stringify(m);m.originalSize!==void 0&&(m.originalSize=Buffer.byteLength(y));let b=be(y);if(m.compressedSize!==void 0){let R={...m,compressedSize:b.length},f=JSON.stringify(R),v=be(f);return c.header("Content-Encoding","gzip"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),v}return c.header("Content-Encoding","gzip"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),b},"gzipHandler");s.get("/gzip",{schema:H},d),s.post("/gzip",{schema:H},d),s.put("/gzip",{schema:H},d),s.patch("/gzip",{schema:H},d),s.delete("/gzip",{schema:H},d);let g=r(async(l,c)=>{let p=[{message:"This is Deflate compressed data from MockHTTP",timestamp:new Date().toISOString(),compression:"deflate",originalSize:0,compressedSize:0},{title:"Deflate Compressed Document",content:"The deflate algorithm is used for compression. ".repeat(40),details:{algorithm:"DEFLATE",method:"zlib deflate",server:"MockHTTP"}},{items:Array.from({length:50},(R,f)=>({index:f,name:`Entry ${f}`,data:`This is test data for entry number ${f}`,timestamp:new Date(Date.now()-f*1e6).toISOString()})),metadata:{compression:"deflate",count:50}}],u=Math.floor(Math.random()*p.length),m=p[u],y=JSON.stringify(m);m.originalSize!==void 0&&(m.originalSize=Buffer.byteLength(y));let b=ye(y);if(m.compressedSize!==void 0){let R={...m,compressedSize:b.length},f=JSON.stringify(R),v=ye(f);return c.header("Content-Encoding","deflate"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),v}return c.header("Content-Encoding","deflate"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),b},"deflateHandler");s.get("/deflate",{schema:D},g),s.post("/deflate",{schema:D},g),s.put("/deflate",{schema:D},g),s.patch("/deflate",{schema:D},g),s.delete("/deflate",{schema:D},g);let h=r(async(l,c)=>{let p=[{message:"This is Brotli compressed data from MockHTTP",timestamp:new Date().toISOString(),compression:"br",algorithm:"Brotli",originalSize:0,compressedSize:0},{title:"Brotli Compression Example",description:"Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling.",content:"Brotli compression typically achieves 20-26% better compression ratios than gzip. ".repeat(10),metadata:{encoding:"br",quality:11,server:"MockHTTP"}},{benchmark:{title:"Compression Benchmark Data",results:Array.from({length:75},(R,f)=>({id:`test-${f}`,name:`Benchmark Test ${f}`,value:Math.random()*1e3,unit:"ms",timestamp:new Date(Date.now()-f*6e4).toISOString()})),summary:{compression:"brotli",totalTests:75,encoding:"br"}}}],u=Math.floor(Math.random()*p.length),m=p[u],y=JSON.stringify(m);m.originalSize!==void 0&&(m.originalSize=Buffer.byteLength(y));let b=fe(y);if(m.compressedSize!==void 0){let R={...m,compressedSize:b.length},f=JSON.stringify(R),v=fe(f);return c.header("Content-Encoding","br"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),v}return c.header("Content-Encoding","br"),c.header("Content-Type","application/json"),c.type("application/octet-stream"),b},"brotliHandler");s.get("/brotli",{schema:$},h),s.post("/brotli",{schema:$},h),s.put("/brotli",{schema:$},h),s.patch("/brotli",{schema:$},h),s.delete("/brotli",{schema:$},h)},"responseFormatRoutes");var st={description:"Handles cache validation and retrieval.",tags:["Response Inspection"],response:{200:{type:"string",description:"The cached content or resource."},304:{type:"null",description:"Indicates the resource has not been modified."}}},ot={description:"Sets a Cache-Control header for n seconds.",tags:["Response Inspection"],params:{type:"object",properties:{value:{type:"integer",description:"Number of seconds to cache the resource."}},required:["value"]},response:{200:{type:"string",description:"Confirmation message for setting cache."}}},re=r(s=>{s.get("/cache",{schema:st},async(e,t)=>{let o=e.headers["if-modified-since"],a=e.headers["if-none-match"];if(o??a){await t.status(304).send();return}await t.send("Cache content or resource response here.")}),s.get("/cache/:value",{schema:ot},async(e,t)=>{let{value:o}=e.params;await t.header("Cache-Control",`max-age=${o}`).send(`Cache-Control set for ${o} seconds.`)})},"cacheRoutes");var rt={description:"Handles ETag-based conditional requests.",tags:["Response Inspection"],params:{type:"object",properties:{etag:{type:"string",description:"The ETag value for the resource."}},required:["etag"]},response:{200:{type:"string",description:"Resource content for matching ETag."},304:{type:"null",description:"Indicates the resource has not been modified."},412:{type:"null",description:"Indicates a precondition failed due to mismatched ETag."}}},ae=r(s=>{s.get("/etag/:etag",{schema:rt},async(e,t)=>{let{etag:o}=e.params,a=e.headers["if-none-match"],i=e.headers["if-match"];if(a===o){await t.status(304).send();return}if(i&&i!==o){await t.status(412).send();return}await t.header("ETag",o).send("Resource content for matching ETag.")})},"etagRoutes");import{escape as M}from"html-escaper";var ve={description:"Returns a set of response headers based on the query string.",tags:["Response Inspection"],querystring:{type:"object",additionalProperties:!0,description:"Freeform query string parameters to be added as response headers."},response:{200:{type:"object",additionalProperties:!0,description:"Response headers as per query string parameters."}}},ie=r(s=>{s.get("/response-headers",{schema:ve},async(e,t)=>{let o=e.query;for(let[i,n]of Object.entries(o))t.header(i,M(n));let a=Object.fromEntries(Object.entries(o).map(([i,n])=>[i,M(n)]));await t.send(a)}),s.post("/response-headers",{schema:ve},async(e,t)=>{let o=e.query??{},a=e.body??{};for(let[n,d]of Object.entries(o))t.header(n,M(d));for(let[n,d]of Object.entries(a))t.header(n,M(d));let i=Object.fromEntries(Object.entries({...o,...a}).map(([n,d])=>[n,M(d)]));await t.send(i)})},"responseHeadersRoutes");var Te=r(s=>{s.get("/sitemap.xml",{schema:{hide:!0}},async(e,t)=>{try{let o=e.headers.host??"mockhttp.org",n=`<?xml version="1.0" encoding="UTF-8"?>
1660
236
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1661
237
  <url>
1662
- <loc>${protocol}://${host}/</loc>
238
+ <loc>${e.headers["x-forwarded-proto"]?.includes("https")?"https":"http"}://${o}/</loc>
1663
239
  <changefreq>monthly</changefreq>
1664
240
  <priority>1.0</priority>
1665
241
  </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 ",
1935
- tags: [
1936
- "Status Codes"
1937
- ],
1938
- params: {
1939
- type: "object",
1940
- properties: {
1941
- code: {
1942
- type: "string",
1943
- description: "HTTP status code to be returned"
1944
- }
1945
- }
1946
- },
1947
- response: {
1948
- 200: {
1949
- type: "object",
1950
- properties: {
1951
- status: {
1952
- type: "string"
1953
- }
1954
- }
1955
- }
1956
- }
1957
- };
1958
- var statusCodeRoute = /* @__PURE__ */ __name((fastify) => {
1959
- const handler = /* @__PURE__ */ __name(async (request, reply) => {
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
- });
1967
- }, "handler");
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 = `
242
+ </urlset>`;await t.type("text/xml").send(n)}catch(o){s.log.error(o),await t.status(500).send({error:"Internal Server Error"})}})},"sitemapRoute");import{randomInt as at}from"crypto";var ne=[{code:100,message:"Continue"},{code:101,message:"Switching Protocols"},{code:102,message:"Processing"},{code:103,message:"Early Hints"},{code:200,message:"OK"},{code:201,message:"Created"},{code:202,message:"Accepted"},{code:203,message:"Non-Authoritative Information"},{code:204,message:"No Content"},{code:205,message:"Reset Content"},{code:206,message:"Partial Content"},{code:207,message:"Multi-Status"},{code:208,message:"Already Reported"},{code:226,message:"IM Used"},{code:300,message:"Multiple Choices"},{code:301,message:"Moved Permanently"},{code:302,message:"Found"},{code:303,message:"See Other"},{code:304,message:"Not Modified"},{code:305,message:"Use Proxy"},{code:306,message:"Switch Proxy"},{code:307,message:"Temporary Redirect"},{code:308,message:"Permanent Redirect"},{code:400,message:"Bad Request"},{code:401,message:"Unauthorized"},{code:402,message:"Payment Required"},{code:403,message:"Forbidden"},{code:404,message:"Not Found"},{code:405,message:"Method Not Allowed"},{code:406,message:"Not Acceptable"},{code:407,message:"Proxy Authentication Required"},{code:408,message:"Request Timeout"},{code:409,message:"Conflict"},{code:410,message:"Gone"},{code:411,message:"Length Required"},{code:412,message:"Precondition Failed"},{code:413,message:"Payload Too Large"},{code:414,message:"URI Too Long"},{code:415,message:"Unsupported Media Type"},{code:416,message:"Range Not Satisfiable"},{code:417,message:"Expectation Failed"},{code:418,message:"I'm a teapot"},{code:421,message:"Misdirected Request"},{code:422,message:"Unprocessable Entity"},{code:423,message:"Locked"},{code:424,message:"Failed Dependency"},{code:425,message:"Too Early"},{code:426,message:"Upgrade Required"},{code:428,message:"Precondition Required"},{code:429,message:"Too Many Requests"},{code:431,message:"Request Header Fields Too Large"},{code:451,message:"Unavailable For Legal Reasons"},{code:500,message:"Internal Server Error"},{code:501,message:"Not Implemented"},{code:502,message:"Bad Gateway"},{code:503,message:"Service Unavailable"},{code:504,message:"Gateway Timeout"},{code:505,message:"HTTP Version Not Supported"},{code:506,message:"Variant Also Negotiates"},{code:507,message:"Insufficient Storage"},{code:508,message:"Loop Detected"},{code:510,message:"Not Extended"},{code:511,message:"Network Authentication Required"}],A={description:"Return status code or random status code if more than one are given ",tags:["Status Codes"],params:{type:"object",properties:{code:{type:"string",description:"HTTP status code to be returned"}}},response:{200:{type:"object",properties:{status:{type:"string"}}}}},Se=r(s=>{let e=r(async(t,o)=>{let{code:a}=t.params;(!a||!ne.map(i=>i.code).includes(Number(a)))&&(a=ne[at(0,ne.length)].code.toString()),await o.code(Number(a)).send({status:`Response with status code ${a}`})},"handler");s.get("/status/:code?",{schema:A},e),s.post("/status/:code?",{schema:A},e),s.put("/status/:code?",{schema:A},e),s.patch("/status/:code?",{schema:A},e),s.delete("/status/:code?",{schema:A},e)},"statusCodeRoute");import{fastifySwaggerUi as nt}from"@fastify/swagger-ui";var je={name:"@jaredwray/mockhttp",version:"1.0.0",description:"Mock Http - Easy way to mock http with httpbin replacement",type:"module",main:"dist/index.js",directories:{test:"test"},scripts:{lint:"biome check --write --error-on-warnings",test:"pnpm lint && vitest run --coverage","test:ci":"biome check --error-on-warnings && vitest run --coverage",dev:"pnpm lint && tsx src/index.ts --watch",build:"rimraf ./dist && tsup src/index.ts --format esm --dts --clean --minify","docker:build":"docker build -t jaredwray/mockhttp .","docker:run":"docker run -p 3000:3000 jaredwray/mockhttp","docker:push":"docker push jaredwray/mockhttp","docker:stop":'docker stop $(docker ps -a -q --filter ancestor=jaredwray/mockhttp --format="{{.ID}}")',"docker:readme":"tsx ./scripts/create-docker-readme.ts","docker:compose:up":"docker-compose up -d","docker:compose:down":"docker-compose down",prepare:"pnpm run build",start:"node dist/index.js"},repository:{type:"git",url:"git+https://github.com/jaredwray/mockhttp.git"},keywords:["mock","http","httpbin","test"],author:"Jared Wray <me@jaredwray.com>",license:"MIT",bugs:{url:"https://github.com/jaredwray/mockhttp/issues"},homepage:"https://github.com/jaredwray/mockhttp#readme",devDependencies:{"@biomejs/biome":"^2.2.5","@swc/core":"^1.13.5","@types/html-escaper":"^3.0.4","@vitest/coverage-v8":"^3.2.4",rimraf:"^6.0.1",tsup:"^8.5.0",tsx:"^4.20.6",typescript:"^5.9.3",vitest:"^3.2.4"},dependencies:{"@fastify/cookie":"^11.0.2","@fastify/helmet":"^13.0.2","@fastify/static":"^8.2.0","@fastify/swagger":"^9.5.2","@fastify/swagger-ui":"^5.2.3","@scalar/api-reference":"^1.37.0","detect-port":"^2.1.0",fastify:"^5.6.1",hookified:"^1.12.1","html-escaper":"^3.0.3",pino:"^10.0.0","pino-pretty":"^13.1.1"},files:["dist","public","LICENCE"],pnpm:{onlyBuiltDependencies:["@swc/core","unrs-resolver","vue-demi"]}};var ct=`
2071
243
  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
244
 
2073
245
  * [GitHub Repository](https://github.com/jaredwray/mockhttp)
@@ -2077,365 +249,4 @@ A simple HTTP server that can be used to mock HTTP responses for testing purpose
2077
249
  # About mockhttp.org
2078
250
 
2079
251
  [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");
2117
-
2118
- // src/mock-http.ts
2119
- var MockHttp = class extends Hookified {
2120
- static {
2121
- __name(this, "MockHttp");
2122
- }
2123
- _port = 3e3;
2124
- _host = "0.0.0.0";
2125
- _autoDetectPort = true;
2126
- _helmet = true;
2127
- _apiDocs = true;
2128
- _httpBin = {
2129
- httpMethods: true,
2130
- redirects: true,
2131
- requestInspection: true,
2132
- responseInspection: true,
2133
- statusCodes: true,
2134
- cookies: true,
2135
- anything: true,
2136
- auth: true
2137
- };
2138
- _server = Fastify();
2139
- constructor(options) {
2140
- super(options?.hookOptions);
2141
- if (options?.port) {
2142
- this._port = options.port;
2143
- }
2144
- if (options?.host) {
2145
- this._host = options.host;
2146
- }
2147
- if (options?.helmet !== void 0) {
2148
- this._helmet = options.helmet;
2149
- }
2150
- if (options?.apiDocs !== void 0) {
2151
- this._apiDocs = options.apiDocs;
2152
- }
2153
- if (options?.httpBin !== void 0) {
2154
- this._httpBin = options.httpBin;
2155
- }
2156
- }
2157
- /**
2158
- * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
2159
- * @default 3000
2160
- */
2161
- get port() {
2162
- return this._port;
2163
- }
2164
- /**
2165
- * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
2166
- * @default 3000
2167
- */
2168
- set port(port) {
2169
- this._port = port;
2170
- }
2171
- /**
2172
- * The host to listen on. If not provided, defaults to 'localhost'.
2173
- * @default 'localhost'
2174
- */
2175
- get host() {
2176
- return this._host;
2177
- }
2178
- /**
2179
- * The host to listen on. If not provided, defaults to 'localhost'.
2180
- * @default 'localhost'
2181
- */
2182
- set host(host) {
2183
- this._host = host;
2184
- }
2185
- /**
2186
- * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
2187
- * @default true
2188
- */
2189
- get autoDetectPort() {
2190
- return this._autoDetectPort;
2191
- }
2192
- /**
2193
- * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
2194
- * @default true
2195
- */
2196
- set autoDetectPort(autoDetectPort) {
2197
- this._autoDetectPort = autoDetectPort;
2198
- }
2199
- /**
2200
- * Whether to use Helmet for security headers. Defaults to true.
2201
- * @default true
2202
- */
2203
- get helmet() {
2204
- return this._helmet;
2205
- }
2206
- /**
2207
- * Whether to use Helmet for security headers. Defaults to true.
2208
- * @default true
2209
- */
2210
- set helmet(helmet) {
2211
- this._helmet = helmet;
2212
- }
2213
- /**
2214
- * Whether to use Swagger for API documentation. Defaults to true.
2215
- * @default true
2216
- */
2217
- get apiDocs() {
2218
- return this._apiDocs;
2219
- }
2220
- /**
2221
- * Whether to use Swagger for API documentation. Defaults to true.
2222
- * @default true
2223
- */
2224
- set apiDocs(apiDocs) {
2225
- this._apiDocs = apiDocs;
2226
- }
2227
- /**
2228
- * HTTP Bin options. Defaults to all enabled.
2229
- */
2230
- get httpBin() {
2231
- return this._httpBin;
2232
- }
2233
- /**
2234
- * HTTP Bin options. Defaults to all enabled.
2235
- */
2236
- set httpBin(httpBinary) {
2237
- this._httpBin = httpBinary;
2238
- }
2239
- /**
2240
- * The Fastify server instance.
2241
- */
2242
- get server() {
2243
- return this._server;
2244
- }
2245
- /**
2246
- * The Fastify server instance.
2247
- */
2248
- set server(server) {
2249
- this._server = server;
2250
- }
2251
- /**
2252
- * Start the Fastify server. If the server is already running, it will be closed and restarted.
2253
- */
2254
- async start() {
2255
- try {
2256
- if (this._server) {
2257
- await this._server.close();
2258
- }
2259
- this._server = Fastify(fastifyConfig);
2260
- await this._server.register(fastifyStatic, {
2261
- root: path.resolve("./node_modules/@scalar/api-reference/dist"),
2262
- prefix: "/scalar"
2263
- });
2264
- await this.server.register(fastifyStatic, {
2265
- root: path.resolve("./public"),
2266
- decorateReply: false
2267
- });
2268
- await this._server.register(sitemapRoute);
2269
- if (this._helmet) {
2270
- await this._server.register(fastifyHelmet);
2271
- }
2272
- if (this._apiDocs) {
2273
- await this.registerApiDocs();
2274
- }
2275
- const { httpMethods, redirects, requestInspection, responseInspection, statusCodes: statusCodes2, cookies, anything, auth } = this._httpBin;
2276
- if (httpMethods) {
2277
- await this.registerHttpMethods();
2278
- }
2279
- if (statusCodes2) {
2280
- await this.registerStatusCodeRoutes();
2281
- }
2282
- if (requestInspection) {
2283
- await this.registerRequestInspectionRoutes();
2284
- }
2285
- if (responseInspection) {
2286
- await this.registerResponseInspectionRoutes();
2287
- }
2288
- if (redirects) {
2289
- await this.registerRedirectRoutes();
2290
- }
2291
- if (cookies) {
2292
- await this.registerCookieRoutes();
2293
- }
2294
- if (anything) {
2295
- await this.registerAnythingRoutes();
2296
- }
2297
- if (auth) {
2298
- await this.registerAuthRoutes();
2299
- }
2300
- if (this._autoDetectPort) {
2301
- const originalPort = this._port;
2302
- this._port = await this.detectPort();
2303
- if (originalPort !== this._port) {
2304
- this._server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
2305
- }
2306
- }
2307
- await this._server.listen({
2308
- port: this._port,
2309
- host: this._host
2310
- });
2311
- } catch (error) {
2312
- this._server.log.error(error);
2313
- }
2314
- }
2315
- /**
2316
- * Close the Fastify server.
2317
- */
2318
- async close() {
2319
- await this._server.close();
2320
- }
2321
- /**
2322
- * Detect the next available port to run on.
2323
- * @returns The port that is available to run on
2324
- */
2325
- async detectPort() {
2326
- const { port } = this;
2327
- return detect(port);
2328
- }
2329
- /**
2330
- * This will register the API documentation routes including openapi and swagger ui.
2331
- * @param fastifyInstance - the server instance to register the routes on.
2332
- */
2333
- async registerApiDocs(fastifyInstance) {
2334
- const fastify = fastifyInstance ?? this._server;
2335
- await fastify.register(fastifySwagger, fastifySwaggerConfig);
2336
- await registerSwaggerUi(fastify);
2337
- await fastify.register(indexRoute);
2338
- }
2339
- /**
2340
- * Register the HTTP methods routes.
2341
- * @param fastifyInstance - the server instance to register the routes on.
2342
- */
2343
- async registerHttpMethods(fastifyInstance) {
2344
- const fastify = fastifyInstance ?? this._server;
2345
- await fastify.register(getRoute);
2346
- await fastify.register(postRoute);
2347
- await fastify.register(deleteRoute);
2348
- await fastify.register(putRoute);
2349
- await fastify.register(patchRoute);
2350
- }
2351
- /**
2352
- * Register the status code routes.
2353
- * @param fastifyInstance - the server instance to register the routes on.
2354
- */
2355
- async registerStatusCodeRoutes(fastifyInstance) {
2356
- const fastify = fastifyInstance ?? this._server;
2357
- await fastify.register(statusCodeRoute);
2358
- }
2359
- /**
2360
- * Register the request inspection routes.
2361
- * @param fastifyInstance - the server instance to register the routes on.
2362
- */
2363
- async registerRequestInspectionRoutes(fastifyInstance) {
2364
- const fastify = fastifyInstance ?? this._server;
2365
- await fastify.register(ipRoute);
2366
- await fastify.register(headersRoute);
2367
- await fastify.register(userAgentRoute);
2368
- }
2369
- /**
2370
- * Register the response inspection routes.
2371
- * @param fastifyInstance - the server instance to register the routes on.
2372
- */
2373
- async registerResponseInspectionRoutes(fastifyInstance) {
2374
- const fastify = fastifyInstance ?? this._server;
2375
- await fastify.register(cacheRoutes);
2376
- await fastify.register(etagRoutes);
2377
- await fastify.register(plainRoute);
2378
- await fastify.register(responseHeadersRoutes);
2379
- }
2380
- /**
2381
- * Register the redirect routes.
2382
- * @param fastifyInstance - the server instance to register the routes on.
2383
- */
2384
- async registerRedirectRoutes(fastifyInstance) {
2385
- const fastify = fastifyInstance ?? this._server;
2386
- await fastify.register(absoluteRedirectRoute);
2387
- await fastify.register(relativeRedirectRoute);
2388
- await fastify.register(redirectToRoute);
2389
- }
2390
- /**
2391
- * Register the cookie routes.
2392
- * @param fastifyInstance - the server instance to register the routes on.
2393
- */
2394
- async registerCookieRoutes(fastifyInstance) {
2395
- const fastify = fastifyInstance ?? this._server;
2396
- await fastify.register(fastifyCookie);
2397
- await fastify.register(getCookiesRoute);
2398
- await fastify.register(postCookieRoute);
2399
- await fastify.register(deleteCookieRoute);
2400
- }
2401
- /**
2402
- * Register the anything routes.
2403
- * @param fastifyInstance - the server instance to register the routes on.
2404
- */
2405
- async registerAnythingRoutes(fastifyInstance) {
2406
- const fastify = fastifyInstance ?? this._server;
2407
- await fastify.register(anythingRoute);
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
- }
2420
- };
2421
-
2422
- // src/index.ts
2423
- var start = /* @__PURE__ */ __name(async () => {
2424
- const mockHttp = new MockHttp();
2425
- if (process.env.PORT) {
2426
- mockHttp.port = Number.parseInt(process.env.PORT, 10);
2427
- }
2428
- if (process.env.HOST) {
2429
- mockHttp.host = process.env.HOST;
2430
- }
2431
- await mockHttp.start();
2432
- return mockHttp;
2433
- }, "start");
2434
- if (import.meta.url === `file://${process.argv[1]}`) {
2435
- await start();
2436
- }
2437
- export {
2438
- MockHttp as default,
2439
- MockHttp as mockhttp,
2440
- start
2441
- };
252
+ `,Ce={openapi:{info:{title:"Mock HTTP API",description:ct,version:je.version},consumes:["application/json"],produces:["application/json"]}},Pe=r(async s=>{await s.register(nt,{routePrefix:"/docs",uiConfig:{docExpansion:"none",deepLinking:!1},uiHooks:{onRequest(e,t,o){o()},preHandler(e,t,o){o()}},staticCSP:!0,transformSpecification:r((e,t,o)=>e,"transformSpecification"),transformSpecificationClone:!0})},"registerSwaggerUi");import{randomUUID as dt}from"crypto";var L=class{static{r(this,"TapManager")}_injections;constructor(){this._injections=new Map}get injections(){return this._injections}get hasInjections(){return this._injections.size>0}inject(e,t){let o=dt(),a={id:o,response:e,matcher:t};return this._injections.set(o,a),a}removeInjection(e){let t=typeof e=="string"?e:e.id;return this._injections.delete(t)}matchRequest(e){for(let t of this._injections.values())if(this.requestMatches(e,t.matcher))return t}clear(){this._injections.clear()}requestMatches(e,t){if(!t)return!0;if(t.url&&!this.urlMatches(e.url,t.url)||t.hostname&&e.hostname!==t.hostname||t.method&&e.method.toUpperCase()!==t.method.toUpperCase())return!1;if(t.headers){for(let[o,a]of Object.entries(t.headers))if(e.headers[o.toLowerCase()]!==a)return!1}return!0}urlMatches(e,t){let o=e.split("?")[0];if(t===o)return!0;if(t.includes("*")){let a=t.split("*").map(n=>n.replaceAll(/[.*+?^${}()|[\]\\]/g,"\\$&")).join(".*");return new RegExp(`^${a}$`).test(o)}return!1}};var k=class extends lt{static{r(this,"MockHttp")}_port=3e3;_host="0.0.0.0";_autoDetectPort=!0;_helmet=!0;_apiDocs=!0;_httpBin={httpMethods:!0,redirects:!0,requestInspection:!0,responseInspection:!0,responseFormats:!0,statusCodes:!0,cookies:!0,anything:!0,auth:!0};_server=He();_taps=new L;constructor(e){super(e?.hookOptions),e?.port&&(this._port=e.port),e?.host&&(this._host=e.host),e?.helmet!==void 0&&(this._helmet=e.helmet),e?.apiDocs!==void 0&&(this._apiDocs=e.apiDocs),e?.httpBin!==void 0&&(this._httpBin=e.httpBin)}get port(){return this._port}set port(e){this._port=e}get host(){return this._host}set host(e){this._host=e}get autoDetectPort(){return this._autoDetectPort}set autoDetectPort(e){this._autoDetectPort=e}get helmet(){return this._helmet}set helmet(e){this._helmet=e}get apiDocs(){return this._apiDocs}set apiDocs(e){this._apiDocs=e}get httpBin(){return this._httpBin}set httpBin(e){this._httpBin=e}get server(){return this._server}set server(e){this._server=e}get taps(){return this._taps}set taps(e){this._taps=e}async start(){try{this._server&&await this._server.close(),this._server=He(ce),this._server.addHook("onRequest",async(l,c)=>{let p=this._taps.matchRequest(l);if(p){let u=typeof p.response=="function"?p.response(l):p.response,{response:m,statusCode:y=200,headers:b}=u;if(c.code(y),b)for(let[R,f]of Object.entries(b))c.header(R,f);return c.send(m)}}),await this._server.register(_e,{root:qe.resolve("./node_modules/@scalar/api-reference/dist"),prefix:"/scalar"}),await this.server.register(_e,{root:qe.resolve("./public"),decorateReply:!1}),await this._server.register(Te),this._helmet&&await this._server.register(ht),this._apiDocs&&await this.registerApiDocs();let{httpMethods:e,redirects:t,requestInspection:o,responseInspection:a,responseFormats:i,statusCodes:n,cookies:d,anything:g,auth:h}=this._httpBin;if(e&&await this.registerHttpMethods(),n&&await this.registerStatusCodeRoutes(),o&&await this.registerRequestInspectionRoutes(),a&&await this.registerResponseInspectionRoutes(),i&&await this.registerResponseFormatRoutes(),t&&await this.registerRedirectRoutes(),d&&await this.registerCookieRoutes(),g&&await this.registerAnythingRoutes(),h&&await this.registerAuthRoutes(),this._autoDetectPort){let l=this._port;this._port=await this.detectPort(),l!==this._port&&this._server.log.info(`Port ${l} is in use, detected next available port: ${this._port}`)}await this._server.listen({port:this._port,host:this._host})}catch(e){this._server.log.error(e)}}async close(){await this._server.close()}async detectPort(){let{port:e}=this;return ut(e)}async registerApiDocs(e){let t=e??this._server;await t.register(mt,Ce),await Pe(t),await t.register(ge)}async registerHttpMethods(e){let t=e??this._server;await t.register(Y),await t.register(V),await t.register(G),await t.register(X),await t.register(Z)}async registerStatusCodeRoutes(e){await(e??this._server).register(Se)}async registerRequestInspectionRoutes(e){let t=e??this._server;await t.register(se),await t.register(te),await t.register(oe)}async registerResponseInspectionRoutes(e){let t=e??this._server;await t.register(re),await t.register(ae),await t.register(ie)}async registerResponseFormatRoutes(e){await(e??this._server).register(ke)}async registerRedirectRoutes(e){let t=e??this._server;await t.register(K),await t.register(ee),await t.register(Q)}async registerCookieRoutes(e){let t=e??this._server;await t.register(pt),await t.register(N),await t.register(J),await t.register(W)}async registerAnythingRoutes(e){await(e??this._server).register(de)}async registerAuthRoutes(e){let t=e??this._server;await t.register(U),await t.register(F),await t.register(O),await t.register(B)}};var gt=r(async()=>{let s=new k;return I.env.PORT&&(s.port=Number.parseInt(I.env.PORT,10)),I.env.HOST&&(s.host=I.env.HOST),await s.start(),s},"start");import.meta.url===`file://${I.argv[1]}`&&await gt();export{k as default,k as mockhttp,gt as start};