diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c39333e93c..addd40a41b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Zowe CLI package will be documented in this file. +## Recent Changes + +- Enhancement: Changed references in command output from 'Team Configuration' to 'Zowe client configuration'. + ## `8.0.0-next.202403041352` - BugFix: Updated engine to Node 18.12.0. [#2074](https://github.com/zowe/zowe-cli/pull/2074) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 967d92892d..a0faccf099 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes +- LTS Breaking: [#1703](https://github.com/zowe/zowe-cli/issues/1703) + - Renamed class ProfileIO to V1ProfileConversion in package @zowe/imperative class. + - Removed the following obsolete V1 profile functions: + - createProfileDirs + - deleteProfile + - exists + - writeMetaFile + - writeProfile + - Removed the following obsolete V1 profile constant: + - MAX_YAML_DEPTH + - Changed fileToProfileName from public to private + ## `8.0.0-next.202403041352` - BugFix: Updated engine to Node 18.12.0. [#2074](https://github.com/zowe/zowe-cli/pull/2074) diff --git a/packages/imperative/src/config/__tests__/ConfigBuilder.unit.test.ts b/packages/imperative/src/config/__tests__/ConfigBuilder.unit.test.ts index f7e2f63cb5..f37323f3dd 100644 --- a/packages/imperative/src/config/__tests__/ConfigBuilder.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigBuilder.unit.test.ts @@ -11,7 +11,7 @@ import { CredentialManagerFactory, IImperativeConfig } from "../.."; import { Config, ConfigBuilder, IConfig } from "../"; -import { ProfileIO } from "../../profiles"; +import { V1ProfileConversion } from "../../profiles"; import * as config from "../../../__tests__/__integration__/imperative/src/imperative"; import * as lodash from "lodash"; @@ -247,14 +247,14 @@ describe("Config Builder tests", () => { }); it("should successfully convert multiple v1 profiles to config object", async () => { - jest.spyOn(ProfileIO, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); - jest.spyOn(ProfileIO, "getAllProfileNames") + jest.spyOn(V1ProfileConversion, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); + jest.spyOn(V1ProfileConversion, "getAllProfileNames") .mockReturnValueOnce(["apple", "banana", "coconut"]) .mockReturnValueOnce(["almond", "brazil", "cashew"]); - jest.spyOn(ProfileIO, "readMetaFile") + jest.spyOn(V1ProfileConversion, "readMetaFile") .mockReturnValueOnce({ defaultProfile: "apple" } as any) .mockReturnValueOnce({ defaultProfile: "brazil" } as any); - jest.spyOn(ProfileIO, "readProfileFile") + jest.spyOn(V1ProfileConversion, "readProfileFile") .mockReturnValueOnce({ color: "green", secret: "managed by A" }) .mockReturnValueOnce({ color: "yellow", secret: "managed by B" }) .mockReturnValueOnce({ color: "brown", secret: "managed by C" }) @@ -309,12 +309,12 @@ describe("Config Builder tests", () => { mockSecureLoad.mockReturnValueOnce(null); const metaError = new Error("invalid meta file"); const profileError = new Error("invalid profile file"); - jest.spyOn(ProfileIO, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); - jest.spyOn(ProfileIO, "getAllProfileNames") + jest.spyOn(V1ProfileConversion, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); + jest.spyOn(V1ProfileConversion, "getAllProfileNames") .mockReturnValueOnce(["apple", "banana", "coconut"]) .mockReturnValueOnce([]); - jest.spyOn(ProfileIO, "readMetaFile").mockImplementationOnce(() => { throw metaError; }); - jest.spyOn(ProfileIO, "readProfileFile") + jest.spyOn(V1ProfileConversion, "readMetaFile").mockImplementationOnce(() => { throw metaError; }); + jest.spyOn(V1ProfileConversion, "readProfileFile") .mockImplementationOnce(() => ({ color: "green", secret: "managed by A" })) .mockImplementationOnce(() => { throw profileError; }) .mockImplementationOnce(() => ({ color: "brown", secret: "managed by C" })); @@ -350,12 +350,12 @@ describe("Config Builder tests", () => { }); it("should convert v1 property names to v2 names", async () => { - jest.spyOn(ProfileIO, "getAllProfileDirectories").mockReturnValueOnce(["zosmf"]); - jest.spyOn(ProfileIO, "getAllProfileNames") + jest.spyOn(V1ProfileConversion, "getAllProfileDirectories").mockReturnValueOnce(["zosmf"]); + jest.spyOn(V1ProfileConversion, "getAllProfileNames") .mockReturnValueOnce(["LPAR1"]); - jest.spyOn(ProfileIO, "readMetaFile") + jest.spyOn(V1ProfileConversion, "readMetaFile") .mockReturnValueOnce({ defaultProfile: "LPAR1" } as any); - jest.spyOn(ProfileIO, "readProfileFile") + jest.spyOn(V1ProfileConversion, "readProfileFile") .mockReturnValueOnce({ hostname: "should change to host", username: "should change to user", diff --git a/packages/imperative/src/config/src/ConfigBuilder.ts b/packages/imperative/src/config/src/ConfigBuilder.ts index ea94052006..30ff6b1d7b 100644 --- a/packages/imperative/src/config/src/ConfigBuilder.ts +++ b/packages/imperative/src/config/src/ConfigBuilder.ts @@ -11,7 +11,7 @@ import * as path from "path"; import * as lodash from "lodash"; -import { ProfileIO, ProfilesConstants, ProfileUtils } from "../../profiles"; +import { V1ProfileConversion, ProfilesConstants, ProfileUtils } from "../../profiles"; import { IImperativeConfig } from "../../imperative"; import { Config } from "./Config"; import { IConfig } from "./doc/IConfig"; @@ -99,9 +99,9 @@ export class ConfigBuilder { profilesFailed: [] }; - for (const profileType of ProfileIO.getAllProfileDirectories(profilesRootDir)) { + for (const profileType of V1ProfileConversion.getAllProfileDirectories(profilesRootDir)) { const profileTypeDir = path.join(profilesRootDir, profileType); - const profileNames = ProfileIO.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); + const profileNames = V1ProfileConversion.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); if (profileNames.length === 0) { continue; } @@ -109,7 +109,7 @@ export class ConfigBuilder { for (const profileName of profileNames) { try { const profileFilePath = path.join(profileTypeDir, `${profileName}.yaml`); - const profileProps = ProfileIO.readProfileFile(profileFilePath, profileType); + const profileProps = V1ProfileConversion.readProfileFile(profileFilePath, profileType); const secureProps = []; for (const [key, value] of Object.entries(profileProps)) { @@ -139,7 +139,7 @@ export class ConfigBuilder { try { const metaFilePath = path.join(profileTypeDir, `${profileType}_meta.yaml`); - const profileMetaFile = ProfileIO.readMetaFile(metaFilePath); + const profileMetaFile = V1ProfileConversion.readMetaFile(metaFilePath); if (profileMetaFile.defaultProfile != null) { result.config.defaults[profileType] = ProfileUtils.getProfileMapKey(profileType, profileMetaFile.defaultProfile); } diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 395bc1ca16..146be5e6bf 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -398,7 +398,7 @@ export class ProfileInfo { if (!Object.prototype.hasOwnProperty.call(configProperties.defaults, profileType)) { // no default exists for the requested type this.mImpLogger.warn("Found no profile of type '" + - profileType + "' in team config." + profileType + "' in Zowe client configuration." ); return null; } diff --git a/packages/imperative/src/error/src/doc/IImperativeErrorParms.ts b/packages/imperative/src/error/src/doc/IImperativeErrorParms.ts index 50b817efd3..b7f562ea8d 100644 --- a/packages/imperative/src/error/src/doc/IImperativeErrorParms.ts +++ b/packages/imperative/src/error/src/doc/IImperativeErrorParms.ts @@ -25,7 +25,8 @@ export interface IImperativeErrorParms { logger?: Logger; /** * Message tag - prepended to the error message specified. Useful for categorizing error messages - * (e.g. "Profile IO Error"). A ": " is appended automatically (e.g. "Profile IO Error: ") + * (e.g. "V1ProfileConversion Read Error"). + * A ": " is appended automatically (e.g. "V1ProfileConversion Read Error: ") * @type {string} * @memberof IImperativeErrorParms */ diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts index 7d9c66e7df..7d0e16204f 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/convert-profiles/convert-profiles.handler.unit.test.ts @@ -14,7 +14,7 @@ import * as fsExtra from "fs-extra"; import { keyring as keytar } from "@zowe/secrets-for-zowe-sdk"; import { Config, ConfigBuilder, ConfigSchema } from "../../../../../config"; import { IHandlerParameters } from "../../../../../cmd"; -import { ProfileIO } from "../../../../../profiles"; +import { V1ProfileConversion } from "../../../../../profiles"; import { AppSettings } from "../../../../../settings"; import { ImperativeConfig } from "../../../../../utilities"; import * as npmInterface from "../../../../src/plugins/utilities/npm-interface"; @@ -87,7 +87,7 @@ describe("Configuration Convert Profiles command handler", () => { const params = getIHandlerParametersObject(); await handler.process(params); - expect(stdout).toContain("No old profiles were found"); + expect(stdout).toContain("Found no old V1 profiles to convert to a current Zowe client configuration"); expect(stderr).toBe(""); }); @@ -140,7 +140,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.prompt = false; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut"); expect(stderr).toContain("Failed to load fruit profile \"banana\""); expect(stderr).toContain(profileError.message); @@ -169,8 +169,8 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.prompt = false; await handler.process(params); - expect(stdout).toContain("A team configuration file was detected"); - expect(stdout).toContain("No old profiles were found"); + expect(stdout).toContain("A current Zowe client configuration was detected. V1 profiles will not be converted"); + expect(stdout).toContain("Found no old V1 profiles to convert to a current Zowe client configuration"); expect(stdout).not.toContain("Converted fruit profiles: apple, coconut"); expect(updateSchemaSpy).not.toHaveBeenCalled(); expect(mockImperativeConfig.config.save).not.toHaveBeenCalled(); @@ -196,10 +196,10 @@ describe("Configuration Convert Profiles command handler", () => { (params.response.console.prompt as any).mockResolvedValueOnce("y"); await handler.process(params); - expect(stdout).toContain("Detected 1 old profile(s)"); + expect(stdout).toContain("Detected 1 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("The following plug-ins will be removed"); expect(stdout).toContain("Your new profiles have been saved"); - expect(stdout).toContain("Your old profiles have been moved"); + expect(stdout).toContain("Your old V1 profiles have been moved"); expect(stderr).toBe(""); expect(uninstallSpy).toHaveBeenCalled(); expect(configConvertSpy).toHaveBeenCalled(); @@ -216,7 +216,7 @@ describe("Configuration Convert Profiles command handler", () => { (params.response.console.prompt as any).mockResolvedValueOnce("n"); await handler.process(params); - expect(stdout).toContain("Detected 1 old profile(s)"); + expect(stdout).toContain("Detected 1 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("The following plug-ins will be removed"); expect(stderr).toBe(""); expect(uninstallSpy).not.toHaveBeenCalled(); @@ -253,7 +253,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).toContain("Deleting the profiles directory"); expect(stdout).toContain("Deleting secure value for \"@brightside/core/testAcct\""); @@ -298,7 +298,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).toContain("Deleting the profiles directory"); expect(stdout).toContain("Deleting secure value for \"@brightside/core/testAcct\""); @@ -344,7 +344,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).not.toContain("Deleting the profiles directory"); expect(stdout).not.toContain("Deleting secure value for \"@brightside/core/testAcct\""); @@ -392,7 +392,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).toContain("Deleting the profiles directory"); expect(stdout).toContain("Deleting secure value for \"@brightside/core/testAcct\""); @@ -437,7 +437,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("No old profiles were found"); + expect(stdout).toContain("Found no old V1 profiles to convert to a current Zowe client configuration"); expect(stdout).toContain("Deleting the profiles directory"); expect(stdout).toContain("Deleting secure value for \"@brightside/core/testAcct\""); expect(stdout).toContain("Deleting secure value for \"@zowe/cli/testAcct\""); @@ -482,7 +482,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).toContain("Deleting secure value for \"@brightside/core/testAcct\""); expect(stdout).toContain("Deleting secure value for \"@zowe/cli/testAcct\""); @@ -524,7 +524,7 @@ describe("Configuration Convert Profiles command handler", () => { params.arguments.delete = true; await handler.process(params); - expect(stdout).toContain("Detected 3 old profile(s)"); + expect(stdout).toContain("Detected 3 old V1 profile(s) to convert to a current Zowe client configuration"); expect(stdout).toContain("Converted fruit profiles: apple, coconut, banana"); expect(stdout).toContain("Deleting the profiles directory"); expect(stderr).toContain("Keytar or the credential vault are unavailable."); @@ -736,8 +736,8 @@ describe("Configuration Convert Profiles command handler", () => { }); it("getOldProfileCount should find multiple types of profiles", () => { - jest.spyOn(ProfileIO, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); - jest.spyOn(ProfileIO, "getAllProfileNames") + jest.spyOn(V1ProfileConversion, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); + jest.spyOn(V1ProfileConversion, "getAllProfileNames") .mockReturnValueOnce(["apple", "banana", "coconut"]) .mockReturnValueOnce(["almond", "brazil", "cashew"]); diff --git a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.definition.ts b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.definition.ts index 7b54f3294b..04c747bb89 100644 --- a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.definition.ts +++ b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.definition.ts @@ -22,7 +22,7 @@ export const convertProfilesDefinition: ICommandDefinition = { aliases: ["convert"], type: "command", handler: join(__dirname, "convert-profiles.handler"), - summary: "Convert profiles to team config", + summary: "Convert V1 profiles to a current Zowe client configuration", description: `Convert v1 profiles to a global ${ImperativeConfig.instance.rootCommandName}.config.json file.`, options: [{ name: "prompt", @@ -35,10 +35,10 @@ export const convertProfilesDefinition: ICommandDefinition = { type: "boolean" }], examples: [{ - description: "Convert profiles to team config without prompting", + description: "Convert V1 profiles to a new Zowe client configuration without prompting", options: "--no-prompt" }, { - description: "Convert profiles to team config and delete the old profiles", + description: "Convert V1 profiles to a new Zowe client configuration and delete the old V1 profiles", options: "--delete" }] }; diff --git a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts index 771e50d083..a03af88cb2 100644 --- a/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts +++ b/packages/imperative/src/imperative/src/config/cmd/convert-profiles/convert-profiles.handler.ts @@ -15,7 +15,7 @@ import * as path from "path"; import { keyring as keytar } from "@zowe/secrets-for-zowe-sdk"; import { ICommandHandler, IHandlerParameters } from "../../../../../cmd"; import { ConfigBuilder, ConfigSchema } from "../../../../../config"; -import { ProfileIO, ProfileUtils } from "../../../../../profiles"; +import { V1ProfileConversion, ProfileUtils } from "../../../../../profiles"; import { ImperativeConfig } from "../../../../../utilities"; import { AppSettings } from "../../../../../settings"; import { PluginIssues } from "../../../plugins/utilities/PluginIssues"; @@ -55,19 +55,21 @@ export default class ConvertProfilesHandler implements ICommandHandler { const configExists = ImperativeConfig.instance.config?.exists; const oldPluginInfo = this.getOldPluginInfo(); - // Cannot do profiles operations w/ team config + // Cannot do profiles operations w/ current Zowe client config const oldProfileCount = configExists ? 0 : this.getOldProfileCount(profilesRootDir); const oldProfilesDir = `${profilesRootDir.replace(/[\\/]$/, "")}-old`; let skipConversion = false; if (configExists) { - // Warn that a team config was detected - params.response.console.log(`A team configuration file was detected. V1 profiles cannot be loaded for conversion.\n` + - `Run '${cliBin} config list --locations --root' for team configuration file locations.\n`); + // Warn that a current Zowe client config was detected + params.response.console.log( + `A current Zowe client configuration was detected. V1 profiles will not be converted.\n` + + `Run '${cliBin} config list --locations --root' for Zowe configuration file locations.\n` + ); } if (oldPluginInfo.plugins.length == 0 && oldProfileCount === 0) { - params.response.console.log("No old profiles were found to convert from Zowe v1 to TeamConfig."); + params.response.console.log("Found no old V1 profiles to convert to a current Zowe client configuration."); // Exit if we're not deleting if (!(params.arguments.delete != null && params.arguments.delete === true)) { return; @@ -79,7 +81,9 @@ export default class ConvertProfilesHandler implements ICommandHandler { // If this is true, then we know that we want to delete, but there is nothing to convert first. if (!skipConversion) { if (oldProfileCount > 0) { - params.response.console.log(`Detected ${oldProfileCount} old profile(s) to convert from Zowe v1 to TeamConfig.\n`); + params.response.console.log( + `Detected ${oldProfileCount} old V1 profile(s) to convert to a current Zowe client configuration.\n` + ); } if (oldPluginInfo.plugins.length > 0) { @@ -141,7 +145,7 @@ export default class ConvertProfilesHandler implements ICommandHandler { `Run "${cliBin} config edit --global-config" to open this file in your default editor.\n`); if (params.arguments.delete == null || params.arguments.delete === false) { - params.response.console.log(`Your old profiles have been moved to ${oldProfilesDir}.\n` + + params.response.console.log(`Your old V1 profiles have been moved to ${oldProfilesDir}.\n` + `Run "${cliBin} config convert-profiles --delete" if you want to completely remove them.\n\n` + `If you would like to revert back to v1 profiles, or convert your v1 profiles again, rename the 'profiles-old' ` + `directory to 'profiles' and delete the new config file located at ${teamConfig.layerActive().path}.`); @@ -221,11 +225,11 @@ export default class ConvertProfilesHandler implements ICommandHandler { * @returns Number of old profiles found */ private getOldProfileCount(profilesRootDir: string): number { - const profileTypes = ProfileIO.getAllProfileDirectories(profilesRootDir); + const profileTypes = V1ProfileConversion.getAllProfileDirectories(profilesRootDir); let oldProfileCount = 0; for (const profileType of profileTypes) { const profileTypeDir = path.join(profilesRootDir, profileType); - const profileNames = ProfileIO.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); + const profileNames = V1ProfileConversion.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); oldProfileCount += profileNames.length; } return oldProfileCount; diff --git a/packages/imperative/src/imperative/src/config/cmd/report-env/EnvQuery.ts b/packages/imperative/src/imperative/src/config/cmd/report-env/EnvQuery.ts index 385493e968..7b15107314 100644 --- a/packages/imperative/src/imperative/src/config/cmd/report-env/EnvQuery.ts +++ b/packages/imperative/src/imperative/src/config/cmd/report-env/EnvQuery.ts @@ -211,7 +211,7 @@ export class EnvQuery { private static async getConfigInfo( getResult: IGetItemVal, getItemOpts: IGetItemOpts ): Promise { - const teamCfg: string = "Team Config"; + const teamCfg: string = "Zowe Client Config"; const doesProgBarExist: boolean = (getItemOpts?.progressApi) ? true: false; // setup progress bar diff --git a/packages/imperative/src/imperative/src/config/cmd/secure/secure.definition.ts b/packages/imperative/src/imperative/src/config/cmd/secure/secure.definition.ts index efe1eb1c78..db10cab504 100644 --- a/packages/imperative/src/imperative/src/config/cmd/secure/secure.definition.ts +++ b/packages/imperative/src/imperative/src/config/cmd/secure/secure.definition.ts @@ -35,7 +35,7 @@ export const secureDefinition: ICommandDefinition = { }, { name: "prune", - description: "Delete properties stored in the vault for team config files that do not exist.", + description: "Delete properties stored in the vault for Zowe client config files that do not exist.", aliases: ["p"], type: "boolean", defaultValue: false diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts index c627ac4dcc..eff42b2adc 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts @@ -167,7 +167,7 @@ export async function install(packageLocation: string, registry: string, install const requirerFunction = PluginManagementFacility.instance.requirePluginModuleCallback(packageName); const pluginImpConfig = ConfigurationLoader.load(null, packageInfo, requirerFunction); - iConsole.debug(`Checking for global team configuration files to update.`); + iConsole.debug(`Checking for global Zowe client configuration files to update.`); if (PMFConstants.instance.PLUGIN_USING_CONFIG) { // Update the Imperative Configuration to add the profiles introduced by the recently installed plugin diff --git a/packages/imperative/src/profiles/index.ts b/packages/imperative/src/profiles/index.ts index 427dad1903..82f8c44271 100644 --- a/packages/imperative/src/profiles/index.ts +++ b/packages/imperative/src/profiles/index.ts @@ -22,7 +22,7 @@ export * from "./src/doc/definition/IProfileSchema"; export * from "./src/doc/parms/IProfileManager"; export * from "./src/doc/response/IProfileLoaded"; -export * from "./src/utils/ProfileIO"; +export * from "./src/utils/V1ProfileConversion"; export * from "./src/utils/ProfileUtils"; export * from "./src/utils"; diff --git a/packages/imperative/src/profiles/src/utils/ProfileIO.ts b/packages/imperative/src/profiles/src/utils/ProfileIO.ts deleted file mode 100644 index ebda18db90..0000000000 --- a/packages/imperative/src/profiles/src/utils/ProfileIO.ts +++ /dev/null @@ -1,333 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -import { ImperativeError } from "../../../error"; -import { ImperativeConfig } from "../../../utilities"; -import * as fs from "fs"; -import { IProfile } from "../doc/definition/IProfile"; -import { IMetaProfile } from "../doc/definition/IMetaProfile"; -import * as pathPackage from "path"; -import { IO } from "../../../io"; -import { IProfileTypeConfiguration } from "../doc/config/IProfileTypeConfiguration"; - -const readYaml = require("js-yaml"); -const writeYaml = require("yamljs"); - -/** - * Profile IO methods for writing/reading profiles to disk. The profile managers never invoke "fs" directly. - * All "fs" calls are wrapped here and errors are transformed to ImperativeError for error handling/flow throughout - * Imperative. - * - * @export - * @class ProfileIO - */ -export class ProfileIO { - /** - * The yamljs package requires you to indicate the depth for conversion to yaml. Set to max of 9999. - * @static - * @type {number} - * @memberof ProfileIO - */ - public static readonly MAX_YAML_DEPTH: number = 9999; - - /** - * Creates the full set of directories indicated by the path. Used to create the profile root directory and - * type directories. - * @static - * @param {string} path - The directory path to create - creates all necessary subdirectories. - * @memberof ProfileIO - */ - public static createProfileDirs(path: string) { - ProfileIO.crashInTeamConfigMode(); - try { - IO.createDirsSync(path); - } catch (err) { - throw new ImperativeError({ - msg: `An error occurred creating profile directory: "${path}". ` + - `Error Details: ${err.message}`, - additionalDetails: err - }, {tag: ProfileIO.ERROR_ID}); - } - } - - /** - * Read the profile meta file using Yaml "safeLoad" (ensures that no code executes, etc. during the load). The - * meta profile file for a type contains the default profile specification. The meta profile is ALWAYS in YAML - * format (controlled exclusively by the Imperative framework). - * @static - * @param {string} path - The path to the meta profile - * @returns {IMetaProfile} - The meta profile - * @memberof ProfileIO - */ - public static readMetaFile(path: string): IMetaProfile { - ProfileIO.crashInTeamConfigMode(); - - let meta: IMetaProfile; - try { - meta = readYaml.load(fs.readFileSync(path), "utf8"); - } catch (err) { - throw new ImperativeError({ - msg: `Error reading profile file ("${path}"). Error Details: ${err.message}`, - additionalDetails: err - }, {tag: ProfileIO.ERROR_ID}); - } - return meta; - } - - /** - * Accepts a profile object and writes the profile to the specified location (and optionally converts - * the profile to YAML format - the default for Imperative profiles). - * @static - * @param {string} fullFilePath - the fully qualified profile path, file, & extension. - * @param {IProfile} profile - the profile object to write to disk. - * @memberof ProfileIO - */ - public static writeProfile(fullFilePath: string, profile: IProfile): void { - ProfileIO.crashInTeamConfigMode(); - - try { - /** - * Write the YAML file - clone the object first and remove the name. Imperative will not persist the - * name within the profile (but needs it when loading/using). - */ - const profileCopy = JSON.parse(JSON.stringify(profile)); - delete profileCopy.name; - - /** - * If yaml = true, we will attempt to convert to yaml format before persisting. - */ - const writeProfile: any = writeYaml.stringify(profileCopy, ProfileIO.MAX_YAML_DEPTH); - - /** - * Attempt to write the profile - always encoded in utf-8 - */ - fs.writeFileSync(fullFilePath, writeProfile, {encoding: "utf8"}); - } catch (err) { - throw new ImperativeError({ - msg: `Profile IO Error: Error creating profile file ("${fullFilePath}"). Error Details: ${err.message}`, - additionalDetails: err.message - }); - } - } - - /** - * Delete the profile and ensure it is gone. - * @static - * @param {string} name - the profile object - really only used for error messages - * @param {string} fullFilePath - the full file path to delete - * @memberof ProfileIO - */ - public static deleteProfile(name: string, fullFilePath: string) { - ProfileIO.crashInTeamConfigMode(); - - try { - /** - * Attempt to remove the file and ensure that it was removed successfully - */ - fs.unlinkSync(fullFilePath); - if (fs.existsSync(fullFilePath)) { - const errorMsg: string = `The profile ${name} was unable to be deleted. ` + - `Please check the path indicated here and try to remove the profile manually: ${fullFilePath}`; - throw new ImperativeError({ - msg: errorMsg - }, {tag: ProfileIO.ERROR_ID}); - } - } catch (deleteErr) { - /** - * If an error occurred, rethrow if already instance of ImperativeError OR transform and throw - */ - if (deleteErr instanceof ImperativeError) { - throw deleteErr; - } else { - throw new ImperativeError({ - msg: `An unexpected profile delete error occurred for profile "${name}". ` + - `Error Details: ${deleteErr.message}.`, - additionalDetails: deleteErr - }, {tag: ProfileIO.ERROR_ID}); - } - } - } - - /** - * Checks if the file specified exists. - * @static - * @param {string} path - The file path - * @returns {string} - the path to the existing file or NULL if not found - * @memberof ProfileIO - */ - public static exists(path: string): string { - ProfileIO.crashInTeamConfigMode(); - - let found: string; - try { - found = (fs.existsSync(path)) ? path : undefined; - } catch (e) { - throw new ImperativeError({ - msg: `An error occurred checking for the existance of "${path}". Error Details: ${e.message}`, - additionalDetails: e - }, {tag: ProfileIO.ERROR_ID}); - } - return found; - } - - /** - * Converts the meta to yaml and writes to disk - * @static - * @param {IMetaProfile} meta - The meta profile contents to write to disk - * @param {string} path - The path to the meta profile - * @memberof ProfileIO - */ - public static writeMetaFile(meta: IMetaProfile, path: string) { - ProfileIO.crashInTeamConfigMode(); - - try { - const yamlString: any = writeYaml.stringify(meta, ProfileIO.MAX_YAML_DEPTH); - fs.writeFileSync(path, yamlString, {encoding: "utf8"}); - } catch (e) { - throw new ImperativeError({ - msg: `An error occurred converting and writing the meta profile to "${path}". ` + - `Error Details: ${e.message}`, - additionalDetails: e - }, {tag: ProfileIO.ERROR_ID}); - } - } - - /** - * Extracts the profile name from the file path/name - * @static - * @param {string} file - the file path to extract the profile name - * @param {string} ext - the extension of the file - * @returns {string} - the profile name - * @memberof ProfileIO - */ - public static fileToProfileName(file: string, ext: string): string { - ProfileIO.crashInTeamConfigMode(); - - file = pathPackage.basename(file); - return file.substring(0, file.lastIndexOf(ext)); - } - - /** - * Accepts the profiles root directory and returns all directories within. The directories within the root - * directory are all assumed to be profile type directories (potentially containing a meta file and profiles - * of that type). - * @static - * @param {string} profileRootDirectory - The profiles root directory to obtain all profiles from. - * @returns {string[]} - The list of profiles returned or a blank array - * @memberof ProfileIO - */ - public static getAllProfileDirectories(profileRootDirectory: string): string[] { - ProfileIO.crashInTeamConfigMode(); - - let names: string[] = []; - try { - names = fs.readdirSync(profileRootDirectory); - names = names.filter((name) => { - // only return directories, not files - const stats = fs.statSync(pathPackage.join(profileRootDirectory, name)); - return stats.isDirectory(); - }); - } catch (e) { - throw new ImperativeError({ - msg: `An error occurred attempting to read all profile directories from "${profileRootDirectory}". ` + - `Error Details: ${e.message}`, - additionalDetails: e - }, {tag: ProfileIO.ERROR_ID}); - } - return names; - } - - /** - * Accepts the profile directory location for a type, reads all filenames, and returns a list of - * profile names that are present within the directory (excluding the meta profile) - * @static - * @param {string} profileTypeDir - The directory for the type - * @param {string} ext - the extension for the profile files (normally YAML) - * @param {string} metaNameForType - the meta name for this type - * @returns {string[]} - A list of all profile names (without path/ext) - * @memberof ProfileIO - */ - public static getAllProfileNames(profileTypeDir: string, ext: string, metaNameForType: string): string[] { - ProfileIO.crashInTeamConfigMode(); - - const names: string[] = []; - try { - let profileFiles = fs.readdirSync(profileTypeDir); - profileFiles = profileFiles.filter((file) => { - const fullFile = pathPackage.resolve(profileTypeDir, file); - const isYamlFile = fullFile.length > ext.length && fullFile.substring( - fullFile.length - ext.length) === ext; - return isYamlFile && ProfileIO.fileToProfileName(fullFile, ext) !== metaNameForType; - }); - for (const file of profileFiles) { - names.push(ProfileIO.fileToProfileName(file, ext)); - } - } catch (e) { - throw new ImperativeError({ - msg: `An error occurred attempting to read all profile names from "${profileTypeDir}". ` + - `Error Details: ${e.message}`, - additionalDetails: e - }, {tag: ProfileIO.ERROR_ID}); - } - return names; - } - - /** - * Read a profile from disk. Profiles are always assumed to be YAML (YAML "safeLoad" is invoked to perform the load). - * @static - * @param {string} filePath - Path to the profile. - * @param {string} type - The profile type; used to populate the "type" in the profile object (type property not persisted on disk). - * @returns {IProfile} - The profile object. - * @memberof ProfileIO - */ - public static readProfileFile(filePath: string, type: string): IProfile { - ProfileIO.crashInTeamConfigMode(); - - let profile: IProfile; - try { - profile = readYaml.load(fs.readFileSync(filePath, "utf8")); - } catch (err) { - throw new ImperativeError({ - msg: `Error reading profile file ("${filePath}"). Error Details: ${err.message}`, - additionalDetails: err - }, {tag: ProfileIO.ERROR_ID}); - } - return profile; - } - - /** - * Crash if we detect that we are running in team-config mode. - * You should not be able to operate on old-school profiles - * when you are in team-config mode. Give a meaningful - * message as part of our crash. - */ - private static crashInTeamConfigMode() { - if (ImperativeConfig.instance.config?.exists) { - try { - throw new Error("A Zowe V1 profile operation was attempted with a Zowe team configuration in use."); - } catch (err) { - throw new ImperativeError({ - msg: err.message, - additionalDetails: err.stack, - }, {tag: ProfileIO.ERROR_ID}); - } - } - } - - /** - * Error IO tag for Imperative Errors - * @private - * @static - * @type {string} - * @memberof ProfileIO - */ - private static ERROR_ID: string = "Profile IO Error"; -} diff --git a/packages/imperative/src/profiles/src/utils/V1ProfileConversion.ts b/packages/imperative/src/profiles/src/utils/V1ProfileConversion.ts new file mode 100644 index 0000000000..361eb7c94a --- /dev/null +++ b/packages/imperative/src/profiles/src/utils/V1ProfileConversion.ts @@ -0,0 +1,185 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { ImperativeError } from "../../../error"; +import { ImperativeConfig } from "../../../utilities"; +import * as fs from "fs"; +import { IProfile } from "../doc/definition/IProfile"; +import { IMetaProfile } from "../doc/definition/IMetaProfile"; +import * as pathPackage from "path"; +import { IProfileTypeConfiguration } from "../doc/config/IProfileTypeConfiguration"; + +const readYaml = require("js-yaml"); + +/** + * V1ProfileConversion methods for reading profiles from disk. The profile managers never invoke "fs" directly. + * All "fs" calls are wrapped here and errors are transformed to ImperativeError for error handling/flow throughout + * Imperative. + * + * @export + * @class V1ProfileConversion + */ +export class V1ProfileConversion { + /** + * Read the profile meta file using Yaml "safeLoad" (ensures that no code executes, etc. during the load). The + * meta profile file for a type contains the default profile specification. The meta profile is ALWAYS in YAML + * format (controlled exclusively by the Imperative framework). + * @static + * @param {string} path - The path to the meta profile + * @returns {IMetaProfile} - The meta profile + * @memberof V1ProfileConversion + */ + public static readMetaFile(path: string): IMetaProfile { + V1ProfileConversion.crashInTeamConfigMode(); + + let meta: IMetaProfile; + try { + meta = readYaml.load(fs.readFileSync(path), "utf8"); + } catch (err) { + throw new ImperativeError({ + msg: `Error reading profile file ("${path}"). Error Details: ${err.message}`, + additionalDetails: err + }, {tag: V1ProfileConversion.ERROR_ID}); + } + return meta; + } + + /** + * Accepts the profiles root directory and returns all directories within. The directories within the root + * directory are all assumed to be profile type directories (potentially containing a meta file and profiles + * of that type). + * @static + * @param {string} profileRootDirectory - The profiles root directory to obtain all profiles from. + * @returns {string[]} - The list of profiles returned or a blank array + * @memberof V1ProfileConversion + */ + public static getAllProfileDirectories(profileRootDirectory: string): string[] { + V1ProfileConversion.crashInTeamConfigMode(); + + let names: string[] = []; + try { + names = fs.readdirSync(profileRootDirectory); + names = names.filter((name) => { + // only return directories, not files + const stats = fs.statSync(pathPackage.join(profileRootDirectory, name)); + return stats.isDirectory(); + }); + } catch (e) { + throw new ImperativeError({ + msg: `An error occurred attempting to read all profile directories from "${profileRootDirectory}". ` + + `Error Details: ${e.message}`, + additionalDetails: e + }, {tag: V1ProfileConversion.ERROR_ID}); + } + return names; + } + + /** + * Accepts the profile directory location for a type, reads all filenames, and returns a list of + * profile names that are present within the directory (excluding the meta profile) + * @static + * @param {string} profileTypeDir - The directory for the type + * @param {string} ext - the extension for the profile files (normally YAML) + * @param {string} metaNameForType - the meta name for this type + * @returns {string[]} - A list of all profile names (without path/ext) + * @memberof V1ProfileConversion + */ + public static getAllProfileNames(profileTypeDir: string, ext: string, metaNameForType: string): string[] { + V1ProfileConversion.crashInTeamConfigMode(); + + const names: string[] = []; + try { + let profileFiles = fs.readdirSync(profileTypeDir); + profileFiles = profileFiles.filter((file) => { + const fullFile = pathPackage.resolve(profileTypeDir, file); + const isYamlFile = fullFile.length > ext.length && fullFile.substring( + fullFile.length - ext.length) === ext; + return isYamlFile && V1ProfileConversion.fileToProfileName(fullFile, ext) !== metaNameForType; + }); + for (const file of profileFiles) { + names.push(V1ProfileConversion.fileToProfileName(file, ext)); + } + } catch (e) { + throw new ImperativeError({ + msg: `An error occurred attempting to read all profile names from "${profileTypeDir}". ` + + `Error Details: ${e.message}`, + additionalDetails: e + }, {tag: V1ProfileConversion.ERROR_ID}); + } + return names; + } + + /** + * Read a profile from disk. Profiles are always assumed to be YAML (YAML "safeLoad" is invoked to perform the load). + * @static + * @param {string} filePath - Path to the profile. + * @param {string} type - The profile type; used to populate the "type" in the profile object (type property not persisted on disk). + * @returns {IProfile} - The profile object. + * @memberof V1ProfileConversion + */ + public static readProfileFile(filePath: string, type: string): IProfile { + V1ProfileConversion.crashInTeamConfigMode(); + + let profile: IProfile; + try { + profile = readYaml.load(fs.readFileSync(filePath, "utf8")); + } catch (err) { + throw new ImperativeError({ + msg: `Error reading profile file ("${filePath}"). Error Details: ${err.message}`, + additionalDetails: err + }, {tag: V1ProfileConversion.ERROR_ID}); + } + return profile; + } + + /** + * Crash if we detect that we are running in team-config mode. + * You should not be able to operate on old-school profiles + * when you are in team-config mode. Give a meaningful + * message as part of our crash. + */ + private static crashInTeamConfigMode() { + if (ImperativeConfig.instance.config?.exists) { + try { + throw new Error( + "Attempted to convert a Zowe V1 profile when a newer Zowe client configuration already exists." + ); + } catch (err) { + throw new ImperativeError({ + msg: err.message, + additionalDetails: err.stack, + }, {tag: V1ProfileConversion.ERROR_ID}); + } + } + } + + /** + * Extracts the profile name from the file path/name + * @static + * @param {string} file - the file path to extract the profile name + * @param {string} ext - the extension of the file + * @returns {string} - the profile name + * @memberof V1ProfileConversion + */ + private static fileToProfileName(file: string, ext: string): string { + file = pathPackage.basename(file); + return file.substring(0, file.lastIndexOf(ext)); + } + + /** + * Error IO tag for Imperative Errors + * @private + * @static + * @type {string} + * @memberof V1ProfileConversion + */ + private static ERROR_ID: string = "V1ProfileConversion Read Error"; +} diff --git a/packages/imperative/src/profiles/src/utils/__mocks__/ProfileIO.ts b/packages/imperative/src/profiles/src/utils/__mocks__/V1ProfileConversion.ts similarity index 70% rename from packages/imperative/src/profiles/src/utils/__mocks__/ProfileIO.ts rename to packages/imperative/src/profiles/src/utils/__mocks__/V1ProfileConversion.ts index 02ef409757..4d60126de3 100644 --- a/packages/imperative/src/profiles/src/utils/__mocks__/ProfileIO.ts +++ b/packages/imperative/src/profiles/src/utils/__mocks__/V1ProfileConversion.ts @@ -32,199 +32,20 @@ import { import { IProfileTypeConfiguration } from "../../doc/config/IProfileTypeConfiguration"; /** - * Mocked profile IO class - for the most part, just reacts differently based on the profile name/path specified + * Mocked V1ProfileConversion class - for the most part, just reacts differently based on the profile name/path specified * to simulate certain profile conditions for testing the manager. * * @export - * @class ProfileIO + * @class V1ProfileConversion */ -export class ProfileIO { - /** - * Mocked exists checks if the constructed file path contains the profile or meta file name and - * returns the path to the caller. Allows you to indicate what profiles should exist just by the name - * in your tests. - * @static - * @param {string} path - * @returns {string} - * @memberof ProfileIO - */ - public static exists(path: string): string { - if (path.indexOf("green_apple") >= 0) { - return path; - } - if (path.indexOf("red_apple") >= 0) { - return path; - } - if (path.indexOf("green_dependency_apple") >= 0) { - return path; - } - if (path.indexOf("sweet_strawberry") >= 0) { - return path; - } - if (path.indexOf("mackintosh_apple") >= 0) { - return path; - } - if (path.indexOf("mackintosh_error_apple") >= 0) { - return path; - } - if (path.indexOf("sugar_coated_strawberry") >= 0) { - return path; - } - if (path.indexOf("tasty_apples") >= 0) { - return path; - } - if (path.indexOf("chocolate_covered") >= 0) { - return path; - } - if (path.indexOf("old_apple") >= 0) { - return path; - } - if (path.indexOf("banana_with_grape_dep") >= 0) { - return path; - } - if (path.indexOf("grape_with_banana_circular_dep") >= 0) { - return path; - } - if (path.indexOf("apple_with_two_req_dep_circular") >= 0) { - return path; - } - if (path.indexOf("grape_with_apple_circular_dep") >= 0) { - return path; - } - if (path.indexOf("throw_the_apple") >= 0) { - return path; - } - if (path.indexOf("bad_mango") >= 0) { - return path; - } - if (path.indexOf("good_apple") >= 0) { - return path; - } - if (path.indexOf("misshapen_apple") >= 0) { - return path; - } - if (path.indexOf(BLUEBERRY_PROFILE_TYPE + "_meta") >= 0) { - return path; - } - if (path.indexOf("sweet_blueberry") >= 0) { - return path; - } - if (path.indexOf("apples_and_strawberries_and_bananas") >= 0) { - return path; - } - if (path.indexOf("bundle_of_bananas") >= 0) { - return path; - } - if (path.indexOf("chocolate_strawberries") >= 0) { - return path; - } - if (path.indexOf("apples_and_grapes_and_strawberries_and_bananas") >= 0) { - return path; - } - if (path.indexOf("green_grapes") >= 0) { - return path; - } - if (path.indexOf("bananas_and_grapes") >= 0) { - return path; - } - if (path.indexOf("apples_and_grapes_with_error_and_strawberries_and_bananas") >= 0) { - return path; - } - if (path.indexOf("bananas_and_error_grapes") >= 0) { - return path; - } - if (path.indexOf("bad_grapes") >= 0) { - return path; - } - if (path.indexOf("bananas_error_and_grapes") >= 0) { - return path; - } - if (path.indexOf("apples_and_grapes_not_found_and_strawberries_and_bananas") >= 0) { - return path; - } - if (path.indexOf("apple_has_circular") >= 0) { - return path; - } - if (path.indexOf("strawberry_and_apple") >= 0) { - return path; - } - if (path.indexOf("tart_blueberry") >= 0) { - return path; - } - - // The following group is for detecting if configurations already exist - if (path.indexOf(FRUIT_BASKET) >= 0 && path.indexOf(STRAWBERRY_PROFILE_TYPE) >= 0) { - return path; - } - if (path.indexOf(FRUIT_BASKET) >= 0 && path.indexOf(BLUEBERRY_PROFILE_TYPE) >= 0) { - return path; - } - if (path.indexOf(FRUIT_BASKET) >= 0 && path.indexOf(APPLE_PROFILE_TYPE) >= 0) { - return path; - } - if (path.indexOf(FRUIT_BASKET) >= 0 && path.indexOf(GRAPE_PROFILE_TYPE) >= 0) { - return path; - } - - // Used on an optional dependency load - if (path.indexOf("strawberry_no_apple") >= 0) { - return path; - } - - // Used on an optional dependency load - where the dependency is not found - if (path.indexOf("strawberry_not_found_apple") >= 0) { - return path; - } - - return null; - } - - /** - * Usually just succeeds - but throws an error depending on profile name - * @static - * @param {string} name - * @param {string} fullFilePath - * @memberof ProfileIO - */ - public static deleteProfile(name: string, fullFilePath: string) { - if (name.indexOf("mackintosh_error_apple") >= 0) { - throw new Error("IO ERROR DELETING THE APPLE"); - } - } - - /** - * Just here to "succeed" - * @static - * @param {string} path - * @memberof ProfileIO - */ - public static createProfileDirs(path: string) { - // Nothing needs to happen here during the tests - just needs to "succeed" - } - - /** - * Write profile usually succeeds (unless told to throw an error - based on the input profile name) - * @static - * @param {string} fullFilePath - * @param {IProfile} profile - * @memberof ProfileIO - */ - public static writeProfile(fullFilePath: string, profile: IProfile) { - // A profile name of "throw_the_apple", simulates a write failure - // throwing a simple error - just to ensure that there are no unhandled promise rejections, etc - // and that the error is returned to the caller of the "save" API - if (fullFilePath.indexOf("throw_the_apple") >= 0) { - throw new Error("Write file unexpected failure"); - } - } - +export class V1ProfileConversion { /** * Mocks the get all profile directores - for the most part, if a certain string is found within the path * input, a certain list will be responded. * @static * @param {string} profileRootDirectory * @returns {string[]} - * @memberof ProfileIO + * @memberof V1ProfileConversion */ public static getAllProfileDirectories(profileRootDirectory: string): string[] { if (profileRootDirectory.indexOf(FRUIT_BASKET_BAD) >= 0 || profileRootDirectory.indexOf(FRUIT_BASKET_WORSE) >= 0) { @@ -241,7 +62,7 @@ export class ProfileIO { * @static * @param {string} path * @returns {IMetaProfile} - * @memberof ProfileIO + * @memberof V1ProfileConversion */ public static readMetaFile(path: string): IMetaProfile { @@ -335,20 +156,6 @@ export class ProfileIO { return null; } - /** - * Write meta file mocked - only throws and error if the path indicates a particular fruit type - * @static - * @param {IMetaProfile} meta - * @param {string} path - * @memberof ProfileIO - */ - public static writeMetaFile(meta: IMetaProfile, path: string) { - // Mango type causes a throw error from writing the meta file - if (path.indexOf(MANGO_PROFILE_TYPE) >= 0) { - throw new Error("Error writing the meta file"); - } - } - /** * Returns "all" mocked profile names. Used in delete and other tests to check for dependencies, etc. * @static @@ -356,7 +163,7 @@ export class ProfileIO { * @param {string} ext * @param {string} metaNameForType * @returns {string[]} - * @memberof ProfileIO + * @memberof V1ProfileConversion */ public static getAllProfileNames(profileTypeDir: string, ext: string, metaNameForType: string): string[] { if (profileTypeDir.indexOf("apple") >= 0) { @@ -376,7 +183,7 @@ export class ProfileIO { * @param {string} filePath * @param {string} type * @returns {IProfile} - * @memberof ProfileIO + * @memberof V1ProfileConversion */ public static readProfileFile(filePath: string, type: string): IProfile { @@ -693,6 +500,6 @@ export class ProfileIO { }; } - throw new Error("Profile IO Mock did NOT have a profile for: " + filePath); + throw new Error("V1ProfileConversion Mock did NOT have a profile for: " + filePath); } } diff --git a/packages/imperative/src/profiles/src/utils/__tests__/ProfileIO.unit.test.ts b/packages/imperative/src/profiles/src/utils/__tests__/ProfileIO.unit.test.ts deleted file mode 100644 index e434c42096..0000000000 --- a/packages/imperative/src/profiles/src/utils/__tests__/ProfileIO.unit.test.ts +++ /dev/null @@ -1,604 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -import Mock = jest.Mock; - -jest.mock("fs"); -jest.mock("../../../../io/src/IO"); -jest.mock("js-yaml"); -jest.mock("yamljs"); -jest.mock("../../../../utilities/src/ImperativeConfig"); - -import * as fs from "fs"; -import { IO } from "../../../../io/src/IO"; -import { ProfileIO } from "../ProfileIO"; -import { ImperativeError } from "../../../../error/index"; -import { - BANANA_PROFILE_TYPE, - BLUEBERRY_PROFILE_TYPE, - BLUEBERRY_TYPE_SCHEMA, - STRAWBERRY_PROFILE_TYPE -} from "../../../../cmd/__tests__/profiles/TestConstants"; -import { IMetaProfile, IProfile } from "../../../../index"; -import { IProfileTypeConfiguration } from "../../doc/config/IProfileTypeConfiguration"; -import { ImperativeConfig } from "../../../../utilities"; - -const readYaml = require("js-yaml"); -const writeYaml = require("yamljs"); - -const mocks = { - createDirsSync: jest.spyOn(IO, "createDirsSync"), - safeLoad: jest.spyOn(readYaml, "load"), - writeFileSync: jest.spyOn(fs, "writeFileSync"), - yamlStringify: jest.spyOn(writeYaml, "stringify"), - unlinkSync: jest.spyOn(fs, "unlinkSync"), - existsSync: jest.spyOn(fs, "existsSync"), - readdirSync: jest.spyOn(fs, "readdirSync"), - readFileSync: jest.spyOn(fs, "readFileSync"), - statSync: jest.spyOn(fs, "statSync") -}; - -const TEST_DIR_PATH: string = "/__tests__/__results__/data/.testHomeDir"; -const err: string = "IO ERROR!"; - -describe("Profile IO", () => { - beforeEach(() => { - // Mocks need cleared after every test for clean test runs - jest.resetAllMocks(); - }); - - it("should be able to create all profile directories", () => { - mocks.createDirsSync.mockImplementation(((args: any) => { - return; - }) as any); - ProfileIO.createProfileDirs(TEST_DIR_PATH); - expect(mocks.createDirsSync).toHaveBeenCalledWith(TEST_DIR_PATH); - }); - - it("should throw an Imperative Error if an IO error occurs when creating profile directories", () => { - mocks.createDirsSync.mockImplementation((args: any) => { - throw new Error(err); - }); - let error; - try { - ProfileIO.createProfileDirs(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(mocks.createDirsSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: An error occurred creating profile directory:"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should be able to read the meta file", () => { - const meta = { - defaultProfile: [{ - name: "sweet_blueberry", - type: BLUEBERRY_PROFILE_TYPE - }], - configuration: { - type: BLUEBERRY_PROFILE_TYPE, - schema: BLUEBERRY_TYPE_SCHEMA - } - }; - - mocks.safeLoad.mockImplementation((args: any) => { - return meta; - }); - - const readMeta = ProfileIO.readMetaFile(TEST_DIR_PATH); - expect(readMeta).toBeDefined(); - expect(readMeta).toMatchSnapshot(); - }); - - it("should throw an imperative error if an error occurs reading the meta file", () => { - const meta = { - defaultProfile: [{ - name: "sweet_blueberry", - type: BLUEBERRY_PROFILE_TYPE - }], - configuration: { - type: BLUEBERRY_PROFILE_TYPE, - schema: BLUEBERRY_TYPE_SCHEMA - } - }; - - mocks.safeLoad.mockImplementation((args: any) => { - throw new Error(err); - }); - - let error; - try { - const readMeta = ProfileIO.readMetaFile(TEST_DIR_PATH); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: Error reading profile file"); - }); - - it("should be able to write a profile", () => { - const prof: IProfile = { - name: "strawberries", - type: "strawberry", - amount: 1000 - }; - mocks.yamlStringify.mockImplementation((args: any) => { - return prof; - }); - let written; - mocks.writeFileSync.mockImplementation(((fullFilePath: string, profile: IProfile) => { - written = profile; - return; - }) as any); - ProfileIO.writeProfile(TEST_DIR_PATH, prof); - expect(written).toBeDefined(); - expect(written).toEqual(prof); - }); - - it("should throw an imperative error if a write profile IO error occurs", () => { - const prof: IProfile = { - name: "strawberries", - type: "strawberry", - amount: 1000 - }; - mocks.yamlStringify.mockImplementation((args: any) => { - return prof; - }); - mocks.writeFileSync.mockImplementation(((fullFilePath: string, profile: IProfile) => { - throw new Error(err); - })as never); - let error; - try { - ProfileIO.writeProfile(TEST_DIR_PATH, prof); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: Error creating profile file"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should allow a delete of a profile", () => { - mocks.unlinkSync.mockImplementation(((args: any): any => { - return; - }) as any); - mocks.existsSync.mockImplementation((args: any): any => { - return undefined; - }); - const profname: string = "bad_apple"; - const fullPath: string = TEST_DIR_PATH + "/" + profname + ".yaml"; - ProfileIO.deleteProfile("bad_apple", fullPath); - expect(mocks.unlinkSync).toHaveBeenCalledWith(fullPath); - expect(mocks.existsSync).toHaveBeenCalledWith(fullPath); - }); - - it("should throw an imperative error if the file is not deleted", () => { - const profname: string = "bad_apple"; - const fullPath: string = TEST_DIR_PATH + "/" + profname + ".yaml"; - mocks.unlinkSync.mockImplementation(((args: any) => { - return; - }) as any); - mocks.existsSync.mockImplementation(((args: any) => { - return fullPath; - }) as any); - let error; - try { - ProfileIO.deleteProfile("bad_apple", fullPath); - } catch (e) { - error = e; - } - expect(mocks.unlinkSync).toHaveBeenCalledWith(fullPath); - expect(mocks.existsSync).toHaveBeenCalledWith(fullPath); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: The profile bad_apple was unable to be deleted. Please check " + - "the path indicated here and try to remove the profile manually:"); - }); - - it("should throw an imperative error if an IO error occurs during a delete", () => { - const profname: string = "bad_apple"; - const fullPath: string = TEST_DIR_PATH + "/" + profname + ".yaml"; - mocks.unlinkSync.mockImplementation((args: any) => { - throw new Error(err); - }); - mocks.existsSync.mockImplementation(((args: any) => { - return fullPath; - }) as any); - let error; - try { - ProfileIO.deleteProfile("bad_apple", fullPath); - } catch (e) { - error = e; - } - expect(mocks.unlinkSync).toHaveBeenCalledWith(fullPath); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: An unexpected profile delete error occurred for profile"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should allow us to check if a profile exists", () => { - mocks.existsSync.mockImplementation((args: any): any => { - return undefined; - }); - const profname: string = "bad_apple"; - const fullPath: string = TEST_DIR_PATH + "/" + profname + ".yaml"; - const path = ProfileIO.exists(fullPath); - expect(path).toBeUndefined(); - expect(mocks.existsSync).toHaveBeenCalledWith(fullPath); - }); - - it("should throw an imperative error if an exists IO error occurs", () => { - mocks.existsSync.mockImplementation((args: any) => { - throw new Error(err); - }); - const profname: string = "bad_apple"; - const fullPath: string = TEST_DIR_PATH + "/" + profname + ".yaml"; - let error; - try { - const path = ProfileIO.exists(fullPath); - } catch (e) { - error = e; - } - expect(mocks.existsSync).toHaveBeenCalledWith(fullPath); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Profile IO Error: An error occurred checking for the existance of"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should be able to write a meta file", () => { - const meta: IMetaProfile = { - defaultProfile: "sweet_blueberry", - configuration: { - type: BLUEBERRY_PROFILE_TYPE, - schema: BLUEBERRY_TYPE_SCHEMA - } - }; - mocks.yamlStringify.mockImplementation((args: any) => { - return meta; - }); - let written; - mocks.writeFileSync.mockImplementation(((fullFilePath: string, contents: string, args: any) => { - written = contents; - return; - }) as any); - const metaPath = TEST_DIR_PATH + "/" + BLUEBERRY_PROFILE_TYPE + "_meta.yaml"; - const writeMeta = ProfileIO.writeMetaFile(meta, metaPath); - expect(mocks.writeFileSync).toHaveBeenCalledWith(metaPath, meta, {encoding: "utf8"}); - expect(written).toBeDefined(); - expect(written).toEqual(meta); - }); - - it("should throw an imperative error if an IO error occurrs during writing the meta file", () => { - const meta: IMetaProfile = { - defaultProfile: "sweet_blueberry", - configuration: { - type: BLUEBERRY_PROFILE_TYPE, - schema: BLUEBERRY_TYPE_SCHEMA - } - }; - mocks.yamlStringify.mockImplementation((args: any) => { - return meta; - }); - mocks.writeFileSync.mockImplementation((fullFilePath: string, contents: string, args: any) => { - throw new Error(err); - }); - const metaPath = TEST_DIR_PATH + "/" + BLUEBERRY_PROFILE_TYPE + "_meta.yaml"; - let error; - try { - ProfileIO.writeMetaFile(meta, metaPath); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(mocks.writeFileSync).toHaveBeenCalledWith(metaPath, meta, {encoding: "utf8"}); - expect(error.message).toContain("Profile IO Error: An error occurred converting and writing the meta profile to"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should be able to return the profile name from a file name", () => { - const path = TEST_DIR_PATH + "/" + BLUEBERRY_PROFILE_TYPE + ".yaml"; - const name: string = ProfileIO.fileToProfileName(path as any, ".yaml"); - expect(name).toBe(BLUEBERRY_PROFILE_TYPE); - }); - - it("should return a list of profile types", () => { - const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; - mocks.readdirSync.mockImplementationOnce(((path: any) => { - return types; - }) as any); - mocks.statSync.mockImplementation(((filePath: string) => { - return { - isDirectory: jest.fn(() => { - return true; - }), - }; - }) as any); - const returnedTypes: string[] = ProfileIO.getAllProfileDirectories(TEST_DIR_PATH); - expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(returnedTypes).toEqual(types); - }); - - it("should return a list of profile types but filter out non directory entries", () => { - const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; - mocks.readdirSync.mockImplementationOnce(((path: any) => { - return types; - }) as any); - mocks.statSync.mockImplementation(((filePath: string) => { - return { - isDirectory: jest.fn(() => { - // pretend "banana" is not a directory - return !filePath.includes(BANANA_PROFILE_TYPE); - }), - }; - }) as any); - const returnedTypes: string[] = ProfileIO.getAllProfileDirectories(TEST_DIR_PATH); - expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(returnedTypes).toEqual(types.filter((type) => { - // results shouldn't contain banana - return type !== BANANA_PROFILE_TYPE; - })); - }); - - it("should throw an imperative error if the read directory IO error occurs", () => { - const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; - mocks.readdirSync.mockImplementation((path: any) => { - throw new Error(err); - }); - let error; - try { - const returnedTypes: string[] = ProfileIO.getAllProfileDirectories(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("An error occurred attempting to read all profile directories from"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should return a list of profile names", () => { - const fileNames: string[] = ["rotten.yaml", "fresh.yaml", "apple_meta.yaml"]; - const names: string[] = ["rotten", "fresh"]; - mocks.readdirSync.mockImplementation(((path: any) => { - return fileNames; - }) as any); - const returnedTypes: string[] = ProfileIO.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); - expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(returnedTypes).toEqual(names); - }); - - it("should throw an imperative error if an IO error occurs getting profile names", () => { - const fileNames: string[] = ["rotten.yaml", "fresh.yaml", "apple_meta.yaml"]; - const names: string[] = ["rotten", "fresh"]; - mocks.readdirSync.mockImplementation((path: any) => { - throw new Error(err); - }); - let error; - try { - const returnedTypes: string[] = ProfileIO.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); - } catch (e) { - error = e; - } - expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("An error occurred attempting to read all profile names from"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - - it("should be able to read a profile", () => { - const prof: IProfile = { - name: "strawberries", - type: "strawberry", - amount: 1000 - }; - mocks.safeLoad.mockImplementation((args: any) => { - return prof; - }); - const profile = ProfileIO.readProfileFile(TEST_DIR_PATH, "strawberry"); - expect(profile).toBeDefined(); - expect(profile).toEqual(prof); - }); - - it("should throw an imperative error if a profile IO read error occurs", () => { - const prof: IProfile = { - name: "strawberries", - type: "strawberry", - amount: 1000 - }; - mocks.safeLoad.mockImplementation((args: any) => { - throw new Error(err); - }); - let error; - try { - const profile = ProfileIO.readProfileFile(TEST_DIR_PATH, "strawberry"); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain("Error reading profile file"); - expect(error.message).toContain("Error Details: IO ERROR!"); - }); - describe("Profile operations should crash in team-config mode", () => { - const configModeErr = "Profile IO Error: A Zowe V1 profile operation was attempted with a Zowe team configuration in use."; - - beforeEach(() => { - /* Pretend that we have a team config. - * config is a getter of a property, so mock we the property. - */ - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - get: jest.fn(() => { - return { - exists: true - }; - }) - }); - }); - - afterEach(() => { - // set us back to old-school profile mode - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - get: jest.fn(() => { - return { - exists: false - }; - }) - }); - }); - - it("should crash in createProfileDirs", () => { - let error; - try { - ProfileIO.createProfileDirs(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in readMetaFile", () => { - let error; - try { - ProfileIO.readMetaFile(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in writeProfile", () => { - const prof: IProfile = { - name: "strawberries", - type: "strawberry", - amount: 1000 - }; - - let error; - try { - ProfileIO.writeProfile(TEST_DIR_PATH, prof); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in deleteProfile", () => { - let error; - try { - ProfileIO.deleteProfile("SomeName", TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in exists", () => { - let error; - try { - ProfileIO.exists(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in writeMetaFile", () => { - const meta: IMetaProfile = { - defaultProfile: "sweet_blueberry", - configuration: { - type: BLUEBERRY_PROFILE_TYPE, - schema: BLUEBERRY_TYPE_SCHEMA - } - }; - - let error; - try { - ProfileIO.writeMetaFile(meta, TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in fileToProfileName", () => { - let error; - try { - ProfileIO.fileToProfileName(TEST_DIR_PATH, ".yaml"); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in getAllProfileDirectories", () => { - let error; - try { - ProfileIO.getAllProfileDirectories(TEST_DIR_PATH); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in getAllProfileNames", () => { - let error; - try { - ProfileIO.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - - it("should crash in readProfileFile", () => { - let error; - try { - ProfileIO.readProfileFile(TEST_DIR_PATH, "strawberry"); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error instanceof ImperativeError).toBe(true); - expect(error.message).toContain(configModeErr); - }); - }); -}); diff --git a/packages/imperative/src/profiles/src/utils/__tests__/V1ProfileConversion.unit.test.ts b/packages/imperative/src/profiles/src/utils/__tests__/V1ProfileConversion.unit.test.ts new file mode 100644 index 0000000000..5587d29856 --- /dev/null +++ b/packages/imperative/src/profiles/src/utils/__tests__/V1ProfileConversion.unit.test.ts @@ -0,0 +1,306 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import Mock = jest.Mock; + +jest.mock("fs"); +jest.mock("../../../../io/src/IO"); +jest.mock("js-yaml"); +jest.mock("yamljs"); +jest.mock("../../../../utilities/src/ImperativeConfig"); + +import * as fs from "fs"; +import { IO } from "../../../../io/src/IO"; +import { V1ProfileConversion } from "../V1ProfileConversion"; +import { ImperativeError } from "../../../../error/index"; +import { + BANANA_PROFILE_TYPE, + BLUEBERRY_PROFILE_TYPE, + BLUEBERRY_TYPE_SCHEMA, + STRAWBERRY_PROFILE_TYPE +} from "../../../../cmd/__tests__/profiles/TestConstants"; +import { IMetaProfile, IProfile } from "../../../../index"; +import { IProfileTypeConfiguration } from "../../doc/config/IProfileTypeConfiguration"; +import { ImperativeConfig } from "../../../../utilities"; + +const readYaml = require("js-yaml"); +const writeYaml = require("yamljs"); + +const mocks = { + createDirsSync: jest.spyOn(IO, "createDirsSync"), + safeLoad: jest.spyOn(readYaml, "load"), + writeFileSync: jest.spyOn(fs, "writeFileSync"), + yamlStringify: jest.spyOn(writeYaml, "stringify"), + unlinkSync: jest.spyOn(fs, "unlinkSync"), + existsSync: jest.spyOn(fs, "existsSync"), + readdirSync: jest.spyOn(fs, "readdirSync"), + readFileSync: jest.spyOn(fs, "readFileSync"), + statSync: jest.spyOn(fs, "statSync") +}; + +const TEST_DIR_PATH: string = "/__tests__/__results__/data/.testHomeDir"; +const err: string = "IO ERROR!"; + +describe("V1 Profile Conversion", () => { + beforeEach(() => { + // Mocks need cleared after every test for clean test runs + jest.resetAllMocks(); + }); + + it("should be able to read the meta file", () => { + const meta = { + defaultProfile: [{ + name: "sweet_blueberry", + type: BLUEBERRY_PROFILE_TYPE + }], + configuration: { + type: BLUEBERRY_PROFILE_TYPE, + schema: BLUEBERRY_TYPE_SCHEMA + } + }; + + mocks.safeLoad.mockImplementation((args: any) => { + return meta; + }); + + const readMeta = V1ProfileConversion.readMetaFile(TEST_DIR_PATH); + expect(readMeta).toBeDefined(); + expect(readMeta).toMatchSnapshot(); + }); + + it("should throw an imperative error if an error occurs reading the meta file", () => { + const meta = { + defaultProfile: [{ + name: "sweet_blueberry", + type: BLUEBERRY_PROFILE_TYPE + }], + configuration: { + type: BLUEBERRY_PROFILE_TYPE, + schema: BLUEBERRY_TYPE_SCHEMA + } + }; + + mocks.safeLoad.mockImplementation((args: any) => { + throw new Error(err); + }); + + let error; + try { + const readMeta = V1ProfileConversion.readMetaFile(TEST_DIR_PATH); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain("V1ProfileConversion Read Error: Error reading profile file"); + }); + + it("should return a list of profile types", () => { + const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; + mocks.readdirSync.mockImplementationOnce(((path: any) => { + return types; + }) as any); + mocks.statSync.mockImplementation(((filePath: string) => { + return { + isDirectory: jest.fn(() => { + return true; + }), + }; + }) as any); + const returnedTypes: string[] = V1ProfileConversion.getAllProfileDirectories(TEST_DIR_PATH); + expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); + expect(returnedTypes).toEqual(types); + }); + + it("should return a list of profile types but filter out non directory entries", () => { + const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; + mocks.readdirSync.mockImplementationOnce(((path: any) => { + return types; + }) as any); + mocks.statSync.mockImplementation(((filePath: string) => { + return { + isDirectory: jest.fn(() => { + // pretend "banana" is not a directory + return !filePath.includes(BANANA_PROFILE_TYPE); + }), + }; + }) as any); + const returnedTypes: string[] = V1ProfileConversion.getAllProfileDirectories(TEST_DIR_PATH); + expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); + expect(returnedTypes).toEqual(types.filter((type) => { + // results shouldn't contain banana + return type !== BANANA_PROFILE_TYPE; + })); + }); + + it("should throw an imperative error if the read directory IO error occurs", () => { + const types: string[] = [BLUEBERRY_PROFILE_TYPE, STRAWBERRY_PROFILE_TYPE, BANANA_PROFILE_TYPE]; + mocks.readdirSync.mockImplementation((path: any) => { + throw new Error(err); + }); + let error; + try { + const returnedTypes: string[] = V1ProfileConversion.getAllProfileDirectories(TEST_DIR_PATH); + } catch (e) { + error = e; + } + expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain("An error occurred attempting to read all profile directories from"); + expect(error.message).toContain("Error Details: IO ERROR!"); + }); + + it("should return a list of profile names", () => { + const fileNames: string[] = ["rotten.yaml", "fresh.yaml", "apple_meta.yaml"]; + const names: string[] = ["rotten", "fresh"]; + mocks.readdirSync.mockImplementation(((path: any) => { + return fileNames; + }) as any); + const returnedTypes: string[] = V1ProfileConversion.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); + expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); + expect(returnedTypes).toEqual(names); + }); + + it("should throw an imperative error if an IO error occurs getting profile names", () => { + const fileNames: string[] = ["rotten.yaml", "fresh.yaml", "apple_meta.yaml"]; + const names: string[] = ["rotten", "fresh"]; + mocks.readdirSync.mockImplementation((path: any) => { + throw new Error(err); + }); + let error; + try { + const returnedTypes: string[] = V1ProfileConversion.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); + } catch (e) { + error = e; + } + expect(mocks.readdirSync).toHaveBeenCalledWith(TEST_DIR_PATH); + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain("An error occurred attempting to read all profile names from"); + expect(error.message).toContain("Error Details: IO ERROR!"); + }); + + it("should be able to read a profile", () => { + const prof: IProfile = { + name: "strawberries", + type: "strawberry", + amount: 1000 + }; + mocks.safeLoad.mockImplementation((args: any) => { + return prof; + }); + const profile = V1ProfileConversion.readProfileFile(TEST_DIR_PATH, "strawberry"); + expect(profile).toBeDefined(); + expect(profile).toEqual(prof); + }); + + it("should throw an imperative error if a V1 Profile Conversion read error occurs", () => { + const prof: IProfile = { + name: "strawberries", + type: "strawberry", + amount: 1000 + }; + mocks.safeLoad.mockImplementation((args: any) => { + throw new Error(err); + }); + let error; + try { + const profile = V1ProfileConversion.readProfileFile(TEST_DIR_PATH, "strawberry"); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain("Error reading profile file"); + expect(error.message).toContain("Error Details: IO ERROR!"); + }); + + describe("Profile operations should crash in team-config mode", () => { + const configModeErr = "V1ProfileConversion Read Error: " + + "Attempted to convert a Zowe V1 profile when a newer Zowe client configuration already exists."; + + beforeEach(() => { + /* Pretend that we have a team config. + * config is a getter of a property, so mock we the property. + */ + Object.defineProperty(ImperativeConfig.instance, "config", { + configurable: true, + get: jest.fn(() => { + return { + exists: true + }; + }) + }); + }); + + afterEach(() => { + // set us back to old-school profile mode + Object.defineProperty(ImperativeConfig.instance, "config", { + configurable: true, + get: jest.fn(() => { + return { + exists: false + }; + }) + }); + }); + + it("should crash in readMetaFile", () => { + let error; + try { + V1ProfileConversion.readMetaFile(TEST_DIR_PATH); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain(configModeErr); + }); + + it("should crash in getAllProfileDirectories", () => { + let error; + try { + V1ProfileConversion.getAllProfileDirectories(TEST_DIR_PATH); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain(configModeErr); + }); + + it("should crash in getAllProfileNames", () => { + let error; + try { + V1ProfileConversion.getAllProfileNames(TEST_DIR_PATH, ".yaml", "apple_meta"); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain(configModeErr); + }); + + it("should crash in readProfileFile", () => { + let error; + try { + V1ProfileConversion.readProfileFile(TEST_DIR_PATH, "strawberry"); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error instanceof ImperativeError).toBe(true); + expect(error.message).toContain(configModeErr); + }); + }); +}); diff --git a/packages/imperative/src/profiles/src/utils/__tests__/__snapshots__/V1ProfileConversion.unit.test.ts.snap b/packages/imperative/src/profiles/src/utils/__tests__/__snapshots__/V1ProfileConversion.unit.test.ts.snap new file mode 100644 index 0000000000..7b3284aff2 --- /dev/null +++ b/packages/imperative/src/profiles/src/utils/__tests__/__snapshots__/V1ProfileConversion.unit.test.ts.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`V1 Profile Conversion should be able to read the meta file 1`] = ` +Object { + "configuration": Object { + "schema": Object { + "description": "The simple blueberry configuration", + "properties": Object { + "tart": Object { + "type": "boolean", + }, + }, + "required": Array [ + "tart", + ], + "title": "The simple blueberry configuration", + "type": "object", + }, + "type": "blueberry", + }, + "defaultProfile": Array [ + Object { + "name": "sweet_blueberry", + "type": "blueberry", + }, + ], +} +`; diff --git a/packages/imperative/src/profiles/src/utils/index.ts b/packages/imperative/src/profiles/src/utils/index.ts index bb4079679d..d457f7b632 100644 --- a/packages/imperative/src/profiles/src/utils/index.ts +++ b/packages/imperative/src/profiles/src/utils/index.ts @@ -9,5 +9,5 @@ * */ -export * from "./ProfileIO"; +export * from "./V1ProfileConversion"; export * from "./ProfileUtils"; diff --git a/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts b/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts index 278c2c2fea..7867019962 100644 --- a/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts +++ b/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts @@ -367,7 +367,7 @@ export class ConnectionPropsForSessCfg { if (ConfigUtils.onlyV1ProfilesExist) { connOpts.parms.response.console.log( "Only V1 profiles exist. V1 profiles are no longer supported.\n" + - "You should convert your V1 profiles to a Zowe client team configuration." + "You should convert your V1 profiles to a newer Zowe client configuration." ); } connOpts.parms.response.console.log(