@jaredwray/mockhttp 1.0.1 → 1.2.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,4 +1,1145 @@
1
- var $e=Object.defineProperty;var r=(s,e)=>$e(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 De={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:De,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 U}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=U.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(U.from(s,"utf8"),U.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"]}}},E=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 ze=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=ze(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"),Ue=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=Ue.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 Ee}from"buffer";var Oe=r(s=>{if(!s)return;let[e,t]=s.split(" ");if(!(!e||e.toLowerCase()!=="basic"||!t))try{let o=Ee.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"]}}},G=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 Ge={description:"Handles a DELETE request and returns request information",tags:["HTTP Methods"],response:{200:{type:"object",properties:{method:{type:"string"},headers:{type:"object",additionalProperties:{type:"string"}},body:{oneOf:[{type:"object",additionalProperties:!0},{type:"string"},{type:"null"}]}},required:["method","headers"]}}},J=r(s=>{s.delete("/delete",{schema:Ge},async e=>({method:"DELETE",headers:e.headers,body:e.body}))},"deleteRoute");var Je={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:Je},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(`
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import process2 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 fastifyRateLimit from "@fastify/rate-limit";
12
+ import fastifyStatic from "@fastify/static";
13
+ import { fastifySwagger } from "@fastify/swagger";
14
+ import { detect } from "detect-port";
15
+ import Fastify from "fastify";
16
+ import { Hookified } from "hookified";
17
+
18
+ // src/fastify-config.ts
19
+ function getFastifyConfig(logging = true) {
20
+ return {
21
+ logger: logging ? {
22
+ transport: {
23
+ target: "pino-pretty",
24
+ options: {
25
+ colorize: true,
26
+ translateTime: true,
27
+ ignore: "pid,hostname",
28
+ singleLine: true
29
+ }
30
+ }
31
+ } : false
32
+ };
33
+ }
34
+ __name(getFastifyConfig, "getFastifyConfig");
35
+
36
+ // src/routes/anything/index.ts
37
+ var anythingSchema = {
38
+ description: "Will return anything that you send it",
39
+ tags: [
40
+ "Anything"
41
+ ],
42
+ response: {
43
+ 200: {
44
+ type: "object",
45
+ properties: {
46
+ // Echo back all your params under `args`
47
+ args: {
48
+ type: "object",
49
+ description: "Route parameters",
50
+ additionalProperties: true
51
+ },
52
+ // Echo back whatever was in the request.body
53
+ data: {
54
+ type: "object",
55
+ description: "Request body data",
56
+ additionalProperties: true
57
+ },
58
+ // If you’re using fastify-multipart, you’ll get
59
+ // files via request.files(); this just allows
60
+ // you to mirror them back
61
+ files: {
62
+ type: "object",
63
+ description: "Uploaded files",
64
+ additionalProperties: true
65
+ },
66
+ // If you parse application/x-www-form-urlencoded
67
+ // these fields land in request.body too, but you can
68
+ // mirror them separately if you like
69
+ form: {
70
+ type: "object",
71
+ description: "Form data",
72
+ additionalProperties: true
73
+ },
74
+ headers: {
75
+ type: "object",
76
+ description: "Request headers",
77
+ additionalProperties: true
78
+ },
79
+ json: {
80
+ type: "object",
81
+ description: "Parsed JSON body",
82
+ additionalProperties: true
83
+ },
84
+ method: {
85
+ type: "string",
86
+ description: "HTTP method used"
87
+ },
88
+ origin: {
89
+ type: "string",
90
+ description: "Origin of the request"
91
+ },
92
+ url: {
93
+ type: "string",
94
+ description: "Request URL"
95
+ }
96
+ },
97
+ // Don’t allow any other top-level keys in your response
98
+ additionalProperties: false
99
+ }
100
+ }
101
+ };
102
+ var anythingRoute = /* @__PURE__ */ __name((fastify) => {
103
+ const handler = /* @__PURE__ */ __name(async (request, reply) => {
104
+ const response = {
105
+ args: request.query,
106
+ data: request.body ?? {},
107
+ // biome-ignore lint/suspicious/noExplicitAny: expected
108
+ files: request.raw?.files ?? {},
109
+ form: request.body ?? {},
110
+ headers: request.headers,
111
+ json: request.body ?? {},
112
+ method: request.method,
113
+ origin: request.headers.origin ?? request.headers.host ?? "",
114
+ url: request.url
115
+ };
116
+ await reply.send(response);
117
+ }, "handler");
118
+ const noValidation = {
119
+ schema: anythingSchema,
120
+ // Skip AJV on *this* route
121
+ /* v8 ignore next -- @preserve */
122
+ validatorCompiler: /* @__PURE__ */ __name(() => () => true, "validatorCompiler")
123
+ };
124
+ fastify.get("/anything", noValidation, handler);
125
+ fastify.post("/anything", noValidation, handler);
126
+ fastify.put("/anything", noValidation, handler);
127
+ fastify.patch("/anything", noValidation, handler);
128
+ fastify.delete("/anything", noValidation, handler);
129
+ }, "anythingRoute");
130
+
131
+ // src/routes/auth/basic.ts
132
+ import { Buffer as Buffer2 } from "buffer";
133
+ import { timingSafeEqual } from "crypto";
134
+ var parseBasic = /* @__PURE__ */ __name((header) => {
135
+ if (!header || typeof header !== "string") {
136
+ return void 0;
137
+ }
138
+ const spaceIndex = header.indexOf(" ");
139
+ if (spaceIndex === -1) {
140
+ return void 0;
141
+ }
142
+ const scheme = header.slice(0, spaceIndex);
143
+ const value = header.slice(spaceIndex + 1);
144
+ if (scheme.toLowerCase() !== "basic" || !value) {
145
+ return void 0;
146
+ }
147
+ try {
148
+ const decoded = Buffer2.from(value, "base64").toString("utf8");
149
+ const idx = decoded.indexOf(":");
150
+ if (idx === -1) {
151
+ return void 0;
152
+ }
153
+ const username = decoded.slice(0, idx);
154
+ const password = decoded.slice(idx + 1);
155
+ if (username === "") {
156
+ return void 0;
157
+ }
158
+ return {
159
+ username,
160
+ password
161
+ };
162
+ } catch {
163
+ return void 0;
164
+ }
165
+ }, "parseBasic");
166
+ var safeCompare = /* @__PURE__ */ __name((a, b) => {
167
+ if (a.length !== b.length) {
168
+ return false;
169
+ }
170
+ try {
171
+ return timingSafeEqual(Buffer2.from(a, "utf8"), Buffer2.from(b, "utf8"));
172
+ } catch {
173
+ return false;
174
+ }
175
+ }, "safeCompare");
176
+ var basicAuthSchema = {
177
+ description: "HTTP Basic authentication. Succeeds only if the user/pass provided in the path matches the Basic Authorization header.",
178
+ tags: [
179
+ "Auth"
180
+ ],
181
+ params: {
182
+ type: "object",
183
+ properties: {
184
+ user: {
185
+ type: "string"
186
+ },
187
+ passwd: {
188
+ type: "string"
189
+ }
190
+ },
191
+ required: [
192
+ "user",
193
+ "passwd"
194
+ ]
195
+ },
196
+ response: {
197
+ 200: {
198
+ type: "object",
199
+ properties: {
200
+ authenticated: {
201
+ type: "boolean"
202
+ },
203
+ user: {
204
+ type: "string"
205
+ }
206
+ },
207
+ required: [
208
+ "authenticated",
209
+ "user"
210
+ ]
211
+ },
212
+ 401: {
213
+ type: "object",
214
+ properties: {
215
+ message: {
216
+ type: "string"
217
+ }
218
+ },
219
+ required: [
220
+ "message"
221
+ ]
222
+ }
223
+ }
224
+ };
225
+ var basicAuthRoute = /* @__PURE__ */ __name((fastify) => {
226
+ fastify.get("/basic-auth/:user/:passwd", {
227
+ schema: basicAuthSchema
228
+ }, async (request, reply) => {
229
+ const { user, passwd } = request.params;
230
+ if (!user || typeof user !== "string" || typeof passwd !== "string") {
231
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
232
+ return reply.status(401).send({
233
+ message: "Unauthorized"
234
+ });
235
+ }
236
+ const parsed = parseBasic(request.headers.authorization);
237
+ if (!parsed || !safeCompare(parsed.username, user) || !safeCompare(parsed.password, passwd)) {
238
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
239
+ return reply.status(401).send({
240
+ message: "Unauthorized"
241
+ });
242
+ }
243
+ return reply.send({
244
+ authenticated: true,
245
+ user
246
+ });
247
+ });
248
+ }, "basicAuthRoute");
249
+
250
+ // src/routes/auth/bearer.ts
251
+ var parseBearer = /* @__PURE__ */ __name((header) => {
252
+ if (!header) {
253
+ return void 0;
254
+ }
255
+ const [scheme, token] = header.split(" ");
256
+ if (!scheme || scheme.toLowerCase() !== "bearer" || !token) {
257
+ return void 0;
258
+ }
259
+ return token;
260
+ }, "parseBearer");
261
+ var bearerAuthSchema = {
262
+ description: "HTTP Bearer authentication. Succeeds if any Bearer token is present unless ?required=true is provided.",
263
+ tags: [
264
+ "Auth"
265
+ ],
266
+ querystring: {
267
+ type: "object",
268
+ properties: {
269
+ required: {
270
+ type: "boolean",
271
+ default: true
272
+ }
273
+ }
274
+ },
275
+ response: {
276
+ 200: {
277
+ type: "object",
278
+ properties: {
279
+ authenticated: {
280
+ type: "boolean"
281
+ },
282
+ token: {
283
+ type: "string"
284
+ }
285
+ },
286
+ required: [
287
+ "authenticated",
288
+ "token"
289
+ ]
290
+ },
291
+ 401: {
292
+ type: "object",
293
+ properties: {
294
+ message: {
295
+ type: "string"
296
+ }
297
+ },
298
+ required: [
299
+ "message"
300
+ ]
301
+ }
302
+ }
303
+ };
304
+ var bearerAuthRoute = /* @__PURE__ */ __name((fastify) => {
305
+ fastify.get("/bearer", {
306
+ schema: bearerAuthSchema
307
+ }, async (request, reply) => {
308
+ const token = parseBearer(request.headers.authorization);
309
+ const qs = request.query;
310
+ const required = qs?.required ?? true;
311
+ if (!token && required) {
312
+ void reply.header("WWW-Authenticate", "Bearer");
313
+ return reply.status(401).send({
314
+ message: "Unauthorized"
315
+ });
316
+ }
317
+ if (!token) {
318
+ return reply.send({
319
+ authenticated: false,
320
+ token: ""
321
+ });
322
+ }
323
+ return reply.send({
324
+ authenticated: true,
325
+ token
326
+ });
327
+ });
328
+ }, "bearerAuthRoute");
329
+
330
+ // src/routes/auth/digest.ts
331
+ import crypto from "crypto";
332
+ var makeNonce = /* @__PURE__ */ __name(() => crypto.randomBytes(16).toString("hex"), "makeNonce");
333
+ var algorithms = /* @__PURE__ */ new Set([
334
+ "MD5",
335
+ "md5",
336
+ "SHA-256",
337
+ "sha-256",
338
+ "SHA-512",
339
+ "sha-512"
340
+ ]);
341
+ var hashAlg = /* @__PURE__ */ __name((alg) => {
342
+ if (!alg) {
343
+ return "md5";
344
+ }
345
+ const a = alg.toLowerCase();
346
+ if (a === "md5") {
347
+ return "md5";
348
+ }
349
+ if (a === "sha-256") {
350
+ return "sha256";
351
+ }
352
+ if (a === "sha-512") {
353
+ return "sha512";
354
+ }
355
+ return "md5";
356
+ }, "hashAlg");
357
+ var h = /* @__PURE__ */ __name((algo, data) => crypto.createHash(algo).update(data).digest("hex"), "h");
358
+ var parseDigest = /* @__PURE__ */ __name((header) => {
359
+ if (!header) {
360
+ return void 0;
361
+ }
362
+ const spaceIndex = header.indexOf(" ");
363
+ if (spaceIndex === -1) {
364
+ return void 0;
365
+ }
366
+ const scheme = header.slice(0, spaceIndex);
367
+ const rest = header.slice(spaceIndex + 1);
368
+ if (!scheme || scheme.toLowerCase() !== "digest" || !rest) {
369
+ return void 0;
370
+ }
371
+ const out = {};
372
+ const parts = [];
373
+ let current = "";
374
+ let inQuotes = false;
375
+ for (const char of rest) {
376
+ if (char === '"') {
377
+ inQuotes = !inQuotes;
378
+ current += char;
379
+ } else if (char === "," && !inQuotes) {
380
+ if (current.trim()) {
381
+ parts.push(current.trim());
382
+ }
383
+ current = "";
384
+ } else {
385
+ current += char;
386
+ }
387
+ }
388
+ if (current.trim()) {
389
+ parts.push(current.trim());
390
+ }
391
+ if (parts.length === 0) {
392
+ return out;
393
+ }
394
+ for (const p of parts) {
395
+ const equalIndex = p.indexOf("=");
396
+ if (equalIndex === -1) {
397
+ continue;
398
+ }
399
+ const k = p.slice(0, equalIndex).trim();
400
+ const v = p.slice(equalIndex + 1).trim();
401
+ let value = v;
402
+ if (value.startsWith('"') && value.endsWith('"')) {
403
+ value = value.slice(1, -1);
404
+ }
405
+ out[k] = value;
406
+ }
407
+ return out;
408
+ }, "parseDigest");
409
+ var digestAuthSchema = {
410
+ description: "HTTP Digest authentication. Mirrors httpbin behavior for testing clients.",
411
+ tags: [
412
+ "Auth"
413
+ ],
414
+ params: {
415
+ type: "object",
416
+ properties: {
417
+ qop: {
418
+ type: "string"
419
+ },
420
+ user: {
421
+ type: "string"
422
+ },
423
+ passwd: {
424
+ type: "string"
425
+ },
426
+ algorithm: {
427
+ type: "string"
428
+ },
429
+ stale_after: {
430
+ type: "string"
431
+ }
432
+ },
433
+ required: [
434
+ "qop",
435
+ "user",
436
+ "passwd"
437
+ ]
438
+ }
439
+ };
440
+ var digestAuthRoute = /* @__PURE__ */ __name((fastify) => {
441
+ fastify.get("/digest-auth/:qop/:user/:passwd", {
442
+ schema: digestAuthSchema
443
+ }, async (request, reply) => {
444
+ const { user, passwd } = request.params;
445
+ const realm = "mockhttp";
446
+ const nonce = makeNonce();
447
+ const auth = parseDigest(request.headers.authorization);
448
+ if (!auth) {
449
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
450
+ return reply.status(401).send({
451
+ message: "Unauthorized"
452
+ });
453
+ }
454
+ if (auth.username !== user) {
455
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
456
+ return reply.status(401).send({
457
+ message: "Unauthorized"
458
+ });
459
+ }
460
+ const method = "GET";
461
+ const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
462
+ const algo = hashAlg(auth.algorithm);
463
+ const ha1 = h(algo, `${user}:${realm}:${passwd}`);
464
+ const ha2 = h(algo, `${method}:${uri}`);
465
+ let expected = "";
466
+ expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
467
+ if (!auth.response || auth.response !== expected) {
468
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", nonce="${nonce}", opaque="${makeNonce()}"`);
469
+ return reply.status(401).send({
470
+ message: "Unauthorized"
471
+ });
472
+ }
473
+ return reply.send({
474
+ authenticated: true,
475
+ user
476
+ });
477
+ });
478
+ fastify.get("/digest-auth/:qop/:user/:passwd/:algorithm", {
479
+ schema: digestAuthSchema
480
+ }, async (request, reply) => {
481
+ const { user, passwd, algorithm } = request.params;
482
+ const realm = "mockhttp";
483
+ const algoHeader = algorithms.has(String(algorithm)) ? String(algorithm) : "MD5";
484
+ const nonce = makeNonce();
485
+ const auth = parseDigest(request.headers.authorization);
486
+ if (!auth) {
487
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
488
+ return reply.status(401).send({
489
+ message: "Unauthorized"
490
+ });
491
+ }
492
+ if (auth.username !== user) {
493
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
494
+ return reply.status(401).send({
495
+ message: "Unauthorized"
496
+ });
497
+ }
498
+ const method = "GET";
499
+ const uri = auth.uri ?? request.url.replace(/^https?:\/\/[^/]+/, "");
500
+ const algo = hashAlg(auth.algorithm ?? algoHeader);
501
+ const ha1 = h(algo, `${user}:${realm}:${passwd}`);
502
+ const ha2 = h(algo, `${method}:${uri}`);
503
+ const expected = auth.qop ? h(algo, `${ha1}:${auth.nonce}:${auth.nc}:${auth.cnonce}:${auth.qop}:${ha2}`) : h(algo, `${ha1}:${auth.nonce}:${ha2}`);
504
+ if (!auth.response || auth.response !== expected) {
505
+ void reply.header("WWW-Authenticate", `Digest realm="${realm}", qop="auth,auth-int", algorithm=${algoHeader}, nonce="${nonce}", opaque="${makeNonce()}"`);
506
+ return reply.status(401).send({
507
+ message: "Unauthorized"
508
+ });
509
+ }
510
+ return reply.send({
511
+ authenticated: true,
512
+ user
513
+ });
514
+ });
515
+ }, "digestAuthRoute");
516
+
517
+ // src/routes/auth/hidden-basic.ts
518
+ import { Buffer as Buffer3 } from "buffer";
519
+ var parseBasic2 = /* @__PURE__ */ __name((header) => {
520
+ if (!header) {
521
+ return void 0;
522
+ }
523
+ const [scheme, value] = header.split(" ");
524
+ if (!scheme || scheme.toLowerCase() !== "basic" || !value) {
525
+ return void 0;
526
+ }
527
+ try {
528
+ const decoded = Buffer3.from(value, "base64").toString("utf8");
529
+ const idx = decoded.indexOf(":");
530
+ if (idx === -1) {
531
+ return void 0;
532
+ }
533
+ return {
534
+ username: decoded.slice(0, idx),
535
+ password: decoded.slice(idx + 1)
536
+ };
537
+ } catch {
538
+ return void 0;
539
+ }
540
+ }, "parseBasic");
541
+ var hiddenBasicAuthSchema = {
542
+ description: "HTTP Basic authentication with docs hidden. Mirrors /basic-auth but hides from schema.",
543
+ tags: [
544
+ "Auth"
545
+ ],
546
+ hide: true,
547
+ params: {
548
+ type: "object",
549
+ properties: {
550
+ user: {
551
+ type: "string"
552
+ },
553
+ passwd: {
554
+ type: "string"
555
+ }
556
+ },
557
+ required: [
558
+ "user",
559
+ "passwd"
560
+ ]
561
+ }
562
+ };
563
+ var hiddenBasicAuthRoute = /* @__PURE__ */ __name((fastify) => {
564
+ fastify.get("/hidden-basic-auth/:user/:passwd", {
565
+ schema: hiddenBasicAuthSchema
566
+ }, async (request, reply) => {
567
+ const { user, passwd } = request.params;
568
+ const parsed = parseBasic2(request.headers.authorization);
569
+ if (!parsed || parsed.username !== user || parsed.password !== passwd) {
570
+ void reply.header("WWW-Authenticate", 'Basic realm="mockhttp"');
571
+ return reply.status(401).send({
572
+ message: "Unauthorized"
573
+ });
574
+ }
575
+ return reply.send({
576
+ authenticated: true,
577
+ user
578
+ });
579
+ });
580
+ }, "hiddenBasicAuthRoute");
581
+
582
+ // src/routes/cookies/delete.ts
583
+ var cookiesDeleteSchema = {
584
+ description: "Delete a cookie",
585
+ tags: [
586
+ "Cookies"
587
+ ],
588
+ querystring: {
589
+ type: "object",
590
+ properties: {
591
+ name: {
592
+ type: "string"
593
+ }
594
+ },
595
+ required: [
596
+ "name"
597
+ ],
598
+ additionalProperties: false
599
+ },
600
+ response: {
601
+ 204: {
602
+ type: "null"
603
+ }
604
+ }
605
+ };
606
+ var deleteCookieRoute = /* @__PURE__ */ __name((fastify) => {
607
+ fastify.delete("/cookies", {
608
+ schema: cookiesDeleteSchema
609
+ }, async (request, reply) => {
610
+ const { name } = request.query;
611
+ reply.clearCookie(name, {
612
+ path: "/"
613
+ });
614
+ return reply.status(204).send();
615
+ });
616
+ }, "deleteCookieRoute");
617
+
618
+ // src/routes/cookies/get.ts
619
+ var getCookiesRouteSchema = {
620
+ description: "Return cookies from the request",
621
+ tags: [
622
+ "Cookies"
623
+ ],
624
+ response: {
625
+ 200: {
626
+ type: "object",
627
+ properties: {
628
+ cookies: {
629
+ type: "object",
630
+ additionalProperties: {
631
+ type: "string"
632
+ }
633
+ }
634
+ },
635
+ required: [
636
+ "cookies"
637
+ ]
638
+ }
639
+ }
640
+ };
641
+ var getCookiesRoute = /* @__PURE__ */ __name((fastify) => {
642
+ fastify.get("/cookies", {
643
+ schema: getCookiesRouteSchema
644
+ }, async (request, reply) => {
645
+ reply.type("application/json");
646
+ await reply.send({
647
+ cookies: request.cookies
648
+ });
649
+ });
650
+ }, "getCookiesRoute");
651
+
652
+ // src/routes/cookies/post.ts
653
+ var postCookieRouteSchema = {
654
+ description: "Set a cookie",
655
+ tags: [
656
+ "Cookies"
657
+ ],
658
+ body: {
659
+ type: "object",
660
+ properties: {
661
+ name: {
662
+ type: "string"
663
+ },
664
+ value: {
665
+ type: "string"
666
+ },
667
+ expires: {
668
+ type: "string",
669
+ format: "date-time"
670
+ }
671
+ },
672
+ required: [
673
+ "name",
674
+ "value"
675
+ ],
676
+ additionalProperties: false
677
+ },
678
+ response: {
679
+ 200: {
680
+ type: "object",
681
+ properties: {
682
+ cookies: {
683
+ type: "object",
684
+ additionalProperties: {
685
+ type: "string"
686
+ }
687
+ }
688
+ },
689
+ required: [
690
+ "cookies"
691
+ ]
692
+ }
693
+ }
694
+ };
695
+ var postCookieRoute = /* @__PURE__ */ __name((fastify) => {
696
+ fastify.post("/cookies", {
697
+ schema: postCookieRouteSchema
698
+ }, async (request, reply) => {
699
+ const { name, value, expires } = request.body;
700
+ if (expires) {
701
+ const date = new Date(expires);
702
+ reply.setCookie(name, value, {
703
+ path: "/",
704
+ expires: date
705
+ });
706
+ } else {
707
+ reply.setCookie(name, value, {
708
+ path: "/"
709
+ });
710
+ }
711
+ return reply.status(200).send();
712
+ });
713
+ }, "postCookieRoute");
714
+
715
+ // src/routes/http-methods/delete.ts
716
+ var deleteSchema = {
717
+ description: "Handles a DELETE request and returns request information",
718
+ tags: [
719
+ "HTTP Methods"
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
+ oneOf: [
736
+ {
737
+ type: "object",
738
+ additionalProperties: true
739
+ },
740
+ {
741
+ type: "string"
742
+ },
743
+ {
744
+ type: "null"
745
+ }
746
+ ]
747
+ }
748
+ },
749
+ required: [
750
+ "method",
751
+ "headers"
752
+ ]
753
+ }
754
+ }
755
+ };
756
+ var deleteRoute = /* @__PURE__ */ __name((fastify) => {
757
+ fastify.delete("/delete", {
758
+ schema: deleteSchema
759
+ }, async (request) => ({
760
+ method: "DELETE",
761
+ headers: request.headers,
762
+ body: request.body
763
+ }));
764
+ }, "deleteRoute");
765
+
766
+ // src/routes/http-methods/get.ts
767
+ var getSchema = {
768
+ description: "Returns request information for GET requests",
769
+ tags: [
770
+ "HTTP Methods"
771
+ ],
772
+ querystring: {
773
+ type: "object",
774
+ additionalProperties: true,
775
+ properties: {},
776
+ description: "Query parameters sent in the request"
777
+ },
778
+ headers: {
779
+ type: "object",
780
+ additionalProperties: true,
781
+ properties: {},
782
+ description: "Headers sent in the request"
783
+ },
784
+ response: {
785
+ 200: {
786
+ type: "object",
787
+ properties: {
788
+ method: {
789
+ type: "string"
790
+ },
791
+ headers: {
792
+ type: "object",
793
+ additionalProperties: {
794
+ type: "string"
795
+ }
796
+ },
797
+ queryParams: {
798
+ type: "object",
799
+ additionalProperties: {
800
+ type: "string"
801
+ }
802
+ }
803
+ },
804
+ required: [
805
+ "method",
806
+ "headers",
807
+ "queryParams"
808
+ ]
809
+ }
810
+ }
811
+ };
812
+ var getRoute = /* @__PURE__ */ __name((fastify) => {
813
+ fastify.get("/get", {
814
+ schema: getSchema
815
+ }, async (request, _reply) => ({
816
+ method: "GET",
817
+ headers: request.headers,
818
+ queryParams: request.query
819
+ }));
820
+ }, "getRoute");
821
+
822
+ // src/routes/http-methods/patch.ts
823
+ var patchSchema = {
824
+ description: "Handles a PATCH request and returns request information",
825
+ tags: [
826
+ "HTTP Methods"
827
+ ],
828
+ body: {
829
+ type: "object",
830
+ additionalProperties: true,
831
+ description: "The body of the PATCH request"
832
+ },
833
+ response: {
834
+ 200: {
835
+ type: "object",
836
+ properties: {
837
+ method: {
838
+ type: "string"
839
+ },
840
+ headers: {
841
+ type: "object",
842
+ additionalProperties: {
843
+ type: "string"
844
+ }
845
+ },
846
+ body: {
847
+ type: "object",
848
+ additionalProperties: true
849
+ }
850
+ },
851
+ required: [
852
+ "method",
853
+ "headers",
854
+ "body"
855
+ ]
856
+ }
857
+ }
858
+ };
859
+ var patchRoute = /* @__PURE__ */ __name((fastify) => {
860
+ fastify.patch("/patch", {
861
+ schema: patchSchema
862
+ }, async (request) => ({
863
+ method: "PATCH",
864
+ headers: request.headers,
865
+ body: request.body
866
+ }));
867
+ }, "patchRoute");
868
+
869
+ // src/routes/http-methods/post.ts
870
+ var postSchema = {
871
+ description: "Handles a POST request and returns request information",
872
+ tags: [
873
+ "HTTP Methods"
874
+ ],
875
+ body: {
876
+ type: "object",
877
+ additionalProperties: true,
878
+ description: "The body of the POST request"
879
+ },
880
+ response: {
881
+ 200: {
882
+ type: "object",
883
+ properties: {
884
+ method: {
885
+ type: "string"
886
+ },
887
+ headers: {
888
+ type: "object",
889
+ additionalProperties: {
890
+ type: "string"
891
+ }
892
+ },
893
+ body: {
894
+ type: "object",
895
+ additionalProperties: true
896
+ }
897
+ },
898
+ required: [
899
+ "method",
900
+ "headers",
901
+ "body"
902
+ ]
903
+ }
904
+ }
905
+ };
906
+ var postRoute = /* @__PURE__ */ __name((fastify) => {
907
+ fastify.post("/post", {
908
+ schema: postSchema
909
+ }, async (request) => ({
910
+ method: "POST",
911
+ headers: request.headers,
912
+ body: request.body
913
+ }));
914
+ }, "postRoute");
915
+
916
+ // src/routes/http-methods/put.ts
917
+ var putSchema = {
918
+ description: "Handles a PUT request and returns request information",
919
+ tags: [
920
+ "HTTP Methods"
921
+ ],
922
+ body: {
923
+ type: "object",
924
+ additionalProperties: true,
925
+ description: "The body of the PUT request"
926
+ },
927
+ response: {
928
+ 200: {
929
+ type: "object",
930
+ properties: {
931
+ method: {
932
+ type: "string"
933
+ },
934
+ headers: {
935
+ type: "object",
936
+ additionalProperties: {
937
+ type: "string"
938
+ }
939
+ },
940
+ body: {
941
+ type: "object",
942
+ additionalProperties: true
943
+ }
944
+ },
945
+ required: [
946
+ "method",
947
+ "headers",
948
+ "body"
949
+ ]
950
+ }
951
+ }
952
+ };
953
+ var putRoute = /* @__PURE__ */ __name((fastify) => {
954
+ fastify.put("/put", {
955
+ schema: putSchema
956
+ }, async (request) => ({
957
+ method: "PUT",
958
+ headers: request.headers,
959
+ body: request.body
960
+ }));
961
+ }, "putRoute");
962
+
963
+ // src/routes/images/index.ts
964
+ import { readFileSync } from "fs";
965
+ import { join } from "path";
966
+ var imageSchema = {
967
+ description: "Returns an image based on Accept header (supports jpeg, png, svg, webp)",
968
+ tags: [
969
+ "Images"
970
+ ],
971
+ headers: {
972
+ type: "object",
973
+ properties: {
974
+ accept: {
975
+ type: "string",
976
+ description: "Accept header for content negotiation"
977
+ }
978
+ }
979
+ },
980
+ response: {
981
+ 200: {
982
+ description: "Image file",
983
+ type: "string",
984
+ format: "binary"
985
+ }
986
+ }
987
+ };
988
+ var jpegSchema = {
989
+ description: "Returns a JPEG image",
990
+ tags: [
991
+ "Images"
992
+ ],
993
+ response: {
994
+ 200: {
995
+ description: "JPEG image",
996
+ type: "string",
997
+ format: "binary"
998
+ }
999
+ }
1000
+ };
1001
+ var pngSchema = {
1002
+ description: "Returns a PNG image",
1003
+ tags: [
1004
+ "Images"
1005
+ ],
1006
+ response: {
1007
+ 200: {
1008
+ description: "PNG image",
1009
+ type: "string",
1010
+ format: "binary"
1011
+ }
1012
+ }
1013
+ };
1014
+ var svgSchema = {
1015
+ description: "Returns an SVG image",
1016
+ tags: [
1017
+ "Images"
1018
+ ],
1019
+ response: {
1020
+ 200: {
1021
+ description: "SVG image",
1022
+ type: "string",
1023
+ format: "binary"
1024
+ }
1025
+ }
1026
+ };
1027
+ var webpSchema = {
1028
+ description: "Returns a WEBP image",
1029
+ tags: [
1030
+ "Images"
1031
+ ],
1032
+ response: {
1033
+ 200: {
1034
+ description: "WEBP image",
1035
+ type: "string",
1036
+ format: "binary"
1037
+ }
1038
+ }
1039
+ };
1040
+ var imageRoutes = /* @__PURE__ */ __name((fastify) => {
1041
+ const publicPath = join(process.cwd(), "public");
1042
+ fastify.get("/image", {
1043
+ schema: imageSchema
1044
+ }, async (request, reply) => {
1045
+ const accept = request.headers.accept || "";
1046
+ let file = "logo.png";
1047
+ let contentType = "image/png";
1048
+ if (accept.includes("image/jpeg") || accept.includes("image/jpg")) {
1049
+ file = "logo.jpg";
1050
+ contentType = "image/jpeg";
1051
+ } else if (accept.includes("image/svg+xml") || accept.includes("image/svg")) {
1052
+ file = "logo.svg";
1053
+ contentType = "image/svg+xml";
1054
+ } else if (accept.includes("image/webp")) {
1055
+ file = "logo.webp";
1056
+ contentType = "image/webp";
1057
+ } else if (accept.includes("image/png")) {
1058
+ file = "logo.png";
1059
+ contentType = "image/png";
1060
+ }
1061
+ try {
1062
+ const imagePath = join(publicPath, file);
1063
+ const imageBuffer = readFileSync(imagePath);
1064
+ reply.type(contentType);
1065
+ return imageBuffer;
1066
+ } catch (error) {
1067
+ reply.code(500);
1068
+ return {
1069
+ error: `${error}`
1070
+ };
1071
+ }
1072
+ });
1073
+ fastify.get("/image/jpeg", {
1074
+ schema: jpegSchema
1075
+ }, async (_request, reply) => {
1076
+ try {
1077
+ const imagePath = join(publicPath, "logo.jpg");
1078
+ const imageBuffer = readFileSync(imagePath);
1079
+ reply.type("image/jpeg");
1080
+ return imageBuffer;
1081
+ } catch (error) {
1082
+ reply.code(500);
1083
+ return {
1084
+ error: `${error}`
1085
+ };
1086
+ }
1087
+ });
1088
+ fastify.get("/image/png", {
1089
+ schema: pngSchema
1090
+ }, async (_request, reply) => {
1091
+ try {
1092
+ const imagePath = join(publicPath, "logo.png");
1093
+ const imageBuffer = readFileSync(imagePath);
1094
+ reply.type("image/png");
1095
+ return imageBuffer;
1096
+ } catch (error) {
1097
+ reply.code(500);
1098
+ return {
1099
+ error: `${error}`
1100
+ };
1101
+ }
1102
+ });
1103
+ fastify.get("/image/svg", {
1104
+ schema: svgSchema
1105
+ }, async (_request, reply) => {
1106
+ try {
1107
+ const imagePath = join(publicPath, "logo.svg");
1108
+ const imageBuffer = readFileSync(imagePath);
1109
+ reply.type("image/svg+xml");
1110
+ return imageBuffer;
1111
+ } catch (error) {
1112
+ reply.code(500);
1113
+ return {
1114
+ error: `${error}`
1115
+ };
1116
+ }
1117
+ });
1118
+ fastify.get("/image/webp", {
1119
+ schema: webpSchema
1120
+ }, async (_request, reply) => {
1121
+ try {
1122
+ const imagePath = join(publicPath, "logo.webp");
1123
+ const imageBuffer = readFileSync(imagePath);
1124
+ reply.type("image/webp");
1125
+ return imageBuffer;
1126
+ } catch (error) {
1127
+ reply.code(500);
1128
+ return {
1129
+ error: `${error}`
1130
+ };
1131
+ }
1132
+ });
1133
+ }, "imageRoutes");
1134
+
1135
+ // src/routes/index.ts
1136
+ var indexRoute = /* @__PURE__ */ __name((fastify) => {
1137
+ fastify.get("/", {
1138
+ schema: {
1139
+ hide: true
1140
+ }
1141
+ }, async (_request, reply) => {
1142
+ const redocHtml = `
2
1143
  <!doctype html>
3
1144
  <html>
4
1145
  <head>
@@ -62,23 +1203,405 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
62
1203
  <script src="/scalar/browser/standalone.js"></script>
63
1204
  </body>
64
1205
  </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=`
1206
+ `;
1207
+ await reply.type("text/html").send(redocHtml);
1208
+ });
1209
+ }, "indexRoute");
1210
+
1211
+ // src/routes/redirects/absolute-redirect.ts
1212
+ var absoluteRedirectSchema = {
1213
+ tags: [
1214
+ "Redirects"
1215
+ ],
1216
+ description: "Redirects the request to the target URL using an absolute URL",
1217
+ params: {
1218
+ type: "object",
1219
+ properties: {
1220
+ value: {
1221
+ type: "integer",
1222
+ minimum: 1
1223
+ }
1224
+ },
1225
+ required: [
1226
+ "value"
1227
+ ]
1228
+ },
1229
+ headers: {
1230
+ type: "object",
1231
+ properties: {
1232
+ host: {
1233
+ type: "string"
1234
+ }
1235
+ },
1236
+ required: [
1237
+ "host"
1238
+ ]
1239
+ },
1240
+ response: {
1241
+ 302: {
1242
+ type: "string"
1243
+ }
1244
+ }
1245
+ };
1246
+ var absoluteRedirectRoute = /* @__PURE__ */ __name((fastify) => {
1247
+ fastify.get("/absolute-redirect/:value", {
1248
+ schema: absoluteRedirectSchema
1249
+ }, async (request, reply) => {
1250
+ const { value } = request.params;
1251
+ const { host } = request.headers;
1252
+ const { protocol } = request;
1253
+ let url = `${protocol}://${host}/get`;
1254
+ let html = `
66
1255
  <title>Redirecting...</title>
67
1256
  <h1>Redirecting...</h1>
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=`
1257
+ <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1258
+ `;
1259
+ if (value > 1) {
1260
+ url = `${protocol}://${host}/absolute-redirect/${value - 1}`;
1261
+ html = `
70
1262
  <title>Redirecting...</title>
71
1263
  <h1>Redirecting...</h1>
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=`
1264
+ <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1265
+ `;
1266
+ }
1267
+ await reply.header("Location", url).status(302).type("text/html").send(html);
1268
+ });
1269
+ }, "absoluteRedirectRoute");
1270
+
1271
+ // src/routes/redirects/redirect-to.ts
1272
+ var redirectToSchema = {
1273
+ tags: [
1274
+ "Redirects"
1275
+ ],
1276
+ description: "Redirects the request to the target URL",
1277
+ querystring: {
1278
+ type: "object",
1279
+ properties: {
1280
+ url: {
1281
+ type: "string",
1282
+ format: "uri",
1283
+ description: "The URL to redirect to"
1284
+ },
1285
+ status_code: {
1286
+ type: "string",
1287
+ pattern: "^[1-5][0-9][0-9]$",
1288
+ description: "The HTTP status code to use for redirection"
1289
+ }
1290
+ },
1291
+ required: [
1292
+ "url"
1293
+ ],
1294
+ additionalProperties: false
1295
+ }
1296
+ };
1297
+ var redirectToRoute = /* @__PURE__ */ __name((fastify) => {
1298
+ const handler = /* @__PURE__ */ __name(async (request, reply) => {
1299
+ const { url, status_code } = request.query;
1300
+ const statusCode = status_code ? Number(status_code) : 302;
1301
+ await reply.redirect(url, statusCode);
1302
+ }, "handler");
1303
+ fastify.get("/redirect-to", {
1304
+ schema: redirectToSchema
1305
+ }, handler);
1306
+ fastify.post("/redirect-to", {
1307
+ schema: redirectToSchema
1308
+ }, handler);
1309
+ fastify.put("/redirect-to", {
1310
+ schema: redirectToSchema
1311
+ }, handler);
1312
+ fastify.patch("/redirect-to", {
1313
+ schema: redirectToSchema
1314
+ }, handler);
1315
+ fastify.delete("/redirect-to", {
1316
+ schema: redirectToSchema
1317
+ }, handler);
1318
+ }, "redirectToRoute");
1319
+
1320
+ // src/routes/redirects/relative-redirect.ts
1321
+ var relativeRedirectSchema = {
1322
+ tags: [
1323
+ "Redirects"
1324
+ ],
1325
+ description: "Redirects the request to the target URL using an relative URL",
1326
+ params: {
1327
+ type: "object",
1328
+ properties: {
1329
+ value: {
1330
+ type: "integer",
1331
+ minimum: 1
1332
+ }
1333
+ },
1334
+ required: [
1335
+ "value"
1336
+ ]
1337
+ },
1338
+ headers: {
1339
+ type: "object",
1340
+ properties: {
1341
+ host: {
1342
+ type: "string"
1343
+ }
1344
+ },
1345
+ required: [
1346
+ "host"
1347
+ ]
1348
+ },
1349
+ response: {
1350
+ 302: {
1351
+ type: "string"
1352
+ }
1353
+ }
1354
+ };
1355
+ var relativeRedirectRoute = /* @__PURE__ */ __name((fastify) => {
1356
+ fastify.get("/relative-redirect/:value", {
1357
+ schema: relativeRedirectSchema
1358
+ }, async (request, reply) => {
1359
+ const { value } = request.params;
1360
+ let url = "/get";
1361
+ let html = `
74
1362
  <title>Redirecting...</title>
75
1363
  <h1>Redirecting...</h1>
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=`
1364
+ <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1365
+ `;
1366
+ if (value > 1) {
1367
+ url = `/relative-redirect/${value - 1}`;
1368
+ html = `
78
1369
  <title>Redirecting...</title>
79
1370
  <h1>Redirecting...</h1>
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"}}},$={description:"Returns Deflate-encoded data",tags:["Response Formats"],response:{200:{type:"string",description:"Deflate-encoded content"}}},D={description:"Returns Brotli-encoded data",tags:["Response Formats"],response:{200:{type:"string",description:"Brotli-encoded content"}}},z=["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>
1371
+ <p>You should be redirected automatically to target URL: <a href="${url}">${url}</a>. If not click the link.</p>
1372
+ `;
1373
+ }
1374
+ await reply.header("Location", url).status(302).type("text/html").send(html);
1375
+ });
1376
+ }, "relativeRedirectRoute");
1377
+
1378
+ // src/routes/request-inspection/headers.ts
1379
+ var headersSchema = {
1380
+ description: "Returns all headers of the client request",
1381
+ tags: [
1382
+ "Request Inspection"
1383
+ ],
1384
+ response: {
1385
+ 200: {
1386
+ type: "object",
1387
+ properties: {
1388
+ headers: {
1389
+ type: "object",
1390
+ additionalProperties: {
1391
+ type: "string"
1392
+ },
1393
+ description: "All headers of the client request"
1394
+ }
1395
+ },
1396
+ required: [
1397
+ "headers"
1398
+ ]
1399
+ }
1400
+ }
1401
+ };
1402
+ var headersRoute = /* @__PURE__ */ __name((fastify) => {
1403
+ fastify.get("/headers", {
1404
+ schema: headersSchema
1405
+ }, async (request) => {
1406
+ const { headers } = request;
1407
+ return {
1408
+ headers
1409
+ };
1410
+ });
1411
+ }, "headersRoute");
1412
+
1413
+ // src/routes/request-inspection/ip.ts
1414
+ var ipSchema = {
1415
+ description: "Returns the IP address of the client",
1416
+ tags: [
1417
+ "Request Inspection"
1418
+ ],
1419
+ response: {
1420
+ 200: {
1421
+ type: "object",
1422
+ properties: {
1423
+ ip: {
1424
+ type: "string",
1425
+ description: "The IP address of the client"
1426
+ }
1427
+ },
1428
+ required: [
1429
+ "ip"
1430
+ ]
1431
+ }
1432
+ }
1433
+ };
1434
+ var ipRoute = /* @__PURE__ */ __name((fastify) => {
1435
+ fastify.get("/ip", {
1436
+ schema: ipSchema
1437
+ }, async (request) => {
1438
+ const cloudflareIp = request.headers["cf-connecting-ip"];
1439
+ const forwardedIp = request.headers["x-forwarded-for"];
1440
+ const remoteIp = request.ip;
1441
+ const ip = cloudflareIp ?? (typeof forwardedIp === "string" ? forwardedIp.split(",")[0].trim() : remoteIp);
1442
+ return {
1443
+ ip
1444
+ };
1445
+ });
1446
+ }, "ipRoute");
1447
+
1448
+ // src/routes/request-inspection/user-agent.ts
1449
+ var userAgentSchema = {
1450
+ description: "Returns the User-Agent header of the client request",
1451
+ tags: [
1452
+ "Request Inspection"
1453
+ ],
1454
+ response: {
1455
+ 200: {
1456
+ type: "object",
1457
+ properties: {
1458
+ userAgent: {
1459
+ type: "string",
1460
+ description: "The User-Agent header of the client request"
1461
+ }
1462
+ },
1463
+ required: [
1464
+ "userAgent"
1465
+ ]
1466
+ }
1467
+ }
1468
+ };
1469
+ var userAgentRoute = /* @__PURE__ */ __name((fastify) => {
1470
+ fastify.get("/user-agent", {
1471
+ schema: userAgentSchema
1472
+ }, async (request) => {
1473
+ const userAgent = request.headers["user-agent"];
1474
+ return {
1475
+ userAgent
1476
+ };
1477
+ });
1478
+ }, "userAgentRoute");
1479
+
1480
+ // src/routes/response-formats/index.ts
1481
+ import { brotliCompressSync, deflateSync, gzipSync } from "zlib";
1482
+ var plainSchema = {
1483
+ description: "Returns random plain text content",
1484
+ tags: [
1485
+ "Response Formats"
1486
+ ],
1487
+ response: {
1488
+ 200: {
1489
+ type: "string",
1490
+ description: "Random plain text content"
1491
+ }
1492
+ }
1493
+ };
1494
+ var textSchema = {
1495
+ description: "Returns random text content",
1496
+ tags: [
1497
+ "Response Formats"
1498
+ ],
1499
+ response: {
1500
+ 200: {
1501
+ type: "string",
1502
+ description: "Random text content"
1503
+ }
1504
+ }
1505
+ };
1506
+ var htmlSchema = {
1507
+ description: "Returns random HTML content",
1508
+ tags: [
1509
+ "Response Formats"
1510
+ ],
1511
+ response: {
1512
+ 200: {
1513
+ type: "string",
1514
+ description: "Random HTML content"
1515
+ }
1516
+ }
1517
+ };
1518
+ var xmlSchema = {
1519
+ description: "Returns random XML content",
1520
+ tags: [
1521
+ "Response Formats"
1522
+ ],
1523
+ response: {
1524
+ 200: {
1525
+ type: "string",
1526
+ description: "Random XML content"
1527
+ }
1528
+ }
1529
+ };
1530
+ var jsonSchema = {
1531
+ description: "Returns random JSON content",
1532
+ tags: [
1533
+ "Response Formats"
1534
+ ],
1535
+ response: {
1536
+ 200: {
1537
+ type: "object",
1538
+ description: "Random JSON content",
1539
+ additionalProperties: true
1540
+ }
1541
+ }
1542
+ };
1543
+ var denySchema = {
1544
+ description: "Returns page denied by robots.txt rules",
1545
+ tags: [
1546
+ "Response Formats"
1547
+ ],
1548
+ response: {
1549
+ 403: {
1550
+ type: "string",
1551
+ description: "Page denied by robots.txt rules"
1552
+ }
1553
+ }
1554
+ };
1555
+ var gzipSchema = {
1556
+ description: "Returns GZip-encoded data",
1557
+ tags: [
1558
+ "Response Formats"
1559
+ ],
1560
+ response: {
1561
+ 200: {
1562
+ type: "string",
1563
+ description: "GZip-encoded content"
1564
+ }
1565
+ }
1566
+ };
1567
+ var deflateSchema = {
1568
+ description: "Returns Deflate-encoded data",
1569
+ tags: [
1570
+ "Response Formats"
1571
+ ],
1572
+ response: {
1573
+ 200: {
1574
+ type: "string",
1575
+ description: "Deflate-encoded content"
1576
+ }
1577
+ }
1578
+ };
1579
+ var brotliSchema = {
1580
+ description: "Returns Brotli-encoded data",
1581
+ tags: [
1582
+ "Response Formats"
1583
+ ],
1584
+ response: {
1585
+ 200: {
1586
+ type: "string",
1587
+ description: "Brotli-encoded content"
1588
+ }
1589
+ }
1590
+ };
1591
+ var randomTexts = [
1592
+ "The quick brown fox jumps over the lazy dog.",
1593
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
1594
+ "To be or not to be, that is the question.",
1595
+ "All work and no play makes Jack a dull boy.",
1596
+ "The only way to do great work is to love what you do.",
1597
+ "In the beginning was the Word, and the Word was with God.",
1598
+ "Once upon a time in a galaxy far, far away...",
1599
+ "It was the best of times, it was the worst of times.",
1600
+ "The journey of a thousand miles begins with a single step.",
1601
+ "Knowledge is power. France is bacon."
1602
+ ];
1603
+ var randomHtmlContent = [
1604
+ `<!DOCTYPE html>
82
1605
  <html lang="en">
83
1606
  <head>
84
1607
  <title>Sample Page</title>
@@ -87,7 +1610,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
87
1610
  <h1>Welcome to MockHTTP</h1>
88
1611
  <p>This is a sample HTML page with random content.</p>
89
1612
  </body>
90
- </html>`,`<!DOCTYPE html>
1613
+ </html>`,
1614
+ `<!DOCTYPE html>
91
1615
  <html>
92
1616
  <head>
93
1617
  <meta charset="UTF-8">
@@ -108,7 +1632,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
108
1632
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
109
1633
  </main>
110
1634
  </body>
111
- </html>`,`<!DOCTYPE html>
1635
+ </html>`,
1636
+ `<!DOCTYPE html>
112
1637
  <html lang="en">
113
1638
  <head>
114
1639
  <title>Article Page</title>
@@ -126,7 +1651,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
126
1651
  </blockquote>
127
1652
  </article>
128
1653
  </body>
129
- </html>`,`<!DOCTYPE html>
1654
+ </html>`,
1655
+ `<!DOCTYPE html>
130
1656
  <html>
131
1657
  <head>
132
1658
  <title>Form Example</title>
@@ -143,7 +1669,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
143
1669
  <button type="submit">Submit</button>
144
1670
  </form>
145
1671
  </body>
146
- </html>`,`<!DOCTYPE html>
1672
+ </html>`,
1673
+ `<!DOCTYPE html>
147
1674
  <html lang="en">
148
1675
  <head>
149
1676
  <meta charset="UTF-8">
@@ -157,19 +1684,24 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
157
1684
  <img src="placeholder.jpg" alt="Placeholder" style="max-width: 100%; height: auto;">
158
1685
  </div>
159
1686
  </body>
160
- </html>`],we=[`<?xml version="1.0" encoding="UTF-8"?>
1687
+ </html>`
1688
+ ];
1689
+ var randomXmlContent = [
1690
+ `<?xml version="1.0" encoding="UTF-8"?>
161
1691
  <root>
162
1692
  <message>Hello from MockHTTP</message>
163
- <timestamp>${new Date().toISOString()}</timestamp>
1693
+ <timestamp>${(/* @__PURE__ */ new Date()).toISOString()}</timestamp>
164
1694
  <status>success</status>
165
- </root>`,`<?xml version="1.0" encoding="UTF-8"?>
1695
+ </root>`,
1696
+ `<?xml version="1.0" encoding="UTF-8"?>
166
1697
  <book>
167
1698
  <title>The Great Gatsby</title>
168
1699
  <author>F. Scott Fitzgerald</author>
169
1700
  <year>1925</year>
170
1701
  <publisher>Charles Scribner's Sons</publisher>
171
1702
  <isbn>978-0-7432-7356-5</isbn>
172
- </book>`,`<?xml version="1.0" encoding="UTF-8"?>
1703
+ </book>`,
1704
+ `<?xml version="1.0" encoding="UTF-8"?>
173
1705
  <users>
174
1706
  <user id="1">
175
1707
  <name>John Doe</name>
@@ -181,7 +1713,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
181
1713
  <email>jane@example.com</email>
182
1714
  <role>user</role>
183
1715
  </user>
184
- </users>`,`<?xml version="1.0" encoding="UTF-8"?>
1716
+ </users>`,
1717
+ `<?xml version="1.0" encoding="UTF-8"?>
185
1718
  <product>
186
1719
  <id>12345</id>
187
1720
  <name>Wireless Mouse</name>
@@ -189,7 +1722,8 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
189
1722
  <price currency="USD">29.99</price>
190
1723
  <stock>150</stock>
191
1724
  <category>Electronics</category>
192
- </product>`,`<?xml version="1.0" encoding="UTF-8"?>
1725
+ </product>`,
1726
+ `<?xml version="1.0" encoding="UTF-8"?>
193
1727
  <rss version="2.0">
194
1728
  <channel>
195
1729
  <title>Mock RSS Feed</title>
@@ -199,17 +1733,210 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
199
1733
  <title>First Article</title>
200
1734
  <link>http://example.com/article1</link>
201
1735
  <description>This is the first article in our feed</description>
202
- <pubDate>${new Date().toUTCString()}</pubDate>
1736
+ <pubDate>${(/* @__PURE__ */ new Date()).toUTCString()}</pubDate>
203
1737
  </item>
204
1738
  </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()*z.length),u=z[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()*z.length),u=z[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">
1739
+ </rss>`
1740
+ ];
1741
+ var randomJsonContent = [
1742
+ () => ({
1743
+ message: "Hello from MockHTTP",
1744
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1745
+ status: "success",
1746
+ code: 200
1747
+ }),
1748
+ () => ({
1749
+ user: {
1750
+ id: 1,
1751
+ name: "John Doe",
1752
+ email: "john@example.com",
1753
+ role: "admin",
1754
+ createdAt: "2024-01-15T10:30:00Z"
1755
+ },
1756
+ permissions: [
1757
+ "read",
1758
+ "write",
1759
+ "delete"
1760
+ ]
1761
+ }),
1762
+ () => ({
1763
+ products: [
1764
+ {
1765
+ id: 101,
1766
+ name: "Laptop",
1767
+ price: 999.99,
1768
+ stock: 50
1769
+ },
1770
+ {
1771
+ id: 102,
1772
+ name: "Mouse",
1773
+ price: 29.99,
1774
+ stock: 200
1775
+ }
1776
+ ],
1777
+ total: 2,
1778
+ page: 1
1779
+ }),
1780
+ () => ({
1781
+ config: {
1782
+ theme: "dark",
1783
+ language: "en",
1784
+ notifications: {
1785
+ email: true,
1786
+ push: false,
1787
+ sms: false
1788
+ },
1789
+ privacy: {
1790
+ profile: "public",
1791
+ activity: "friends"
1792
+ }
1793
+ }
1794
+ }),
1795
+ () => ({
1796
+ metrics: {
1797
+ cpu: 45.2,
1798
+ memory: 78.5,
1799
+ disk: 62.3,
1800
+ network: {
1801
+ inbound: "1.2MB/s",
1802
+ outbound: "0.8MB/s"
1803
+ },
1804
+ uptime: "15 days",
1805
+ services: [
1806
+ {
1807
+ name: "api",
1808
+ status: "healthy"
1809
+ },
1810
+ {
1811
+ name: "database",
1812
+ status: "healthy"
1813
+ },
1814
+ {
1815
+ name: "cache",
1816
+ status: "warning"
1817
+ }
1818
+ ]
1819
+ }
1820
+ })
1821
+ ];
1822
+ var responseFormatRoutes = /* @__PURE__ */ __name((fastify) => {
1823
+ const plainHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1824
+ const randomIndex = Math.floor(Math.random() * randomTexts.length);
1825
+ const text = randomTexts[randomIndex];
1826
+ reply.type("text/plain");
1827
+ return text;
1828
+ }, "plainHandler");
1829
+ fastify.get("/plain", {
1830
+ schema: plainSchema
1831
+ }, plainHandler);
1832
+ fastify.post("/plain", {
1833
+ schema: plainSchema
1834
+ }, plainHandler);
1835
+ fastify.put("/plain", {
1836
+ schema: plainSchema
1837
+ }, plainHandler);
1838
+ fastify.patch("/plain", {
1839
+ schema: plainSchema
1840
+ }, plainHandler);
1841
+ fastify.delete("/plain", {
1842
+ schema: plainSchema
1843
+ }, plainHandler);
1844
+ const textHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1845
+ const randomIndex = Math.floor(Math.random() * randomTexts.length);
1846
+ const text = randomTexts[randomIndex];
1847
+ reply.type("text/plain");
1848
+ return text;
1849
+ }, "textHandler");
1850
+ fastify.get("/text", {
1851
+ schema: textSchema
1852
+ }, textHandler);
1853
+ fastify.post("/text", {
1854
+ schema: textSchema
1855
+ }, textHandler);
1856
+ fastify.put("/text", {
1857
+ schema: textSchema
1858
+ }, textHandler);
1859
+ fastify.patch("/text", {
1860
+ schema: textSchema
1861
+ }, textHandler);
1862
+ fastify.delete("/text", {
1863
+ schema: textSchema
1864
+ }, textHandler);
1865
+ const htmlHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1866
+ const randomIndex = Math.floor(Math.random() * randomHtmlContent.length);
1867
+ const html = randomHtmlContent[randomIndex];
1868
+ reply.type("text/html");
1869
+ return html;
1870
+ }, "htmlHandler");
1871
+ fastify.get("/html", {
1872
+ schema: htmlSchema
1873
+ }, htmlHandler);
1874
+ fastify.post("/html", {
1875
+ schema: htmlSchema
1876
+ }, htmlHandler);
1877
+ fastify.put("/html", {
1878
+ schema: htmlSchema
1879
+ }, htmlHandler);
1880
+ fastify.patch("/html", {
1881
+ schema: htmlSchema
1882
+ }, htmlHandler);
1883
+ fastify.delete("/html", {
1884
+ schema: htmlSchema
1885
+ }, htmlHandler);
1886
+ const xmlHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1887
+ const randomIndex = Math.floor(Math.random() * randomXmlContent.length);
1888
+ const xml = randomXmlContent[randomIndex];
1889
+ reply.type("application/xml");
1890
+ return xml;
1891
+ }, "xmlHandler");
1892
+ fastify.get("/xml", {
1893
+ schema: xmlSchema
1894
+ }, xmlHandler);
1895
+ fastify.post("/xml", {
1896
+ schema: xmlSchema
1897
+ }, xmlHandler);
1898
+ fastify.put("/xml", {
1899
+ schema: xmlSchema
1900
+ }, xmlHandler);
1901
+ fastify.patch("/xml", {
1902
+ schema: xmlSchema
1903
+ }, xmlHandler);
1904
+ fastify.delete("/xml", {
1905
+ schema: xmlSchema
1906
+ }, xmlHandler);
1907
+ const jsonHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1908
+ const randomIndex = Math.floor(Math.random() * randomJsonContent.length);
1909
+ const jsonGenerator = randomJsonContent[randomIndex];
1910
+ const json = jsonGenerator();
1911
+ reply.type("application/json");
1912
+ return json;
1913
+ }, "jsonHandler");
1914
+ fastify.get("/json", {
1915
+ schema: jsonSchema
1916
+ }, jsonHandler);
1917
+ fastify.post("/json", {
1918
+ schema: jsonSchema
1919
+ }, jsonHandler);
1920
+ fastify.put("/json", {
1921
+ schema: jsonSchema
1922
+ }, jsonHandler);
1923
+ fastify.patch("/json", {
1924
+ schema: jsonSchema
1925
+ }, jsonHandler);
1926
+ fastify.delete("/json", {
1927
+ schema: jsonSchema
1928
+ }, jsonHandler);
1929
+ const denyHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1930
+ const denyMessages = [
1931
+ `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
206
1932
  <html><head>
207
1933
  <title>403 Forbidden</title>
208
1934
  </head><body>
209
1935
  <h1>Forbidden</h1>
210
1936
  <p>Access denied by robots.txt rules.</p>
211
1937
  <p>The requested resource is disallowed by robots.txt.</p>
212
- </body></html>`,`<!DOCTYPE html>
1938
+ </body></html>`,
1939
+ `<!DOCTYPE html>
213
1940
  <html lang="en">
214
1941
  <head>
215
1942
  <meta charset="UTF-8">
@@ -218,9 +1945,10 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
218
1945
  <body>
219
1946
  <h1>403 Forbidden</h1>
220
1947
  <p>This page is blocked by robots.txt rules.</p>
221
- <p>User-agent: *<br>Disallow: ${l.url}</p>
1948
+ <p>User-agent: *<br>Disallow: ${_request.url}</p>
222
1949
  </body>
223
- </html>`,`<!DOCTYPE html>
1950
+ </html>`,
1951
+ `<!DOCTYPE html>
224
1952
  <html>
225
1953
  <head>
226
1954
  <title>403 - Access Denied</title>
@@ -232,14 +1960,847 @@ var $e=Object.defineProperty;var r=(s,e)=>$e(s,"name",{value:e,configurable:!0})
232
1960
  <hr>
233
1961
  <small>MockHTTP Server</small>
234
1962
  </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:$},g),s.post("/deflate",{schema:$},g),s.put("/deflate",{schema:$},g),s.patch("/deflate",{schema:$},g),s.delete("/deflate",{schema:$},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:D},h),s.post("/brotli",{schema:D},h),s.put("/brotli",{schema:D},h),s.patch("/brotli",{schema:D},h),s.delete("/brotli",{schema:D},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"?>
1963
+ </html>`
1964
+ ];
1965
+ const randomIndex = Math.floor(Math.random() * denyMessages.length);
1966
+ const html = denyMessages[randomIndex];
1967
+ reply.code(403);
1968
+ reply.type("text/html");
1969
+ return html;
1970
+ }, "denyHandler");
1971
+ fastify.get("/deny", {
1972
+ schema: denySchema
1973
+ }, denyHandler);
1974
+ fastify.post("/deny", {
1975
+ schema: denySchema
1976
+ }, denyHandler);
1977
+ fastify.put("/deny", {
1978
+ schema: denySchema
1979
+ }, denyHandler);
1980
+ fastify.patch("/deny", {
1981
+ schema: denySchema
1982
+ }, denyHandler);
1983
+ fastify.delete("/deny", {
1984
+ schema: denySchema
1985
+ }, denyHandler);
1986
+ const gzipHandler = /* @__PURE__ */ __name(async (_request, reply) => {
1987
+ const sampleData = [
1988
+ {
1989
+ message: "This is GZip compressed data from MockHTTP",
1990
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1991
+ compression: "gzip",
1992
+ originalSize: 0,
1993
+ compressedSize: 0
1994
+ },
1995
+ {
1996
+ title: "Sample Document",
1997
+ content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50),
1998
+ metadata: {
1999
+ author: "MockHTTP Server",
2000
+ format: "gzip",
2001
+ version: "1.0"
2002
+ }
2003
+ },
2004
+ {
2005
+ data: Array.from({
2006
+ length: 100
2007
+ }, (_, i) => ({
2008
+ id: i + 1,
2009
+ value: `Item ${i + 1}`,
2010
+ description: `This is item number ${i + 1} in the dataset`
2011
+ })),
2012
+ info: "Large dataset compressed with GZip"
2013
+ }
2014
+ ];
2015
+ const randomIndex = Math.floor(Math.random() * sampleData.length);
2016
+ const data = sampleData[randomIndex];
2017
+ const jsonString = JSON.stringify(data);
2018
+ if (data.originalSize !== void 0) {
2019
+ data.originalSize = Buffer.byteLength(jsonString);
2020
+ }
2021
+ const compressed = gzipSync(jsonString);
2022
+ if (data.compressedSize !== void 0) {
2023
+ const updatedData = {
2024
+ ...data,
2025
+ compressedSize: compressed.length
2026
+ };
2027
+ const updatedJsonString = JSON.stringify(updatedData);
2028
+ const updatedCompressed = gzipSync(updatedJsonString);
2029
+ reply.header("Content-Encoding", "gzip");
2030
+ reply.header("Content-Type", "application/json");
2031
+ reply.type("application/octet-stream");
2032
+ return updatedCompressed;
2033
+ }
2034
+ reply.header("Content-Encoding", "gzip");
2035
+ reply.header("Content-Type", "application/json");
2036
+ reply.type("application/octet-stream");
2037
+ return compressed;
2038
+ }, "gzipHandler");
2039
+ fastify.get("/gzip", {
2040
+ schema: gzipSchema
2041
+ }, gzipHandler);
2042
+ fastify.post("/gzip", {
2043
+ schema: gzipSchema
2044
+ }, gzipHandler);
2045
+ fastify.put("/gzip", {
2046
+ schema: gzipSchema
2047
+ }, gzipHandler);
2048
+ fastify.patch("/gzip", {
2049
+ schema: gzipSchema
2050
+ }, gzipHandler);
2051
+ fastify.delete("/gzip", {
2052
+ schema: gzipSchema
2053
+ }, gzipHandler);
2054
+ const deflateHandler = /* @__PURE__ */ __name(async (_request, reply) => {
2055
+ const sampleData = [
2056
+ {
2057
+ message: "This is Deflate compressed data from MockHTTP",
2058
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2059
+ compression: "deflate",
2060
+ originalSize: 0,
2061
+ compressedSize: 0
2062
+ },
2063
+ {
2064
+ title: "Deflate Compressed Document",
2065
+ content: "The deflate algorithm is used for compression. ".repeat(40),
2066
+ details: {
2067
+ algorithm: "DEFLATE",
2068
+ method: "zlib deflate",
2069
+ server: "MockHTTP"
2070
+ }
2071
+ },
2072
+ {
2073
+ items: Array.from({
2074
+ length: 50
2075
+ }, (_, i) => ({
2076
+ index: i,
2077
+ name: `Entry ${i}`,
2078
+ data: `This is test data for entry number ${i}`,
2079
+ timestamp: new Date(Date.now() - i * 1e6).toISOString()
2080
+ })),
2081
+ metadata: {
2082
+ compression: "deflate",
2083
+ count: 50
2084
+ }
2085
+ }
2086
+ ];
2087
+ const randomIndex = Math.floor(Math.random() * sampleData.length);
2088
+ const data = sampleData[randomIndex];
2089
+ const jsonString = JSON.stringify(data);
2090
+ if (data.originalSize !== void 0) {
2091
+ data.originalSize = Buffer.byteLength(jsonString);
2092
+ }
2093
+ const compressed = deflateSync(jsonString);
2094
+ if (data.compressedSize !== void 0) {
2095
+ const updatedData = {
2096
+ ...data,
2097
+ compressedSize: compressed.length
2098
+ };
2099
+ const updatedJsonString = JSON.stringify(updatedData);
2100
+ const updatedCompressed = deflateSync(updatedJsonString);
2101
+ reply.header("Content-Encoding", "deflate");
2102
+ reply.header("Content-Type", "application/json");
2103
+ reply.type("application/octet-stream");
2104
+ return updatedCompressed;
2105
+ }
2106
+ reply.header("Content-Encoding", "deflate");
2107
+ reply.header("Content-Type", "application/json");
2108
+ reply.type("application/octet-stream");
2109
+ return compressed;
2110
+ }, "deflateHandler");
2111
+ fastify.get("/deflate", {
2112
+ schema: deflateSchema
2113
+ }, deflateHandler);
2114
+ fastify.post("/deflate", {
2115
+ schema: deflateSchema
2116
+ }, deflateHandler);
2117
+ fastify.put("/deflate", {
2118
+ schema: deflateSchema
2119
+ }, deflateHandler);
2120
+ fastify.patch("/deflate", {
2121
+ schema: deflateSchema
2122
+ }, deflateHandler);
2123
+ fastify.delete("/deflate", {
2124
+ schema: deflateSchema
2125
+ }, deflateHandler);
2126
+ const brotliHandler = /* @__PURE__ */ __name(async (_request, reply) => {
2127
+ const sampleData = [
2128
+ {
2129
+ message: "This is Brotli compressed data from MockHTTP",
2130
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2131
+ compression: "br",
2132
+ algorithm: "Brotli",
2133
+ originalSize: 0,
2134
+ compressedSize: 0
2135
+ },
2136
+ {
2137
+ title: "Brotli Compression Example",
2138
+ 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.",
2139
+ content: "Brotli compression typically achieves 20-26% better compression ratios than gzip. ".repeat(10),
2140
+ metadata: {
2141
+ encoding: "br",
2142
+ quality: 11,
2143
+ server: "MockHTTP"
2144
+ }
2145
+ },
2146
+ {
2147
+ benchmark: {
2148
+ title: "Compression Benchmark Data",
2149
+ results: Array.from({
2150
+ length: 75
2151
+ }, (_, i) => ({
2152
+ id: `test-${i}`,
2153
+ name: `Benchmark Test ${i}`,
2154
+ value: Math.random() * 1e3,
2155
+ unit: "ms",
2156
+ timestamp: new Date(Date.now() - i * 6e4).toISOString()
2157
+ })),
2158
+ summary: {
2159
+ compression: "brotli",
2160
+ totalTests: 75,
2161
+ encoding: "br"
2162
+ }
2163
+ }
2164
+ }
2165
+ ];
2166
+ const randomIndex = Math.floor(Math.random() * sampleData.length);
2167
+ const data = sampleData[randomIndex];
2168
+ const jsonString = JSON.stringify(data);
2169
+ if (data.originalSize !== void 0) {
2170
+ data.originalSize = Buffer.byteLength(jsonString);
2171
+ }
2172
+ const compressed = brotliCompressSync(jsonString);
2173
+ if (data.compressedSize !== void 0) {
2174
+ const updatedData = {
2175
+ ...data,
2176
+ compressedSize: compressed.length
2177
+ };
2178
+ const updatedJsonString = JSON.stringify(updatedData);
2179
+ const updatedCompressed = brotliCompressSync(updatedJsonString);
2180
+ reply.header("Content-Encoding", "br");
2181
+ reply.header("Content-Type", "application/json");
2182
+ reply.type("application/octet-stream");
2183
+ return updatedCompressed;
2184
+ }
2185
+ reply.header("Content-Encoding", "br");
2186
+ reply.header("Content-Type", "application/json");
2187
+ reply.type("application/octet-stream");
2188
+ return compressed;
2189
+ }, "brotliHandler");
2190
+ fastify.get("/brotli", {
2191
+ schema: brotliSchema
2192
+ }, brotliHandler);
2193
+ fastify.post("/brotli", {
2194
+ schema: brotliSchema
2195
+ }, brotliHandler);
2196
+ fastify.put("/brotli", {
2197
+ schema: brotliSchema
2198
+ }, brotliHandler);
2199
+ fastify.patch("/brotli", {
2200
+ schema: brotliSchema
2201
+ }, brotliHandler);
2202
+ fastify.delete("/brotli", {
2203
+ schema: brotliSchema
2204
+ }, brotliHandler);
2205
+ }, "responseFormatRoutes");
2206
+
2207
+ // src/routes/response-inspection/cache.ts
2208
+ var cacheGetSchema = {
2209
+ description: "Handles cache validation and retrieval.",
2210
+ tags: [
2211
+ "Response Inspection"
2212
+ ],
2213
+ response: {
2214
+ 200: {
2215
+ type: "string",
2216
+ description: "The cached content or resource."
2217
+ },
2218
+ 304: {
2219
+ type: "null",
2220
+ description: "Indicates the resource has not been modified."
2221
+ }
2222
+ }
2223
+ };
2224
+ var cacheSetSchema = {
2225
+ description: "Sets a Cache-Control header for n seconds.",
2226
+ tags: [
2227
+ "Response Inspection"
2228
+ ],
2229
+ params: {
2230
+ type: "object",
2231
+ properties: {
2232
+ value: {
2233
+ type: "integer",
2234
+ description: "Number of seconds to cache the resource."
2235
+ }
2236
+ },
2237
+ required: [
2238
+ "value"
2239
+ ]
2240
+ },
2241
+ response: {
2242
+ 200: {
2243
+ type: "string",
2244
+ description: "Confirmation message for setting cache."
2245
+ }
2246
+ }
2247
+ };
2248
+ var cacheRoutes = /* @__PURE__ */ __name((fastify) => {
2249
+ fastify.get("/cache", {
2250
+ schema: cacheGetSchema
2251
+ }, async (request, reply) => {
2252
+ const ifModifiedSince = request.headers["if-modified-since"];
2253
+ const ifNoneMatch = request.headers["if-none-match"];
2254
+ if (ifModifiedSince ?? ifNoneMatch) {
2255
+ await reply.status(304).send();
2256
+ return;
2257
+ }
2258
+ await reply.send("Cache content or resource response here.");
2259
+ });
2260
+ fastify.get("/cache/:value", {
2261
+ schema: cacheSetSchema
2262
+ }, async (request, reply) => {
2263
+ const { value } = request.params;
2264
+ await reply.header("Cache-Control", `max-age=${value}`).send(`Cache-Control set for ${value} seconds.`);
2265
+ });
2266
+ }, "cacheRoutes");
2267
+
2268
+ // src/routes/response-inspection/etag.ts
2269
+ var etagSchema = {
2270
+ description: "Handles ETag-based conditional requests.",
2271
+ tags: [
2272
+ "Response Inspection"
2273
+ ],
2274
+ params: {
2275
+ type: "object",
2276
+ properties: {
2277
+ etag: {
2278
+ type: "string",
2279
+ description: "The ETag value for the resource."
2280
+ }
2281
+ },
2282
+ required: [
2283
+ "etag"
2284
+ ]
2285
+ },
2286
+ response: {
2287
+ 200: {
2288
+ type: "string",
2289
+ description: "Resource content for matching ETag."
2290
+ },
2291
+ 304: {
2292
+ type: "null",
2293
+ description: "Indicates the resource has not been modified."
2294
+ },
2295
+ 412: {
2296
+ type: "null",
2297
+ description: "Indicates a precondition failed due to mismatched ETag."
2298
+ }
2299
+ }
2300
+ };
2301
+ var etagRoutes = /* @__PURE__ */ __name((fastify) => {
2302
+ fastify.get("/etag/:etag", {
2303
+ schema: etagSchema
2304
+ }, async (request, reply) => {
2305
+ const { etag } = request.params;
2306
+ const ifNoneMatch = request.headers["if-none-match"];
2307
+ const ifMatch = request.headers["if-match"];
2308
+ if (ifNoneMatch === etag) {
2309
+ await reply.status(304).send();
2310
+ return;
2311
+ }
2312
+ if (ifMatch && ifMatch !== etag) {
2313
+ await reply.status(412).send();
2314
+ return;
2315
+ }
2316
+ await reply.header("ETag", etag).send("Resource content for matching ETag.");
2317
+ });
2318
+ }, "etagRoutes");
2319
+
2320
+ // src/routes/response-inspection/response-headers.ts
2321
+ import { escape as escapeHtml } from "html-escaper";
2322
+ var responseHeadersSchema = {
2323
+ description: "Returns a set of response headers based on the query string.",
2324
+ tags: [
2325
+ "Response Inspection"
2326
+ ],
2327
+ querystring: {
2328
+ type: "object",
2329
+ additionalProperties: true,
2330
+ description: "Freeform query string parameters to be added as response headers."
2331
+ },
2332
+ response: {
2333
+ 200: {
2334
+ type: "object",
2335
+ additionalProperties: true,
2336
+ description: "Response headers as per query string parameters."
2337
+ }
2338
+ }
2339
+ };
2340
+ var responseHeadersRoutes = /* @__PURE__ */ __name((fastify) => {
2341
+ fastify.get("/response-headers", {
2342
+ schema: responseHeadersSchema
2343
+ }, async (request, reply) => {
2344
+ const queryParameters = request.query;
2345
+ for (const [key, value] of Object.entries(queryParameters)) {
2346
+ reply.header(key, escapeHtml(value));
2347
+ }
2348
+ const cleanedResponse = Object.fromEntries(Object.entries(queryParameters).map(([key, value]) => [
2349
+ key,
2350
+ escapeHtml(value)
2351
+ ]));
2352
+ await reply.send(cleanedResponse);
2353
+ });
2354
+ fastify.post("/response-headers", {
2355
+ schema: responseHeadersSchema
2356
+ }, async (request, reply) => {
2357
+ const queryParameters = request.query ?? {};
2358
+ const body = request.body ?? {};
2359
+ for (const [key, value] of Object.entries(queryParameters)) {
2360
+ reply.header(key, escapeHtml(value));
2361
+ }
2362
+ for (const [key, value] of Object.entries(body)) {
2363
+ reply.header(key, escapeHtml(value));
2364
+ }
2365
+ const cleanedResponse = Object.fromEntries(Object.entries({
2366
+ ...queryParameters,
2367
+ ...body
2368
+ }).map(([key, value]) => [
2369
+ key,
2370
+ escapeHtml(value)
2371
+ ]));
2372
+ await reply.send(cleanedResponse);
2373
+ });
2374
+ }, "responseHeadersRoutes");
2375
+
2376
+ // src/routes/sitemap.ts
2377
+ var sitemapRoute = /* @__PURE__ */ __name((fastify) => {
2378
+ fastify.get("/sitemap.xml", {
2379
+ schema: {
2380
+ hide: true
2381
+ }
2382
+ }, async (_request, reply) => {
2383
+ try {
2384
+ const host = _request.headers.host ?? "mockhttp.org";
2385
+ const isSecure = _request.headers["x-forwarded-proto"]?.includes("https");
2386
+ const protocol = isSecure ? "https" : "http";
2387
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
236
2388
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
237
2389
  <url>
238
- <loc>${e.headers["x-forwarded-proto"]?.includes("https")?"https":"http"}://${o}/</loc>
2390
+ <loc>${protocol}://${host}/</loc>
239
2391
  <changefreq>monthly</changefreq>
240
2392
  <priority>1.0</priority>
241
2393
  </url>
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.1",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=`
2394
+ </urlset>`;
2395
+ await reply.type("text/xml").send(xml);
2396
+ } catch (error) {
2397
+ fastify.log.error(error);
2398
+ await reply.status(500).send({
2399
+ error: "Internal Server Error"
2400
+ });
2401
+ }
2402
+ });
2403
+ }, "sitemapRoute");
2404
+
2405
+ // src/routes/status-codes/index.ts
2406
+ import { randomInt } from "crypto";
2407
+ var statusCodes = [
2408
+ {
2409
+ code: 100,
2410
+ message: "Continue"
2411
+ },
2412
+ {
2413
+ code: 101,
2414
+ message: "Switching Protocols"
2415
+ },
2416
+ {
2417
+ code: 102,
2418
+ message: "Processing"
2419
+ },
2420
+ {
2421
+ code: 103,
2422
+ message: "Early Hints"
2423
+ },
2424
+ {
2425
+ code: 200,
2426
+ message: "OK"
2427
+ },
2428
+ {
2429
+ code: 201,
2430
+ message: "Created"
2431
+ },
2432
+ {
2433
+ code: 202,
2434
+ message: "Accepted"
2435
+ },
2436
+ {
2437
+ code: 203,
2438
+ message: "Non-Authoritative Information"
2439
+ },
2440
+ {
2441
+ code: 204,
2442
+ message: "No Content"
2443
+ },
2444
+ {
2445
+ code: 205,
2446
+ message: "Reset Content"
2447
+ },
2448
+ {
2449
+ code: 206,
2450
+ message: "Partial Content"
2451
+ },
2452
+ {
2453
+ code: 207,
2454
+ message: "Multi-Status"
2455
+ },
2456
+ {
2457
+ code: 208,
2458
+ message: "Already Reported"
2459
+ },
2460
+ {
2461
+ code: 226,
2462
+ message: "IM Used"
2463
+ },
2464
+ {
2465
+ code: 300,
2466
+ message: "Multiple Choices"
2467
+ },
2468
+ {
2469
+ code: 301,
2470
+ message: "Moved Permanently"
2471
+ },
2472
+ {
2473
+ code: 302,
2474
+ message: "Found"
2475
+ },
2476
+ {
2477
+ code: 303,
2478
+ message: "See Other"
2479
+ },
2480
+ {
2481
+ code: 304,
2482
+ message: "Not Modified"
2483
+ },
2484
+ {
2485
+ code: 305,
2486
+ message: "Use Proxy"
2487
+ },
2488
+ {
2489
+ code: 306,
2490
+ message: "Switch Proxy"
2491
+ },
2492
+ {
2493
+ code: 307,
2494
+ message: "Temporary Redirect"
2495
+ },
2496
+ {
2497
+ code: 308,
2498
+ message: "Permanent Redirect"
2499
+ },
2500
+ {
2501
+ code: 400,
2502
+ message: "Bad Request"
2503
+ },
2504
+ {
2505
+ code: 401,
2506
+ message: "Unauthorized"
2507
+ },
2508
+ {
2509
+ code: 402,
2510
+ message: "Payment Required"
2511
+ },
2512
+ {
2513
+ code: 403,
2514
+ message: "Forbidden"
2515
+ },
2516
+ {
2517
+ code: 404,
2518
+ message: "Not Found"
2519
+ },
2520
+ {
2521
+ code: 405,
2522
+ message: "Method Not Allowed"
2523
+ },
2524
+ {
2525
+ code: 406,
2526
+ message: "Not Acceptable"
2527
+ },
2528
+ {
2529
+ code: 407,
2530
+ message: "Proxy Authentication Required"
2531
+ },
2532
+ {
2533
+ code: 408,
2534
+ message: "Request Timeout"
2535
+ },
2536
+ {
2537
+ code: 409,
2538
+ message: "Conflict"
2539
+ },
2540
+ {
2541
+ code: 410,
2542
+ message: "Gone"
2543
+ },
2544
+ {
2545
+ code: 411,
2546
+ message: "Length Required"
2547
+ },
2548
+ {
2549
+ code: 412,
2550
+ message: "Precondition Failed"
2551
+ },
2552
+ {
2553
+ code: 413,
2554
+ message: "Payload Too Large"
2555
+ },
2556
+ {
2557
+ code: 414,
2558
+ message: "URI Too Long"
2559
+ },
2560
+ {
2561
+ code: 415,
2562
+ message: "Unsupported Media Type"
2563
+ },
2564
+ {
2565
+ code: 416,
2566
+ message: "Range Not Satisfiable"
2567
+ },
2568
+ {
2569
+ code: 417,
2570
+ message: "Expectation Failed"
2571
+ },
2572
+ {
2573
+ code: 418,
2574
+ message: "I'm a teapot"
2575
+ },
2576
+ {
2577
+ code: 421,
2578
+ message: "Misdirected Request"
2579
+ },
2580
+ {
2581
+ code: 422,
2582
+ message: "Unprocessable Entity"
2583
+ },
2584
+ {
2585
+ code: 423,
2586
+ message: "Locked"
2587
+ },
2588
+ {
2589
+ code: 424,
2590
+ message: "Failed Dependency"
2591
+ },
2592
+ {
2593
+ code: 425,
2594
+ message: "Too Early"
2595
+ },
2596
+ {
2597
+ code: 426,
2598
+ message: "Upgrade Required"
2599
+ },
2600
+ {
2601
+ code: 428,
2602
+ message: "Precondition Required"
2603
+ },
2604
+ {
2605
+ code: 429,
2606
+ message: "Too Many Requests"
2607
+ },
2608
+ {
2609
+ code: 431,
2610
+ message: "Request Header Fields Too Large"
2611
+ },
2612
+ {
2613
+ code: 451,
2614
+ message: "Unavailable For Legal Reasons"
2615
+ },
2616
+ {
2617
+ code: 500,
2618
+ message: "Internal Server Error"
2619
+ },
2620
+ {
2621
+ code: 501,
2622
+ message: "Not Implemented"
2623
+ },
2624
+ {
2625
+ code: 502,
2626
+ message: "Bad Gateway"
2627
+ },
2628
+ {
2629
+ code: 503,
2630
+ message: "Service Unavailable"
2631
+ },
2632
+ {
2633
+ code: 504,
2634
+ message: "Gateway Timeout"
2635
+ },
2636
+ {
2637
+ code: 505,
2638
+ message: "HTTP Version Not Supported"
2639
+ },
2640
+ {
2641
+ code: 506,
2642
+ message: "Variant Also Negotiates"
2643
+ },
2644
+ {
2645
+ code: 507,
2646
+ message: "Insufficient Storage"
2647
+ },
2648
+ {
2649
+ code: 508,
2650
+ message: "Loop Detected"
2651
+ },
2652
+ {
2653
+ code: 510,
2654
+ message: "Not Extended"
2655
+ },
2656
+ {
2657
+ code: 511,
2658
+ message: "Network Authentication Required"
2659
+ }
2660
+ ];
2661
+ var statusSchema = {
2662
+ description: "Return status code or random status code if more than one are given ",
2663
+ tags: [
2664
+ "Status Codes"
2665
+ ],
2666
+ params: {
2667
+ type: "object",
2668
+ properties: {
2669
+ code: {
2670
+ type: "string",
2671
+ description: "HTTP status code to be returned"
2672
+ }
2673
+ }
2674
+ },
2675
+ response: {
2676
+ 200: {
2677
+ type: "object",
2678
+ properties: {
2679
+ status: {
2680
+ type: "string"
2681
+ }
2682
+ }
2683
+ }
2684
+ }
2685
+ };
2686
+ var statusCodeRoute = /* @__PURE__ */ __name((fastify) => {
2687
+ const handler = /* @__PURE__ */ __name(async (request, reply) => {
2688
+ let { code } = request.params;
2689
+ if (!code || !statusCodes.map((status) => status.code).includes(Number(code))) {
2690
+ code = statusCodes[randomInt(0, statusCodes.length)].code.toString();
2691
+ }
2692
+ await reply.code(Number(code)).send({
2693
+ status: `Response with status code ${code}`
2694
+ });
2695
+ }, "handler");
2696
+ fastify.get("/status/:code?", {
2697
+ schema: statusSchema
2698
+ }, handler);
2699
+ fastify.post("/status/:code?", {
2700
+ schema: statusSchema
2701
+ }, handler);
2702
+ fastify.put("/status/:code?", {
2703
+ schema: statusSchema
2704
+ }, handler);
2705
+ fastify.patch("/status/:code?", {
2706
+ schema: statusSchema
2707
+ }, handler);
2708
+ fastify.delete("/status/:code?", {
2709
+ schema: statusSchema
2710
+ }, handler);
2711
+ }, "statusCodeRoute");
2712
+
2713
+ // src/swagger.ts
2714
+ import { fastifySwaggerUi } from "@fastify/swagger-ui";
2715
+
2716
+ // package.json
2717
+ var package_default = {
2718
+ name: "@jaredwray/mockhttp",
2719
+ version: "1.2.0",
2720
+ description: "Mock Http - Easy way to mock http with httpbin replacement",
2721
+ type: "module",
2722
+ main: "dist/index.js",
2723
+ directories: {
2724
+ test: "test"
2725
+ },
2726
+ scripts: {
2727
+ lint: "biome check --write --error-on-warnings",
2728
+ test: "pnpm lint && vitest run --coverage",
2729
+ "test:ci": "biome check --error-on-warnings && vitest run --coverage",
2730
+ dev: "pnpm lint && tsx src/index.ts --watch",
2731
+ build: "rimraf ./dist && tsup src/index.ts --format esm --dts --clean",
2732
+ "docker:build": "docker build -t jaredwray/mockhttp .",
2733
+ "docker:run": "docker run -p 3000:3000 jaredwray/mockhttp",
2734
+ "docker:push": "docker push jaredwray/mockhttp",
2735
+ "docker:stop": 'docker stop $(docker ps -a -q --filter ancestor=jaredwray/mockhttp --format="{{.ID}}")',
2736
+ "docker:readme": "tsx ./scripts/create-docker-readme.ts",
2737
+ "docker:compose:up": "docker-compose up -d",
2738
+ "docker:compose:down": "docker-compose down",
2739
+ prepare: "pnpm run build",
2740
+ start: "node dist/index.js"
2741
+ },
2742
+ repository: {
2743
+ type: "git",
2744
+ url: "git+https://github.com/jaredwray/mockhttp.git"
2745
+ },
2746
+ keywords: [
2747
+ "mock",
2748
+ "http",
2749
+ "httpbin",
2750
+ "testing",
2751
+ "api",
2752
+ "fastify",
2753
+ "docker",
2754
+ "typescript",
2755
+ "rest",
2756
+ "mock-server",
2757
+ "api-testing",
2758
+ "development",
2759
+ "http-testing",
2760
+ "httpbin-alternative",
2761
+ "endpoints"
2762
+ ],
2763
+ author: "Jared Wray <me@jaredwray.com>",
2764
+ license: "MIT",
2765
+ bugs: {
2766
+ url: "https://github.com/jaredwray/mockhttp/issues"
2767
+ },
2768
+ homepage: "https://github.com/jaredwray/mockhttp#readme",
2769
+ devDependencies: {
2770
+ "@biomejs/biome": "^2.3.8",
2771
+ "@swc/core": "^1.15.3",
2772
+ "@types/html-escaper": "^3.0.4",
2773
+ "@vitest/coverage-v8": "^4.0.15",
2774
+ rimraf: "^6.1.2",
2775
+ tsup: "^8.5.1",
2776
+ tsx: "^4.21.0",
2777
+ typescript: "^5.9.3",
2778
+ vitest: "^4.0.15"
2779
+ },
2780
+ dependencies: {
2781
+ "@fastify/cookie": "^11.0.2",
2782
+ "@fastify/helmet": "^13.0.2",
2783
+ "@fastify/rate-limit": "^10.3.0",
2784
+ "@fastify/static": "^8.3.0",
2785
+ "@fastify/swagger": "^9.6.1",
2786
+ "@fastify/swagger-ui": "^5.2.3",
2787
+ "@scalar/api-reference": "^1.40.4",
2788
+ "detect-port": "^2.1.0",
2789
+ fastify: "^5.6.2",
2790
+ hookified: "^1.14.0",
2791
+ "html-escaper": "^3.0.3",
2792
+ pino: "^10.1.0",
2793
+ "pino-pretty": "^13.1.3"
2794
+ },
2795
+ files: [
2796
+ "dist",
2797
+ "public",
2798
+ "LICENCE"
2799
+ ]
2800
+ };
2801
+
2802
+ // src/swagger.ts
2803
+ var description = `
243
2804
  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\`.
244
2805
 
245
2806
  * [GitHub Repository](https://github.com/jaredwray/mockhttp)
@@ -249,4 +2810,628 @@ A simple HTTP server that can be used to mock HTTP responses for testing purpose
249
2810
  # About mockhttp.org
250
2811
 
251
2812
  [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.
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(J),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(G),await t.register(W)}async registerAnythingRoutes(e){await(e??this._server).register(de)}async registerAuthRoutes(e){let t=e??this._server;await t.register(E),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};
2813
+ `;
2814
+ var fastifySwaggerConfig = {
2815
+ openapi: {
2816
+ info: {
2817
+ title: "Mock HTTP API",
2818
+ description,
2819
+ version: package_default.version
2820
+ },
2821
+ consumes: [
2822
+ "application/json"
2823
+ ],
2824
+ produces: [
2825
+ "application/json"
2826
+ ]
2827
+ }
2828
+ };
2829
+ var registerSwaggerUi = /* @__PURE__ */ __name(async (fastify) => {
2830
+ await fastify.register(fastifySwaggerUi, {
2831
+ routePrefix: "/docs",
2832
+ uiConfig: {
2833
+ docExpansion: "none",
2834
+ deepLinking: false
2835
+ },
2836
+ uiHooks: {
2837
+ /* v8 ignore next -- @preserve */
2838
+ onRequest(_request, _reply, next) {
2839
+ next();
2840
+ },
2841
+ /* v8 ignore next -- @preserve */
2842
+ preHandler(_request, _reply, next) {
2843
+ next();
2844
+ }
2845
+ },
2846
+ staticCSP: true,
2847
+ /* v8 ignore next -- @preserve */
2848
+ transformSpecification: /* @__PURE__ */ __name((swaggerObject, _request, _reply) => swaggerObject, "transformSpecification"),
2849
+ transformSpecificationClone: true
2850
+ });
2851
+ }, "registerSwaggerUi");
2852
+
2853
+ // src/tap-manager.ts
2854
+ import { randomUUID } from "crypto";
2855
+ var TapManager = class {
2856
+ static {
2857
+ __name(this, "TapManager");
2858
+ }
2859
+ _injections;
2860
+ constructor() {
2861
+ this._injections = /* @__PURE__ */ new Map();
2862
+ }
2863
+ /**
2864
+ * Get all active injection taps
2865
+ */
2866
+ get injections() {
2867
+ return this._injections;
2868
+ }
2869
+ /**
2870
+ * Check if there are any active injections
2871
+ */
2872
+ get hasInjections() {
2873
+ return this._injections.size > 0;
2874
+ }
2875
+ /**
2876
+ * Inject a custom response for requests matching the given criteria
2877
+ * @param response - The response configuration or a function that returns it
2878
+ * @param matcher - Optional criteria to match requests
2879
+ * @returns A tap object that can be used to remove the injection
2880
+ */
2881
+ inject(response, matcher) {
2882
+ const id = randomUUID();
2883
+ const tap = {
2884
+ id,
2885
+ response,
2886
+ matcher
2887
+ };
2888
+ this._injections.set(id, tap);
2889
+ return tap;
2890
+ }
2891
+ /**
2892
+ * Remove an injection by its tap object or ID
2893
+ * @param tapOrId - The tap object or tap ID to remove
2894
+ * @returns true if the injection was removed, false if it wasn't found
2895
+ */
2896
+ removeInjection(tapOrId) {
2897
+ const id = typeof tapOrId === "string" ? tapOrId : tapOrId.id;
2898
+ return this._injections.delete(id);
2899
+ }
2900
+ /**
2901
+ * Find the first injection that matches the given request
2902
+ * @param request - The Fastify request object
2903
+ * @returns The matching injection tap, or undefined if no match
2904
+ */
2905
+ matchRequest(request) {
2906
+ for (const tap of this._injections.values()) {
2907
+ if (this.requestMatches(request, tap.matcher)) {
2908
+ return tap;
2909
+ }
2910
+ }
2911
+ return void 0;
2912
+ }
2913
+ /**
2914
+ * Clear all injections
2915
+ */
2916
+ clear() {
2917
+ this._injections.clear();
2918
+ }
2919
+ /**
2920
+ * Check if a request matches the given matcher criteria
2921
+ * @param request - The Fastify request object
2922
+ * @param matcher - The matcher criteria (undefined matches all requests)
2923
+ * @returns true if the request matches
2924
+ */
2925
+ requestMatches(request, matcher) {
2926
+ if (!matcher) {
2927
+ return true;
2928
+ }
2929
+ if (matcher.url) {
2930
+ if (!this.urlMatches(request.url, matcher.url)) {
2931
+ return false;
2932
+ }
2933
+ }
2934
+ if (matcher.hostname) {
2935
+ if (request.hostname !== matcher.hostname) {
2936
+ return false;
2937
+ }
2938
+ }
2939
+ if (matcher.method) {
2940
+ if (request.method.toUpperCase() !== matcher.method.toUpperCase()) {
2941
+ return false;
2942
+ }
2943
+ }
2944
+ if (matcher.headers) {
2945
+ for (const [key, value] of Object.entries(matcher.headers)) {
2946
+ const requestHeader = request.headers[key.toLowerCase()];
2947
+ if (requestHeader !== value) {
2948
+ return false;
2949
+ }
2950
+ }
2951
+ }
2952
+ return true;
2953
+ }
2954
+ /**
2955
+ * Check if a URL matches a pattern (supports wildcards with *)
2956
+ * @param url - The URL to test
2957
+ * @param pattern - The pattern to match against
2958
+ * @returns true if the URL matches the pattern
2959
+ */
2960
+ urlMatches(url, pattern) {
2961
+ const urlPath = url.split("?")[0];
2962
+ if (pattern === urlPath) {
2963
+ return true;
2964
+ }
2965
+ if (pattern.includes("*")) {
2966
+ const regexPattern = pattern.split("*").map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
2967
+ const regex = new RegExp(`^${regexPattern}$`);
2968
+ return regex.test(urlPath);
2969
+ }
2970
+ return false;
2971
+ }
2972
+ };
2973
+
2974
+ // src/mock-http.ts
2975
+ var MockHttp = class extends Hookified {
2976
+ static {
2977
+ __name(this, "MockHttp");
2978
+ }
2979
+ _port = 3e3;
2980
+ _host = "0.0.0.0";
2981
+ _autoDetectPort = true;
2982
+ _helmet = true;
2983
+ _apiDocs = true;
2984
+ _logging = true;
2985
+ _httpBin = {
2986
+ httpMethods: true,
2987
+ redirects: true,
2988
+ requestInspection: true,
2989
+ responseInspection: true,
2990
+ responseFormats: true,
2991
+ statusCodes: true,
2992
+ cookies: true,
2993
+ anything: true,
2994
+ auth: true,
2995
+ images: true
2996
+ };
2997
+ _rateLimit = {
2998
+ max: 1e3,
2999
+ timeWindow: "1 minute",
3000
+ allowList: [
3001
+ "127.0.0.1",
3002
+ "::1"
3003
+ ]
3004
+ };
3005
+ _server = Fastify();
3006
+ _taps = new TapManager();
3007
+ constructor(options) {
3008
+ super(options?.hookOptions);
3009
+ if (options?.port) {
3010
+ this._port = options.port;
3011
+ }
3012
+ if (options?.host) {
3013
+ this._host = options.host;
3014
+ }
3015
+ if (options?.helmet !== void 0) {
3016
+ this._helmet = options.helmet;
3017
+ }
3018
+ if (options?.apiDocs !== void 0) {
3019
+ this._apiDocs = options.apiDocs;
3020
+ }
3021
+ if (options?.httpBin !== void 0) {
3022
+ this._httpBin = options.httpBin;
3023
+ }
3024
+ if (options?.rateLimit !== void 0) {
3025
+ if (options.rateLimit === false) {
3026
+ this._rateLimit = void 0;
3027
+ } else {
3028
+ this._rateLimit = options.rateLimit;
3029
+ }
3030
+ }
3031
+ if (options?.logging !== void 0) {
3032
+ this._logging = options.logging;
3033
+ }
3034
+ }
3035
+ /**
3036
+ * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
3037
+ * @default 3000
3038
+ */
3039
+ get port() {
3040
+ return this._port;
3041
+ }
3042
+ /**
3043
+ * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
3044
+ * @default 3000
3045
+ */
3046
+ set port(port) {
3047
+ this._port = port;
3048
+ }
3049
+ /**
3050
+ * The host to listen on. If not provided, defaults to 'localhost'.
3051
+ * @default 'localhost'
3052
+ */
3053
+ get host() {
3054
+ return this._host;
3055
+ }
3056
+ /**
3057
+ * The host to listen on. If not provided, defaults to 'localhost'.
3058
+ * @default 'localhost'
3059
+ */
3060
+ set host(host) {
3061
+ this._host = host;
3062
+ }
3063
+ /**
3064
+ * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
3065
+ * @default true
3066
+ */
3067
+ get autoDetectPort() {
3068
+ return this._autoDetectPort;
3069
+ }
3070
+ /**
3071
+ * Whether to automatically detect the next available port if the provided port is in use. Defaults to true.
3072
+ * @default true
3073
+ */
3074
+ set autoDetectPort(autoDetectPort) {
3075
+ this._autoDetectPort = autoDetectPort;
3076
+ }
3077
+ /**
3078
+ * Whether to use Helmet for security headers. Defaults to true.
3079
+ * @default true
3080
+ */
3081
+ get helmet() {
3082
+ return this._helmet;
3083
+ }
3084
+ /**
3085
+ * Whether to use Helmet for security headers. Defaults to true.
3086
+ * @default true
3087
+ */
3088
+ set helmet(helmet) {
3089
+ this._helmet = helmet;
3090
+ }
3091
+ /**
3092
+ * Whether to use Swagger for API documentation. Defaults to true.
3093
+ * @default true
3094
+ */
3095
+ get apiDocs() {
3096
+ return this._apiDocs;
3097
+ }
3098
+ /**
3099
+ * Whether to use Swagger for API documentation. Defaults to true.
3100
+ * @default true
3101
+ */
3102
+ set apiDocs(apiDocs) {
3103
+ this._apiDocs = apiDocs;
3104
+ }
3105
+ /**
3106
+ * Whether to enable logging. Defaults to true.
3107
+ * @default true
3108
+ */
3109
+ get logging() {
3110
+ return this._logging;
3111
+ }
3112
+ /**
3113
+ * Whether to enable logging. Defaults to true.
3114
+ * @default true
3115
+ */
3116
+ set logging(logging) {
3117
+ this._logging = logging;
3118
+ }
3119
+ /**
3120
+ * HTTP Bin options. Defaults to all enabled.
3121
+ */
3122
+ get httpBin() {
3123
+ return this._httpBin;
3124
+ }
3125
+ /**
3126
+ * HTTP Bin options. Defaults to all enabled.
3127
+ */
3128
+ set httpBin(httpBinary) {
3129
+ this._httpBin = httpBinary;
3130
+ }
3131
+ /**
3132
+ * Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
3133
+ * Set to undefined to disable rate limiting, or provide custom options to configure.
3134
+ * @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
3135
+ */
3136
+ get rateLimit() {
3137
+ return this._rateLimit;
3138
+ }
3139
+ /**
3140
+ * Rate limiting options. Defaults to 1000 requests per minute (localhost excluded).
3141
+ * Set to undefined to disable rate limiting, or provide custom options to configure.
3142
+ *
3143
+ * Note: Changing this property requires restarting the server (close() then start()) for changes to take effect.
3144
+ * @default { max: 1000, timeWindow: "1 minute", allowList: ["127.0.0.1", "::1"] }
3145
+ */
3146
+ set rateLimit(rateLimit) {
3147
+ this._rateLimit = rateLimit;
3148
+ }
3149
+ /**
3150
+ * The Fastify server instance.
3151
+ */
3152
+ get server() {
3153
+ return this._server;
3154
+ }
3155
+ /**
3156
+ * The Fastify server instance.
3157
+ */
3158
+ set server(server) {
3159
+ this._server = server;
3160
+ }
3161
+ /**
3162
+ * The TapManager instance for managing injection taps.
3163
+ */
3164
+ get taps() {
3165
+ return this._taps;
3166
+ }
3167
+ /**
3168
+ * The TapManager instance for managing injection taps.
3169
+ */
3170
+ set taps(taps) {
3171
+ this._taps = taps;
3172
+ }
3173
+ /**
3174
+ * Start the Fastify server. If the server is already running, it will be closed and restarted.
3175
+ */
3176
+ async start() {
3177
+ try {
3178
+ if (this._server) {
3179
+ await this._server.close();
3180
+ }
3181
+ this._server = Fastify(getFastifyConfig(this._logging));
3182
+ this._server.addHook("onRequest", async (request, reply) => {
3183
+ const matchedTap = this._taps.matchRequest(request);
3184
+ if (matchedTap) {
3185
+ const injectionResponse = typeof matchedTap.response === "function" ? matchedTap.response(request) : matchedTap.response;
3186
+ const { response, statusCode = 200, headers } = injectionResponse;
3187
+ reply.code(statusCode);
3188
+ if (headers) {
3189
+ for (const [key, value] of Object.entries(headers)) {
3190
+ reply.header(key, value);
3191
+ }
3192
+ }
3193
+ return reply.send(response);
3194
+ }
3195
+ });
3196
+ await this._server.register(fastifyStatic, {
3197
+ root: path.resolve("./node_modules/@scalar/api-reference/dist"),
3198
+ prefix: "/scalar"
3199
+ });
3200
+ await this.server.register(fastifyStatic, {
3201
+ root: path.resolve("./public"),
3202
+ decorateReply: false
3203
+ });
3204
+ await this._server.register(sitemapRoute);
3205
+ if (this._helmet) {
3206
+ await this._server.register(fastifyHelmet, {
3207
+ contentSecurityPolicy: {
3208
+ directives: {
3209
+ defaultSrc: [
3210
+ "'self'"
3211
+ ],
3212
+ scriptSrc: [
3213
+ "'self'",
3214
+ "'unsafe-inline'",
3215
+ "'wasm-unsafe-eval'"
3216
+ ],
3217
+ styleSrc: [
3218
+ "'self'",
3219
+ "'unsafe-inline'"
3220
+ ],
3221
+ imgSrc: [
3222
+ "'self'",
3223
+ "data:",
3224
+ "https:"
3225
+ ],
3226
+ fontSrc: [
3227
+ "'self'",
3228
+ "data:"
3229
+ ],
3230
+ connectSrc: [
3231
+ "'self'"
3232
+ ],
3233
+ frameSrc: [
3234
+ "'self'"
3235
+ ],
3236
+ objectSrc: [
3237
+ "'none'"
3238
+ ],
3239
+ upgradeInsecureRequests: []
3240
+ }
3241
+ }
3242
+ });
3243
+ }
3244
+ if (this._rateLimit) {
3245
+ await this._server.register(fastifyRateLimit, this._rateLimit);
3246
+ }
3247
+ if (this._apiDocs) {
3248
+ await this.registerApiDocs();
3249
+ }
3250
+ const { httpMethods, redirects, requestInspection, responseInspection, responseFormats, statusCodes: statusCodes2, cookies, anything, auth, images } = this._httpBin;
3251
+ if (httpMethods) {
3252
+ await this.registerHttpMethods();
3253
+ }
3254
+ if (statusCodes2) {
3255
+ await this.registerStatusCodeRoutes();
3256
+ }
3257
+ if (requestInspection) {
3258
+ await this.registerRequestInspectionRoutes();
3259
+ }
3260
+ if (responseInspection) {
3261
+ await this.registerResponseInspectionRoutes();
3262
+ }
3263
+ if (responseFormats) {
3264
+ await this.registerResponseFormatRoutes();
3265
+ }
3266
+ if (redirects) {
3267
+ await this.registerRedirectRoutes();
3268
+ }
3269
+ if (cookies) {
3270
+ await this.registerCookieRoutes();
3271
+ }
3272
+ if (anything) {
3273
+ await this.registerAnythingRoutes();
3274
+ }
3275
+ if (auth) {
3276
+ await this.registerAuthRoutes();
3277
+ }
3278
+ if (images) {
3279
+ await this.registerImageRoutes();
3280
+ }
3281
+ if (this._autoDetectPort) {
3282
+ const originalPort = this._port;
3283
+ this._port = await this.detectPort();
3284
+ if (originalPort !== this._port) {
3285
+ this._server.log.info(`Port ${originalPort} is in use, detected next available port: ${this._port}`);
3286
+ }
3287
+ }
3288
+ await this._server.listen({
3289
+ port: this._port,
3290
+ host: this._host
3291
+ });
3292
+ } catch (error) {
3293
+ this._server.log.error(error);
3294
+ }
3295
+ }
3296
+ /**
3297
+ * Close the Fastify server.
3298
+ */
3299
+ async close() {
3300
+ await this._server.close();
3301
+ }
3302
+ /**
3303
+ * Detect the next available port to run on.
3304
+ * @returns The port that is available to run on
3305
+ */
3306
+ async detectPort() {
3307
+ const { port } = this;
3308
+ return detect(port);
3309
+ }
3310
+ /**
3311
+ * This will register the API documentation routes including openapi and swagger ui.
3312
+ * @param fastifyInstance - the server instance to register the routes on.
3313
+ */
3314
+ async registerApiDocs(fastifyInstance) {
3315
+ const fastify = fastifyInstance ?? this._server;
3316
+ await fastify.register(fastifySwagger, fastifySwaggerConfig);
3317
+ await registerSwaggerUi(fastify);
3318
+ await fastify.register(indexRoute);
3319
+ }
3320
+ /**
3321
+ * Register the HTTP methods routes.
3322
+ * @param fastifyInstance - the server instance to register the routes on.
3323
+ */
3324
+ async registerHttpMethods(fastifyInstance) {
3325
+ const fastify = fastifyInstance ?? this._server;
3326
+ await fastify.register(getRoute);
3327
+ await fastify.register(postRoute);
3328
+ await fastify.register(deleteRoute);
3329
+ await fastify.register(putRoute);
3330
+ await fastify.register(patchRoute);
3331
+ }
3332
+ /**
3333
+ * Register the status code routes.
3334
+ * @param fastifyInstance - the server instance to register the routes on.
3335
+ */
3336
+ async registerStatusCodeRoutes(fastifyInstance) {
3337
+ const fastify = fastifyInstance ?? this._server;
3338
+ await fastify.register(statusCodeRoute);
3339
+ }
3340
+ /**
3341
+ * Register the request inspection routes.
3342
+ * @param fastifyInstance - the server instance to register the routes on.
3343
+ */
3344
+ async registerRequestInspectionRoutes(fastifyInstance) {
3345
+ const fastify = fastifyInstance ?? this._server;
3346
+ await fastify.register(ipRoute);
3347
+ await fastify.register(headersRoute);
3348
+ await fastify.register(userAgentRoute);
3349
+ }
3350
+ /**
3351
+ * Register the response inspection routes.
3352
+ * @param fastifyInstance - the server instance to register the routes on.
3353
+ */
3354
+ async registerResponseInspectionRoutes(fastifyInstance) {
3355
+ const fastify = fastifyInstance ?? this._server;
3356
+ await fastify.register(cacheRoutes);
3357
+ await fastify.register(etagRoutes);
3358
+ await fastify.register(responseHeadersRoutes);
3359
+ }
3360
+ async registerResponseFormatRoutes(fastifyInstance) {
3361
+ const fastify = fastifyInstance ?? this._server;
3362
+ await fastify.register(responseFormatRoutes);
3363
+ }
3364
+ /**
3365
+ * Register the redirect routes.
3366
+ * @param fastifyInstance - the server instance to register the routes on.
3367
+ */
3368
+ async registerRedirectRoutes(fastifyInstance) {
3369
+ const fastify = fastifyInstance ?? this._server;
3370
+ await fastify.register(absoluteRedirectRoute);
3371
+ await fastify.register(relativeRedirectRoute);
3372
+ await fastify.register(redirectToRoute);
3373
+ }
3374
+ /**
3375
+ * Register the cookie routes.
3376
+ * @param fastifyInstance - the server instance to register the routes on.
3377
+ */
3378
+ async registerCookieRoutes(fastifyInstance) {
3379
+ const fastify = fastifyInstance ?? this._server;
3380
+ await fastify.register(fastifyCookie);
3381
+ await fastify.register(getCookiesRoute);
3382
+ await fastify.register(postCookieRoute);
3383
+ await fastify.register(deleteCookieRoute);
3384
+ }
3385
+ /**
3386
+ * Register the anything routes.
3387
+ * @param fastifyInstance - the server instance to register the routes on.
3388
+ */
3389
+ async registerAnythingRoutes(fastifyInstance) {
3390
+ const fastify = fastifyInstance ?? this._server;
3391
+ await fastify.register(anythingRoute);
3392
+ }
3393
+ /**
3394
+ * Register the auth routes.
3395
+ * @param fastifyInstance - the server instance to register the routes on.
3396
+ */
3397
+ async registerAuthRoutes(fastifyInstance) {
3398
+ const fastify = fastifyInstance ?? this._server;
3399
+ await fastify.register(basicAuthRoute);
3400
+ await fastify.register(hiddenBasicAuthRoute);
3401
+ await fastify.register(bearerAuthRoute);
3402
+ await fastify.register(digestAuthRoute);
3403
+ }
3404
+ /**
3405
+ * Register the image routes.
3406
+ * @param fastifyInstance - the server instance to register the routes on.
3407
+ */
3408
+ async registerImageRoutes(fastifyInstance) {
3409
+ const fastify = fastifyInstance ?? this._server;
3410
+ await fastify.register(imageRoutes);
3411
+ }
3412
+ };
3413
+
3414
+ // src/index.ts
3415
+ var start = /* @__PURE__ */ __name(async () => {
3416
+ const mockHttp = new MockHttp();
3417
+ if (process2.env.PORT) {
3418
+ mockHttp.port = Number.parseInt(process2.env.PORT, 10);
3419
+ }
3420
+ if (process2.env.HOST) {
3421
+ mockHttp.host = process2.env.HOST;
3422
+ }
3423
+ if (process2.env.LOGGING === "false") {
3424
+ mockHttp.logging = false;
3425
+ }
3426
+ await mockHttp.start();
3427
+ return mockHttp;
3428
+ }, "start");
3429
+ if (import.meta.url === `file://${process2.argv[1]}`) {
3430
+ await start();
3431
+ }
3432
+ export {
3433
+ MockHttp as default,
3434
+ MockHttp as mockhttp,
3435
+ start
3436
+ };
3437
+ /* v8 ignore next -- @preserve */