Repository: pmcelhaney/counterfact
Pull request #64 (2022-07-05 18:11:12)

Add a tools object that will be provided to the request
It provides some useful functionality:


- `tools.oneOf(array)` returns a random item from an array (used for selecting a random example from a an OpenAPI schema)
- `tools.accepts(contentType)` identifies whether the request accepts a particular content type




Patch:
diff --git a/.changeset/gentle-kangaroos-switch.md b/.changeset/gentle-kangaroos-switch.md
new file mode 100644
index 0000000..0256756
--- /dev/null
+++ b/.changeset/gentle-kangaroos-switch.md
@@ -0,0 +1,5 @@
+---
+"counterfact": patch
+---
+
+Add a tools object that will be provided to the request
diff --git a/src/dispatcher.js b/src/dispatcher.js
index 341c9cf..08c790f 100644
--- a/src/dispatcher.js
+++ b/src/dispatcher.js
@@ -1,3 +1,5 @@
+import { Tools } from "./tools.js";
+
 export class Dispatcher {
   registry;
 
@@ -5,12 +7,12 @@ export class Dispatcher {
     this.registry = registry;
   }
 
-  request({ method, path, body, query }) {
+  request({ method, path, headers, body, query }) {
     return this.registry.endpoint(
       method,
       path
     )({
-      // path: parts.slice(remainingParts).join("/"),
+      tools: new Tools({ headers }),
 
       reduce: (reducer) => {
         this.registry.context = reducer(this.registry.context);
diff --git a/src/tools.js b/src/tools.js
new file mode 100644
index 0000000..6c04cb9
--- /dev/null
+++ b/src/tools.js
@@ -0,0 +1,28 @@
+export class Tools {
+  constructor({ headers = {} } = {}) {
+    this.headers = headers;
+  }
+
+  oneOf(array) {
+    return array[Math.floor(Math.random() * array.length)];
+  }
+
+  accepts(contentType) {
+    const acceptHeader = this.headers.Accept;
+
+    if (!acceptHeader) {
+      return true;
+    }
+
+    const acceptTypes = acceptHeader.split(",");
+
+    return acceptTypes.some((acceptType) => {
+      const [type, subtype] = acceptType.split("/");
+
+      return (
+        (type === "*" || type === contentType.split("/")[0]) &&
+        (subtype === "*" || subtype === contentType.split("/")[1])
+      );
+    });
+  }
+}


Test patch:
diff --git a/test/dispatcher.test.js b/test/dispatcher.test.js
index 405742b..1396bbb 100644
--- a/test/dispatcher.test.js
+++ b/test/dispatcher.test.js
@@ -72,6 +72,39 @@ describe("a dispatcher", () => {
     expect(response.body).toBe("Searching for stores near 90210!");
   });
 
+  it("passes a tools object", async () => {
+    const registry = new Registry();
+
+    registry.add("/a", {
+      GET({ tools }) {
+        return { body: tools.accepts("text/html") };
+      },
+    });
+
+    const dispatcher = new Dispatcher(registry);
+    const htmlResponse = await dispatcher.request({
+      method: "GET",
+      path: "/a",
+
+      headers: {
+        Accept: "text/html",
+      },
+    });
+
+    expect(htmlResponse.body).toBe(true);
+
+    const textResponse = await dispatcher.request({
+      method: "GET",
+      path: "/a",
+
+      headers: {
+        Accept: "text/plain",
+      },
+    });
+
+    expect(textResponse.body).toBe(false);
+  });
+
   it("passes status code in the response", async () => {
     const registry = new Registry();
 
diff --git a/test/tools.test.js b/test/tools.test.js
new file mode 100644
index 0000000..30559b1
--- /dev/null
+++ b/test/tools.test.js
@@ -0,0 +1,39 @@
+import { Tools } from "../src/tools.js";
+
+describe("tools", () => {
+  it("oneOf()", () => {
+    const tools = new Tools();
+
+    expect(["A", "B", "C"]).toContain(tools.oneOf(["A", "B", "C"]));
+  });
+
+  it.each`
+    contentType           | acceptHeader
+    ${"what/ever"}        | ${undefined}
+    ${"text/html"}        | ${"text/html"}
+    ${"text/html"}        | ${"text/*"}
+    ${"application/json"} | ${"*/json"}
+    ${"text/*"}           | ${"text/*"}
+  `(
+    "accept('$contentType') returns true when the accept header is $acceptHeader",
+    ({ contentType, acceptHeader }) => {
+      const tools = new Tools({ headers: { Accept: acceptHeader } });
+
+      expect(tools.accepts(contentType)).toBe(true);
+    }
+  );
+
+  it.each`
+    contentType           | acceptHeader
+    ${"application/json"} | ${"text/*"}
+    ${"text/html"}        | ${"text/plain"}
+    ${"application/json"} | ${"text/json"}
+  `(
+    "accept('$contentType') returns false when the accept header is $acceptHeader",
+    ({ contentType, acceptHeader }) => {
+      const tools = new Tools({ headers: { Accept: acceptHeader } });
+
+      expect(tools.accepts(contentType)).toBe(false);
+    }
+  );
+});