Skip to content

Commit

Permalink
Merge pull request #731 from pmcelhaney/unit-tests
Browse files Browse the repository at this point in the history
unit tests
  • Loading branch information
pmcelhaney committed Jan 20, 2024
2 parents 9978acc + 8f028d5 commit 87eaad6
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-ducks-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"counterfact": patch
---

CodeGenerator#watch() now waits until Chokidar completes its initial scan before resolving
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 18.x
node-version: 20.x
cache: yarn
id: setup-node
- name: Get Node Version
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/debug-windows.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Debug Windows

on:
pull_request:
workflow_dispatch:

env:
Expand Down
5 changes: 0 additions & 5 deletions bin/counterfact.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { program } from "commander";
import createDebug from "debug";
import open from "open";

import { migrate } from "../dist/migrations/0.27.js";
import { counterfact } from "../dist/server/app.js";

const taglinesFile = await readFile(
Expand Down Expand Up @@ -44,10 +43,6 @@ async function main(source, destination) {
.join(process.cwd(), destination)
.replaceAll("\\", "/");

debug("migrating code from before 0.27.0");
migrate(destinationPath);
debug("done with migration");

const basePath = nodePath.resolve(destinationPath).replaceAll("\\", "/");

debug("options: %o", options);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dist"
],
"scripts": {
"test": "yarn node --experimental-vm-modules ./node_modules/jest-cli/bin/jest --testPathIgnorePatterns=black-box --forceExit",
"test": "yarn node --experimental-vm-modules ./node_modules/jest-cli/bin/jest --testPathIgnorePatterns=black-box",
"test:black-box": "rimraf dist && rimraf out && yarn build && yarn node --experimental-vm-modules ./node_modules/jest-cli/bin/jest black-box --forceExit --coverage=false",
"test:mutants": "stryker run stryker.config.json",
"build": "tsc && copyfiles -f \"src/client/**\" dist/client && copyfiles -f \"src/server/*.d.ts\" dist/server",
Expand Down Expand Up @@ -76,7 +76,7 @@
"rimraf": "5.0.5",
"stryker-cli": "1.0.2",
"supertest": "6.3.4",
"using-temporary-files": "2.1.0"
"using-temporary-files": "^2.1.1"
},
"dependencies": {
"@hapi/accept": "6.0.3",
Expand Down
59 changes: 0 additions & 59 deletions src/migrations/0.27.js

This file was deleted.

18 changes: 16 additions & 2 deletions src/server/code-generator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { type FSWatcher, watch } from "chokidar";

import { generate } from "../typescript-generator/generate.js";
import { waitForEvent } from "../util/wait-for-event.js";

export class CodeGenerator {
export class CodeGenerator extends EventTarget {
private readonly openapiPath: string;

private readonly destination: string;

private watcher: FSWatcher | undefined;

public constructor(openApiPath: string, destination: string) {
super();
this.openapiPath = openApiPath;
this.destination = destination;
}
Expand All @@ -22,8 +24,20 @@ export class CodeGenerator {
}

this.watcher = watch(this.openapiPath).on("change", () => {
void generate(this.openapiPath, this.destination);
// eslint-disable-next-line promise/prefer-await-to-then
void generate(this.openapiPath, this.destination).then(
() => {
this.dispatchEvent(new Event("generate"));
return true;
},
() => {
this.dispatchEvent(new Event("failed"));
return false;
},
);
});

await waitForEvent(this.watcher, "ready");
}

public async stopWatching(): Promise<void> {
Expand Down
2 changes: 2 additions & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export interface Config {
proxyEnabled: boolean;
proxyUrl: string;
}

export const DUMMY_EXPORT_FOR_TEST_COVERAGE = 1;
4 changes: 2 additions & 2 deletions src/typescript-generator/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ async function buildCacheDirectory(destination) {

async function getPathsFromSpecification(specification) {
try {
return await specification.requirementAt("#/paths");
return (await specification.requirementAt("#/paths")) ?? new Set();
} catch (error) {
process.stderr.write(
`Could not find #/paths in the specification.\n${error}\n`,
);

return [];
return new Set();
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/typescript-generator/repository.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import nodePath, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
Expand Down Expand Up @@ -49,12 +50,22 @@ export class Repository {
}
}

copyCoreFiles(destination) {
async copyCoreFiles(destination) {
const destinationPath = nodePath
.join(destination, "types.d.ts")
.replaceAll("\\", "/");

if (!existsSync(destinationPath)) {
return false;
}

await ensureDirectoryExists(destination);

return fs.copyFile(
nodePath
.join(__dirname, "../../dist/server/types.d.ts")
.replaceAll("\\", "/"),
nodePath.join(destination, "types.d.ts").replaceAll("\\", "/"),
destinationPath,
);
}

Expand Down
28 changes: 28 additions & 0 deletions src/util/wait-for-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { EventEmitter } from "koa";

/**
* Creates a promise that resolves when a specified event is fired on the given EventTarget.
* @param {EventTarget | EventEmitter} target - The target to listen for the event on.
* @param {string} eventName - The name of the event to listen for.
* @returns {Promise<Event>} A promise that resolves with the event object when the event is fired.
*/
export async function waitForEvent(
target: EventEmitter | EventTarget,
eventName: string,
) {
// eslint-disable-next-line promise/avoid-new
return await new Promise((resolve) => {
const handler = (event: unknown) => {
if (target instanceof EventTarget) {
target.removeEventListener(eventName, handler);
}
resolve(event);
};

if (target instanceof EventTarget) {
target.addEventListener(eventName, handler);
} else {
target.once(eventName, handler);
}
});
}
75 changes: 75 additions & 0 deletions test/server/code-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { usingTemporaryFiles } from "using-temporary-files";

import { CodeGenerator } from "../../src/server/code-generator.js";
import { waitForEvent } from "../../src/util/wait-for-event.js";

const OPENAPI = {
components: { schemas: { Example: { type: "string" } } },
openapi: "3.0.3",

paths: {
"/example": {
get: {
responses: {
default: {
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/Example",
},
},
},
},
},
},
},
},
};

describe("a CodeGenerator", () => {
it("generates code for a component", async () => {
await usingTemporaryFiles(async ({ add, path, read }) => {
await add("openapi.json", JSON.stringify(OPENAPI));
const generator = new CodeGenerator(
path("./openapi.json"),
path("./out"),
);

await generator.watch();

const exampleComponent = await read("./out/components/Example.ts");

await generator.stopWatching();

expect(exampleComponent).toEqual("export type Example = string;\n");
});
});

it("updates the code when the spec changes", async () => {
await usingTemporaryFiles(async ({ add, path, read }) => {
await add("openapi.json", JSON.stringify(OPENAPI));
const generator = new CodeGenerator(
path("./openapi.json"),
path("./out"),
);

const changed = { ...OPENAPI };
changed.components.schemas.Example.type = "integer";

await generator.watch();

await add("openapi.json", JSON.stringify(changed));

await waitForEvent(generator, "generate");

const exampleComponent = await read("./out/components/Example.ts");

await generator.stopWatching();

expect(exampleComponent).toEqual("export type Example = number;\n");
});
});
});
5 changes: 5 additions & 0 deletions test/server/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DUMMY_EXPORT_FOR_TEST_COVERAGE } from "../../src/server/config.js";

it("does nothing, just including because C8 coverage doesn't recognize type-only files", () => {
expect(DUMMY_EXPORT_FOR_TEST_COVERAGE).toBe(DUMMY_EXPORT_FOR_TEST_COVERAGE);
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11118,10 +11118,10 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"

using-temporary-files@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/using-temporary-files/-/using-temporary-files-2.1.0.tgz#4143e9209a51258160ffbe0446c10f0369c3ff51"
integrity sha512-rBtpFh9wmGLkEfqgZqoi/CvARt9LVElu2AkSv++lwwgR64ejWjqXA/Mc38b5G8GaYwc6KuNQMbF+u53QNIHa3w==
using-temporary-files@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/using-temporary-files/-/using-temporary-files-2.1.1.tgz#6cfbf8fd1da9f45d6b0e09c86f24ce9d9ae0c4a7"
integrity sha512-+JuVdSvvj3HTEW39jaLWu1SqDjQ6F+kF9mchtIMqpLc7iMs3Ytkj5ZqTntoSJPTSOml5CME+74piHBtCMjYq6A==

util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
Expand Down

0 comments on commit 87eaad6

Please sign in to comment.