From 0244b941b35925de8d240a9abbeb658c8282ed17 Mon Sep 17 00:00:00 2001 From: Deepanshu Dhruw Date: Wed, 15 Jun 2022 23:53:24 +0530 Subject: [PATCH] feat: added support for passing env.json file to test cmd (#2373) --- packages/hoppscotch-cli/README.md | 15 +++- .../src/__tests__/commands/test.spec.ts | 43 ++++++++++- ...heckFilePath.spec.ts => checkFile.spec.ts} | 14 ++-- .../collection/collectionsRunner.spec.ts | 77 +++++++++++-------- .../getters/getEffectiveFinalMetaData.spec.ts | 2 + .../mutators/parseCollectionData.spec.ts | 6 +- .../getEffectiveRESTRequest.spec.ts | 2 + .../src/__tests__/samples/env-flag-envs.json | 7 ++ .../src/__tests__/samples/env-flag-tests.json | 22 ++++++ packages/hoppscotch-cli/src/commands/test.ts | 11 +-- packages/hoppscotch-cli/src/handlers/error.ts | 7 +- packages/hoppscotch-cli/src/index.ts | 16 ++-- .../hoppscotch-cli/src/options/test/env.ts | 64 +++++++++++++++ .../hoppscotch-cli/src/types/collections.ts | 9 +++ packages/hoppscotch-cli/src/types/commands.ts | 5 ++ packages/hoppscotch-cli/src/types/errors.ts | 3 +- packages/hoppscotch-cli/src/types/request.ts | 12 +-- packages/hoppscotch-cli/src/utils/checks.ts | 64 +++++++++------ .../hoppscotch-cli/src/utils/collections.ts | 11 ++- packages/hoppscotch-cli/src/utils/mutators.ts | 15 +++- 20 files changed, 309 insertions(+), 96 deletions(-) rename packages/hoppscotch-cli/src/__tests__/functions/checks/{checkFilePath.spec.ts => checkFile.spec.ts} (57%) create mode 100644 packages/hoppscotch-cli/src/__tests__/samples/env-flag-envs.json create mode 100644 packages/hoppscotch-cli/src/__tests__/samples/env-flag-tests.json create mode 100644 packages/hoppscotch-cli/src/options/test/env.ts create mode 100644 packages/hoppscotch-cli/src/types/collections.ts create mode 100644 packages/hoppscotch-cli/src/types/commands.ts diff --git a/packages/hoppscotch-cli/README.md b/packages/hoppscotch-cli/README.md index f1a70d75c..9e806c009 100644 --- a/packages/hoppscotch-cli/README.md +++ b/packages/hoppscotch-cli/README.md @@ -24,13 +24,26 @@ hopp [options or commands] arguments - Displays the help text -3. #### **`hopp test `** +3. #### **`hopp test [options] `** - Interactive CLI to accept Hoppscotch collection JSON path - Parses the collection JSON and executes each requests - Executes pre-request script. - Outputs the response of each request. - Executes and outputs test-script response. + #### Options: + ##### `-e ` / `--env ` + - Accepts path to env.json with contents in below format: + ```json + { + "ENV1":"value1", + "ENV2":"value2" + } + ``` + - You can now access those variables using `pw.env.get('')` + + Taking the above example, `pw.env.get("ENV1")` will return `"value1"` + ## Install Install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running: diff --git a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts index 6850f5ff3..0b69f443b 100644 --- a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts @@ -8,7 +8,7 @@ describe("Test 'hopp test ' command:", () => { const { stdout } = await execAsync(cmd); const out = getErrorCode(stdout); - expect(out).toBe("NO_FILE_PATH"); + expect(out).toBe("INVALID_ARGUMENT"); }); test("Collection file not found.", async () => { @@ -42,7 +42,7 @@ describe("Test 'hopp test ' command:", () => { const { stdout } = await execAsync(cmd); const out = getErrorCode(stdout); - expect(out).toBe("FILE_NOT_JSON"); + expect(out).toBe("INVALID_FILE_TYPE"); }); test("Some errors occured (exit code 1).", async () => { @@ -62,3 +62,42 @@ describe("Test 'hopp test ' command:", () => { expect(error).toBeNull(); }); }); + +describe("Test 'hopp test --env ' command:", () => { + const VALID_TEST_CMD = `node ./bin/hopp test ${getTestJsonFilePath( + "passes.json" + )}`; + + test("No env file path provided.", async () => { + const cmd = `${VALID_TEST_CMD} --env`; + const { stdout } = await execAsync(cmd); + const out = getErrorCode(stdout); + + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("ENV file not JSON type.", async () => { + const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`; + const { stdout } = await execAsync(cmd); + const out = getErrorCode(stdout); + + expect(out).toBe("INVALID_FILE_TYPE"); + }); + + test("ENV file not found.", async () => { + const cmd = `${VALID_TEST_CMD} --env notfound.json`; + const { stdout } = await execAsync(cmd); + const out = getErrorCode(stdout); + + expect(out).toBe("FILE_NOT_FOUND"); + }); + + // test("No errors occured (exit code 0).", async () => { + // const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json"); + // const ENV_PATH = getTestJsonFilePath("env-flag-envs.json"); + // const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`; + // const { error } = await execAsync(cmd); + + // expect(error).toBeNull(); + // }); +}); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFilePath.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts similarity index 57% rename from packages/hoppscotch-cli/src/__tests__/functions/checks/checkFilePath.spec.ts rename to packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts index 7b7366d66..2ae9ba885 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFilePath.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts @@ -1,10 +1,12 @@ import { HoppCLIError } from "../../../types/errors"; -import { checkFilePath } from "../../../utils/checks"; +import { checkFile } from "../../../utils/checks"; -describe("checkFilePath", () => { +import "@relmify/jest-fp-ts"; + +describe("checkFile", () => { test("File doesn't exists.", () => { return expect( - checkFilePath("./src/samples/this-file-not-exists.json")() + checkFile("./src/samples/this-file-not-exists.json")() ).resolves.toSubsetEqualLeft({ code: "FILE_NOT_FOUND", }); @@ -12,15 +14,15 @@ describe("checkFilePath", () => { test("File not of JSON type.", () => { return expect( - checkFilePath("./src/__tests__/samples/notjson.txt")() + checkFile("./src/__tests__/samples/notjson.txt")() ).resolves.toSubsetEqualLeft({ - code: "FILE_NOT_JSON", + code: "INVALID_FILE_TYPE", }); }); test("Existing JSON file.", () => { return expect( - checkFilePath("./src/__tests__/samples/passes.json")() + checkFile("./src/__tests__/samples/passes.json")() ).resolves.toBeRight(); }); }); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts index dcfe69dd6..04f869989 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts @@ -37,6 +37,8 @@ const SAMPLE_RESOLVED_RESPONSE = { headers: [], }; +const SAMPLE_ENVS = { global: [], selected: [] }; + describe("collectionsRunner", () => { beforeEach(() => { jest.clearAllMocks(); @@ -47,19 +49,24 @@ describe("collectionsRunner", () => { }); test("Empty HoppCollection.", () => { - return expect(collectionsRunner([])()).resolves.toStrictEqual([]); + return expect( + collectionsRunner({ collections: [], envs: SAMPLE_ENVS })() + ).resolves.toStrictEqual([]); }); test("Empty requests and folders in collection.", () => { return expect( - collectionsRunner([ - { - v: 1, - name: "name", - folders: [], - requests: [], - }, - ])() + collectionsRunner({ + collections: [ + { + v: 1, + name: "name", + folders: [], + requests: [], + }, + ], + envs: SAMPLE_ENVS, + })() ).resolves.toMatchObject([]); }); @@ -67,14 +74,17 @@ describe("collectionsRunner", () => { (axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE); return expect( - collectionsRunner([ - { - v: 1, - name: "collection", - folders: [], - requests: [SAMPLE_HOPP_REQUEST], - }, - ])() + collectionsRunner({ + collections: [ + { + v: 1, + name: "collection", + folders: [], + requests: [SAMPLE_HOPP_REQUEST], + }, + ], + envs: SAMPLE_ENVS, + })() ).resolves.toMatchObject([ { path: "collection/request", @@ -89,21 +99,24 @@ describe("collectionsRunner", () => { (axios as unknown as jest.Mock).mockResolvedValue(SAMPLE_RESOLVED_RESPONSE); return expect( - collectionsRunner([ - { - v: 1, - name: "collection", - folders: [ - { - v: 1, - name: "folder", - folders: [], - requests: [SAMPLE_HOPP_REQUEST], - }, - ], - requests: [], - }, - ])() + collectionsRunner({ + collections: [ + { + v: 1, + name: "collection", + folders: [ + { + v: 1, + name: "folder", + folders: [], + requests: [SAMPLE_HOPP_REQUEST], + }, + ], + requests: [], + }, + ], + envs: SAMPLE_ENVS, + })() ).resolves.toMatchObject([ { path: "collection/folder/request", diff --git a/packages/hoppscotch-cli/src/__tests__/functions/getters/getEffectiveFinalMetaData.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/getters/getEffectiveFinalMetaData.spec.ts index 637064bac..2a55b79ac 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/getters/getEffectiveFinalMetaData.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/getters/getEffectiveFinalMetaData.spec.ts @@ -1,6 +1,8 @@ import { Environment } from "@hoppscotch/data"; import { getEffectiveFinalMetaData } from "../../../utils/getters"; +import "@relmify/jest-fp-ts"; + const DEFAULT_ENV = { name: "name", variables: [{ key: "PARAM", value: "parsed_param" }], diff --git a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts index f1fc79eab..236858376 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts @@ -1,12 +1,14 @@ import { HoppCLIError } from "../../../types/errors"; import { parseCollectionData } from "../../../utils/mutators"; +import "@relmify/jest-fp-ts"; + describe("parseCollectionData", () => { test("Reading non-existing file.", () => { return expect( - parseCollectionData("./src/__tests__/samples/notexist.txt")() + parseCollectionData("./src/__tests__/samples/notexist.json")() ).resolves.toSubsetEqualLeft({ - code: "UNKNOWN_ERROR", + code: "FILE_NOT_FOUND", }); }); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/pre-request/getEffectiveRESTRequest.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/pre-request/getEffectiveRESTRequest.spec.ts index 0aa46fcc9..1ab4e9783 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/pre-request/getEffectiveRESTRequest.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/pre-request/getEffectiveRESTRequest.spec.ts @@ -3,6 +3,8 @@ import { EffectiveHoppRESTRequest } from "../../../interfaces/request"; import { HoppCLIError } from "../../../types/errors"; import { getEffectiveRESTRequest } from "../../../utils/pre-request"; +import "@relmify/jest-fp-ts"; + const DEFAULT_ENV = { name: "name", variables: [ diff --git a/packages/hoppscotch-cli/src/__tests__/samples/env-flag-envs.json b/packages/hoppscotch-cli/src/__tests__/samples/env-flag-envs.json new file mode 100644 index 000000000..36d3babeb --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/samples/env-flag-envs.json @@ -0,0 +1,7 @@ +{ + "URL": "https://echo.hoppscotch.io", + "HOST": "echo.hoppscotch.io", + "X-COUNTRY": "IN", + "BODY_VALUE": "body_value", + "BODY_KEY": "body_key" +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/env-flag-tests.json b/packages/hoppscotch-cli/src/__tests__/samples/env-flag-tests.json new file mode 100644 index 000000000..8533bf2dc --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/samples/env-flag-tests.json @@ -0,0 +1,22 @@ +{ + "v": 1, + "name": "env-flag-tests", + "folders": [], + "requests": [ + { + "v": "1", + "endpoint": "<>", + "name": "test1", + "params": [], + "headers": [], + "method": "POST", + "auth": { "authType": "none", "authActive": true }, + "preRequestScript": "", + "testScript": "const HOST = pw.env.get(\"HOST\");\nconst UNSET_ENV = pw.env.get(\"UNSET_ENV\");\nconst EXPECTED_URL = \"https://echo.hoppscotch.io\";\nconst URL = pw.env.get(\"URL\");\nconst X_COUNTRY = pw.env.get(\"X-COUNTRY\");\nconst BODY_VALUE = pw.env.get(\"BODY_VALUE\");\n\n// Check JSON response property\npw.test(\"Check headers properties.\", ()=> {\n pw.expect(pw.response.body.headers.host).toBe(HOST);\n\t pw.expect(pw.response.body.headers[\"x-country\"]).toBe(X_COUNTRY); \n});\n\npw.test(\"Check data properties.\", () => {\n\tconst DATA = pw.response.body.data;\n \n pw.expect(DATA).toBeType(\"string\");\n pw.expect(JSON.parse(DATA).body_key).toBe(BODY_VALUE);\n});\n\npw.test(\"Check request URL.\", () => {\n pw.expect(URL).toBe(EXPECTED_URL);\n})\n\npw.test(\"Check unset ENV.\", () => {\n pw.expect(UNSET_ENV).toBeType(\"undefined\");\n})", + "body": { + "contentType": "application/json", + "body": "{\n \"<>\":\"<>\"\n}" + } + } + ] +} diff --git a/packages/hoppscotch-cli/src/commands/test.ts b/packages/hoppscotch-cli/src/commands/test.ts index 349b4da96..7159f4632 100644 --- a/packages/hoppscotch-cli/src/commands/test.ts +++ b/packages/hoppscotch-cli/src/commands/test.ts @@ -6,14 +6,15 @@ import { collectionsRunnerResult, } from "../utils/collections"; import { handleError } from "../handlers/error"; -import { checkFilePath } from "../utils/checks"; import { parseCollectionData } from "../utils/mutators"; +import { parseEnvsData } from "../options/test/env"; +import { TestCmdOptions } from "../types/commands"; -export const test = (path: string) => async () => { +export const test = (path: string, options: TestCmdOptions) => async () => { await pipe( - path, - checkFilePath, - TE.chain(parseCollectionData), + TE.Do, + TE.bind("envs", () => parseEnvsData(options.env)), + TE.bind("collections", () => parseCollectionData(path)), TE.chainTaskK(collectionsRunner), TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)), TE.mapLeft((e) => { diff --git a/packages/hoppscotch-cli/src/handlers/error.ts b/packages/hoppscotch-cli/src/handlers/error.ts index e0b340701..7e640e2fa 100644 --- a/packages/hoppscotch-cli/src/handlers/error.ts +++ b/packages/hoppscotch-cli/src/handlers/error.ts @@ -48,9 +48,7 @@ export const handleError = (error: HoppError) => { case "UNKNOWN_COMMAND": ERROR_MSG = `Unavailable command: ${error.command}`; break; - case "FILE_NOT_JSON": - ERROR_MSG = `Please check file type: ${error.path}`; - break; + case "MALFORMED_ENV_FILE": case "MALFORMED_COLLECTION": ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`; break; @@ -60,6 +58,9 @@ export const handleError = (error: HoppError) => { case "PARSING_ERROR": ERROR_MSG = `Unable to parse -\n${error.data}`; break; + case "INVALID_FILE_TYPE": + ERROR_MSG = `Please provide file of extension type: ${error.data}`; + break; case "REQUEST_ERROR": case "TEST_SCRIPT_ERROR": case "PRE_REQUEST_SCRIPT_ERROR": diff --git a/packages/hoppscotch-cli/src/index.ts b/packages/hoppscotch-cli/src/index.ts index 2c0a34a1c..d93248f68 100644 --- a/packages/hoppscotch-cli/src/index.ts +++ b/packages/hoppscotch-cli/src/index.ts @@ -5,14 +5,16 @@ import { version } from "../package.json"; import { test } from "./commands/test"; import { handleError } from "./handlers/error"; -const accent = chalk.greenBright +const accent = chalk.greenBright; /** * * Program Default Configuration */ const CLI_BEFORE_ALL_TXT = `hopp: The ${accent( "Hoppscotch" -)} CLI - Version ${version} (${accent("https://hoppscotch.io")}) ${chalk.black.bold.bgYellowBright(" ALPHA ")} \n`; +)} CLI - Version ${version} (${accent( + "https://hoppscotch.io" +)}) ${chalk.black.bold.bgYellowBright(" ALPHA ")} \n`; const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent( "https://docs.hoppscotch.io/cli" @@ -44,14 +46,18 @@ program.exitOverride().configureOutput({ program .command("test") .argument( - "[file]", + "", "path to a hoppscotch collection.json file for CI testing" ) + .option("-e, --env ", "path to an environment variables json file") .allowExcessArguments(false) .allowUnknownOption(false) .description("running hoppscotch collection.json file") - .addHelpText("after", `\nFor help, head on to ${accent("https://docs.hoppscotch.io/cli#test")}`) - .action(async (path) => await test(path)()); + .addHelpText( + "after", + `\nFor help, head on to ${accent("https://docs.hoppscotch.io/cli#test")}` + ) + .action(async (path, options) => await test(path, options)()); export const cli = async (args: string[]) => { try { diff --git a/packages/hoppscotch-cli/src/options/test/env.ts b/packages/hoppscotch-cli/src/options/test/env.ts new file mode 100644 index 000000000..057bb8f0c --- /dev/null +++ b/packages/hoppscotch-cli/src/options/test/env.ts @@ -0,0 +1,64 @@ +import fs from "fs/promises"; +import { pipe } from "fp-ts/function"; +import * as TE from "fp-ts/TaskEither"; +import * as E from "fp-ts/Either"; +import * as J from "fp-ts/Json"; +import * as A from "fp-ts/Array"; +import * as S from "fp-ts/string"; +import isArray from "lodash/isArray"; +import { HoppCLIError, error } from "../../types/errors"; +import { HoppEnvs, HoppEnvPair } from "../../types/request"; +import { checkFile } from "../../utils/checks"; + +/** + * Parses env json file for given path and validates the parsed env json object. + * @param path Path of env.json file to be parsed. + * @returns For successful parsing we get HoppEnvs object. + */ +export const parseEnvsData = ( + path: unknown +): TE.TaskEither => + !S.isString(path) + ? TE.right({ global: [], selected: [] }) + : pipe( + // Checking if the env.json file exists or not. + checkFile(path), + + // Trying to read given env json file path. + TE.chainW((checkedPath) => + TE.tryCatch( + () => fs.readFile(checkedPath), + (reason) => + error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) + ) + ), + + // Trying to JSON parse the read file data and mapping the entries to HoppEnvPairs. + TE.chainEitherKW((data) => + pipe( + data.toString(), + J.parse, + E.map((jsonData) => + jsonData && typeof jsonData === "object" && !isArray(jsonData) + ? pipe( + jsonData, + Object.entries, + A.map( + ([key, value]) => + { + key, + value: S.isString(value) + ? value + : JSON.stringify(value), + } + ) + ) + : [] + ), + E.map((envPairs) => { global: [], selected: envPairs }), + E.mapLeft((e) => + error({ code: "MALFORMED_ENV_FILE", path, data: E.toError(e) }) + ) + ) + ) + ); diff --git a/packages/hoppscotch-cli/src/types/collections.ts b/packages/hoppscotch-cli/src/types/collections.ts new file mode 100644 index 000000000..57794954f --- /dev/null +++ b/packages/hoppscotch-cli/src/types/collections.ts @@ -0,0 +1,9 @@ +import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; +import { HoppEnvs } from "./request"; + +export type CollectionRunnerParam = { + collections: HoppCollection[]; + envs: HoppEnvs; +}; + +export type HoppCollectionFileExt = "json"; diff --git a/packages/hoppscotch-cli/src/types/commands.ts b/packages/hoppscotch-cli/src/types/commands.ts new file mode 100644 index 000000000..59de3536c --- /dev/null +++ b/packages/hoppscotch-cli/src/types/commands.ts @@ -0,0 +1,5 @@ +export type TestCmdOptions = { + env: string; +}; + +export type HoppEnvFileExt = "json"; diff --git a/packages/hoppscotch-cli/src/types/errors.ts b/packages/hoppscotch-cli/src/types/errors.ts index 9b896fa6b..d6baf8730 100644 --- a/packages/hoppscotch-cli/src/types/errors.ts +++ b/packages/hoppscotch-cli/src/types/errors.ts @@ -15,7 +15,6 @@ type HoppErrors = { FILE_NOT_FOUND: HoppErrorPath; UNKNOWN_COMMAND: HoppErrorCmd; MALFORMED_COLLECTION: HoppErrorPath & HoppErrorData; - FILE_NOT_JSON: HoppErrorPath; NO_FILE_PATH: {}; PRE_REQUEST_SCRIPT_ERROR: HoppErrorData; PARSING_ERROR: HoppErrorData; @@ -24,6 +23,8 @@ type HoppErrors = { SYNTAX_ERROR: HoppErrorData; REQUEST_ERROR: HoppErrorData; INVALID_ARGUMENT: HoppErrorData; + MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData; + INVALID_FILE_TYPE: HoppErrorData; }; export type HoppErrorCode = keyof HoppErrors; diff --git a/packages/hoppscotch-cli/src/types/request.ts b/packages/hoppscotch-cli/src/types/request.ts index 4a5e990e0..10b53a146 100644 --- a/packages/hoppscotch-cli/src/types/request.ts +++ b/packages/hoppscotch-cli/src/types/request.ts @@ -7,15 +7,11 @@ export type FormDataEntry = { value: string | Blob; }; +export type HoppEnvPair = { key: string; value: string }; + export type HoppEnvs = { - global: { - key: string; - value: string; - }[]; - selected: { - key: string; - value: string; - }[]; + global: HoppEnvPair[]; + selected: HoppEnvPair[]; }; export type CollectionStack = { diff --git a/packages/hoppscotch-cli/src/utils/checks.ts b/packages/hoppscotch-cli/src/utils/checks.ts index 658195f47..53764ce15 100644 --- a/packages/hoppscotch-cli/src/utils/checks.ts +++ b/packages/hoppscotch-cli/src/utils/checks.ts @@ -9,8 +9,12 @@ import { import * as A from "fp-ts/Array"; import * as S from "fp-ts/string"; import * as TE from "fp-ts/TaskEither"; -import { error, HoppCLIError, HoppErrnoException } from "../types/errors"; +import * as E from "fp-ts/Either"; +import curryRight from "lodash/curryRight"; import { CommanderError } from "commander"; +import { error, HoppCLIError, HoppErrnoException } from "../types/errors"; +import { HoppCollectionFileExt } from "../types/collections"; +import { HoppEnvFileExt } from "../types/commands"; /** * Determines whether an object has a property with given name. @@ -68,42 +72,56 @@ export const isRESTCollection = ( }; /** - * Checks if the given file path exists and is of JSON type. + * Checks if the file path matches the requried file type with of required extension. + * @param path The input file path to check. + * @param extension The required extension for input file path. + * @returns Absolute path for valid file extension OR HoppCLIError in case of error. + */ +export const checkFileExt = curryRight( + ( + path: unknown, + extension: HoppCollectionFileExt | HoppEnvFileExt + ): E.Either => + pipe( + path, + E.fromPredicate(S.isString, (_) => error({ code: "NO_FILE_PATH" })), + E.chainW( + E.fromPredicate(S.endsWith(`.${extension}`), (_) => + error({ code: "INVALID_FILE_TYPE", data: extension }) + ) + ) + ) +); + +/** + * Checks if the given file path exists and is of given type. * @param path The input file path to check. * @returns Absolute path for valid file path OR HoppCLIError in case of error. */ -export const checkFilePath = ( - path: string -): TE.TaskEither => +export const checkFile = (path: unknown): TE.TaskEither => pipe( path, - /** - * Check the path type and returns string if passes else HoppCLIError. - */ + // Checking if path is string. TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })), + /** + * After checking file path, we map file path to absolute path and check + * if file is of given extension type. + */ + TE.map(join), + TE.chainEitherK(checkFileExt("json")), + /** * Trying to access given file path. * If successfully accessed, we return the path from predicate step. * Else return HoppCLIError with code FILE_NOT_FOUND. */ - TE.chainFirstW( + TE.chainFirstW((checkedPath) => TE.tryCatchK( - () => pipe(path, join, fs.access), - () => error({ code: "FILE_NOT_FOUND", path: path }) - ) - ), - - /** - * On successfully accessing given file path, we map file path to - * absolute path and return abs file path if file is JSON type. - */ - TE.map(join), - TE.chainW( - TE.fromPredicate(S.endsWith(".json"), (absPath) => - error({ code: "FILE_NOT_JSON", path: absPath }) - ) + () => fs.access(checkedPath), + () => error({ code: "FILE_NOT_FOUND", path: checkedPath }) + )() ) ); diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index d87b6395e..3bd8cd1f8 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -27,21 +27,24 @@ import { import { getTestMetrics } from "./test"; import { DEFAULT_DURATION_PRECISION } from "./constants"; import { getPreRequestMetrics } from "./pre-request"; +import { CollectionRunnerParam } from "../types/collections"; const { WARN, FAIL } = exceptionColors; /** * Processes each requests within collections to prints details of subsequent requests, * tests and to display complete errors-report, failed-tests-report and test-metrics. - * @param collections Array of hopp-collection with hopp-requests to be processed. + * @param param Data of hopp-collection with hopp-requests, envs to be processed. * @returns List of report for each processed request. */ export const collectionsRunner = - (collections: HoppCollection[]): T.Task => + (param: CollectionRunnerParam): T.Task => async () => { - const envs: HoppEnvs = { global: [], selected: [] }; + const envs: HoppEnvs = param.envs; const requestsReport: RequestReport[] = []; - const collectionStack: CollectionStack[] = getCollectionStack(collections); + const collectionStack: CollectionStack[] = getCollectionStack( + param.collections + ); while (collectionStack.length) { // Pop out top-most collection from stack to be processed. diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts index eb145d881..990ce4595 100644 --- a/packages/hoppscotch-cli/src/utils/mutators.ts +++ b/packages/hoppscotch-cli/src/utils/mutators.ts @@ -6,7 +6,7 @@ import * as J from "fp-ts/Json"; import { pipe } from "fp-ts/function"; import { FormDataEntry } from "../types/request"; import { error, HoppCLIError } from "../types/errors"; -import { isRESTCollection, isHoppErrnoException } from "./checks"; +import { isRESTCollection, isHoppErrnoException, checkFile } from "./checks"; import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; /** @@ -49,10 +49,17 @@ export const parseCollectionData = ( path: string ): TE.TaskEither[]> => pipe( + TE.of(path), + + // Checking if given file path exists or not. + TE.chain(checkFile), + // Trying to read give collection json path. - TE.tryCatch( - () => pipe(path, fs.readFile), - (reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) + TE.chainW((checkedPath) => + TE.tryCatch( + () => fs.readFile(checkedPath), + (reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) + ) ), // Checking if parsed file data is array.