diff --git a/.github/workflows/ci-default.yml b/.github/workflows/ci-default.yml index febdaaf..fd8abd6 100644 --- a/.github/workflows/ci-default.yml +++ b/.github/workflows/ci-default.yml @@ -3,39 +3,48 @@ name: CI default branch on: push: branches: [development] + pull_request: + branches: [development] jobs: linting: + name: Run linters runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Run linters + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup Node uses: actions/setup-node@v2 with: node-version: 16.x cache: "npm" - - run: npm ci - - run: make ci/linting + - name: Install Node modules + run: npm ci + - name: Run eslint + run: make ci/linting testing: + name: Run tests on ${{ matrix.os }} for Node ${{ matrix.node-version }} needs: linting strategy: matrix: os: [ubuntu-latest, windows-latest] node-version: [14.x, 16.x] - runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: OS ${{ matrix.os }} - Node ${{ matrix.node-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup OS ${{ matrix.os }} - Node ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} cache: "npm" - - run: npm ci - - run: make ci/coverage - - name: Coveralls + - name: Install Node modules + run: npm ci + - name: Run tests with Jest + run: make ci/coverage + - name: Prepare coverage result uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -44,10 +53,11 @@ jobs: parallel: true finish: + name: Indicate completion to Coveralls.io needs: testing runs-on: ubuntu-latest steps: - - name: Coveralls Finished + - name: Finished coverage collection uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci-feature.yml b/.github/workflows/ci-feature.yml deleted file mode 100644 index 4db0384..0000000 --- a/.github/workflows/ci-feature.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CI feature branches / pull requests - -on: - pull_request: - branches: [development] - -jobs: - linting: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Run linters - uses: actions/setup-node@v2 - with: - node-version: 16.x - cache: "npm" - - run: npm ci - - run: make ci/linting - - testing: - needs: linting - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - node-version: [14.x, 16.x] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - name: OS ${{ matrix.os }} - Node ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" - - run: npm ci - - run: make ci/coverage - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - flag-name: OS-${{ matrix.os }}_Node-${{ matrix.node-version }} - path-to-lcov: ./src/.coverage/lcov.info - parallel: true - - finish: - needs: testing - runs-on: ubuntu-latest - steps: - - name: Coveralls Finished - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./src/.coverage/lcov.info - parallel-finished: true diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 64e2383..e9165f9 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -6,17 +6,23 @@ on: jobs: build: + name: Release to npm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v2 + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup Node + uses: actions/setup-node@v2 with: node-version: "16.x" registry-url: "https://registry.npmjs.org" - - run: npm ci - - run: make ci/linting - - run: make ci/testing - - run: make ci/release + - name: Install Node modules + run: npm ci + - name: Run linters + run: make ci/linting + - name: Run test suite + run: make ci/testing + - name: Perform the release + run: make ci/release env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/Makefile b/Makefile index a45883c..d059477 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ dev/coverage : | $(STAMP_NODE_INSTALL) # Run the test suite. dev/test : | $(STAMP_NODE_INSTALL) - npx jest --config .jestrc.json --verbose + npx jest --config .jestrc.json --verbose main.spec.ts .PHONY : dev/test # Run eslint against all files in the current directory. diff --git a/README.md b/README.md index 5fde3fe..0176ee0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ providing a manifest file to match the orginal filename to the new one. Just install _buster_ from **npm**: ```bash -npm install --save-dev @mischback/buster +$ npm install --save-dev @mischback/buster ``` Most likely you will want to install it as a development dependency for internal @@ -30,11 +30,65 @@ usage. After installation, _buster_ is available using the following command: ```bash -npx buster +$ npx buster +``` + +### Example Usage + +```bash +$ tree .manual-test +.manual-test +├── blank.css +├── index.ts +└── lib + └── hashwalker.ts + +$ npx buster -i "./.manual-test" -e "ts" +[date and time] INFO [buster] No mode specified, using "copy" +[date and time] INFO [buster] No output file specified, using "asset-manifest.json" + +$ tree .manual-test +.manual-test +├── asset-manifest.json +├── blank.css +├── index.dd99a19417.ts +├── index.ts +└── lib + ├── hashwalker.84f1a289e8.ts + └── hashwalker.ts + +$ cat .manual-test/asset-manifest.json +{"index.ts":"index.dd99a19417.ts","lib/hashwalker.ts":"lib/hashwalker.84f1a289e8.ts"}% ``` ## Configuration +_buster_ is configured by command line parameters. + +- `--commonPathLength`: manually override the length of filepath's that is to be + preserved in the manifest file +- `--debug`, `-d`: activate debug mode, providing more log messages + - default: `false` +- `--extension`, `-e`: a file extension to include during processing; may be specified multiple times + - default: `["css", "js"]` +- `--hashLength`: the length of the hash to be appended to the filename + - default: `10` +- `--input`, `-i`: the actual input file or directory + - **mandator parameter**, no default value +- `--mode`, `-m`: operation mode, either `"copy"` or `"rename"` + - default: `copy` + - `MODE_COPY` as specified by `copy` will copy the source file to a new file + with the content's hash appended to the filename + - `MODE_RENAME` as specified by `rename` will rename the existing file +- `--outFile`, `-o`: filename and path of the manifest file to be created + - default: `"asset-manifest.json"` + - **Please note:** If just a filename is specified, the output file will be + created relative to `input`: if `input` is a directory, it will be placed + inside this directory; if `input` is a file, it will be placed into the same + directory; if a path and filename is specified, that location will be useds +- `--quiet`, `-q`: suppress all log messages + - default: `false` + ## Contributing Issues, pull requests and feature requests are welcome. Just use the project's diff --git a/package-lock.json b/package-lock.json index c60b263..5f7c40e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@mischback/fileobject-walker": "1.0.0", + "@mischback/min-async-json-interface": "1.0.1", "stdio": "2.1.1", "tslog": "3.2.2" }, @@ -995,6 +996,11 @@ "resolved": "https://registry.npmjs.org/@mischback/fileobject-walker/-/fileobject-walker-1.0.0.tgz", "integrity": "sha512-oa47jI73e4iJ8UF9kVYIPGLyBz25yF7iTLiO4ze7aEwk89MokiJ5smWFHgCnyw2CJCrrbtk1DaSjVpgj84uxEQ==" }, + "node_modules/@mischback/min-async-json-interface": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@mischback/min-async-json-interface/-/min-async-json-interface-1.0.1.tgz", + "integrity": "sha512-MOtAEXojYHO8iv6g9LkmBbrnG11R0dqJm1ACVWAsWsgh3FwSmDIgJGMoyq7rV6kLhIB1w9gdl1ohRCZfKIj35w==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2148,9 +2154,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.895", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.895.tgz", - "integrity": "sha512-9Ww3fB8CWctjqHwkOt7DQbMZMpal2x2reod+/lU4b9axO1XJEDUpPMBxs7YnjLhhqpKXIIB5SRYN/B4K0QpvyQ==", + "version": "1.3.896", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.896.tgz", + "integrity": "sha512-NcGkBVXePiuUrPLV8IxP43n1EOtdg+dudVjrfVEUd/bOqpQUFZ2diL5PPYzbgEhZFEltdXV3AcyKwGnEQ5lhMA==", "dev": true }, "node_modules/emittery": { @@ -6383,6 +6389,11 @@ "resolved": "https://registry.npmjs.org/@mischback/fileobject-walker/-/fileobject-walker-1.0.0.tgz", "integrity": "sha512-oa47jI73e4iJ8UF9kVYIPGLyBz25yF7iTLiO4ze7aEwk89MokiJ5smWFHgCnyw2CJCrrbtk1DaSjVpgj84uxEQ==" }, + "@mischback/min-async-json-interface": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@mischback/min-async-json-interface/-/min-async-json-interface-1.0.1.tgz", + "integrity": "sha512-MOtAEXojYHO8iv6g9LkmBbrnG11R0dqJm1ACVWAsWsgh3FwSmDIgJGMoyq7rV6kLhIB1w9gdl1ohRCZfKIj35w==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7259,9 +7270,9 @@ } }, "electron-to-chromium": { - "version": "1.3.895", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.895.tgz", - "integrity": "sha512-9Ww3fB8CWctjqHwkOt7DQbMZMpal2x2reod+/lU4b9axO1XJEDUpPMBxs7YnjLhhqpKXIIB5SRYN/B4K0QpvyQ==", + "version": "1.3.896", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.896.tgz", + "integrity": "sha512-NcGkBVXePiuUrPLV8IxP43n1EOtdg+dudVjrfVEUd/bOqpQUFZ2diL5PPYzbgEhZFEltdXV3AcyKwGnEQ5lhMA==", "dev": true }, "emittery": { diff --git a/package.json b/package.json index 62931b9..102c9cd 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "homepage": "https://github.com/Mischback/buster#readme", "dependencies": { "@mischback/fileobject-walker": "1.0.0", + "@mischback/min-async-json-interface": "1.0.1", "stdio": "2.1.1", "tslog": "3.2.2" }, diff --git a/src/lib/json-interface.spec.ts b/src/lib/json-interface.spec.ts deleted file mode 100644 index 6a29220..0000000 --- a/src/lib/json-interface.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MIT - -/* test specific imports */ -import { describe, expect, it, jest } from "@jest/globals"; - -/* mock library imports */ -jest.mock("fs/promises"); - -/* import the subject under test (SUT) */ -import { - JsonInterfaceError, - loadJsonFromFile, - writeJsonToFile, -} from "./json-interface"; - -/* additional imports */ -import { readFile, writeFile } from "fs/promises"; - -describe("loadJsonFromFile()...", () => { - it("...rejects with an error if readFile() fails", () => { - /* define the parameter */ - const testFile = "testfile"; - - /* setup mocks and spies */ - (readFile as jest.Mock).mockRejectedValue(new Error("foo")); - - /* make the assertions */ - return loadJsonFromFile(testFile).catch((err) => { - expect(err).toBeInstanceOf(JsonInterfaceError); - expect(readFile).toHaveBeenCalledTimes(1); - expect(readFile).toHaveBeenCalledWith(testFile, expect.anything()); - }); - }); - - it("...rejects with an error if JSON.parse() fails", () => { - /* define the parameter */ - const testFile = "testfile"; - - /* setup mocks and spies */ - (readFile as jest.Mock).mockResolvedValue("foo"); - JSON.parse = jest.fn(() => { - throw new Error("foo"); - }); - - /* make the assertions */ - return loadJsonFromFile(testFile).catch((err) => { - expect(err).toBeInstanceOf(JsonInterfaceError); - expect(readFile).toHaveBeenCalledTimes(1); - expect(readFile).toHaveBeenCalledWith(testFile, expect.anything()); - expect(JSON.parse).toHaveBeenCalledTimes(1); - expect(JSON.parse).toHaveBeenCalledWith("foo"); - }); - }); - - it("...resolves with parsed JSON object", () => { - /* define the parameter */ - const testFile = "testfile"; - const testResult = { foo: "bar" }; - - /* setup mocks and spies */ - (readFile as jest.Mock).mockResolvedValue("foo"); - JSON.parse = jest.fn().mockReturnValue(testResult); - - /* make the assertions */ - return loadJsonFromFile(testFile).then((retVal) => { - expect(retVal).toStrictEqual(testResult); - expect(readFile).toHaveBeenCalledTimes(1); - expect(readFile).toHaveBeenCalledWith(testFile, expect.anything()); - expect(JSON.parse).toHaveBeenCalledTimes(1); - expect(JSON.parse).toHaveBeenCalledWith("foo"); - }); - }); -}); - -describe("writeJsonToFile()...", () => { - it("...rejects with an error if writeFile() fails", () => { - /* define the parameter */ - const testFile = "testfile"; - const testJson = { foo: "bar" } as unknown; - - /* setup mocks and spies */ - (writeFile as jest.Mock).mockRejectedValue(new Error("foo")); - - /* make the assertions */ - return writeJsonToFile(testFile, testJson).catch((err) => { - expect(err).toBeInstanceOf(JsonInterfaceError); - expect(writeFile).toHaveBeenCalledTimes(1); - expect(writeFile).toHaveBeenCalledWith(testFile, expect.anything()); - }); - }); - - it("...resolves if writeFile() succeeds", () => { - /* define the parameter */ - const testFile = "testfile"; - const testJson = { foo: "bar" } as unknown; - - /* setup mocks and spies */ - (writeFile as jest.Mock).mockResolvedValue(undefined); - JSON.stringify = jest.fn((_value: any) => { - return "foo"; - }); - - /* make the assertions */ - return writeJsonToFile(testFile, testJson).then((retVal) => { - expect(retVal).toBe(undefined); - expect(writeFile).toHaveBeenCalledTimes(1); - expect(writeFile).toHaveBeenCalledWith(testFile, expect.anything()); - expect(JSON.stringify).toHaveBeenCalledTimes(1); - expect(JSON.stringify).toHaveBeenCalledWith(testJson); - }); - }); -}); diff --git a/src/lib/json-interface.ts b/src/lib/json-interface.ts deleted file mode 100644 index a557163..0000000 --- a/src/lib/json-interface.ts +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MIT - -/* library imports */ -import { readFile, writeFile } from "fs/promises"; - -export class JsonInterfaceError extends Error { - originalError: Error; - - constructor(message: string, err: Error) { - super(message); - this.originalError = err; - } -} - -/** - * Read a given file and parse its content as JSON - * - * @param filename - The file to be read provided as string - * @returns A Promise, resolving to the file content as JSON - * - * The returned JSON object will be of type "unknown" and must be casted to - * the required target type. - */ -export function loadJsonFromFile(filename: string): Promise { - return new Promise((resolve, reject) => { - readFile(filename, "utf-8") - .then((fileContent) => { - try { - const parsedContent = JSON.parse(fileContent) as unknown; - return resolve(parsedContent); - } catch (err) { - return reject( - new JsonInterfaceError( - `Error while parsing content of "${filename}"`, - err as Error - ) - ); - } - }) - .catch((err: Error) => { - return reject( - new JsonInterfaceError(`Could not read "${filename}"`, err) - ); - }); - }); -} - -/** - * Write a given object to a file in JSON - * - * @param filename - The file to be written to - * @param jsonData - The object to be written to the file - * @returns A Promise, resolving to 0 of type JsonInterfaceRetValSuccess - */ -export function writeJsonToFile( - filename: string, - jsonData: unknown -): Promise { - return new Promise((resolve, reject) => { - writeFile(filename, JSON.stringify(jsonData)) - .then(() => { - return resolve(); - }) - .catch((err: Error) => { - return reject( - new JsonInterfaceError(`Could not write to file "${filename}"`, err) - ); - }); - }); -} diff --git a/src/lib/manifest.spec.ts b/src/lib/manifest.spec.ts index dde00b6..0eac99a 100644 --- a/src/lib/manifest.spec.ts +++ b/src/lib/manifest.spec.ts @@ -4,14 +4,14 @@ import { beforeAll, describe, expect, it, jest } from "@jest/globals"; /* mock library imports */ -jest.mock("./json-interface"); +jest.mock("@mischback/min-async-json-interface"); /* import the subject under test (SUT) */ import { BusterManifestError, createManifestFile } from "./manifest"; /* additional imports */ +import { writeJsonToFile } from "@mischback/min-async-json-interface"; import { BusterConfig, MODE_COPY } from "./configure"; -import { writeJsonToFile } from "./json-interface"; import { logger } from "./logging"; import { HashWalkerResult } from "./hashwalker/hashwalker"; diff --git a/src/lib/manifest.ts b/src/lib/manifest.ts index 11f4df5..f0e5d4f 100644 --- a/src/lib/manifest.ts +++ b/src/lib/manifest.ts @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT /* library imports */ +import { writeJsonToFile } from "@mischback/min-async-json-interface"; /* internal imports */ import { BusterConfig } from "./configure"; import { BusterError } from "./errors"; import { HashWalkerResult } from "./hashwalker/hashwalker"; -import { writeJsonToFile } from "./json-interface"; import { logger } from "./logging"; export class BusterManifestError extends BusterError { diff --git a/src/main.spec.ts b/src/main.spec.ts new file mode 100644 index 0000000..233442f --- /dev/null +++ b/src/main.spec.ts @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: MIT + +/* test specific imports */ +import { beforeAll, describe, expect, it, jest } from "@jest/globals"; + +/* mock library imports */ +jest.mock("./lib/configure"); +jest.mock("./lib/logging"); +jest.mock("./lib/manifest"); +jest.mock("./lib/hashwalker/hashwalker"); + +/* import the subject under test (SUT) */ +import { busterMain } from "./main"; + +/* additional imports */ +import { FileObjectWalkerError } from "@mischback/fileobject-walker"; +import { + applyDebugConfiguration, + logger, + suppressLogOutput, +} from "./lib/logging"; +import { getConfig, BusterConfigError, checkConfig } from "./lib/configure"; +import { BusterManifestError, createManifestFile } from "./lib/manifest"; +import { BusterFileSystemError } from "./lib/hashwalker/filesystem"; +import { BusterHashError } from "./lib/hashwalker/hash"; +import { hashWalker } from "./lib/hashwalker/hashwalker"; + +/* Run these before actually starting the test suite */ +beforeAll(() => { + /* The test subject relies on "tslog" to provide log messages. + * For running the test-suite, actually printing the log messages to the + * console is unwanted, so the output is suppressed. + */ + logger.setSettings({ + suppressStdOutput: true, + }); +}); + +describe("busterMain()...", () => { + it("...attaches SIGINT handler", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter", "-q"]; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockRejectedValue(new Error("foo")); + const processSpy = jest.spyOn(process, "on"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(70); + expect(processSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...correctly activates quiet mode", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter", "-q"]; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockRejectedValue(new Error("foo")); + (suppressLogOutput as jest.Mock).mockImplementation(); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(70); + expect(suppressLogOutput).toHaveBeenCalledTimes(1); + }); + }); + + it("...correctly activates debug mode", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter", "-d"]; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockRejectedValue(new Error("foo")); + (applyDebugConfiguration as jest.Mock).mockImplementation(); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(70); + expect(applyDebugConfiguration).toHaveBeenCalledTimes(1); + }); + }); + + it("...overrides quiet mode with debug mode", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter", "-q", "-d"]; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockRejectedValue(new Error("foo")); + (suppressLogOutput as jest.Mock).mockImplementation(); + (applyDebugConfiguration as jest.Mock).mockImplementation(); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(70); + expect(suppressLogOutput).toHaveBeenCalledTimes(1); + expect(applyDebugConfiguration).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_CONFIG_ERROR on BusterConfigError by getConfig()", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockRejectedValue( + new BusterConfigError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(78); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + // BusterConfigError is mocked aswell, so it does not work to access its + // message in an expect block. + // expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_CONFIG_ERROR on BusterConfigError by checkConfig()", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockResolvedValue("foo"); + (checkConfig as jest.Mock).mockRejectedValue( + new BusterConfigError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(78); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + // BusterConfigError is mocked aswell, so it does not work to access its + // message in an expect block. + // expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_PROCESSING_ERROR on BusterHashError", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockResolvedValue("foo"); + (checkConfig as jest.Mock).mockResolvedValue("foo"); + (hashWalker as jest.Mock).mockRejectedValue( + new BusterHashError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(10); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_PROCESSING_ERROR on BusterFileSystemError", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockResolvedValue("foo"); + (checkConfig as jest.Mock).mockResolvedValue("foo"); + (hashWalker as jest.Mock).mockRejectedValue( + new BusterFileSystemError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(10); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_PROCESSING_ERROR on FileObjectWalkerError", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockResolvedValue("foo"); + (checkConfig as jest.Mock).mockResolvedValue("foo"); + (hashWalker as jest.Mock).mockRejectedValue( + new FileObjectWalkerError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(10); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("...returns EXIT_PROCESSING_ERROR on BusterManifestError", () => { + /* define the parameter */ + const testArgv: string[] = ["doesn't", "matter"]; + const testErrorMessage = "testError"; + + /* setup mocks and spies */ + (getConfig as jest.Mock).mockResolvedValue("foo"); + (checkConfig as jest.Mock).mockResolvedValue("foo"); + (createManifestFile as jest.Mock).mockRejectedValue( + new BusterManifestError(testErrorMessage) + ); + const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerFatalSpy = jest.spyOn(logger, "fatal"); + + /* make the assertions */ + return busterMain(testArgv).catch((err) => { + expect(err).toBe(10); + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith(testErrorMessage); + expect(loggerFatalSpy).toHaveBeenCalledTimes(1); + }); + }); +});