Skip to content

Commit

Permalink
add error handling to core cli (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiroki0525 authored Nov 5, 2023
1 parent 6d2d216 commit a3744db
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 35 deletions.
6 changes: 6 additions & 0 deletions .changeset/wild-cups-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@dandori/core": patch
"@dandori/cli": patch
---

add error handling to core cli
103 changes: 85 additions & 18 deletions packages/cli/src/core/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import DandoriCoreCli from "../index";
import generateDandoriTasks, {
ChatGPTFunctionCallModel,
DandoriTask,
OptionalTaskPropsOption,
} from "@dandori/core";
import { logger } from "@dandori/libs";

const tasks: DandoriTask[] = [
{
Expand All @@ -31,6 +33,7 @@ vi.mock("@dandori/core", () => ({

describe("DandoriCoreCli", () => {
const mockConsole = vi.spyOn(console, "log").mockImplementation(() => {});
const mockLogError = vi.spyOn(logger, "error").mockImplementation(() => {});
const inputFileName = "DandoriCoreCli.txt";
const inputFileText = "DandoriCoreCli";
const loadProcessArgv = (options: string[]) => {
Expand Down Expand Up @@ -68,18 +71,49 @@ describe("DandoriCoreCli", () => {
});

describe("with -m option", () => {
const model: ChatGPTFunctionCallModel = "gpt-4-0613";
describe("valid argument", () => {
const model: ChatGPTFunctionCallModel = "gpt-4-0613";

beforeEach(async () => {
loadProcessArgv(["-m", model]);
await new DandoriCoreCli().run();
beforeEach(async () => {
loadProcessArgv(["-m", model]);
await new DandoriCoreCli().run();
});

it("call generateDandoriTasks with valid model", () => {
expect(generateDandoriTasks).toHaveBeenCalledWith(inputFileText, {
envFilePath: undefined,
chatGPTModel: model,
optionalTaskProps: undefined,
});
});
});

it("call generateDandoriTasks with envFilePath", () => {
expect(generateDandoriTasks).toHaveBeenCalledWith(inputFileText, {
envFilePath: undefined,
chatGPTModel: model,
optionalTaskProps: undefined,
describe("invalid argument", () => {
const model = "invalid-model";
const supportedChatGPTModels: ChatGPTFunctionCallModel[] = [
"gpt-3.5-turbo-0613",
"gpt-4-0613",
];
const expectedMessage = `Unsupported model: ${model}. Supported models are ${supportedChatGPTModels.join(
", ",
)}`;

beforeEach(() => {
loadProcessArgv(["-m", model]);
});

it("throw Error with valid message", async () => {
await expect(new DandoriCoreCli().run()).rejects.toThrow(
expectedMessage,
);
});

it("call logger.error with valid message", async () => {
try {
await new DandoriCoreCli().run();
} catch {
expect(mockLogError).toHaveBeenCalledWith(expectedMessage);
}
});
});
});
Expand All @@ -102,18 +136,51 @@ describe("DandoriCoreCli", () => {
});

describe("with -o option", () => {
const optionalTaskProps = "deadline,description";
describe("valid argument", () => {
const optionalTaskProps = "deadline,description";

beforeEach(async () => {
loadProcessArgv(["-o", optionalTaskProps]);
await new DandoriCoreCli().run();
beforeEach(async () => {
loadProcessArgv(["-o", optionalTaskProps]);
await new DandoriCoreCli().run();
});

it("call generateDandoriTasks with valid optionalTaskProps", () => {
expect(generateDandoriTasks).toHaveBeenCalledWith(inputFileText, {
envFilePath: undefined,
chatGPTModel: undefined,
optionalTaskProps: optionalTaskProps.split(","),
});
});
});

it("call generateDandoriTasks with envFilePath", () => {
expect(generateDandoriTasks).toHaveBeenCalledWith(inputFileText, {
envFilePath: undefined,
chatGPTModel: undefined,
optionalTaskProps: optionalTaskProps.split(","),
describe("invalid argument", () => {
const optionalTaskProps = "invalid";
const supportedOptionalTaskProps: OptionalTaskPropsOption = [
"description",
"deadline",
"assignee",
"all",
];
const expectedMessage = `Unsupported optional task props: ${optionalTaskProps}. Supported optional task props are ${supportedOptionalTaskProps.join(
", ",
)}`;

beforeEach(() => {
loadProcessArgv(["-o", optionalTaskProps]);
});

it("throw Error with valid message", async () => {
await expect(new DandoriCoreCli().run()).rejects.toThrow(
expectedMessage,
);
});

it("call logger.error with valid message", async () => {
try {
await new DandoriCoreCli().run();
} catch {
expect(mockLogError).toHaveBeenCalledWith(expectedMessage);
}
});
});
});
Expand Down
51 changes: 45 additions & 6 deletions packages/cli/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,37 @@ import chalk from "chalk";
import generateDandoriTasks, {
ChatGPTFunctionCallModel,
DandoriTask,
OptionalTaskProps,
OptionalTaskPropsOption,
} from "@dandori/core";
import { readFile } from "fs/promises";
import { loadFile } from "@dandori/libs";
import { loadFile, logger } from "@dandori/libs";

const supportedChatGPTModels: ChatGPTFunctionCallModel[] = [
"gpt-3.5-turbo-0613",
"gpt-4-0613",
];

const isSupportedChatGPTModels = (
model?: string,
): model is ChatGPTFunctionCallModel | undefined =>
model === undefined ||
supportedChatGPTModels.includes(model as ChatGPTFunctionCallModel);

const supportedOptionalTaskProps: OptionalTaskPropsOption = [
"description",
"deadline",
"assignee",
"all",
];

const isSupportedOptionalTaskProps = (
props?: string[],
): props is OptionalTaskPropsOption | undefined =>
props === undefined ||
props.every((prop) =>
supportedOptionalTaskProps.includes(prop as OptionalTaskProps),
);

export default class DandoriCoreCli {
private inputFile: string = "";
Expand Down Expand Up @@ -48,15 +75,27 @@ export default class DandoriCoreCli {
const { envFile, optionalTaskProps, model } = this.program.opts<{
envFile?: string;
optionalTaskProps?: string;
model?: ChatGPTFunctionCallModel;
model?: string;
}>();
// TODO: Error Handling of invalid options
if (!isSupportedChatGPTModels(model)) {
const logMessage = `Unsupported model: ${model}. Supported models are ${supportedChatGPTModels.join(
", ",
)}`;
logger.error(logMessage);
throw new Error(logMessage);
}
const inputOptionalTaskProps = optionalTaskProps?.split(",");
if (!isSupportedOptionalTaskProps(inputOptionalTaskProps)) {
const logMessage = `Unsupported optional task props: ${optionalTaskProps}. Supported optional task props are ${supportedOptionalTaskProps.join(
", ",
)}`;
logger.error(logMessage);
throw new Error(logMessage);
}
return generateDandoriTasks(source.toString(), {
envFilePath: envFile,
chatGPTModel: model,
optionalTaskProps: optionalTaskProps?.split(
",",
) as OptionalTaskPropsOption,
optionalTaskProps: inputOptionalTaskProps,
});
}
}
6 changes: 2 additions & 4 deletions packages/core/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import generateDandoriTasks, {
DandoriTaskOptionalProperty,
DandoriTaskProperty,
DandoriTaskRequiredProperty,
OptionalAllDandoriTaskPropertiesName,
DandoriTaskOptionalAllProperty,
} from "../index";
import { describe, beforeEach, afterEach, it, vi, expect, Mock } from "vitest";
import OpenAI from "openai";
Expand Down Expand Up @@ -217,9 +217,7 @@ describe("generateDandoriTasks", () => {

describe("with all argument", () => {
const source = "with all argument";
const optionalTaskProps: OptionalAllDandoriTaskPropertiesName[] = [
"all",
];
const optionalTaskProps: DandoriTaskOptionalAllProperty[] = ["all"];

beforeEach(async () => {
result = await generateDandoriTasks(source, {
Expand Down
13 changes: 6 additions & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,16 @@ export type DandoriTaskOptionalProperty = Exclude<
DandoriTaskRequiredProperty
>;

export type OptionalAllDandoriTaskPropertiesName = "all";
const optionalAllDandoriTaskPropertiesName: OptionalAllDandoriTaskPropertiesName =
"all";
export type OptionalTaskPropsOption = (
export type DandoriTaskOptionalAllProperty = "all";
const dandoriTaskOptionalAllProperty: DandoriTaskOptionalAllProperty = "all";
export type OptionalTaskProps =
| DandoriTaskOptionalProperty
| OptionalAllDandoriTaskPropertiesName
)[];
| DandoriTaskOptionalAllProperty;
export type OptionalTaskPropsOption = OptionalTaskProps[];
const notIncludeAdditionalAllPropsName = (
props: OptionalTaskPropsOption,
): props is DandoriTaskOptionalProperty[] =>
!props.includes(optionalAllDandoriTaskPropertiesName);
!props.includes(dandoriTaskOptionalAllProperty);

export type GenerateDandoriTasksOptions = {
chatGPTModel?: ChatGPTFunctionCallModel;
Expand Down

0 comments on commit a3744db

Please sign in to comment.