diff --git a/docs/cli.md b/docs/cli.md index f4906bd..dbe1f20 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -53,9 +53,9 @@ Options: -s, --subreddit The subreddit to search (default: "rabbits") -j, --json Output as JSON -q, --query-metadata Include query metadata in result - -o Output to file --sort Sort by (choices: "hot", "new", "top", default: "hot") -h, --help display help for command +✨ Done in 0.32s. ``` ## JSON output @@ -95,11 +95,3 @@ This defaults to "rabbits" $ randombunny --subreddit rabbits $ randombunny -s horses ``` - -## Output to file - -If you'd rather send the output to a file, you can supply the `-o` flag. - -``` -$ randombunny -o ~/Desktop/output.txt -``` diff --git a/src/cli.ts b/src/cli.ts index 0d739e2..13ce7f7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,7 +2,8 @@ import { Command, Option } from "commander"; import randomBunny from "./index"; import ICliOptions from "./contracts/ICliOptions"; import { exit } from "process"; -import CliHelper from "./helpers/cliHelper"; +import OutputHelper from "./helpers/outputHelper"; +import { writeFileSync } from "fs"; const program = new Command(); @@ -21,4 +22,21 @@ program.parse(); const options: ICliOptions = program.opts(); randomBunny(options.subreddit, options.sort) - .then((response) => exit(CliHelper.Endpoint(response, options))); \ No newline at end of file + .then((response) => { + if (response.IsSuccess) { + const output = OutputHelper.GenerateOutput(response, options); + + if (options.o) { + writeFileSync(options.o, output); + } else { + console.log(output); + } + + exit(0); + } else { + const error = response.Error!; + + console.error(error.Message, error.Code); + exit(1); + } + }); \ No newline at end of file diff --git a/src/helpers/cliHelper.ts b/src/helpers/cliHelper.ts deleted file mode 100644 index 2776f2e..0000000 --- a/src/helpers/cliHelper.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { writeFileSync } from "fs"; -import ICliOptions from "../contracts/ICliOptions"; -import IReturnResult from "../contracts/IReturnResult"; -import OutputHelper from "./outputHelper"; - -export default class CliHelper { - public static Endpoint(response: IReturnResult, options: ICliOptions): number { - if (response.IsSuccess) { - const output = OutputHelper.GenerateOutput(response, options); - - if (options.o) { - writeFileSync(options.o, output); - } else { - console.log(output); - } - - return 0; - } else { - const error = response.Error!; - - console.error(error.Message, error.Code); - return 1; - } - } -} \ No newline at end of file diff --git a/tests/cli.test.ts b/tests/cli.test.ts new file mode 100644 index 0000000..66fecbe --- /dev/null +++ b/tests/cli.test.ts @@ -0,0 +1,187 @@ +import { exec } from "child_process"; +import path from "path"; + +describe('default', () => { + test('GIVEN no options are supplied, EXPECT standard output', async () => { + const result = await cli([], '.'); + + const keys = result.stdout.split('\n') + .flatMap(x => x.split(' = ')[0]) + .filter(x => x && x.length > 0); + const values = result.stdout.split('\n') + .flatMap(x => x.split(' = ')[1]) + .filter(x => x && x.length > 0); + + + expect(result.code).toBe(0); + expect(keys).toStrictEqual(['Archived', 'Downvotes', 'Hidden', 'Permalink', 'Subreddit', 'Subreddit Subscribers', 'Title', 'Upvotes', 'Url']); + expect(values.length).toBe(9); + }, 5000); + + test('GIVEN an error occurs, EXPECT error output', async () => { + const result = await cli(['-s', 'textonly'], '.'); + + expect(result.code).toBe(1); + expect(result.stderr).toBeDefined(); + }, 5000); +}); + +describe('version', () => { + test('GIVEN -V flag is supplied, EXPECT version returned', async () => { + const result = await cli(['-V'], '.'); + + expect(result.code).toBe(0); + expect(result.stdout).toBe('2.2\n'); + }); + + test('GIVEN --version is supplied, EXPECT version returned', async () => { + const result = await cli(['--version'], '.'); + + expect(result.code).toBe(0); + expect(result.stdout).toBe('2.2\n'); + }); +}); + +describe('help', () => { + test('GIVEN -h is supplied, EXPECT help returned', async () => { + const result = await cli(['-h'], '.'); + + expect(result.code).toBe(0); + expect(result.stdout.split('\n')[0]).toBe('Usage: random-bunny [options]'); + }); + + test('GIVEN --help is supplied, EXPECT help returned', async () => { + const result = await cli(['--help'], '.'); + + expect(result.code).toBe(0); + expect(result.stdout.split('\n')[0]).toBe('Usage: random-bunny [options]'); + }); +}); + +describe('subreddit', () => { + test('GIVEN -s is not supplied, EXPECT subreddit to be defaulted', async () => { + const result = await cli([], '.'); + + const subreddit = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0] == 'Subreddit')! + .split(' = ')[1]; + + expect(subreddit).toBe('Rabbits'); + }, 5000); + + test('GIVEN -s is supplied, EXPECT subreddit to be changed', async () => { + const result = await cli(['-s', 'pics'], '.'); + + const subreddit = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0] == 'Subreddit')! + .split(' = ')[1]; + + expect(subreddit).toBe('pics'); + }, 5000); + + test('GIVEN --subreddit is supplied, EXPECT subreddit to be changed', async () => { + const result = await cli(['--subreddit', 'pics'], '.'); + + const subreddit = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0] == 'Subreddit')! + .split(' = ')[1]; + + expect(subreddit).toBe('pics'); + }, 5000); +}); + +describe('json', () => { + test('GIVEN -j is supplied, EXPECT output to be valid JSON', async () => { + const result = await cli(['-j'], '.'); + + const json = JSON.parse(result.stdout); + + expect(json).toBeDefined(); + }, 5000); + + test('GIVEN --json is supplied, EXPECT output to be valid JSON', async () => { + const result = await cli(['--json'], '.'); + + const json = JSON.parse(result.stdout); + + expect(json).toBeDefined(); + }, 5000); +}); + +describe('sort', () => { + test('GIVEN --sort is not supplied, EXPECT sort to be defaulted', async () => { + const result = await cli(['-q'], '.'); + + const sortBy = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0] == 'Query.Sort By')! + .split(' = ')[1]; + + expect(sortBy).toBe('hot'); + }, 5000); + + test('GIVEN --sort is supplied WITH a valid input, EXPECT sort to be used', async () => { + const result = await cli(['-q', '--sort', 'new'], '.'); + + const sortBy = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0] == 'Query.Sort By')! + .split(' = ')[1]; + + expect(sortBy).toBe('new'); + }, 5000); + + test('GIVEN --sort is supplied WITH an invalid input, EXPECT error', async () => { + const result = await cli(['-q', '--sort', 'invalid'], '.'); + + expect(result.code).toBe(1); + expect(result.stderr).toBe("error: option '--sort ' argument 'invalid' is invalid. Allowed choices are hot, new, top.\n"); + }, 5000); +}); + +describe('query-metadata', () => { + test('GIVEN --query-metadata is not supplied, EXPECT no query metadata returned', async () => { + const result = await cli([], '.'); + + const query = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0].startsWith('Query')); + + expect(query).toBeUndefined(); + }, 5000); + + test('GIVEN --query-metadata is supplied, EXPECT query metadata returned', async () => { + const result = await cli(['--query-metadata'], '.'); + + const query = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0].startsWith('Query')); + + expect(query).toBeDefined(); + }, 5000); + + test('GIVEN -q is supplied, EXPECT query metadata returned', async () => { + const result = await cli(['-q'], '.'); + + const query = result.stdout.split('\n') + .find(x => x && x.length > 0 && x.split(' = ')[0].startsWith('Query')); + + expect(query).toBeDefined(); + }, 5000); +}); + +function cli(args: string[], cwd: string): Promise { + return new Promise(resolve => { + exec(`node ${path.resolve('./dist/cli.js')} ${args.join(' ')}`, + { cwd }, + (error, stdout, stderr) => { resolve({ + code: error && error.code ? error.code : 0, + error, + stdout, + stderr }); + }); + }); +} + +interface cliResult { + code: number, + error: any, + stdout: string, + stderr: string, +} \ No newline at end of file diff --git a/tests/helpers/__snapshots__/outputHelper.test.ts.snap b/tests/helpers/__snapshots__/outputHelper.test.ts.snap deleted file mode 100644 index 0aa5dcc..0000000 --- a/tests/helpers/__snapshots__/outputHelper.test.ts.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GenerateOutput EXPECT standout output to be returned 1`] = ` -"Archived = false -Downvotes = 0 -Hidden = false -Permalink = /r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/ -Subreddit = Rabbits -Subreddit Subscribers = 654751 -Title = This is my Ms Bear! -Upvotes = 17 -Url = https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d" -`; - -exports[`GenerateOutput GIVEN options.json is true, EXPECT output to be returned as JSON 1`] = `"{"Archived":false,"Downs":0,"Hidden":false,"Permalink":"/r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/","Subreddit":"Rabbits","SubredditSubscribers":654751,"Title":"This is my Ms Bear!","Ups":17,"Url":"https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d"}"`; - -exports[`GenerateOutput GIVEN options.queryMetadata is supplied, EXPECT query metadata to be added 1`] = ` -"Archived = false -Downvotes = 0 -Hidden = false -Permalink = /r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/ -Subreddit = Rabbits -Subreddit Subscribers = 654751 -Title = This is my Ms Bear! -Upvotes = 17 -Url = https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d -Query.Subreddit = rabbits -Query.Sort By = hot" -`; diff --git a/tests/helpers/cliHelper.test.ts b/tests/helpers/cliHelper.test.ts deleted file mode 100644 index 3ea6687..0000000 --- a/tests/helpers/cliHelper.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import fs from "fs"; -import CliHelper from "../../src/helpers/cliHelper"; -import ICliOptions from "../../src/contracts/ICliOptions"; -import IReturnResult from "../../src/contracts/IReturnResult"; -import OutputHelper from "../../src/helpers/outputHelper"; -import { ErrorCode } from "../../src/constants/ErrorCode"; - -describe("Endpoint", () => { - describe("GIVEN response is successful", () => { - test("GIVEN options.o is defined, EXPECT output written to file", () => { - // Arrange - const response = { - IsSuccess: true, - } as IReturnResult; - - const options = { - o: "file.txt", - } as ICliOptions; - - OutputHelper.GenerateOutput = jest.fn().mockReturnValue("test output"); - - fs.writeFileSync = jest.fn(); - - console.log = jest.fn(); - - console.error = jest.fn(); - - // Act - const result = CliHelper.Endpoint(response, options); - - // Assert - expect(result).toBe(0); - - expect(OutputHelper.GenerateOutput).toHaveBeenCalledTimes(1); - expect(OutputHelper.GenerateOutput).toHaveBeenCalledWith(response, options); - - expect(fs.writeFileSync).toHaveBeenCalledTimes(1); - expect(fs.writeFileSync).toHaveBeenCalledWith("file.txt", "test output"); - - expect(console.log).not.toHaveBeenCalled(); - - expect(console.error).not.toHaveBeenCalled(); - }); - - test("GIVEN options.o is undefined, EXPECT output logged to console", () => { - // Arrange - const response = { - IsSuccess: true, - } as IReturnResult; - - const options = { - o: undefined, - } as ICliOptions; - - OutputHelper.GenerateOutput = jest.fn().mockReturnValue("test output"); - - fs.writeFileSync = jest.fn(); - - console.log = jest.fn(); - - console.error = jest.fn(); - - // Act - const result = CliHelper.Endpoint(response, options); - - // Assert - expect(result).toBe(0); - - expect(OutputHelper.GenerateOutput).toHaveBeenCalledTimes(1); - expect(OutputHelper.GenerateOutput).toHaveBeenCalledWith(response, options); - - expect(fs.writeFileSync).not.toHaveBeenCalled(); - - expect(console.log).toHaveBeenCalledTimes(1); - expect(console.log).toHaveBeenCalledWith("test output"); - - expect(console.error).not.toHaveBeenCalled(); - }); - }); - - test("GIVEN response is failure, EXPECT error logged to console", () => { - // Arrange - const response = { - IsSuccess: false, - Error: { - Message: "error message", - Code: ErrorCode.FailedToFetchReddit, - }, - } as IReturnResult; - - const options = { - o: "file.txt", - } as ICliOptions; - - OutputHelper.GenerateOutput = jest.fn().mockReturnValue("test output"); - - fs.writeFileSync = jest.fn(); - - console.log = jest.fn(); - - console.error = jest.fn(); - - // Act - const result = CliHelper.Endpoint(response, options); - - // Assert - expect(result).toBe(1); - - expect(OutputHelper.GenerateOutput).not.toHaveBeenCalled(); - - expect(fs.writeFileSync).not.toHaveBeenCalled(); - - expect(console.log).not.toHaveBeenCalled(); - - expect(console.error).toHaveBeenCalledTimes(1); - expect(console.error).toHaveBeenCalledWith("error message", ErrorCode.FailedToFetchReddit); - }); -}); \ No newline at end of file diff --git a/tests/helpers/outputHelper.test.ts b/tests/helpers/outputHelper.test.ts deleted file mode 100644 index 8c9893d..0000000 --- a/tests/helpers/outputHelper.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import ICliOptions from "../../src/contracts/ICliOptions"; -import IReturnResult from "../../src/contracts/IReturnResult"; -import OutputHelper from "../../src/helpers/outputHelper"; - -describe("GenerateOutput", () => { - test("EXPECT standout output to be returned", () => { - // Arrange - const response = { - IsSuccess: true, - Query: { - subreddit: "rabbits", - sortBy: "hot", - }, - Result: { - Archived: false, - Downs: 0, - Hidden: false, - Permalink: "/r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/", - Subreddit: "Rabbits", - SubredditSubscribers: 654751, - Title: "This is my Ms Bear!", - Ups: 17, - Url: "https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d", - }, - } as IReturnResult; - - const options = {} as ICliOptions; - - // Act - const result = OutputHelper.GenerateOutput(response, options); - - // Assert - expect(result).toMatchSnapshot(); - }); - - test("GIVEN options.json is true, EXPECT output to be returned as JSON", () => { - // Arrange - const response = { - IsSuccess: true, - Query: { - subreddit: "rabbits", - sortBy: "hot", - }, - Result: { - Archived: false, - Downs: 0, - Hidden: false, - Permalink: "/r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/", - Subreddit: "Rabbits", - SubredditSubscribers: 654751, - Title: "This is my Ms Bear!", - Ups: 17, - Url: "https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d", - }, - } as IReturnResult; - - const options = { - json: true, - } as ICliOptions; - - // Act - const result = OutputHelper.GenerateOutput(response, options); - - // Assert - expect(result).toMatchSnapshot(); - }); - - test("GIVEN options.queryMetadata is supplied, EXPECT query metadata to be added", () => { - // Arrange - const response = { - IsSuccess: true, - Query: { - subreddit: "rabbits", - sortBy: "hot", - }, - Result: { - Archived: false, - Downs: 0, - Hidden: false, - Permalink: "/r/Rabbits/comments/1dj8pbt/this_is_my_ms_bear/", - Subreddit: "Rabbits", - SubredditSubscribers: 654751, - Title: "This is my Ms Bear!", - Ups: 17, - Url: "https://preview.redd.it/d5yno653zf7d1.jpg?width=640&crop=smart&auto=webp&s=5064d1caec3c12ac2855eb57ff131d0b313d5e9d", - }, - } as IReturnResult; - - const options = { - queryMetadata: true, - } as ICliOptions; - - // Act - const result = OutputHelper.GenerateOutput(response, options); - - // Assert - expect(result).toMatchSnapshot(); - }); -}); \ No newline at end of file