Repository: pmcelhaney/counterfact
Pull request #456 (2023-06-15 17:56:57)

fix $.proxy() -- it wasn't calling fetch() correctly


Patch:
diff --git a/.changeset/grumpy-cycles-battle.md b/.changeset/grumpy-cycles-battle.md
new file mode 100644
index 0000000..5226c7d
--- /dev/null
+++ b/.changeset/grumpy-cycles-battle.md
@@ -0,0 +1,5 @@
+---
+"counterfact": patch
+---
+
+fix $.proxy() -- it's better but still experimental
diff --git a/jest.config.json b/jest.config.json
index 56b14b5..19ef2e9 100644
--- a/jest.config.json
+++ b/jest.config.json
@@ -11,10 +11,10 @@
 
   "coverageThreshold": {
     "global": {
-      "branches": 45,
-      "functions": 49,
-      "lines": 54,
-      "statements": -301
+      "branches": 80,
+      "functions": 80,
+      "lines": 80,
+      "statements": 80
     }
   }
 }
diff --git a/package.json b/package.json
index 184087a..d200f93 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
     "eslint-plugin-unused-imports": "^2.0.0",
     "husky": "8.0.3",
     "jest": "28.1.2",
+    "node-mocks-http": "^1.12.2",
     "nodemon": "2.0.22",
     "stryker-cli": "1.0.2",
     "supertest": "6.3.3"
diff --git a/src/server/dispatcher.js b/src/server/dispatcher.js
index 99afce2..97932ed 100644
--- a/src/server/dispatcher.js
+++ b/src/server/dispatcher.js
@@ -47,7 +47,7 @@ export class Dispatcher {
     return this.openApiDocument?.paths?.[path]?.[method.toLowerCase()];
   }
 
-  async request({ method, path, headers, body, query }) {
+  async request({ method, path, headers, body, query, req }) {
     const { matchedPath } = this.registry.handler(path);
     const operation = this.operationForPathAndMethod(matchedPath, method);
 
@@ -69,18 +69,21 @@ export class Dispatcher {
         )
       ),
 
-      proxy: async (hostOrOptions) => {
-        const options =
-          typeof hostOrOptions === "string"
-            ? { host: hostOrOptions }
-            : hostOrOptions;
-
-        const fetchResponse = await this.fetch(options.host, {
+      proxy: async (url) => {
+        if (
+          body &&
+          headers.contentType &&
+          headers.contentType !== "application/json"
+        ) {
+          throw new Error(
+            `$.proxy() is currently limited to application/json requests. You tried to proxy to ${url} with a Content-Type of ${headers.contentType}. Please open an issue at https://github.com/pmcelhaney/counterfact/issues and prod me to fix this limitation.`
+          );
+        }
+
+        const fetchResponse = await this.fetch(url + req.path, {
           method,
-          path,
           headers: new Headers(headers),
-          query,
-          ...options,
+          body: body ? JSON.stringify(body) : undefined,
         });
 
         const responseHeaders = Object.fromEntries(
diff --git a/src/server/koa-middleware.js b/src/server/koa-middleware.js
index 40626ab..fe13f82 100644
--- a/src/server/koa-middleware.js
+++ b/src/server/koa-middleware.js
@@ -16,6 +16,7 @@ export function koaMiddleware(dispatcher, options = {}, proxy = koaProxy) {
       headers,
       body,
       query,
+      req: ctx.req,
     });
 
     /* eslint-disable require-atomic-updates */
diff --git a/yarn.lock b/yarn.lock
index 3cf4021..799c8de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3185,7 +3185,7 @@ abbrev@1:
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
-accepts@^1.3.5:
+accepts@^1.3.5, accepts@^1.3.7:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
   integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -4100,7 +4100,7 @@ constant-case@^3.0.4:
     tslib "^2.0.3"
     upper-case "^2.0.2"
 
-content-disposition@~0.5.2:
+content-disposition@^0.5.3, content-disposition@~0.5.2:
   version "0.5.4"
   resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
   integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
@@ -4438,7 +4438,7 @@ depd@2.0.0, depd@^2.0.0, depd@~2.0.0:
   resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
   integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
 
-depd@~1.1.2:
+depd@^1.1.0, depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
   integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
@@ -5943,7 +5943,7 @@ formidable@^2.1.1:
     once "^1.4.0"
     qs "^6.11.0"
 
-fresh@~0.5.2:
+fresh@^0.5.2, fresh@~0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
@@ -7996,6 +7996,11 @@ meow@^9.0.0:
     type-fest "^0.18.0"
     yargs-parser "^20.2.3"
 
+merge-descriptors@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+  integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+
 merge-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -8231,6 +8236,11 @@ mime@2.6.0:
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
   integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
 
+mime@^1.3.4:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
 mimic-fn@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
@@ -8415,6 +8425,22 @@ node-int64@^0.4.0:
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
   integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
 
+node-mocks-http@^1.12.2:
+  version "1.12.2"
+  resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.12.2.tgz#382b1824f625c2d41da82efedde936b9404e7fdd"
+  integrity sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==
+  dependencies:
+    accepts "^1.3.7"
+    content-disposition "^0.5.3"
+    depd "^1.1.0"
+    fresh "^0.5.2"
+    merge-descriptors "^1.0.1"
+    methods "^1.1.2"
+    mime "^1.3.4"
+    parseurl "^1.3.3"
+    range-parser "^1.2.0"
+    type-is "^1.6.18"
+
 node-releases@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -8818,7 +8844,7 @@ parse-json@^5.0.0, parse-json@^5.2.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parseurl@^1.3.2:
+parseurl@^1.3.2, parseurl@^1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
@@ -9276,6 +9302,11 @@ ramda@0.25.0:
   resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9"
   integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==
 
+range-parser@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
 raw-body@^2.3.3:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
@@ -10791,7 +10822,7 @@ type-fest@^0.8.1:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
   integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
 
-type-is@^1.6.16:
+type-is@^1.6.16, type-is@^1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
   integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==


Test patch:
diff --git a/test/server/dispatcher.proxy.test.js b/test/server/dispatcher.proxy.test.js
index 88a0e9c..27d879c 100644
--- a/test/server/dispatcher.proxy.test.js
+++ b/test/server/dispatcher.proxy.test.js
@@ -16,25 +16,29 @@ describe("a dispatcher passes a proxy function to the operation", () => {
 
     const dispatcher = new Dispatcher(registry, new ContextRegistry());
 
-    dispatcher.fetch = (url, options) => ({
+    dispatcher.fetch = (url) => ({
       status: 200,
-      headers: new Headers([["content-type", "application/test"]]),
+      headers: new Headers([["content-type", "application/json"]]),
 
       text() {
-        return `body from ${url}${options.path}`;
+        return `body from ${url}`;
       },
     });
 
     const response = await dispatcher.request({
       method: "GET",
       path: "/a",
+
+      req: {
+        path: "/a?x=1",
+      },
     });
 
-    expect(response.body).toBe("body from https://example.com/a");
+    expect(response.body).toBe("body from https://example.com/a?x=1");
     expect(response.headers).toStrictEqual({
-      "content-type": "application/test",
+      "content-type": "application/json",
     });
-    expect(response.contentType).toBe("application/test");
+    expect(response.contentType).toBe("application/json");
     expect(response.status).toBe(200);
   });
 });
diff --git a/test/server/koa-middleware.test.js b/test/server/koa-middleware.test.js
index 9b95644..f97b17c 100644
--- a/test/server/koa-middleware.test.js
+++ b/test/server/koa-middleware.test.js
@@ -25,6 +25,10 @@ describe("koa middleware", () => {
     const middleware = koaMiddleware(dispatcher);
     const ctx = {
       request: { path: "/hello", method: "POST", body: { name: "Homer" } },
+
+      req: {
+        path: "/hello",
+      },
     };
 
     await middleware(ctx);