@jaredwray/mockhttp 1.8.0 → 1.9.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.
Files changed (3) hide show
  1. package/dist/index.d.mts +45 -22
  2. package/dist/index.mjs +169 -113
  3. package/package.json +17 -17
package/dist/index.d.mts CHANGED
@@ -1,34 +1,51 @@
1
1
  import { RateLimitPluginOptions } from "@fastify/rate-limit";
2
2
  import { FastifyInstance, FastifyRequest } from "fastify";
3
3
  import { Hookified, HookifiedOptions } from "hookified";
4
-
5
4
  //#region src/bin-store.d.ts
6
5
  /**
7
6
  * A request captured by a bin, including method, headers, body and metadata.
8
7
  */
9
8
  type CapturedRequest = {
10
- /** Unique identifier for this captured request */id: string; /** Identifier of the bin that captured this request */
11
- binId: string; /** HTTP method (GET, POST, etc.) */
12
- method: string; /** Request URL path + query, relative to the bin (e.g. "/foo?x=1") */
13
- url: string; /** Sub-path after the bin id ("/" if the request hit the bin root) */
14
- path: string; /** Parsed query string parameters */
15
- query: Record<string, string | string[]>; /** Request headers */
16
- headers: Record<string, string | string[]>; /** Client IP address as reported by Fastify */
17
- remoteAddress: string; /** Content-Type header value, if any */
18
- contentType?: string; /** Original body length in bytes */
19
- bodySize: number; /** Body payload as utf8 text, base64-encoded bytes, or null when empty */
20
- body: string | null; /** Encoding used for the `body` field */
21
- bodyEncoding: "utf8" | "base64" | "none"; /** True if the body was longer than the configured `maxBodySize` */
22
- truncated: boolean; /** ISO timestamp of when the request was captured */
9
+ /** Unique identifier for this captured request */
10
+ id: string;
11
+ /** Identifier of the bin that captured this request */
12
+ binId: string;
13
+ /** HTTP method (GET, POST, etc.) */
14
+ method: string;
15
+ /** Request URL path + query, relative to the bin (e.g. "/foo?x=1") */
16
+ url: string;
17
+ /** Sub-path after the bin id ("/" if the request hit the bin root) */
18
+ path: string;
19
+ /** Parsed query string parameters */
20
+ query: Record<string, string | string[]>;
21
+ /** Request headers */
22
+ headers: Record<string, string | string[]>;
23
+ /** Client IP address as reported by Fastify */
24
+ remoteAddress: string;
25
+ /** Content-Type header value, if any */
26
+ contentType?: string;
27
+ /** Original body length in bytes */
28
+ bodySize: number;
29
+ /** Body payload as utf8 text, base64-encoded bytes, or null when empty */
30
+ body: string | null;
31
+ /** Encoding used for the `body` field */
32
+ bodyEncoding: "utf8" | "base64" | "none";
33
+ /** True if the body was longer than the configured `maxBodySize` */
34
+ truncated: boolean;
35
+ /** ISO timestamp of when the request was captured */
23
36
  capturedAt: string;
24
37
  };
25
38
  /**
26
39
  * Metadata for a bin (without its captured requests).
27
40
  */
28
41
  type Bin = {
29
- /** Unique short identifier for the bin */id: string; /** ISO timestamp of bin creation */
30
- createdAt: string; /** ISO timestamp at which the bin expires */
31
- expiresAt: string; /** Number of requests currently stored in the bin */
42
+ /** Unique short identifier for the bin */
43
+ id: string;
44
+ /** ISO timestamp of bin creation */
45
+ createdAt: string;
46
+ /** ISO timestamp at which the bin expires */
47
+ expiresAt: string;
48
+ /** Number of requests currently stored in the bin */
32
49
  requestCount: number;
33
50
  };
34
51
  /**
@@ -85,11 +102,17 @@ declare class InMemoryBinStore implements BinStore {
85
102
  * Configuration options for {@link BinManager}.
86
103
  */
87
104
  type BinManagerOptions = {
88
- /** Storage backend. Defaults to a new {@link InMemoryBinStore}. */store?: BinStore; /** Bin lifetime in milliseconds. Defaults to 24 hours. */
89
- defaultTtlMs?: number; /** Maximum number of requests retained per bin (FIFO). Defaults to 100. */
90
- maxRequestsPerBin?: number; /** Maximum captured body size in bytes. Larger bodies are truncated. Defaults to 1 MiB. */
91
- maxBodySize?: number; /** Length of generated bin and request ids. Defaults to 12. */
92
- idLength?: number; /** Interval between cleanup sweeps in milliseconds. Defaults to 1 minute. */
105
+ /** Storage backend. Defaults to a new {@link InMemoryBinStore}. */
106
+ store?: BinStore;
107
+ /** Bin lifetime in milliseconds. Defaults to 24 hours. */
108
+ defaultTtlMs?: number;
109
+ /** Maximum number of requests retained per bin (FIFO). Defaults to 100. */
110
+ maxRequestsPerBin?: number;
111
+ /** Maximum captured body size in bytes. Larger bodies are truncated. Defaults to 1 MiB. */
112
+ maxBodySize?: number;
113
+ /** Length of generated bin and request ids. Defaults to 12. */
114
+ idLength?: number;
115
+ /** Interval between cleanup sweeps in milliseconds. Defaults to 1 minute. */
93
116
  cleanupIntervalMs?: number;
94
117
  };
95
118
  /**
package/dist/index.mjs CHANGED
@@ -70,9 +70,9 @@ var InMemoryBinStore = class {
70
70
  };
71
71
  //#endregion
72
72
  //#region src/bin-manager.ts
73
- const DEFAULT_TTL_MS = 1440 * 60 * 1e3;
73
+ const DEFAULT_TTL_MS = 864e5;
74
74
  const DEFAULT_MAX_REQUESTS = 100;
75
- const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
75
+ const DEFAULT_MAX_BODY_SIZE = 1048576;
76
76
  const DEFAULT_ID_LENGTH = 12;
77
77
  const DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
78
78
  /**
@@ -228,18 +228,19 @@ function encodeSet(...elements) {
228
228
  }
229
229
  function encodeInteger(value) {
230
230
  let buf;
231
- if (typeof value === "number") if (value === 0) buf = Buffer.from([0]);
232
- else {
233
- const bytes = [];
234
- let temp = value;
235
- while (temp > 0) {
236
- bytes.unshift(temp & 255);
237
- temp >>= 8;
238
- }
239
- if (bytes[0] & 128) bytes.unshift(0);
240
- buf = Buffer.from(bytes);
241
- }
242
- else if (value.length > 0 && value[0] & 128) buf = Buffer.concat([Buffer.from([0]), value]);
231
+ if (typeof value === "number") {
232
+ if (value === 0) buf = Buffer.from([0]);
233
+ else {
234
+ const bytes = [];
235
+ let temp = value;
236
+ while (temp > 0) {
237
+ bytes.unshift(temp & 255);
238
+ temp >>= 8;
239
+ }
240
+ if (bytes[0] & 128) bytes.unshift(0);
241
+ buf = Buffer.from(bytes);
242
+ }
243
+ } else if (value.length > 0 && value[0] & 128) buf = Buffer.concat([Buffer.from([0]), value]);
243
244
  else buf = value;
244
245
  return encodeTlv(2, buf);
245
246
  }
@@ -690,7 +691,7 @@ const bearerAuthRoute = (fastify) => {
690
691
  //#endregion
691
692
  //#region src/routes/auth/digest.ts
692
693
  const makeNonce = () => crypto.randomBytes(16).toString("hex");
693
- const algorithms = new Set([
694
+ const algorithms = /* @__PURE__ */ new Set([
694
695
  "MD5",
695
696
  "md5",
696
697
  "SHA-256",
@@ -1374,7 +1375,7 @@ function generateSeededBytes$1(n, seed) {
1374
1375
  for (let i = 0; i < n; i++) bytes[i] = Math.floor(random() * 256);
1375
1376
  return bytes;
1376
1377
  }
1377
- const MAX_BYTES$3 = 100 * 1024;
1378
+ const MAX_BYTES$3 = 102400;
1378
1379
  const bytesRoute = (fastify) => {
1379
1380
  fastify.get("/bytes/:n", { schema: bytesSchema }, async (request, reply) => {
1380
1381
  const n = Number.parseInt(request.params.n, 10);
@@ -1501,7 +1502,7 @@ const DEFAULT_CODE = 200;
1501
1502
  const DEFAULT_DELAY = 2;
1502
1503
  const MAX_DURATION = 10;
1503
1504
  const MAX_DELAY = 10;
1504
- const MAX_BYTES$2 = 10 * 1024 * 1024;
1505
+ const MAX_BYTES$2 = 10485760;
1505
1506
  const dripRoute = (fastify) => {
1506
1507
  fastify.get("/drip", { schema: dripSchema }, async (request, reply) => {
1507
1508
  /* v8 ignore next 2 -- @preserve */
@@ -1627,7 +1628,7 @@ const rangeSchema = {
1627
1628
  }
1628
1629
  }
1629
1630
  };
1630
- const MAX_BYTES$1 = 100 * 1024;
1631
+ const MAX_BYTES$1 = 102400;
1631
1632
  function sleep(ms) {
1632
1633
  return new Promise((resolve) => setTimeout(resolve, ms));
1633
1634
  }
@@ -1808,7 +1809,7 @@ function generateSeededBytes(n, seed) {
1808
1809
  for (let i = 0; i < n; i++) bytes[i] = Math.floor(random() * 256);
1809
1810
  return bytes;
1810
1811
  }
1811
- const MAX_BYTES = 100 * 1024;
1812
+ const MAX_BYTES = 102400;
1812
1813
  const DEFAULT_CHUNK_SIZE = 10240;
1813
1814
  const streamBytesRoute = (fastify) => {
1814
1815
  fastify.get("/stream-bytes/:n", { schema: streamBytesSchema }, async (request, reply) => {
@@ -2115,7 +2116,8 @@ const imageRoutes = (fastify) => {
2115
2116
  contentType = "image/png";
2116
2117
  }
2117
2118
  try {
2118
- const imageBuffer = readFileSync(join(publicPath, file));
2119
+ const imagePath = join(publicPath, file);
2120
+ const imageBuffer = readFileSync(imagePath);
2119
2121
  reply.type(contentType);
2120
2122
  return imageBuffer;
2121
2123
  } catch (error) {
@@ -2127,7 +2129,8 @@ const imageRoutes = (fastify) => {
2127
2129
  });
2128
2130
  fastify.get("/image/jpeg", { schema: jpegSchema }, async (_request, reply) => {
2129
2131
  try {
2130
- const imageBuffer = readFileSync(join(publicPath, "logo.jpg"));
2132
+ const imagePath = join(publicPath, "logo.jpg");
2133
+ const imageBuffer = readFileSync(imagePath);
2131
2134
  reply.type("image/jpeg");
2132
2135
  return imageBuffer;
2133
2136
  } catch (error) {
@@ -2139,7 +2142,8 @@ const imageRoutes = (fastify) => {
2139
2142
  });
2140
2143
  fastify.get("/image/png", { schema: pngSchema }, async (_request, reply) => {
2141
2144
  try {
2142
- const imageBuffer = readFileSync(join(publicPath, "logo.png"));
2145
+ const imagePath = join(publicPath, "logo.png");
2146
+ const imageBuffer = readFileSync(imagePath);
2143
2147
  reply.type("image/png");
2144
2148
  return imageBuffer;
2145
2149
  } catch (error) {
@@ -2151,7 +2155,8 @@ const imageRoutes = (fastify) => {
2151
2155
  });
2152
2156
  fastify.get("/image/svg", { schema: svgSchema }, async (_request, reply) => {
2153
2157
  try {
2154
- const imageBuffer = readFileSync(join(publicPath, "logo.svg"));
2158
+ const imagePath = join(publicPath, "logo.svg");
2159
+ const imageBuffer = readFileSync(imagePath);
2155
2160
  reply.type("image/svg+xml");
2156
2161
  return imageBuffer;
2157
2162
  } catch (error) {
@@ -2163,7 +2168,8 @@ const imageRoutes = (fastify) => {
2163
2168
  });
2164
2169
  fastify.get("/image/webp", { schema: webpSchema }, async (_request, reply) => {
2165
2170
  try {
2166
- const imageBuffer = readFileSync(join(publicPath, "logo.webp"));
2171
+ const imagePath = join(publicPath, "logo.webp");
2172
+ const imageBuffer = readFileSync(imagePath);
2167
2173
  reply.type("image/webp");
2168
2174
  return imageBuffer;
2169
2175
  } catch (error) {
@@ -2438,46 +2444,78 @@ const userAgentRoute = (fastify) => {
2438
2444
  };
2439
2445
  //#endregion
2440
2446
  //#region src/routes/response-formats/index.ts
2441
- const plainSchema = {
2442
- description: "Returns random plain text content",
2443
- tags: ["Response Formats"],
2444
- response: { 200: {
2447
+ const templateIndexParams = {
2448
+ type: "object",
2449
+ properties: { index: {
2445
2450
  type: "string",
2446
- description: "Random plain text content"
2451
+ description: "Optional 1-based template index for stable, repeatable output. Omit for a random template."
2447
2452
  } }
2448
2453
  };
2454
+ const templateNotFoundResponse = {
2455
+ type: "object",
2456
+ description: "The requested template index does not exist",
2457
+ properties: { error: { type: "string" } }
2458
+ };
2459
+ const plainSchema = {
2460
+ description: "Returns plain text content. A random template is selected unless a 1-based template index is provided (e.g. /plain/1) for stable output.",
2461
+ tags: ["Response Formats"],
2462
+ params: templateIndexParams,
2463
+ response: {
2464
+ 200: {
2465
+ type: "string",
2466
+ description: "Plain text content"
2467
+ },
2468
+ 404: templateNotFoundResponse
2469
+ }
2470
+ };
2449
2471
  const textSchema = {
2450
- description: "Returns random text content",
2472
+ description: "Returns text content. A random template is selected unless a 1-based template index is provided (e.g. /text/1) for stable output.",
2451
2473
  tags: ["Response Formats"],
2452
- response: { 200: {
2453
- type: "string",
2454
- description: "Random text content"
2455
- } }
2474
+ params: templateIndexParams,
2475
+ response: {
2476
+ 200: {
2477
+ type: "string",
2478
+ description: "Text content"
2479
+ },
2480
+ 404: templateNotFoundResponse
2481
+ }
2456
2482
  };
2457
2483
  const htmlSchema = {
2458
- description: "Returns random HTML content",
2484
+ description: "Returns HTML content. A random template is selected unless a 1-based template index is provided (e.g. /html/1) for stable output.",
2459
2485
  tags: ["Response Formats"],
2460
- response: { 200: {
2461
- type: "string",
2462
- description: "Random HTML content"
2463
- } }
2486
+ params: templateIndexParams,
2487
+ response: {
2488
+ 200: {
2489
+ type: "string",
2490
+ description: "HTML content"
2491
+ },
2492
+ 404: templateNotFoundResponse
2493
+ }
2464
2494
  };
2465
2495
  const xmlSchema = {
2466
- description: "Returns random XML content",
2496
+ description: "Returns XML content. A random template is selected unless a 1-based template index is provided (e.g. /xml/1) for stable output.",
2467
2497
  tags: ["Response Formats"],
2468
- response: { 200: {
2469
- type: "string",
2470
- description: "Random XML content"
2471
- } }
2498
+ params: templateIndexParams,
2499
+ response: {
2500
+ 200: {
2501
+ type: "string",
2502
+ description: "XML content"
2503
+ },
2504
+ 404: templateNotFoundResponse
2505
+ }
2472
2506
  };
2473
2507
  const jsonSchema = {
2474
- description: "Returns random JSON content",
2508
+ description: "Returns JSON content. A random template is selected unless a 1-based template index is provided (e.g. /json/1) for stable output.",
2475
2509
  tags: ["Response Formats"],
2476
- response: { 200: {
2477
- type: "object",
2478
- description: "Random JSON content",
2479
- additionalProperties: true
2480
- } }
2510
+ params: templateIndexParams,
2511
+ response: {
2512
+ 200: {
2513
+ type: "object",
2514
+ description: "JSON content",
2515
+ additionalProperties: true
2516
+ },
2517
+ 404: templateNotFoundResponse
2518
+ }
2481
2519
  };
2482
2520
  const denySchema = {
2483
2521
  description: "Returns page denied by robots.txt rules",
@@ -2613,7 +2651,7 @@ const randomXmlContent = [
2613
2651
  `<?xml version="1.0" encoding="UTF-8"?>
2614
2652
  <root>
2615
2653
  <message>Hello from MockHTTP</message>
2616
- <timestamp>${(/* @__PURE__ */ new Date()).toISOString()}</timestamp>
2654
+ <timestamp>2024-01-15T10:30:00.000Z</timestamp>
2617
2655
  <status>success</status>
2618
2656
  </root>`,
2619
2657
  `<?xml version="1.0" encoding="UTF-8"?>
@@ -2656,19 +2694,19 @@ const randomXmlContent = [
2656
2694
  <title>First Article</title>
2657
2695
  <link>http://example.com/article1</link>
2658
2696
  <description>This is the first article in our feed</description>
2659
- <pubDate>${(/* @__PURE__ */ new Date()).toUTCString()}</pubDate>
2697
+ <pubDate>Mon, 15 Jan 2024 10:30:00 GMT</pubDate>
2660
2698
  </item>
2661
2699
  </channel>
2662
2700
  </rss>`
2663
2701
  ];
2664
2702
  const randomJsonContent = [
2665
- () => ({
2703
+ {
2666
2704
  message: "Hello from MockHTTP",
2667
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2705
+ timestamp: "2024-01-15T10:30:00.000Z",
2668
2706
  status: "success",
2669
2707
  code: 200
2670
- }),
2671
- () => ({
2708
+ },
2709
+ {
2672
2710
  user: {
2673
2711
  id: 1,
2674
2712
  name: "John Doe",
@@ -2681,8 +2719,8 @@ const randomJsonContent = [
2681
2719
  "write",
2682
2720
  "delete"
2683
2721
  ]
2684
- }),
2685
- () => ({
2722
+ },
2723
+ {
2686
2724
  products: [{
2687
2725
  id: 101,
2688
2726
  name: "Laptop",
@@ -2696,8 +2734,8 @@ const randomJsonContent = [
2696
2734
  }],
2697
2735
  total: 2,
2698
2736
  page: 1
2699
- }),
2700
- () => ({ config: {
2737
+ },
2738
+ { config: {
2701
2739
  theme: "dark",
2702
2740
  language: "en",
2703
2741
  notifications: {
@@ -2709,8 +2747,8 @@ const randomJsonContent = [
2709
2747
  profile: "public",
2710
2748
  activity: "friends"
2711
2749
  }
2712
- } }),
2713
- () => ({ metrics: {
2750
+ } },
2751
+ { metrics: {
2714
2752
  cpu: 45.2,
2715
2753
  memory: 78.5,
2716
2754
  disk: 62.3,
@@ -2733,60 +2771,71 @@ const randomJsonContent = [
2733
2771
  status: "warning"
2734
2772
  }
2735
2773
  ]
2736
- } })
2774
+ } }
2737
2775
  ];
2776
+ const resolveTemplateIndex = (index, length) => {
2777
+ if (index === void 0) return Math.floor(Math.random() * length);
2778
+ const value = Number(index);
2779
+ if (!Number.isInteger(value) || value < 1 || value > length) return -1;
2780
+ return value - 1;
2781
+ };
2782
+ const templateNotFound = (reply, length) => reply.code(404).send({ error: `Template index must be between 1 and ${length}` });
2738
2783
  const responseFormatRoutes = (fastify) => {
2739
- const plainHandler = async (_request, reply) => {
2740
- const text = randomTexts[Math.floor(Math.random() * randomTexts.length)];
2784
+ const plainHandler = async (request, reply) => {
2785
+ const index = resolveTemplateIndex(request.params.index, randomTexts.length);
2786
+ if (index === -1) return templateNotFound(reply, randomTexts.length);
2741
2787
  reply.type("text/plain");
2742
- return text;
2788
+ return randomTexts[index];
2743
2789
  };
2744
- fastify.get("/plain", { schema: plainSchema }, plainHandler);
2745
- fastify.post("/plain", { schema: plainSchema }, plainHandler);
2746
- fastify.put("/plain", { schema: plainSchema }, plainHandler);
2747
- fastify.patch("/plain", { schema: plainSchema }, plainHandler);
2748
- fastify.delete("/plain", { schema: plainSchema }, plainHandler);
2749
- const textHandler = async (_request, reply) => {
2750
- const text = randomTexts[Math.floor(Math.random() * randomTexts.length)];
2790
+ fastify.get("/plain/:index?", { schema: plainSchema }, plainHandler);
2791
+ fastify.post("/plain/:index?", { schema: plainSchema }, plainHandler);
2792
+ fastify.put("/plain/:index?", { schema: plainSchema }, plainHandler);
2793
+ fastify.patch("/plain/:index?", { schema: plainSchema }, plainHandler);
2794
+ fastify.delete("/plain/:index?", { schema: plainSchema }, plainHandler);
2795
+ const textHandler = async (request, reply) => {
2796
+ const index = resolveTemplateIndex(request.params.index, randomTexts.length);
2797
+ if (index === -1) return templateNotFound(reply, randomTexts.length);
2751
2798
  reply.type("text/plain");
2752
- return text;
2799
+ return randomTexts[index];
2753
2800
  };
2754
- fastify.get("/text", { schema: textSchema }, textHandler);
2755
- fastify.post("/text", { schema: textSchema }, textHandler);
2756
- fastify.put("/text", { schema: textSchema }, textHandler);
2757
- fastify.patch("/text", { schema: textSchema }, textHandler);
2758
- fastify.delete("/text", { schema: textSchema }, textHandler);
2759
- const htmlHandler = async (_request, reply) => {
2760
- const html = randomHtmlContent[Math.floor(Math.random() * randomHtmlContent.length)];
2801
+ fastify.get("/text/:index?", { schema: textSchema }, textHandler);
2802
+ fastify.post("/text/:index?", { schema: textSchema }, textHandler);
2803
+ fastify.put("/text/:index?", { schema: textSchema }, textHandler);
2804
+ fastify.patch("/text/:index?", { schema: textSchema }, textHandler);
2805
+ fastify.delete("/text/:index?", { schema: textSchema }, textHandler);
2806
+ const htmlHandler = async (request, reply) => {
2807
+ const index = resolveTemplateIndex(request.params.index, randomHtmlContent.length);
2808
+ if (index === -1) return templateNotFound(reply, randomHtmlContent.length);
2761
2809
  reply.type("text/html");
2762
- return html;
2810
+ return randomHtmlContent[index];
2763
2811
  };
2764
- fastify.get("/html", { schema: htmlSchema }, htmlHandler);
2765
- fastify.post("/html", { schema: htmlSchema }, htmlHandler);
2766
- fastify.put("/html", { schema: htmlSchema }, htmlHandler);
2767
- fastify.patch("/html", { schema: htmlSchema }, htmlHandler);
2768
- fastify.delete("/html", { schema: htmlSchema }, htmlHandler);
2769
- const xmlHandler = async (_request, reply) => {
2770
- const xml = randomXmlContent[Math.floor(Math.random() * randomXmlContent.length)];
2812
+ fastify.get("/html/:index?", { schema: htmlSchema }, htmlHandler);
2813
+ fastify.post("/html/:index?", { schema: htmlSchema }, htmlHandler);
2814
+ fastify.put("/html/:index?", { schema: htmlSchema }, htmlHandler);
2815
+ fastify.patch("/html/:index?", { schema: htmlSchema }, htmlHandler);
2816
+ fastify.delete("/html/:index?", { schema: htmlSchema }, htmlHandler);
2817
+ const xmlHandler = async (request, reply) => {
2818
+ const index = resolveTemplateIndex(request.params.index, randomXmlContent.length);
2819
+ if (index === -1) return templateNotFound(reply, randomXmlContent.length);
2771
2820
  reply.type("application/xml");
2772
- return xml;
2821
+ return randomXmlContent[index];
2773
2822
  };
2774
- fastify.get("/xml", { schema: xmlSchema }, xmlHandler);
2775
- fastify.post("/xml", { schema: xmlSchema }, xmlHandler);
2776
- fastify.put("/xml", { schema: xmlSchema }, xmlHandler);
2777
- fastify.patch("/xml", { schema: xmlSchema }, xmlHandler);
2778
- fastify.delete("/xml", { schema: xmlSchema }, xmlHandler);
2779
- const jsonHandler = async (_request, reply) => {
2780
- const jsonGenerator = randomJsonContent[Math.floor(Math.random() * randomJsonContent.length)];
2781
- const json = jsonGenerator();
2823
+ fastify.get("/xml/:index?", { schema: xmlSchema }, xmlHandler);
2824
+ fastify.post("/xml/:index?", { schema: xmlSchema }, xmlHandler);
2825
+ fastify.put("/xml/:index?", { schema: xmlSchema }, xmlHandler);
2826
+ fastify.patch("/xml/:index?", { schema: xmlSchema }, xmlHandler);
2827
+ fastify.delete("/xml/:index?", { schema: xmlSchema }, xmlHandler);
2828
+ const jsonHandler = async (request, reply) => {
2829
+ const index = resolveTemplateIndex(request.params.index, randomJsonContent.length);
2830
+ if (index === -1) return templateNotFound(reply, randomJsonContent.length);
2782
2831
  reply.type("application/json");
2783
- return json;
2832
+ return structuredClone(randomJsonContent[index]);
2784
2833
  };
2785
- fastify.get("/json", { schema: jsonSchema }, jsonHandler);
2786
- fastify.post("/json", { schema: jsonSchema }, jsonHandler);
2787
- fastify.put("/json", { schema: jsonSchema }, jsonHandler);
2788
- fastify.patch("/json", { schema: jsonSchema }, jsonHandler);
2789
- fastify.delete("/json", { schema: jsonSchema }, jsonHandler);
2834
+ fastify.get("/json/:index?", { schema: jsonSchema }, jsonHandler);
2835
+ fastify.post("/json/:index?", { schema: jsonSchema }, jsonHandler);
2836
+ fastify.put("/json/:index?", { schema: jsonSchema }, jsonHandler);
2837
+ fastify.patch("/json/:index?", { schema: jsonSchema }, jsonHandler);
2838
+ fastify.delete("/json/:index?", { schema: jsonSchema }, jsonHandler);
2790
2839
  const denyHandler = async (_request, reply) => {
2791
2840
  const denyMessages = [
2792
2841
  `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
@@ -2869,7 +2918,8 @@ const responseFormatRoutes = (fastify) => {
2869
2918
  ...data,
2870
2919
  compressedSize: compressed.length
2871
2920
  };
2872
- const updatedCompressed = gzipSync(JSON.stringify(updatedData));
2921
+ const updatedJsonString = JSON.stringify(updatedData);
2922
+ const updatedCompressed = gzipSync(updatedJsonString);
2873
2923
  reply.header("Content-Encoding", "gzip");
2874
2924
  reply.header("Content-Type", "application/json");
2875
2925
  reply.type("application/octet-stream");
@@ -2925,7 +2975,8 @@ const responseFormatRoutes = (fastify) => {
2925
2975
  ...data,
2926
2976
  compressedSize: compressed.length
2927
2977
  };
2928
- const updatedCompressed = deflateSync(JSON.stringify(updatedData));
2978
+ const updatedJsonString = JSON.stringify(updatedData);
2979
+ const updatedCompressed = deflateSync(updatedJsonString);
2929
2980
  reply.header("Content-Encoding", "deflate");
2930
2981
  reply.header("Content-Type", "application/json");
2931
2982
  reply.type("application/octet-stream");
@@ -2986,7 +3037,8 @@ const responseFormatRoutes = (fastify) => {
2986
3037
  ...data,
2987
3038
  compressedSize: compressed.length
2988
3039
  };
2989
- const updatedCompressed = brotliCompressSync(JSON.stringify(updatedData));
3040
+ const updatedJsonString = JSON.stringify(updatedData);
3041
+ const updatedCompressed = brotliCompressSync(updatedJsonString);
2990
3042
  reply.header("Content-Encoding", "br");
2991
3043
  reply.header("Content-Type", "application/json");
2992
3044
  reply.type("application/octet-stream");
@@ -3449,7 +3501,7 @@ A simple HTTP server that can be used to mock HTTP responses for testing purpose
3449
3501
 
3450
3502
  [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.
3451
3503
  `,
3452
- version: "1.8.0"
3504
+ version: "1.9.0"
3453
3505
  },
3454
3506
  consumes: ["application/json"],
3455
3507
  produces: ["application/json"]
@@ -3618,14 +3670,18 @@ var MockHttp = class extends Hookified {
3618
3670
  if (options?.helmet !== void 0) this._helmet = options.helmet;
3619
3671
  if (options?.apiDocs !== void 0) this._apiDocs = options.apiDocs;
3620
3672
  if (options?.httpBin !== void 0) this._httpBin = options.httpBin;
3621
- if (options?.rateLimit !== void 0) if (options.rateLimit === false) this._rateLimit = void 0;
3622
- else this._rateLimit = options.rateLimit;
3673
+ if (options?.rateLimit !== void 0) {
3674
+ if (options.rateLimit === false) this._rateLimit = void 0;
3675
+ else this._rateLimit = options.rateLimit;
3676
+ }
3623
3677
  if (options?.logging !== void 0) this._logging = options.logging;
3624
3678
  if (options?.http2 !== void 0) this._http2 = options.http2;
3625
3679
  if (options?.http1 !== void 0) this._http1 = options.http1;
3626
- if (options?.https !== void 0) if (options.https === true) this._https = { autoGenerate: true };
3627
- else if (options.https === false) this._https = void 0;
3628
- else this._https = options.https;
3680
+ if (options?.https !== void 0) {
3681
+ if (options.https === true) this._https = { autoGenerate: true };
3682
+ else if (options.https === false) this._https = void 0;
3683
+ else this._https = options.https;
3684
+ }
3629
3685
  }
3630
3686
  /**
3631
3687
  * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaredwray/mockhttp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Mock Http - Easy way to mock http with httpbin replacement",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -39,27 +39,27 @@
39
39
  },
40
40
  "homepage": "https://github.com/jaredwray/mockhttp#readme",
41
41
  "devDependencies": {
42
- "@biomejs/biome": "^2.4.16",
43
- "@swc/core": "^1.15.41",
42
+ "@biomejs/biome": "^2.5.8",
43
+ "@swc/core": "^1.15.47",
44
44
  "@types/html-escaper": "^3.0.4",
45
- "@vitest/coverage-v8": "^4.1.8",
45
+ "@vitest/coverage-v8": "^4.1.10",
46
46
  "rimraf": "^6.1.3",
47
- "tsdown": "^0.22.2",
48
- "tsx": "^4.22.4",
49
- "typescript": "6.0.3",
50
- "vitest": "^4.1.8"
47
+ "tsdown": "^0.22.14",
48
+ "tsx": "^4.23.12",
49
+ "typescript": "7.0.2",
50
+ "vitest": "^4.1.10"
51
51
  },
52
52
  "dependencies": {
53
- "@fastify/cookie": "^11.0.2",
54
- "@fastify/helmet": "^13.0.2",
55
- "@fastify/rate-limit": "^11.0.0",
56
- "@fastify/static": "^9.1.3",
57
- "@fastify/swagger": "^9.7.0",
58
- "@fastify/swagger-ui": "^6.0.0",
59
- "@scalar/api-reference": "^1.59.3",
53
+ "@fastify/cookie": "^11.1.2",
54
+ "@fastify/helmet": "^13.1.0",
55
+ "@fastify/rate-limit": "^11.2.0",
56
+ "@fastify/static": "^10.1.3",
57
+ "@fastify/swagger": "^9.8.1",
58
+ "@fastify/swagger-ui": "^6.1.1",
59
+ "@scalar/api-reference": "^1.64.1",
60
60
  "detect-port": "^2.1.0",
61
- "fastify": "^5.8.5",
62
- "hookified": "^3.0.0",
61
+ "fastify": "^5.11.3",
62
+ "hookified": "^3.0.2",
63
63
  "html-escaper": "^3.0.3",
64
64
  "pino": "^10.3.1",
65
65
  "pino-pretty": "^13.1.3"