sourcecred/src/cli/command.test.js
William Chargin 4c433d417e
cli: add command infrastructure and test utils (#740)
Summary:
This commit introduces the notion of a `Command`, which is simply a
function that takes command-line arguments and interacts with the real
world. This infrastructure will enable us to write a well-tested CLI.

The `Command` interface is asynchronous because commands like `load`
need to block on promise resolution (for loading GitHub and Git data).
This is annoying for testing, but does not actually appear to be a
problem in practice.

Test Plan:
Unit tests added. See later commits for real-world usage.

wchargin-branch: cli-command-infrastructure
2018-09-02 15:48:47 -07:00

79 lines
3.0 KiB
JavaScript

// @flow
import {type Command, handlingErrors} from "./command";
describe("cli/command", () => {
describe("handlingErrors", () => {
it("passes through arguments appropriately", async () => {
const expectedArgs = ["arg", "arg", "arg"];
const expectedStdio = {out: () => {}, err: () => {}};
let called = false;
const cmd: Command = async (args, stdio) => {
expect(args).toBe(expectedArgs);
expect(args).toEqual(["arg", "arg", "arg"]);
expect(stdio).toBe(expectedStdio);
called = true;
return 0;
};
await handlingErrors(cmd)(expectedArgs, expectedStdio);
expect(called).toBe(true);
});
it("passes through a return value", async () => {
const cmd: Command = (args) => Promise.resolve(parseInt(args[0], 10));
const stdio = {out: () => {}, err: () => {}};
expect(await handlingErrors(cmd)(["0"], stdio)).toBe(0);
expect(await handlingErrors(cmd)(["1"], stdio)).toBe(1);
expect(await handlingErrors(cmd)(["2"], stdio)).toBe(2);
});
it("handles a thrown Error", async () => {
const stdio = {out: jest.fn(), err: jest.fn()};
const cmd: Command = () => {
throw new Error("wat");
};
const exitCode = await handlingErrors(cmd)([], stdio);
expect(exitCode).toBe(1);
expect(stdio.out).toHaveBeenCalledTimes(0);
expect(stdio.err).toHaveBeenCalledTimes(1);
expect(stdio.err.mock.calls[0]).toHaveLength(1);
expect(stdio.err.mock.calls[0][0]).toMatch(/^Error: wat\n *at cmd/);
});
it("handles a thrown string", async () => {
const stdio = {out: jest.fn(), err: jest.fn()};
const cmd: Command = () => {
// This is bad form, but we should try not to die in case clients do it.
// eslint-disable-next-line no-throw-literal
throw "wat";
};
const exitCode = await handlingErrors(cmd)([], stdio);
expect(exitCode).toBe(1);
expect(stdio.out).toHaveBeenCalledTimes(0);
expect(stdio.err).toHaveBeenCalledTimes(1);
expect(stdio.err.mock.calls).toEqual([['"wat"']]);
});
it("handles a rejection with an Error", async () => {
const stdio = {out: jest.fn(), err: jest.fn()};
const cmd: Command = () => Promise.reject(new Error("wat"));
const exitCode = await handlingErrors(cmd)([], stdio);
expect(exitCode).toBe(1);
expect(stdio.out).toHaveBeenCalledTimes(0);
expect(stdio.err).toHaveBeenCalledTimes(1);
expect(stdio.err.mock.calls[0]).toHaveLength(1);
expect(stdio.err.mock.calls[0][0]).toMatch(/^Error: wat\n *at cmd/);
});
it("handles rejection with a string", async () => {
const stdio = {out: jest.fn(), err: jest.fn()};
const cmd: Command = () => Promise.reject("wat");
const exitCode = await handlingErrors(cmd)([], stdio);
expect(exitCode).toBe(1);
expect(stdio.out).toHaveBeenCalledTimes(0);
expect(stdio.err).toHaveBeenCalledTimes(1);
expect(stdio.err.mock.calls).toEqual([['"wat"']]);
});
});
});