Repository: pmcelhaney/counterfact
Pull request #92 (2022-07-18 21:56:12)

unit test for JsonSchemaCoder


Patch:
diff --git a/.changeset/wicked-dingos-lay.md b/.changeset/wicked-dingos-lay.md
new file mode 100644
index 0000000..0917557
--- /dev/null
+++ b/.changeset/wicked-dingos-lay.md
@@ -0,0 +1,5 @@
+---
+"counterfact": minor
+---
+
+use example values (if present) when generating random data
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70d63de..840a79d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,5 @@
 # counterfact
 
-## 0.5.0
-
-### Minor Changes
-
-- 5e72a6c: CLI to generate TypeScript files
-- de5e92b: use example values (if present) when generating random data
-
 ## 0.4.0
 
 ### Minor Changes
diff --git a/README.md b/README.md
index 885d981..2bbca64 100644
--- a/README.md
+++ b/README.md
@@ -2,15 +2,15 @@
 
 [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpmcelhaney%2Fcounterfact%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main)
 
-Counterfact is (will be) a tool that helps front end developers and back end developers collaborate by quickly building reference implementations of [OpenAPI](https://www.openapis.org/) specs.
+Counterfact is (will be) a tool that helps front end developers and back end developers collaborate by quickly building reference implementations of OpenAPI specs.
 
-Counterfact is **not** for **production**.
+Counterfact is _not_ for _production_.
 
-Counterfact is **great** for **testing the UI**, both manual and automated testing. UI testing can be quite tedious and brittle in part because the _arrange_ part of Arrange, Act, Assert -- getting the server into a particular state -- is often slow, unpredictable, and complex. Counterfact bypasses the accidental complexity associated with arranging a real server's state, making tests that isolate the UI fast, repeatable, and easy to maintain.
+Counterfact is _great_ for _testing the UI_, both manual and automated testing. UI testing can be quite tedious and brittle in part because the _arrange_ part of Arrange, Act, Assert -- getting the server into a particular state -- is often slow, unpredictable, and complex. Counterfact bypasses the accidental complexity associated with arranging a real server's state, making tests that isolate the UI fast, repeatable, and easy to maintain.
 
 It accomplishes that without over-isolating. You can run a full scenario: log in, make changes, log in with a different user, and see the effects of those changes. The server you're testing against is a _real_ server from the UI's perspective: it implements business logic and maintains state. It's only not real in the sense that it doesn't have to support 10,000 concurrent users, protect real data, go through a deployment pipeline, or maintain 99.99% uptime.
 
-Counterfact is **great** for **communication between front end and back end teams**. OpenAPI does an excellent job defining the inputs and outputs of a RESTful service, but it can't tell us how a server is supposed to behave. Counterfact fills that gap. The feedback cycle is orders of magnitude faster than making changes to a real, scalable back end and then seeing how those changes affect the UI/UX.
+Counterfact is _great_ for _communication between front end and back end teams_. OpenAPI does an excellent job defining the inputs and outputs of a RESTful service, but it can't tell us how a server is supposed to behave. Counterfact fills that gap. The feedback cycle is orders of magnitude faster than making changes to a real, scalable back end and then seeing how those changes affect the UI/UX.
 
 ## Road to 1.0
 
@@ -45,7 +45,7 @@ We can see the effects of the code changes _without restarting the server_.
 And then after running
 
 ```sh
-npm run counterfact generate /path/to/copy/of/petstore/with/some/changes.yaml
+yarn generate /path/to/copy/of/petstore/with/some/changes.yaml
 ```
 
 it regenerates the code except for the files that we changed. Of the files we changed, the IDE (via TypeScript and ESLint) tells us if the code needs to be modified to conform to the updated spec.
diff --git a/bin/counterfact.js b/bin/counterfact.js
deleted file mode 100755
index 42d72da..0000000
--- a/bin/counterfact.js
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env node
-import nodePath from "node:path";
-
-import { generate } from "../src/typescript-generator/generate.js";
-
-const EXPECTED_ARGUMENT_COUNT = 5;
-
-if (
-  process.argv.length !== EXPECTED_ARGUMENT_COUNT ||
-  process.argv[2] !== "generate"
-) {
-  // eslint-disable-next-line no-console
-  console.log("Usage: counterfact generate <source> <destination>");
-  // eslint-disable-next-line node/no-process-exit, no-process-exit
-  process.exit(1);
-}
-
-const [, source, destination] = process.argv
-  .slice(2)
-  .map((pathString) => nodePath.join(process.cwd(), pathString));
-
-generate(source, destination);
diff --git a/package.json b/package.json
index ebe1f95..56cf0c4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "counterfact",
-  "version": "0.5.0",
+  "version": "0.4.0",
   "description": "a library for building a fake REST API for testing",
   "type": "module",
   "main": "./src/counterfact.js",
@@ -19,9 +19,6 @@
   "engines": {
     "node": ">=16.9.0"
   },
-  "bin": {
-    "counterfact": "./bin/counterfact.js"
-  },
   "scripts": {
     "test": "yarn node --experimental-vm-modules $(yarn bin jest)",
     "test:mutants": "stryker run stryker.config.json",
@@ -58,5 +55,6 @@
     "js-yaml": "^4.1.0",
     "prettier": "^2.7.1",
     "json-schema-faker": "^0.5.0-rcv.44"
+
   }
 }
diff --git a/src/typescript-generator/generate.js b/src/typescript-generator/generate.js
index 8a4705b..3e4c919 100644
--- a/src/typescript-generator/generate.js
+++ b/src/typescript-generator/generate.js
@@ -4,19 +4,23 @@ import { Repository } from "./repository.js";
 import { Specification } from "./specification.js";
 import { OperationCoder } from "./operation-coder.js";
 
-export async function generate(source, destination) {
-  const specification = new Specification(path.dirname(source));
+const [source, destination] = process.argv
+  .slice(2)
+  .map((pathString) => path.join(process.cwd(), pathString));
 
-  const repository = new Repository();
-  const requirement = await specification.requirementAt(
-    `${path.basename(source)}#/paths`
-  );
+const specification = new Specification(path.dirname(source));
 
-  requirement.forEach(([key, pathDefinition]) => {
-    pathDefinition.forEach(([, operation]) => {
-      repository.get(`paths${key}.ts`).export(new OperationCoder(operation));
-    });
+const repository = new Repository();
+const requirement = await specification.requirementAt(
+  `${path.basename(source)}#/paths`
+);
+
+requirement.forEach(([key, pathDefinition]) => {
+  pathDefinition.forEach(([, operation]) => {
+    repository.get(`paths${key}.ts`).export(new OperationCoder(operation));
   });
+});
+
+await repository.writeFiles(destination);
 
-  await repository.writeFiles(destination);
-}
+export {};
diff --git a/src/typescript-generator/schema-coder.js b/src/typescript-generator/schema-coder.js
index 922483c..2027d5d 100644
--- a/src/typescript-generator/schema-coder.js
+++ b/src/typescript-generator/schema-coder.js
@@ -68,6 +68,12 @@ export class SchemaCoder extends Coder {
       return this.arraySchema(script);
     }
 
+    if (["integer", "number"].includes(type)) {
+      return JSON.stringify(
+        scrubSchema({ ...this.requirement.data, type: "number" })
+      );
+    }
+
     return JSON.stringify(scrubSchema(this.requirement.data));
   }
 }


Test patch:
diff --git a/test/typescript-generator/schema-coder.test.js b/test/typescript-generator/schema-coder.test.js
new file mode 100644
index 0000000..561be4d
--- /dev/null
+++ b/test/typescript-generator/schema-coder.test.js
@@ -0,0 +1,124 @@
+import prettier from "prettier";
+
+import { SchemaCoder } from "../../src/typescript-generator/schema-coder.js";
+import { Requirement } from "../../src/typescript-generator/requirement.js";
+
+function format(code) {
+  return prettier.format(code, { parser: "typescript" });
+}
+
+describe("a SchemaCoder", () => {
+  it("generates a list of potential names", () => {
+    const coder = new SchemaCoder(
+      new Requirement({ $ref: "/components/schemas/Example" })
+    );
+
+    const [one, two, three] = coder.names();
+
+    expect([one, two, three]).toStrictEqual([
+      "ExampleSchema",
+      "ExampleSchema2",
+      "ExampleSchema3",
+    ]);
+  });
+
+  it("when given a $ref, creates an import", () => {
+    const coder = new SchemaCoder(
+      new Requirement({ $ref: "/components/schemas/Example" })
+    );
+
+    const script = {
+      import(c) {
+        this.importedCoder = c;
+
+        return "aVariable";
+      },
+    };
+
+    const result = coder.write(script);
+
+    expect(script.importedCoder).toBe(coder);
+    expect(result).toBe("aVariable");
+  });
+
+  it.each`
+    type         | output
+    ${"string"}  | ${'{"type":"string"}'}
+    ${"number"}  | ${'{"type":"number"}'}
+    ${"integer"} | ${'{"type":"number"}'}
+  `("generates a type declaration for $type", ({ type, output }) => {
+    const coder = new SchemaCoder(
+      new Requirement({ type, xml: "should be ignored" })
+    );
+    const result = coder.write({});
+
+    expect(result).toBe(output);
+  });
+
+  it("generates a type declaration for an object", () => {
+    const coder = new SchemaCoder(
+      new Requirement({
+        type: "object",
+
+        properties: {
+          name: { type: "string" },
+          age: { type: "integer" },
+        },
+
+        xml: "should be ignored",
+      })
+    );
+
+    const expected = format(`const x = { 
+      type: "object",
+      required: [],
+      properties: { "name": {"type":"string"}, "age": {"type":"number"} }
+    }`);
+
+    expect(format(`const x = ${coder.write()}`)).toStrictEqual(expected);
+  });
+
+  it("generates a type declaration for an array", () => {
+    const coder = new SchemaCoder(
+      new Requirement({
+        type: "array",
+        items: { type: "string" },
+        xml: "should be ignored",
+      })
+    );
+
+    const expected = format(
+      `const x = { 
+          type: "array", 
+          items: { type: "string" } 
+      };`
+    );
+
+    expect(format(`const x = ${coder.write()}`)).toStrictEqual(expected);
+  });
+
+  it("has type JSONSchema6", () => {
+    const coder = new SchemaCoder(
+      new Requirement({
+        type: "array",
+        items: { type: "string" },
+        xml: "should be ignored",
+      })
+    );
+
+    const script = {
+      importExternalType(name, path) {
+        this.imported = { name, path };
+
+        return name;
+      },
+    };
+
+    expect(coder.typeDeclaration(undefined, script)).toBe("JSONSchema6");
+
+    expect(script.imported).toStrictEqual({
+      name: "JSONSchema6",
+      path: "json-schema",
+    });
+  });
+});