diff --git a/src/commands/deployment/onboard.test.ts b/src/commands/deployment/onboard.test.ts index 3ff873c71..c65e5c2d1 100644 --- a/src/commands/deployment/onboard.test.ts +++ b/src/commands/deployment/onboard.test.ts @@ -24,6 +24,7 @@ import { validateValues, } from "./onboard"; import * as onboardImpl from "./onboard"; +import { getErrorMessage } from "../../lib/errorBuilder"; beforeAll(() => { enableVerboseLogging(); @@ -237,9 +238,7 @@ describe("test validateValues function", () => { vals.storageAccountName = "#123"; expect(() => { validateValues(vals); - }).toThrow( - "The value for storage account name is invalid. Lowercase letters and numbers are allowed." - ); + }).toThrow(getErrorMessage("validation-err-storage-account-name-invalid")); }); it("[-ve]: invalid storageTableName value", () => { const vals = getMockedValues(); diff --git a/src/commands/hld/pipeline.test.ts b/src/commands/hld/pipeline.test.ts index 698643b89..86e6476c2 100644 --- a/src/commands/hld/pipeline.test.ts +++ b/src/commands/hld/pipeline.test.ts @@ -3,6 +3,7 @@ import * as azdo from "../../lib/azdoClient"; import { BUILD_SCRIPT_URL } from "../../lib/constants"; import { getRepositoryName } from "../../lib/gitutils"; import { disableVerboseLogging, enableVerboseLogging } from "../../logger"; +import { getErrorMessage } from "../../lib/errorBuilder"; jest.mock("../../lib/pipelines/pipelines"); import { @@ -88,7 +89,7 @@ const orgNameTest = (hasVal: boolean): void => { if (hasVal) { expect(() => populateValues(data)).toThrow( - "Organization names must start with a letter or number, followed by letters, numbers or hyphens, and must end with a letter or number." + getErrorMessage("validation-err-org-name") ); } else { expect(() => populateValues(data)).toThrow( diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 20209a23b..5e21a4b2a 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -40,6 +40,8 @@ import { create as createSetupLog } from "../lib/setup/setupLog"; import { logger } from "../logger"; import decorator from "./setup.decorator.json"; import { createStorage } from "../lib/setup/azureStorage"; +import { build as buildError, log as logError } from "../lib/errorBuilder"; +import { errorStatusCode } from "../lib/errorStatusCode"; import { ConfigYaml } from "../types"; interface CommandOptions { @@ -211,6 +213,8 @@ export const execute = async ( createSetupLog(rc); await exitFn(0); } catch (err) { + logError(buildError(errorStatusCode.CMD_EXE_ERR, "setup-cmd-failed", err)); + const msg = getErrorMessage(requestContext, err); // requestContext will not be created if input validation failed @@ -218,8 +222,6 @@ export const execute = async ( requestContext.error = msg; } createSetupLog(requestContext); - - logger.error(msg); await exitFn(1); } }; diff --git a/src/lib/errorBuilder.ts b/src/lib/errorBuilder.ts index 69521376a..9499edf26 100644 --- a/src/lib/errorBuilder.ts +++ b/src/lib/errorBuilder.ts @@ -8,6 +8,36 @@ interface ErrorParam { errorKey: string; values: string[]; } + +/** + * Returns error message + * + * @param errorInstance Error instance + */ +export const getErrorMessage = (errorInstance: string | ErrorParam): string => { + let key = ""; + let values: string[] | undefined = undefined; + + if (typeof errorInstance === "string") { + key = errorInstance; + } else { + key = errorInstance.errorKey; + values = errorInstance.values; + } + + // if key is found in i18n json + if (key in errors) { + let results = errors[key]; + if (values) { + values.forEach((val, i) => { + const re = new RegExp("\\{" + i + "}", "g"); + results = results.replace(re, val); + }); + } + return `${key}: ${results}`; + } + return key; +}; class ErrorChain extends Error { errorCode: number; details: string | undefined; @@ -25,28 +55,7 @@ class ErrorChain extends Error { * @param errorInstance Error instance */ getErrorMessage(errorInstance: string | ErrorParam): string { - let key = ""; - let values: string[] | undefined = undefined; - - if (typeof errorInstance === "string") { - key = errorInstance; - } else { - key = errorInstance.errorKey; - values = errorInstance.values; - } - - // if key is found in i18n json - if (key in errors) { - let results = errors[key]; - if (values) { - values.forEach((val, i) => { - const re = new RegExp("\\{" + i + "}", "g"); - results = results.replace(re, val); - }); - } - return `${key}: ${results}`; - } - return key; + return getErrorMessage(errorInstance); } /** * Generates error messages and have them in messages array. diff --git a/src/lib/i18n.json b/src/lib/i18n.json index 1edebb85f..525a2f694 100644 --- a/src/lib/i18n.json +++ b/src/lib/i18n.json @@ -16,6 +16,8 @@ "storageKeVaultName": "Enter key vault name (have the value as empty and hit enter key to skip)" }, "errors": { + "setup-cmd-failed": "Setup command was not successfully executed.", + "hld-init-cmd-failed": "Hld init command was not successfully executed.", "hld-init-cmd-project-path-missing": "Value for project path was not provided. Provide it.", @@ -96,6 +98,45 @@ "deployment-table-update-manifest-commit-id-failed": "Could not update manifest commit Id.", "deployment-table-update-manifest-commit-id-failed-no-generation": "No manifest generation found to update manifest commit {0}.", "deployment-table-add-src-to-acr-pipeline": "Could not add source to ACR pipeline information to storage table.", - "deployment-table-add-acr-to-hld-pipeline": "Could not add ACR to HLD pipeline information to storage table." + "deployment-table-add-acr-to-hld-pipeline": "Could not add ACR to HLD pipeline information to storage table.", + + "validation-err-org-name-missing": "Organization name was missing. Provide it.", + "validation-err-org-name": "Organization name must start with a letter or number, followed by letters, numbers or hyphens, and must end with a letter or number.", + "validation-err-password-missing": "Password was missing. Provide it.", + "validation-err-password-too-short": "Password was too short, it must be more than 8 characters long. Reenter password.", + "validation-err-project-name-missing": "Project name was missing. Provide it.", + "validation-err-project-name-too-long": "Project name was too long, it cannot be longer than 64 characters.", + "validation-err-project-name-begin-underscore": "Project name was invalid as it cannot begin with an underscore", + "validation-err-project-name-period": "Project name was invalid as it cannot begin or end with a period", + "validation-err-project-name-special-char": "Project name can't contain special characters, such as / : \\ ~ & % ; @ ' \" ? < > | # $ * } { , + = [ ]", + "validation-err-personal-access-token-missing": "Personal access token was missing. Provide it", + + "validation-err-service-principal-id-missing": "Service Principal Id was missing. Provide it.", + "validation-err-service-principal-id-invalid": "Service Principal Id was invalid. Check and re-enter.", + "validation-err-service-principal-pwd-missing": "Service Principal Password was missing. Provide it.", + "validation-err-service-principal-pwd-invalid": "Service Principal Password was invalid. Check and re-enter.", + "validation-err-service-principal-tenant-id-missing": "Service Principal Tenant Id was missing. Provide it.", + "validation-err-service-principal-tenant-id-invalid": "Service Principal Tenant Id was invalid. Check and re-enter.", + + "validation-err-subscription-id-missing": "Subscription Id was missing. Provide it.", + "validation-err-subscription-id-invalid": "Subscription Id was invalid. Check and re-enter.", + + "validation-err-storage-account-name-missing": "Storage Account Name was missing. Provide it.", + "validation-err-storage-account-name-invalid": "Storage Account Name was invalid. Only lowercase letters and numbers are allowed.", + "validation-err-storage-account-name-length": "Storage Account Name was invalid. It has to be between 3 and 24 characters long.", + "validation-err-storage-table-name-missing": "Storage Table Name was missing. Provide it.", + "validation-err-storage-table-name-invalid": "The value for storage table name is invalid. It has to be alphanumeric and start with an alphabet.", + "validation-err-storage-table-name-length": "The value for storage table name is invalid. It has to be between 3 and 63 characters long.", + "validation-err-storage-partition-key-missing": "Storage Partition Key was missing. Provide it.", + "validation-err-storage-partition-key-invalid": "The value for storage partition key is invalid. /, \\, # and ? characters are not allowed.", + "validation-err-acr-missing": "Azure Container Registry Name was missing. Provide it.", + "validation-err-acr-invalid": "The value for Azure Container Registry name was invalid. It has to be alphanumeric.", + "validation-err-acr-length": "The value for Azure Container Registry name was invalid as it has to be between 5 and 50 characters long.", + "validation-err-storage-key-vault-invalid": "Storage Key Vault was invalid as it cannot only has dash and alphanumeric characters.", + "validation-err-storage-key-vault-start-letter": "Storage Key Vault was invalid as it must start with a letter.", + "validation-err-storage-key-vault-end-char": "Storage Key Vault was invalid as it must end with letter or digit.", + "validation-err-storage-key-vault-hyphen": "Storage Key Vault was invalid as it cannot contain consecutive hyphens.", + "validation-err-storage-key-vault-length": "Storage Key Vault was invalid as it has to be between 3 and 24 characters long.", + "validation-err-storage-access-key-missing": "Storage Access Key was missing. Provide it." } } diff --git a/src/lib/setup/prompt.ts b/src/lib/setup/prompt.ts index c29ada23d..6be676357 100644 --- a/src/lib/setup/prompt.ts +++ b/src/lib/setup/prompt.ts @@ -2,16 +2,16 @@ import fs from "fs"; import inquirer from "inquirer"; import * as promptBuilder from "../promptBuilder"; import { - validateAccessToken, + validateAccessTokenThrowable, validateACRName, - validateOrgName, - validateProjectName, - validateServicePrincipalId, - validateServicePrincipalPassword, - validateServicePrincipalTenantId, - validateSubscriptionId, - validateStorageAccountName, - validateStorageTableName, + validateOrgNameThrowable, + validateProjectNameThrowable, + validateServicePrincipalIdThrowable, + validateServicePrincipalPasswordThrowable, + validateServicePrincipalTenantIdThrowable, + validateSubscriptionIdThrowable, + validateStorageAccountNameThrowable, + validateStorageTableNameThrowable, } from "../validator"; import { ACR_NAME, @@ -182,24 +182,12 @@ export const validationServicePrincipalInfoFromFile = ( // file needs to contain sp information if user // choose not to create SP if (!rc.toCreateSP) { - const vSPId = validateServicePrincipalId(map.az_sp_id); - if (typeof vSPId === "string") { - throw new Error(vSPId); - } - const vSPPassword = validateServicePrincipalPassword(map.az_sp_password); - if (typeof vSPPassword === "string") { - throw new Error(vSPPassword); - } - const vSPTenantId = validateServicePrincipalTenantId(map.az_sp_tenant); - if (typeof vSPTenantId === "string") { - throw new Error(vSPTenantId); - } + validateServicePrincipalIdThrowable(map.az_sp_id); + validateServicePrincipalPasswordThrowable(map.az_sp_password); + validateServicePrincipalTenantIdThrowable(map.az_sp_tenant); } - const vSubscriptionId = validateSubscriptionId(map.az_subscription_id); - if (typeof vSubscriptionId === "string") { - throw new Error(vSubscriptionId); - } + validateSubscriptionIdThrowable(map.az_subscription_id); rc.subscriptionId = map.az_subscription_id; } }; @@ -234,32 +222,11 @@ export const getAnswerFromFile = (file: string): RequestContext => { const map = parseInformationFromFile(file); map["azdo_project_name"] = map.azdo_project_name || DEFAULT_PROJECT_NAME; - const vOrgName = validateOrgName(map.azdo_org_name); - if (typeof vOrgName === "string") { - throw new Error(vOrgName); - } - - const vProjectName = validateProjectName(map.azdo_project_name); - if (typeof vProjectName === "string") { - throw new Error(vProjectName); - } - - const vToken = validateAccessToken(map.azdo_pat); - if (typeof vToken === "string") { - throw new Error(vToken); - } - - const vStorageAccountName = validateStorageAccountName( - map.az_storage_account_name - ); - if (typeof vStorageAccountName === "string") { - throw new Error(vStorageAccountName); - } - - const vStorageTable = validateStorageTableName(map.az_storage_table); - if (typeof vStorageTable === "string") { - throw new Error(vStorageTable); - } + validateOrgNameThrowable(map.azdo_org_name); + validateProjectNameThrowable(map.azdo_project_name); + validateAccessTokenThrowable(map.azdo_pat); + validateStorageAccountNameThrowable(map.az_storage_account_name); + validateStorageTableNameThrowable(map.az_storage_table); const rc: RequestContext = { accessToken: map.azdo_pat, @@ -276,7 +243,6 @@ export const getAnswerFromFile = (file: string): RequestContext => { rc.toCreateAppRepo = map.az_create_app === "true"; validationServicePrincipalInfoFromFile(rc, map); - return rc; }; diff --git a/src/lib/validator.test.ts b/src/lib/validator.test.ts index b55e9370b..4487ac7eb 100644 --- a/src/lib/validator.test.ts +++ b/src/lib/validator.test.ts @@ -5,8 +5,8 @@ import { isDashHex, isIntegerString, isPortNumberString, - ORG_NAME_VIOLATION, validateAccessToken, + validateAccessTokenThrowable, validateACRName, validateForNonEmptyValue, validateOrgName, @@ -19,12 +19,16 @@ import { validateServicePrincipalPassword, validateServicePrincipalTenantId, validateStorageAccountName, + validateStorageAccountNameThrowable, validateStorageAccessKey, validateStorageKeyVaultName, validateStoragePartitionKey, validateStorageTableName, + validateStorageTableNameThrowable, validateSubscriptionId, + validateSubscriptionIdThrowable, } from "./validator"; +import { getErrorMessage } from "./errorBuilder"; describe("Tests on validator helper functions", () => { it("Test hasValue function", () => { @@ -142,25 +146,31 @@ describe("Validating executable prerequisites in spk-config", () => { describe("test validateOrgName function", () => { it("empty value and value with space", () => { - expect(validateOrgName("")).toBe("Must enter an organization"); - expect(validateOrgName(" ")).toBe("Must enter an organization"); + expect(validateOrgName("")).toBe( + getErrorMessage("validation-err-org-name-missing") + ); + expect(validateOrgName(" ")).toBe( + getErrorMessage("validation-err-org-name-missing") + ); expect(() => { validateOrgNameThrowable(""); - }).toThrow(); + }).toThrow(getErrorMessage("validation-err-org-name-missing")); expect(() => { validateOrgNameThrowable(" "); - }).toThrow(); + }).toThrow(getErrorMessage("validation-err-org-name-missing")); }); it("invalid value", () => { const values = ["-abc", ".abc", "abc.", "a b"]; values.forEach((v) => { - expect(validateOrgName(v)).toBe(ORG_NAME_VIOLATION); + expect(validateOrgName(v)).toBe( + getErrorMessage("validation-err-org-name") + ); }); values.forEach((v) => { expect(() => { validateOrgNameThrowable(v); - }).toThrow(); + }).toThrow(getErrorMessage("validation-err-org-name")); }); }); it("valid value", () => { @@ -173,8 +183,12 @@ describe("test validateOrgName function", () => { describe("test validateProjectName function", () => { it("empty value and value with space", () => { - expect(validateProjectName("")).toBe("Must enter a project name"); - expect(validateProjectName(" ")).toBe("Must enter a project name"); + expect(validateProjectName("")).toBe( + getErrorMessage("validation-err-project-name-missing") + ); + expect(validateProjectName(" ")).toBe( + getErrorMessage("validation-err-project-name-missing") + ); expect(() => { validateProjectNameThrowable(""); @@ -186,7 +200,7 @@ describe("test validateProjectName function", () => { it("value over 64 chars long", () => { const val = "a".repeat(65); expect(validateProjectName(val)).toBe( - "Project name cannot be longer than 64 characters" + getErrorMessage("validation-err-project-name-too-long") ); expect(() => { @@ -195,19 +209,19 @@ describe("test validateProjectName function", () => { }); it("invalid value", () => { expect(validateProjectName("_abc")).toBe( - "Project name cannot begin with an underscore" + getErrorMessage("validation-err-project-name-begin-underscore") ); expect(validateProjectName(".abc")).toBe( - "Project name cannot begin or end with a period" + getErrorMessage("validation-err-project-name-period") ); expect(validateProjectName("abc.")).toBe( - "Project name cannot begin or end with a period" + getErrorMessage("validation-err-project-name-period") ); expect(validateProjectName(".abc.")).toBe( - "Project name cannot begin or end with a period" + getErrorMessage("validation-err-project-name-period") ); expect(validateProjectName("a*b")).toBe( - `Project name can't contain special characters, such as / : \\ ~ & % ; @ ' " ? < > | # $ * } { , + = [ ]` + getErrorMessage("validation-err-project-name-special-char") ); ["_abc", ".abc", "abc.", ".abc.", "a*b"].forEach((val) => { @@ -225,8 +239,11 @@ describe("test validateProjectName function", () => { describe("test validateAccessToken function", () => { it("empty value", () => { expect(validateAccessToken("")).toBe( - "Must enter a personal access token with read/write/manage permissions" + getErrorMessage("validation-err-personal-access-token-missing") ); + expect(() => { + validateAccessTokenThrowable(""); + }).toThrow(); }); it("validate value", () => { expect(validateAccessToken("mysecretshhhh")).toBe(true); @@ -246,22 +263,23 @@ describe("test validateServicePrincipal functions", () => { [ { fn: validateServicePrincipalId, - prop: "Service Principal Id", + missing: "validation-err-service-principal-id-missing", + invalid: "validation-err-service-principal-id-invalid", }, { fn: validateServicePrincipalPassword, - prop: "Service Principal Password", + missing: "validation-err-service-principal-pwd-missing", + invalid: "validation-err-service-principal-pwd-invalid", }, { fn: validateServicePrincipalTenantId, - prop: "Service Principal Tenant Id", + missing: "validation-err-service-principal-tenant-id-missing", + invalid: "validation-err-service-principal-tenant-id-invalid", }, ].forEach((item) => { - expect(item.fn("")).toBe(`Must enter a ${item.prop}.`); + expect(item.fn("")).toBe(getErrorMessage(item.missing)); expect(item.fn("b510c1ff-358c-4ed4-96c8-eb23f42bb65b")).toBe(true); - expect(item.fn(".eb23f42bb65b")).toBe( - `The value for ${item.prop} is invalid.` - ); + expect(item.fn(".eb23f42bb65b")).toBe(getErrorMessage(item.invalid)); }); }); }); @@ -269,29 +287,42 @@ describe("test validateServicePrincipal functions", () => { describe("test validateSubscriptionId function", () => { it("sanity test", () => { expect(validateSubscriptionId("")).toBe( - "Must enter a subscription identifier." + getErrorMessage("validation-err-subscription-id-missing") ); expect(validateSubscriptionId("xyz")).toBe( - "The value for subscription identifier is invalid." + getErrorMessage("validation-err-subscription-id-invalid") ); expect(validateSubscriptionId("abc123-456")).toBeTruthy(); + expect(() => { + validateSubscriptionIdThrowable(""); + }).toThrow(); + expect(() => { + validateSubscriptionIdThrowable("xyz"); + }).toThrow(); }); }); describe("test validateStorageAccountName test", () => { it("sanity test", () => { expect(validateStorageAccountName("")).toBe( - "Must enter a storage account name." + getErrorMessage("validation-err-storage-account-name-missing") ); expect(validateStorageAccountName("XYZ123")).toBe( - "The value for storage account name is invalid. Lowercase letters and numbers are allowed." + getErrorMessage("validation-err-storage-account-name-invalid") ); expect(validateStorageAccountName("ab")).toBe( - "The value for storage account name is invalid. It has to be between 3 and 24 characters long" + getErrorMessage("validation-err-storage-account-name-length") ); expect(validateStorageAccountName("12345678a".repeat(3))).toBe( - "The value for storage account name is invalid. It has to be between 3 and 24 characters long" + getErrorMessage("validation-err-storage-account-name-length") ); + + ["", "XYZ123", "ab", "12345678a".repeat(3)].forEach((val) => { + expect(() => { + validateStorageAccountNameThrowable(val); + }).toThrow(); + }); + expect(validateStorageAccountName("abc123456")).toBeTruthy(); }); }); @@ -299,29 +330,37 @@ describe("test validateStorageAccountName test", () => { describe("test validateStorageTableName test", () => { it("sanity test", () => { expect(validateStorageTableName("")).toBe( - "Must enter a storage table name." + getErrorMessage("validation-err-storage-table-name-missing") ); expect(validateStorageTableName("XYZ123*")).toBe( - "The value for storage table name is invalid. It has to be alphanumeric and start with an alphabet." + getErrorMessage("validation-err-storage-table-name-invalid") ); expect(validateStorageTableName("1XYZ123")).toBe( - "The value for storage table name is invalid. It has to be alphanumeric and start with an alphabet." + getErrorMessage("validation-err-storage-table-name-invalid") ); expect(validateStorageTableName("ab")).toBe( - "The value for storage table name is invalid. It has to be between 3 and 63 characters long" + getErrorMessage("validation-err-storage-table-name-length") ); expect(validateStorageTableName("a123456789".repeat(7))).toBe( - "The value for storage table name is invalid. It has to be between 3 and 63 characters long" + getErrorMessage("validation-err-storage-table-name-length") ); expect(validateStorageTableName("abc123456")).toBeTruthy(); + + ["", "XYZ123*", "1XYZ123", "ab", "a123456789".repeat(7)].forEach((val) => { + expect(() => { + validateStorageTableNameThrowable(val); + }).toThrow(); + }); }); }); describe("test validatePassword test", () => { it("sanity test", () => { - expect(validatePassword("")).toBe("Must enter a value."); + expect(validatePassword("")).toBe( + getErrorMessage("validation-err-password-missing") + ); expect(validatePassword("1234567")).toBe( - "Must be more than 8 characters long." + getErrorMessage("validation-err-password-too-short") ); expect(validatePassword("abcd1234")).toBeTruthy(); expect(validatePassword("abcdefg123456678")).toBeTruthy(); @@ -331,11 +370,11 @@ describe("test validatePassword test", () => { describe("test validateStoragePartitionKey test", () => { it("sanity test", () => { expect(validateStoragePartitionKey("")).toBe( - "Must enter a storage partition key." + getErrorMessage("validation-err-storage-partition-key-missing") ); ["abc\\", "abc/", "abc?", "abc#"].forEach((s) => { expect(validateStoragePartitionKey(s)).toBe( - "The value for storage partition key is invalid. /, \\, # and ? characters are not allowed." + getErrorMessage("validation-err-storage-partition-key-invalid") ); }); expect(validateStoragePartitionKey("abcdefg123456678")).toBeTruthy(); @@ -345,18 +384,17 @@ describe("test validateStoragePartitionKey test", () => { describe("test validateACRName function", () => { it("sanity test", () => { expect(validateACRName("")).toBe( - "Must enter an Azure Container Registry Name." + getErrorMessage("validation-err-acr-missing") ); expect(validateACRName("xyz-")).toBe( - "The value for Azure Container Registry Name is invalid." + getErrorMessage("validation-err-acr-invalid") ); expect(validateACRName("1")).toBe( - "The value for Azure Container Registry Name is invalid because it has to be between 5 and 50 characters long." + getErrorMessage("validation-err-acr-length") ); expect(validateACRName("1234567890a".repeat(10))).toBe( - "The value for Azure Container Registry Name is invalid because it has to be between 5 and 50 characters long." + getErrorMessage("validation-err-acr-length") ); - expect(validateACRName("abc12356")).toBeTruthy(); }); }); @@ -365,22 +403,22 @@ describe("test validateStorageKeyVaultName function", () => { it("sanity test", () => { expect(validateStorageKeyVaultName("")).toBeTruthy(); expect(validateStorageKeyVaultName("ab*")).toBe( - "The value for Key Value Name is invalid." + getErrorMessage("validation-err-storage-key-vault-invalid") ); expect(validateStorageKeyVaultName("1abc0")).toBe( - "Key Value Name must start with a letter." + getErrorMessage("validation-err-storage-key-vault-start-letter") ); expect(validateStorageKeyVaultName("abc0-")).toBe( - "Key Value Name must end with letter or digit." + getErrorMessage("validation-err-storage-key-vault-end-char") ); expect(validateStorageKeyVaultName("a--b")).toBe( - "Key Value Name cannot contain consecutive hyphens." + getErrorMessage("validation-err-storage-key-vault-hyphen") ); expect(validateStorageKeyVaultName("ab")).toBe( - "The value for Key Vault Name is invalid because it has to be between 3 and 24 characters long." + getErrorMessage("validation-err-storage-key-vault-length") ); expect(validateStorageKeyVaultName("a12345678".repeat(3))).toBe( - "The value for Key Vault Name is invalid because it has to be between 3 and 24 characters long." + getErrorMessage("validation-err-storage-key-vault-length") ); expect(validateStorageKeyVaultName("abc-12356")).toBeTruthy(); }); @@ -389,7 +427,7 @@ describe("test validateStorageKeyVaultName function", () => { describe("test validateStorageAccessKey function", () => { it("sanity test", () => { expect(validateStorageAccessKey("")).toBe( - "Must enter an Storage Access Key." + getErrorMessage("validation-err-storage-access-key-missing") ); expect(validateStorageAccessKey("abc-12356")).toBeTruthy(); }); diff --git a/src/lib/validator.ts b/src/lib/validator.ts index 6aef4c680..3c0f5a264 100644 --- a/src/lib/validator.ts +++ b/src/lib/validator.ts @@ -1,9 +1,8 @@ import shelljs from "shelljs"; import { Config } from "../config"; import { logger } from "../logger"; - -export const ORG_NAME_VIOLATION = - "Organization names must start with a letter or number, followed by letters, numbers or hyphens, and must end with a letter or number."; +import { build as buildError, getErrorMessage } from "./errorBuilder"; +import { errorStatusCode } from "./errorStatusCode"; /** * Values to be validated @@ -101,7 +100,7 @@ export const validatePrereqs = ( */ export const validateOrgName = (value: string): string | boolean => { if (!hasValue((value || "").trim())) { - return "Must enter an organization"; + return getErrorMessage("validation-err-org-name-missing"); } const pass = value.match( /^[0-9a-zA-Z][^\s]*[0-9a-zA-Z]$/ // No Spaces @@ -109,13 +108,13 @@ export const validateOrgName = (value: string): string | boolean => { if (pass) { return true; } - return ORG_NAME_VIOLATION; + return getErrorMessage("validation-err-org-name"); }; export const validateOrgNameThrowable = (value: string): void => { const err = validateOrgName(value); if (typeof err == "string") { - throw Error(err); + throw buildError(errorStatusCode.VALIDATION_ERR, err); } }; @@ -138,10 +137,10 @@ export const isDashAlphaNumeric = (value: string): boolean => { */ export const validatePassword = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a value."; + return getErrorMessage("validation-err-password-missing"); } if (value.length < 8) { - return "Must be more than 8 characters long."; + return getErrorMessage("validation-err-password-too-short"); } return true; }; @@ -153,16 +152,16 @@ export const validatePassword = (value: string): string | boolean => { */ export const validateProjectName = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a project name"; + return getErrorMessage("validation-err-project-name-missing"); } if (value.length > 64) { - return "Project name cannot be longer than 64 characters"; + return getErrorMessage("validation-err-project-name-too-long"); } if (value.startsWith("_")) { - return "Project name cannot begin with an underscore"; + return getErrorMessage("validation-err-project-name-begin-underscore"); } if (value.startsWith(".") || value.endsWith(".")) { - return "Project name cannot begin or end with a period"; + return getErrorMessage("validation-err-project-name-period"); } const invalidChars = [ @@ -192,7 +191,7 @@ export const validateProjectName = (value: string): string | boolean => { "]", ]; if (invalidChars.some((x) => value.indexOf(x) !== -1)) { - return `Project name can't contain special characters, such as / : \\ ~ & % ; @ ' " ? < > | # $ * } { , + = [ ]`; + return getErrorMessage("validation-err-project-name-special-char"); } return true; @@ -201,7 +200,7 @@ export const validateProjectName = (value: string): string | boolean => { export const validateProjectNameThrowable = (value: string): void => { const err = validateProjectName(value); if (typeof err == "string") { - throw Error(err); + throw buildError(errorStatusCode.VALIDATION_ERR, err); } }; @@ -212,20 +211,28 @@ export const validateProjectNameThrowable = (value: string): void => { */ export const validateAccessToken = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a personal access token with read/write/manage permissions"; + return getErrorMessage("validation-err-personal-access-token-missing"); } return true; }; +export const validateAccessTokenThrowable = (value: string): void => { + const err = validateAccessToken(value); + if (typeof err == "string") { + throw buildError(errorStatusCode.VALIDATION_ERR, err); + } +}; + export const validateServicePrincipal = ( value: string, - property: string + missing: string, + invalid: string ): string | boolean => { if (!hasValue(value)) { - return `Must enter a ${property}.`; + return getErrorMessage(missing); } if (!isDashHex(value)) { - return `The value for ${property} is invalid.`; + return getErrorMessage(invalid); } return true; }; @@ -236,7 +243,23 @@ export const validateServicePrincipal = ( * @param value service principal id */ export const validateServicePrincipalId = (value: string): string | boolean => { - return validateServicePrincipal(value, "Service Principal Id"); + return validateServicePrincipal( + value, + "validation-err-service-principal-id-missing", + "validation-err-service-principal-id-invalid" + ); +}; + +/** + * Validate service principal id + * + * @param value service principal id + */ +export const validateServicePrincipalIdThrowable = (value: string): void => { + const msg = validateServicePrincipalId(value); + if (typeof msg === "string") { + throw buildError(errorStatusCode.VALIDATION_ERR, msg); + } }; /** @@ -247,7 +270,25 @@ export const validateServicePrincipalId = (value: string): string | boolean => { export const validateServicePrincipalPassword = ( value: string ): string | boolean => { - return validateServicePrincipal(value, "Service Principal Password"); + return validateServicePrincipal( + value, + "validation-err-service-principal-pwd-missing", + "validation-err-service-principal-pwd-invalid" + ); +}; + +/** + * Validate service principal password + * + * @param value service principal password + */ +export const validateServicePrincipalPasswordThrowable = ( + value: string +): void => { + const msg = validateServicePrincipalPassword(value); + if (typeof msg === "string") { + throw buildError(errorStatusCode.VALIDATION_ERR, msg); + } }; /** @@ -258,7 +299,25 @@ export const validateServicePrincipalPassword = ( export const validateServicePrincipalTenantId = ( value: string ): string | boolean => { - return validateServicePrincipal(value, "Service Principal Tenant Id"); + return validateServicePrincipal( + value, + "validation-err-service-principal-tenant-id-missing", + "validation-err-service-principal-tenant-id-invalid" + ); +}; + +/** + * Validate service principal tenant Id + * + * @param value service principal tenant Id + */ +export const validateServicePrincipalTenantIdThrowable = ( + value: string +): void => { + const msg = validateServicePrincipalTenantId(value); + if (typeof msg === "string") { + throw buildError(errorStatusCode.VALIDATION_ERR, msg); + } }; /** @@ -268,14 +327,26 @@ export const validateServicePrincipalTenantId = ( */ export const validateSubscriptionId = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a subscription identifier."; + return getErrorMessage("validation-err-subscription-id-missing"); } if (!isDashHex(value)) { - return "The value for subscription identifier is invalid."; + return getErrorMessage("validation-err-subscription-id-invalid"); } return true; }; +/** + * Validate subscription identifier + * + * @param value subscription identifier + */ +export const validateSubscriptionIdThrowable = (value: string): void => { + const msg = validateSubscriptionId(value); + if (typeof msg === "string") { + throw buildError(errorStatusCode.VALIDATION_ERR, msg); + } +}; + /** * Returns true if storage account name is valid. * @@ -283,13 +354,13 @@ export const validateSubscriptionId = (value: string): string | boolean => { */ export const validateStorageAccountName = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a storage account name."; + return getErrorMessage("validation-err-storage-account-name-missing"); } if (!value.match(/^[a-z0-9]+$/)) { - return "The value for storage account name is invalid. Lowercase letters and numbers are allowed."; + return getErrorMessage("validation-err-storage-account-name-invalid"); } if (value.length < 3 || value.length > 24) { - return "The value for storage account name is invalid. It has to be between 3 and 24 characters long"; + return getErrorMessage("validation-err-storage-account-name-length"); } return true; }; @@ -302,7 +373,7 @@ export const validateStorageAccountName = (value: string): string | boolean => { export const validateStorageAccountNameThrowable = (value: string): void => { const msg = validateStorageAccountName(value); if (typeof msg === "string") { - throw Error(msg); + throw buildError(errorStatusCode.VALIDATION_ERR, msg); } }; @@ -313,13 +384,13 @@ export const validateStorageAccountNameThrowable = (value: string): void => { */ export const validateStorageTableName = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter a storage table name."; + return getErrorMessage("validation-err-storage-table-name-missing"); } if (!value.match(/^[A-Za-z][A-Za-z0-9]*$/)) { - return "The value for storage table name is invalid. It has to be alphanumeric and start with an alphabet."; + return getErrorMessage("validation-err-storage-table-name-invalid"); } if (value.length < 3 || value.length > 63) { - return "The value for storage table name is invalid. It has to be between 3 and 63 characters long"; + return getErrorMessage("validation-err-storage-table-name-length"); } return true; }; @@ -332,7 +403,7 @@ export const validateStorageTableName = (value: string): string | boolean => { export const validateStorageTableNameThrowable = (value: string): void => { const msg = validateStorageTableName(value); if (typeof msg === "string") { - throw Error(msg); + throw buildError(errorStatusCode.VALIDATION_ERR, msg); } }; @@ -345,10 +416,10 @@ export const validateStoragePartitionKey = ( value: string ): string | boolean => { if (!hasValue(value)) { - return "Must enter a storage partition key."; + return getErrorMessage("validation-err-storage-partition-key-missing"); } if (value.match(/[/\\#?]/)) { - return "The value for storage partition key is invalid. /, \\, # and ? characters are not allowed."; + return getErrorMessage("validation-err-storage-partition-key-invalid"); } return true; }; @@ -360,13 +431,13 @@ export const validateStoragePartitionKey = ( */ export const validateACRName = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter an Azure Container Registry Name."; + return getErrorMessage("validation-err-acr-missing"); } if (!isAlphaNumeric(value)) { - return "The value for Azure Container Registry Name is invalid."; + return getErrorMessage("validation-err-acr-invalid"); } if (value.length < 5 || value.length > 50) { - return "The value for Azure Container Registry Name is invalid because it has to be between 5 and 50 characters long."; + return getErrorMessage("validation-err-acr-length"); } return true; }; @@ -378,26 +449,26 @@ export const validateStorageKeyVaultName = ( return true; // optional } if (!isDashAlphaNumeric(value)) { - return "The value for Key Value Name is invalid."; + return getErrorMessage("validation-err-storage-key-vault-invalid"); } if (!value.match(/^[a-zA-Z]/)) { - return "Key Value Name must start with a letter."; + return getErrorMessage("validation-err-storage-key-vault-start-letter"); } if (!value.match(/[a-zA-Z0-9]$/)) { - return "Key Value Name must end with letter or digit."; + return getErrorMessage("validation-err-storage-key-vault-end-char"); } if (value.indexOf("--") !== -1) { - return "Key Value Name cannot contain consecutive hyphens."; + return getErrorMessage("validation-err-storage-key-vault-hyphen"); } if (value.length < 3 || value.length > 24) { - return "The value for Key Vault Name is invalid because it has to be between 3 and 24 characters long."; + return getErrorMessage("validation-err-storage-key-vault-length"); } return true; }; export const validateStorageAccessKey = (value: string): string | boolean => { if (!hasValue(value)) { - return "Must enter an Storage Access Key."; + return getErrorMessage("validation-err-storage-access-key-missing"); } return true; };