From 52293a67195c3a1ef964fa1919c1b22754370cc7 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Thu, 13 Jun 2024 12:04:08 -0400 Subject: [PATCH 01/16] Fix bad comment reference to PluginIssues Signed-off-by: Gene Johnston --- packages/imperative/src/utilities/src/ImperativeConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/imperative/src/utilities/src/ImperativeConfig.ts b/packages/imperative/src/utilities/src/ImperativeConfig.ts index 2b0d4cdbc4..3fa6f1e1f6 100644 --- a/packages/imperative/src/utilities/src/ImperativeConfig.ts +++ b/packages/imperative/src/utilities/src/ImperativeConfig.ts @@ -81,11 +81,11 @@ export class ImperativeConfig { private mConfig: Config; /** - * Gets a single instance of the PluginIssues. On the first call of - * ImperativeConfig.instance, a new Plugin Issues object is initialized and returned. + * Gets a single instance of the ImperativeConfig. On the first call of + * ImperativeConfig.instance, a new ImperativeConfig object is initialized and returned. * Every subsequent call will use the one that was first created. * - * @returns {ImperativeConfig} The newly initialized PMF object. + * @returns {ImperativeConfig} The newly initialized ImperativeConfig object. */ public static get instance(): ImperativeConfig { if (this.mInstance == null) { From a1484cb2f5f6f9b8995e97e3f6fd9f0a0c5569ea Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Tue, 18 Jun 2024 14:33:26 -0400 Subject: [PATCH 02/16] Initialize additional properties within the ConvertV1Profiles class to support Zowe Explorer Signed-off-by: Gene Johnston --- .../src/config/src/ConvertV1Profiles.ts | 419 ++++++++++-------- .../src/config/src/doc/IConvertV1Profiles.ts | 10 +- 2 files changed, 236 insertions(+), 193 deletions(-) diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index 549bd2cf95..07fc51e354 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -11,6 +11,7 @@ import * as fs from "fs"; import * as path from "path"; +import { readFileSync } from "jsonfile"; import { removeSync } from "fs-extra"; import stripAnsi = require("strip-ansi"); import { V1ProfileRead, ProfilesConstants, ProfileUtils } from "../../profiles"; @@ -21,8 +22,8 @@ import { IConvertV1ProfOpts, ConvertMsg, ConvertMsgFmt, IConvertV1ProfResult } f import { IImperativeOverrides } from "../../imperative/src/doc/IImperativeOverrides"; import { keyring } from "@zowe/secrets-for-zowe-sdk"; import { AppSettings } from "../../settings"; +import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; import { ImperativeConfig } from "../../utilities"; -import { PluginIssues } from "../../imperative/src/plugins/utilities/PluginIssues"; import { CredentialManagerOverride } from "../../security/src/CredentialManagerOverride"; import { OverridesLoader } from "../../imperative/src/OverridesLoader"; import { ConfigSchema } from "./ConfigSchema"; @@ -41,6 +42,8 @@ interface IOldPluginInfo { export class ConvertV1Profiles { private static readonly noCfgFilePathNm: string = "CouldNotGetPathToConfigFile"; + private static readonly builtInCredMgrNm: string = "@zowe/cli"; + private static readonly credMgrKey: string = "CredentialManager"; private static convertOpts: IConvertV1ProfOpts = null; private static convertResult: IConvertV1ProfResult = null; @@ -49,14 +52,23 @@ export class ConvertV1Profiles { private static zoweKeyRing: typeof keyring = undefined; /** - * Convert V1 profiles into a current zowe client config. - * Remove old credential manager overrides. - * Uninstall old SCS plugin. - * Delete old V1 profiles if requested. + * Convert V1 profiles into a zowe.config.json file. + * + * It will also do the following: + * Create a zowe.schema.json file. + * Migrate V1 secure properties into the current consolidated Zowe client secure properties. + * Replace old SCS-plugin credential manager override with the Zowe embedded SCS. + * Delete old V1 profiles (and old V1 secure properties) if requested. * * Calling this function after having already converted, will not attempt to * convert again. However it will still delete the old profiles if requested. * + * You should **NOT** initialize the secure credential manager before calling this function. + * The CredMgr can only be initialized once. If the old V1 SCS-plugin happens to be configured + * as the CredMgr when this function is called, the old V1 SCS-plugin CredMgr is unable + * to create the current consolidated Zowe client secure properties. Users will have to + * re-enter all of their credentials. + * * @param convertOpts Options that will control the conversion process. * @returns Result object into which messages and stats are stored. */ @@ -65,7 +77,7 @@ export class ConvertV1Profiles { ConvertV1Profiles.convertResult = { msgs: [], v1ScsPluginName: null, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles.noCfgFilePathNm, numProfilesFound: 0, profilesConverted: {}, @@ -79,23 +91,26 @@ export class ConvertV1Profiles { ConvertV1Profiles.profilesRootDir = ProfileUtils.constructProfilesRootDirectory(ImperativeConfig.instance.cliHome); ConvertV1Profiles.oldProfilesDir = `${ConvertV1Profiles.profilesRootDir.replace(/[\\/]$/, "")}-old`; - if (ConvertV1Profiles.isConversionNeeded()) { + if (await ConvertV1Profiles.isConversionNeeded()) { + ConvertV1Profiles.replaceOldCredMgrOverride(); + await ConvertV1Profiles.initCredMgr(); await ConvertV1Profiles.moveV1ProfilesToConfigFile(); - await ConvertV1Profiles.removeOldOverrides(); + } + + // Report if the old SCS plugin should be uninstalled + if (ConvertV1Profiles.convertResult.v1ScsPluginName != null) { + ConvertV1Profiles.addToConvertMsgs( + ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, + `The obsolete plug-in '${ConvertV1Profiles.convertResult.v1ScsPluginName}' should be removed ` + + `because it is now part of the core Zowe client.` + ); } if (convertOpts.deleteV1Profs){ await ConvertV1Profiles.deleteV1Profiles(); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Encountered the following error while trying to convert V1 profiles:" - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs("Encountered the following error while trying to convert V1 profiles:", error); } return ConvertV1Profiles.convertResult; @@ -105,8 +120,19 @@ export class ConvertV1Profiles { * Detect whether we must convert any V1 profiles to a zowe client configuration. * @returns True means we must do a conversion. False otherwise. */ - private static isConversionNeeded(): boolean { + private static async isConversionNeeded(): Promise { ConvertV1Profiles.convertResult.numProfilesFound = 0; + + if (ImperativeConfig.instance.config == null) { + // Initialization for VSCode extensions does not create the config property, so create it now. + ImperativeConfig.instance.config = await Config.load( + ImperativeConfig.instance.loadedConfig.name, + { + homeDir: ImperativeConfig.instance.loadedConfig.defaultHome + } + ); + } + if (ImperativeConfig.instance.config?.exists) { // We do not convert if we already have an existing zowe client config ConvertV1Profiles.putCfgFileNmInResult(ImperativeConfig.instance.config); @@ -125,21 +151,14 @@ export class ConvertV1Profiles { if (ConvertV1Profiles.convertResult.numProfilesFound === 0) { ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE, noProfilesMsg); } - } catch (caughtErr) { - ConvertV1Profiles.convertResult.numProfilesFound = 0; - + } catch (error) { // did the profiles directory not exist? - if (caughtErr?.additionalDetails?.code === "ENOENT") { + if (error?.additionalDetails?.code === "ENOENT") { ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE, noProfilesMsg); } else { // must have been some sort of I/O error - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to get V1 profiles in "${ConvertV1Profiles.profilesRootDir}".` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(caughtErr.message) + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to get V1 profiles in "${ConvertV1Profiles.profilesRootDir}".`, error ); } } @@ -147,6 +166,88 @@ export class ConvertV1Profiles { return (ConvertV1Profiles.convertResult.numProfilesFound > 0); } + /** + * Replace any detected oldCredMgr override entry in settings.json with the Zowe embedded credMgr. + * + * After the replacement of the credential manager override, we can then initialize + * credential manager later in this class. + */ + private static replaceOldCredMgrOverride(): void { + const oldPluginInfo = ConvertV1Profiles.getOldPluginInfo(); + for (const override of oldPluginInfo.overrides) { + if (override === ConvertV1Profiles.credMgrKey) { + try { + AppSettings.instance.set("overrides", ConvertV1Profiles.credMgrKey, CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); + if (ImperativeConfig.instance.loadedConfig.overrides?.CredentialManager != null) { + delete ImperativeConfig.instance.loadedConfig.overrides.CredentialManager; + } + } catch (error) { + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to replace credential manager override setting.", error); + } + } + } + + /* We only had one override in V1 - the old SCS plugin. + * So, despite the array for multiple plugins, we just report + * the first plugin name in the array as the plugin that our + * caller should uninstall. + */ + ConvertV1Profiles.convertResult.v1ScsPluginName = + (oldPluginInfo.plugins.length > 0) ? oldPluginInfo.plugins[0] : null; + } + + /** + * Initialize credential manager so that we can migrate the secure properties that are + * stored for V1 profiles to new secure properties for the converted config that we will create. + * + * For all CLI commands other than convert-profiles, the credential manager is loaded in + * Imperative.init and frozen with Object.freeze so it cannot be modified later on. + * Because convert-profiles cannot create new secure properties for the converted config + * (if the old SCS plugin credMgr is already loaded), Imperative.init does not load the + * credential manager for the convert-profiles command. + * + * VSCode extensions must also avoid initializing the Credential Manager before calling + * ConvertV1Profiles.convert. + * + * If we encounter an error when trying to initialize the credential manager, we report (through + * ConvertV1Profiles.convertResult.credsWereMigrated) that creds were not migrated. + */ + private static async initCredMgr(): Promise { + if (!CredentialManagerFactory.initialized) { + // we must initialize credMgr to get and store credentials + try { + if (Object.hasOwn(ImperativeConfig.instance, "callerPackageJson")) { + // Since callerPackageJson exists, we know that we are in the convert-profiles command. + // We now initialize CredMgr like all other CLI commands. + await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, ImperativeConfig.instance.callerPackageJson); + } else { + // Since callerPackageJson is not set in ImperativeConfig, we are in a VSCode extension. + // Initialize CredMgr with default values. + await CredentialManagerFactory.initialize({ + service: null, + Manager: null, + displayName: null, + invalidOnFailure: null + }); + + // Load CredMgr with some initialization properties that we create. + // OverridesLoader crashes unless the overrides property exists. + // The only thing that OverridesLoader wants from package.json is the name. + if (!ImperativeConfig.instance.loadedConfig?.overrides?.CredentialManager) { + ImperativeConfig.instance.loadedConfig.overrides = {}; + } + const callerPackageJson: any = { + name: ConvertV1Profiles.builtInCredMgrNm, + }; + await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, callerPackageJson); + } + } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to initialize CredentialManager", error); + } + } + } + /** * Move the contents of existing v1 profiles to a zowe client config file. * @@ -155,12 +256,6 @@ export class ConvertV1Profiles { private static async moveV1ProfilesToConfigFile(): Promise { const convertedConfig: IConfig = Config.empty(); - /* Only the convert-profiles command is able to disable the credential manager - * and reload it. For all other commands, the credential manager is loaded in - * `Imperative.init` and frozen with `Object.freeze` so cannot be modified later on. - */ - await OverridesLoader.ensureCredentialManagerLoaded(); - for (const profileType of V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir)) { const profileTypeDir = path.join(ConvertV1Profiles.profilesRootDir, profileType); const profileNames = V1ProfileRead.getAllProfileNames(profileTypeDir, ".yaml", `${profileType}_meta`); @@ -197,15 +292,9 @@ export class ConvertV1Profiles { ...(ConvertV1Profiles.convertResult.profilesConverted[profileType] || []), profileName ]; } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.convertResult.profilesFailed.push({ name: profileName, type: profileType, error }); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to read "${profileType}" profile named "${profileName}"` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to read "${profileType}" profile named "${profileName}"`, error); } } @@ -217,14 +306,7 @@ export class ConvertV1Profiles { } } catch (error) { ConvertV1Profiles.convertResult.profilesFailed.push({ type: profileType, error }); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to find default "${profileType}" profile.` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to find default "${profileType}" profile.`, error); } } @@ -262,6 +344,7 @@ export class ConvertV1Profiles { const newConfig = ImperativeConfig.instance.config; newConfig.api.layers.activate(false, true); newConfig.api.layers.merge(convertedConfig); + ConvertV1Profiles.loadV1Schemas(); ConfigSchema.updateSchema(); await newConfig.save(); ConvertV1Profiles.putCfgFileNmInResult(newConfig); @@ -278,13 +361,8 @@ export class ConvertV1Profiles { ); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to rename profiles directory to ${ConvertV1Profiles.oldProfilesDir}:` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to rename profiles directory to ${ConvertV1Profiles.oldProfilesDir}:`, error ); } @@ -295,6 +373,32 @@ export class ConvertV1Profiles { ); } + /** + * Load V1 profile schemas, which will not have been loaded for VSCode extensions. + */ + private static loadV1Schemas(): void { + if (!Object.hasOwn(ImperativeConfig.instance.loadedConfig, "profiles")) { + // since no schemas are loaded, we read them from the V1 profiles directory + ImperativeConfig.instance.loadedConfig.profiles = []; + const v1ProfileTypes = fs.existsSync(ConvertV1Profiles.profilesRootDir) ? + V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir) : []; + + for (const profType of v1ProfileTypes) { + const schemaFileNm = path.join(ConvertV1Profiles.profilesRootDir, profType, profType + "_meta.yaml"); + if (fs.existsSync(schemaFileNm)) { + try { + const schemaContent = V1ProfileRead.readMetaFile(schemaFileNm); + ImperativeConfig.instance.loadedConfig.profiles.push(schemaContent.configuration); + } catch (error) { + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to load schema for profile type ${profType} from file "${schemaFileNm}"`, error + ); + } + } + } + } + } + /** * Put the path name to the config file, obtained from the supplied Config object, * into our result object. @@ -305,14 +409,7 @@ export class ConvertV1Profiles { try { ConvertV1Profiles.convertResult.cfgFilePathNm = configForPath?.layerActive().path; } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - "Failed to retrieve the path to the config file." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message - ); + ConvertV1Profiles.addExceptionToConvertMsgs("Failed to retrieve the path to the config file.", error); } if (!ConvertV1Profiles.convertResult.cfgFilePathNm) { ConvertV1Profiles.convertResult.cfgFilePathNm = ConvertV1Profiles.noCfgFilePathNm; @@ -339,20 +436,15 @@ export class ConvertV1Profiles { ); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to delete the profiles directory '${ConvertV1Profiles.oldProfilesDir}'` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to delete the profiles directory '${ConvertV1Profiles.oldProfilesDir}'`, error ); } // Delete the securely stored credentials const isZoweKeyRingAvailable = await ConvertV1Profiles.checkZoweKeyRingAvailable(); if (isZoweKeyRingAvailable) { - const knownServices = ["@brightside/core", "@zowe/cli", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; + const knownServices = [ConvertV1Profiles.builtInCredMgrNm, "@brightside/core", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; for (const service of knownServices) { const accounts = await ConvertV1Profiles.findOldSecureProps(service); for (const account of accounts) { @@ -381,85 +473,6 @@ export class ConvertV1Profiles { } } - /** - * Remove any old credential manager overrides. - */ - private static async removeOldOverrides(): Promise { - /* Replace any detected oldCredMgr override entry in settings.json with the Zowe embedded credMgr. - * Only the convert-profiles command is able to disable the credential manager - * and reload it. For all other commands, the credential manager is loaded in - * `Imperative.init` and frozen with `Object.freeze` so cannot be modified later on. - * - * Unlike a CLI command (which gets re-initialized on the next command), long-running apps - * must re-initialize the credential manager with a call to CredentialManagerFactory.initialize. - * That initialize function can only be called once within a running process. - * ConvertV1Profiles.convertResult.reInitCredMgr will be set to true to tell our calling app - * that the app must be restarted. - */ - const oldPluginInfo = ConvertV1Profiles.getOldPluginInfo(); - for (const override of oldPluginInfo.overrides) { - if (override === "CredentialManager") { - try { - AppSettings.instance.set("overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); - if (ImperativeConfig.instance.loadedConfig.overrides.CredentialManager != null) { - delete ImperativeConfig.instance.loadedConfig.overrides.CredentialManager; - } - if (CredentialManagerFactory.initialized ) { - // We cannot re-initialize CredMgr, so let our caller know. - ConvertV1Profiles.convertResult.reInitCredMgr = true; - } else { - /* We can initialize the CredMgr. - * At this point, we have a new config file. Load that new config, so that - * ImperativeConfig.instance.config.exists is true when we call OverridesLoader. - */ - ImperativeConfig.instance.config = await Config.load(ImperativeConfig.instance.rootCommandName, - { homeDir: ImperativeConfig.instance.cliHome } - ); - - // Load the overrides that we just set. That will re-initialize the CredMgr. - await OverridesLoader.load( - ImperativeConfig.instance.loadedConfig, - ImperativeConfig.instance.callerPackageJson - ); - } - } catch (error) { - ConvertV1Profiles.convertResult.reInitCredMgr = true; - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Failed to replace credential manager override setting." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); - } - } - } - - // Report any plugin that we will uninstall - if (oldPluginInfo.plugins.length > 0) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - "The following plug-ins will be removed because they are now part of the core CLI and are no longer needed:" - ); - - for (const nextPlugin of oldPluginInfo.plugins) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.INDENT, - nextPlugin - ); - } - } - - /* We only had one override in V1 - the old SCS plugin. - * So, despite the array for multiple plugins, we just report - * the first plugin name in the array as the plugin that our - * caller should uninstall. - */ - ConvertV1Profiles.convertResult.v1ScsPluginName = - (oldPluginInfo.plugins.length > 0) ? oldPluginInfo.plugins[0] : null; - } - /** * Convert a set of known property names to their new names * for V2 conformance (and later releases). @@ -536,20 +549,32 @@ export class ConvertV1Profiles { }; // if the old SCS plugin is our credential manager, record that it should be replaced - const credMgrKey = "CredentialManager"; let currCredMgr; try { - currCredMgr = AppSettings.instance.get("overrides", credMgrKey); + // have AppSettings been initialized? + AppSettings.instance; + } catch (error) { + let settingsFile: string = "NotSetYet"; + try { + // A VSCode extension will not have initialized AppSettings, so initialize it now + settingsFile = path.join(ImperativeConfig.instance.cliHome, "settings", "imperative.json"); + const defaultSettings: ISettingsFile = { + overrides: {} + } as any; + defaultSettings.overrides[ConvertV1Profiles.credMgrKey] = ConvertV1Profiles.builtInCredMgrNm; + AppSettings.initialize(settingsFile, defaultSettings); + } catch(error) { + currCredMgr = null; + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to initialize AppSettings overrides from '${settingsFile}'.`, error); + } + } + + // get the current credMgr from AppSettings + try { + currCredMgr = AppSettings.instance.get("overrides", ConvertV1Profiles.credMgrKey); } catch(error) { currCredMgr = null; - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed trying to read '${credMgrKey}' overrides.` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - stripAnsi(error.message) - ); + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed trying to read '${ConvertV1Profiles.credMgrKey}' overrides.`, error); } // we leave the 'false' indicator unchanged to allow for the use of no credMgr @@ -557,7 +582,7 @@ export class ConvertV1Profiles { // if any of the old SCS credMgr names are found, record that we want to replace the credMgr for (const oldOverrideName of [oldScsPluginNm, "KeytarCredentialManager", "Zowe-Plugin", "Broadcom-Plugin"]) { if (currCredMgr.includes(oldOverrideName)) { - pluginInfo.overrides.push(credMgrKey); + pluginInfo.overrides.push(ConvertV1Profiles.credMgrKey); break; } } @@ -565,26 +590,38 @@ export class ConvertV1Profiles { try { // Only record the need to uninstall the SCS plug-in if it is currently installed - if (oldScsPluginNm in PluginIssues.instance.getInstalledPlugins()) { + if ( ConvertV1Profiles.isPluginInstalled(oldScsPluginNm)) { pluginInfo.plugins.push(oldScsPluginNm); } - } catch (caughtErr) { + } catch (error) { // report all errors except the absence of the plugins.json file - if (!caughtErr.message.includes("ENOENT")) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Failed trying to get the set of installed plugins." - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - caughtErr.message - ); + if (!error.message.includes("ENOENT")) { + ConvertV1Profiles.addExceptionToConvertMsgs("Failed trying to get the set of installed plugins.", error); } } return pluginInfo; } + /** + * Report whether the specified plugin is installed. + * @param pluginName name of the plugin to search for. + * @returns True if plugin is installed. False otherwise. + */ + private static isPluginInstalled(pluginName: string): boolean { + const pluginsFileNm = path.join(ImperativeConfig.instance.cliHome, "plugins", "plugins.json"); + try { + const pluginsFileJson = readFileSync(pluginsFileNm); + if (Object.hasOwn(pluginsFileJson, pluginName)) { + return true; + } + } + catch (ioErr) { + ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file '${pluginsFileNm}'`, ioErr); + } + return false; + } + /** * Get the number of old profiles present in the CLI home dir. * @param profilesRootDir Root profiles directory @@ -632,13 +669,9 @@ export class ConvertV1Profiles { oldSecurePropNames.push(element.account); } } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - `Encountered an error while gathering secure properties for service '${acct}':` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.convertResult.credsWereMigrated = false; + ConvertV1Profiles.addExceptionToConvertMsgs( + `Encountered an error while gathering secure properties for service '${acct}':`, error ); } return oldSecurePropNames; @@ -656,19 +689,28 @@ export class ConvertV1Profiles { try { success = await ConvertV1Profiles.zoweKeyRing.deletePassword(acct, propName); } catch (error) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE, - `Encountered an error while deleting secure data for service '${acct}/${propName}':` - ); - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, - error.message + ConvertV1Profiles.addExceptionToConvertMsgs( + `Encountered an error while deleting secure data for service '${acct}/${propName}':`, error ); success = false; } return success; } + /** + * Add a new message to the V1 profile conversion messages that reports a caught exception. + * + * @param introMsg An introductory message describing what action was being attempted when we failed. + * @param error The exception that we caught. + */ + private static addExceptionToConvertMsgs(introMsg: string, error: Error): void { + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, introMsg); + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, "Reason: " + stripAnsi(error.message)); + if (Object.hasOwn(error, "stack")) { + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.INDENT, stripAnsi(error.stack)); + } + } + /** * Add a new message to the V1 profile conversion messages. * @param msgFormat Formatting clues for the message. @@ -680,6 +722,5 @@ export class ConvertV1Profiles { } const newMsg = new ConvertMsg(msgFormat, msgText); ConvertV1Profiles.convertResult.msgs.push(newMsg); - } } diff --git a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts index 2d36403d4d..ec30b70dee 100644 --- a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts @@ -64,12 +64,14 @@ export interface IConvertV1ProfResult { v1ScsPluginName: string | null; /** + * This property indicates whether secure credentials were migrated during conversion. + * * If the old V1 Secure Credential Store plugin was supplying the credential manager - * override, that CredMgr has been replaced with the Zowe CLI built-in credential manager. - * This property indicates whether the caller must re-initialize the credential Manager - * by restarting its app. + * override and the CredentialManager was initialized before calling this function, + * profile conversion will not be able to migrate credentials from the old SCS plugin + * to the current embedded Secure Credential Store. */ - reInitCredMgr: boolean; + credsWereMigrated: boolean; /** * The following properties contain information about the success or failure of From cb217de580c34e38edb2597b42dae7103f07df70 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Thu, 20 Jun 2024 08:55:06 -0400 Subject: [PATCH 03/16] Start a new paragraph for uninstall messages Signed-off-by: Gene Johnston --- .../config/cmd/convert-profiles/convert-profiles.handler.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 21cd8c8777..24f3d8898b 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 @@ -61,12 +61,14 @@ export default class ConvertProfilesHandler implements ICommandHandler { try { uninstallPlugin(convertResult.v1ScsPluginName); const newMsg = new ConvertMsg( - ConvertMsgFmt.REPORT_LINE, `Uninstalled plug-in "${convertResult.v1ScsPluginName}"` + ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, + `Uninstalled plug-in "${convertResult.v1ScsPluginName}"` ); convertResult.msgs.push(newMsg); } catch (error) { let newMsg = new ConvertMsg( - ConvertMsgFmt.ERROR_LINE, `Failed to uninstall plug-in "${convertResult.v1ScsPluginName}"` + ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, + `Failed to uninstall plug-in "${convertResult.v1ScsPluginName}"` ); convertResult.msgs.push(newMsg); From 3ad80a0d53c0de05085834d9568ef4747de2012d Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Thu, 20 Jun 2024 15:17:43 -0400 Subject: [PATCH 04/16] Log error if using old SCS plugin. Improve output messages. Signed-off-by: Gene Johnston --- .../src/config/src/ConvertV1Profiles.ts | 132 +++++++++++------- .../convert-profiles.handler.ts | 4 +- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index 07fc51e354..624ca57fe0 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -42,9 +42,12 @@ interface IOldPluginInfo { export class ConvertV1Profiles { private static readonly noCfgFilePathNm: string = "CouldNotGetPathToConfigFile"; - private static readonly builtInCredMgrNm: string = "@zowe/cli"; private static readonly credMgrKey: string = "CredentialManager"; + private static readonly oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; + private static readonly builtInCredMgrNm: string = "@zowe/cli"; + private static callerIsExternalApp: boolean = false; + private static oldScsPluginWasConfigured: boolean = false; private static convertOpts: IConvertV1ProfOpts = null; private static convertResult: IConvertV1ProfResult = null; private static profilesRootDir: string = "NotYetSet"; @@ -73,6 +76,8 @@ export class ConvertV1Profiles { * @returns Result object into which messages and stats are stored. */ public static async convert(convertOpts: IConvertV1ProfOpts): Promise { + ConvertV1Profiles.callerIsExternalApp = false; + // initialize our result, which will be used by our utility functions, and returned by us ConvertV1Profiles.convertResult = { msgs: [], @@ -97,17 +102,24 @@ export class ConvertV1Profiles { await ConvertV1Profiles.moveV1ProfilesToConfigFile(); } + if (convertOpts.deleteV1Profs) { + await ConvertV1Profiles.deleteV1Profiles(); + } + // Report if the old SCS plugin should be uninstalled if (ConvertV1Profiles.convertResult.v1ScsPluginName != null) { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `The obsolete plug-in '${ConvertV1Profiles.convertResult.v1ScsPluginName}' should be removed ` + - `because it is now part of the core Zowe client.` - ); - } + let verb = "will"; + if (ConvertV1Profiles.callerIsExternalApp) { + verb = "should"; + } + let uninstallMsg: string = `The obsolete plug-in ${ConvertV1Profiles.convertResult.v1ScsPluginName} ` + + `${verb} be uninstalled because the SCS is now embedded within the Zowe clients.`; - if (convertOpts.deleteV1Profs){ - await ConvertV1Profiles.deleteV1Profiles(); + if (ConvertV1Profiles.callerIsExternalApp) { + uninstallMsg += ` Zowe CLI plugins can only be uninstalled by the CLI. Use the command ` + + `'zowe plugins uninstall ${ConvertV1Profiles.convertResult.v1ScsPluginName}'.`; + } + ConvertV1Profiles.addToConvertMsgs(ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, uninstallMsg); } } catch (error) { ConvertV1Profiles.addExceptionToConvertMsgs("Encountered the following error while trying to convert V1 profiles:", error); @@ -125,6 +137,7 @@ export class ConvertV1Profiles { if (ImperativeConfig.instance.config == null) { // Initialization for VSCode extensions does not create the config property, so create it now. + ConvertV1Profiles.callerIsExternalApp = true; ImperativeConfig.instance.config = await Config.load( ImperativeConfig.instance.loadedConfig.name, { @@ -144,7 +157,7 @@ export class ConvertV1Profiles { } else { // with no client config, the existence of old V1 profiles dictates if we will convert const noProfilesMsg = `Did not convert any V1 profiles because no V1 profiles were found at ` + - `"${ConvertV1Profiles.profilesRootDir}".`; + `${ConvertV1Profiles.profilesRootDir}.`; try { ConvertV1Profiles.convertResult.numProfilesFound = ConvertV1Profiles.getOldProfileCount(ConvertV1Profiles.profilesRootDir); @@ -158,7 +171,7 @@ export class ConvertV1Profiles { } else { // must have been some sort of I/O error ConvertV1Profiles.addExceptionToConvertMsgs( - `Failed to get V1 profiles in "${ConvertV1Profiles.profilesRootDir}".`, error + `Failed to get V1 profiles in ${ConvertV1Profiles.profilesRootDir}.`, error ); } } @@ -182,6 +195,7 @@ export class ConvertV1Profiles { delete ImperativeConfig.instance.loadedConfig.overrides.CredentialManager; } } catch (error) { + ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.addExceptionToConvertMsgs("Failed to replace credential manager override setting.", error); } } @@ -213,34 +227,35 @@ export class ConvertV1Profiles { * ConvertV1Profiles.convertResult.credsWereMigrated) that creds were not migrated. */ private static async initCredMgr(): Promise { - if (!CredentialManagerFactory.initialized) { + if (CredentialManagerFactory.initialized) { + if (ConvertV1Profiles.oldScsPluginWasConfigured) { + Logger.getImperativeLogger().error( + `Credential manager has already been initialized with the old SCS plugin ` + + `${ConvertV1Profiles.oldScsPluginNm}. Old credentials cannot be migrated.` + ); + } + } else { // we must initialize credMgr to get and store credentials try { - if (Object.hasOwn(ImperativeConfig.instance, "callerPackageJson")) { - // Since callerPackageJson exists, we know that we are in the convert-profiles command. - // We now initialize CredMgr like all other CLI commands. - await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, ImperativeConfig.instance.callerPackageJson); - } else { - // Since callerPackageJson is not set in ImperativeConfig, we are in a VSCode extension. - // Initialize CredMgr with default values. - await CredentialManagerFactory.initialize({ - service: null, - Manager: null, - displayName: null, - invalidOnFailure: null - }); - - // Load CredMgr with some initialization properties that we create. - // OverridesLoader crashes unless the overrides property exists. - // The only thing that OverridesLoader wants from package.json is the name. - if (!ImperativeConfig.instance.loadedConfig?.overrides?.CredentialManager) { - ImperativeConfig.instance.loadedConfig.overrides = {}; - } - const callerPackageJson: any = { - name: ConvertV1Profiles.builtInCredMgrNm, - }; - await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, callerPackageJson); + // Initialize CredMgr with default values. + await CredentialManagerFactory.initialize({ + service: null, + Manager: null, + displayName: null, + invalidOnFailure: null + }); + + // OverridesLoader crashes unless the overrides property exists. + if (!ImperativeConfig.instance.loadedConfig?.overrides?.CredentialManager) { + ImperativeConfig.instance.loadedConfig.overrides = {}; } + + // The only thing that OverridesLoader wants from package.json is the name. + const callerPackageJson = { + name: ConvertV1Profiles.builtInCredMgrNm, + }; + + await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, callerPackageJson); } catch (error) { ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.addExceptionToConvertMsgs("Failed to initialize CredentialManager", error); @@ -294,7 +309,9 @@ export class ConvertV1Profiles { } catch (error) { ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.convertResult.profilesFailed.push({ name: profileName, type: profileType, error }); - ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to read "${profileType}" profile named "${profileName}"`, error); + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to read '${profileType}' profile named '${profileName}'`, error + ); } } @@ -306,7 +323,7 @@ export class ConvertV1Profiles { } } catch (error) { ConvertV1Profiles.convertResult.profilesFailed.push({ type: profileType, error }); - ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to find default "${profileType}" profile.`, error); + ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to find default '${profileType}' profile.`, error); } } @@ -379,6 +396,7 @@ export class ConvertV1Profiles { private static loadV1Schemas(): void { if (!Object.hasOwn(ImperativeConfig.instance.loadedConfig, "profiles")) { // since no schemas are loaded, we read them from the V1 profiles directory + ConvertV1Profiles.callerIsExternalApp = true; ImperativeConfig.instance.loadedConfig.profiles = []; const v1ProfileTypes = fs.existsSync(ConvertV1Profiles.profilesRootDir) ? V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir) : []; @@ -391,7 +409,7 @@ export class ConvertV1Profiles { ImperativeConfig.instance.loadedConfig.profiles.push(schemaContent.configuration); } catch (error) { ConvertV1Profiles.addExceptionToConvertMsgs( - `Failed to load schema for profile type ${profType} from file "${schemaFileNm}"`, error + `Failed to load schema for profile type ${profType} from file ${schemaFileNm}`, error ); } } @@ -427,41 +445,45 @@ export class ConvertV1Profiles { removeSync(ConvertV1Profiles.oldProfilesDir); ConvertV1Profiles.addToConvertMsgs( ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `Deleted the old profiles directory '${ConvertV1Profiles.oldProfilesDir}'.` + `Deleted the old profiles directory ${ConvertV1Profiles.oldProfilesDir}.` ); } else { ConvertV1Profiles.addToConvertMsgs( ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `The old profiles directory '${ConvertV1Profiles.oldProfilesDir}' did not exist.` + `The old profiles directory ${ConvertV1Profiles.oldProfilesDir} did not exist.` ); } } catch (error) { ConvertV1Profiles.addExceptionToConvertMsgs( - `Failed to delete the profiles directory '${ConvertV1Profiles.oldProfilesDir}'`, error + `Failed to delete the profiles directory ${ConvertV1Profiles.oldProfilesDir}`, error ); } // Delete the securely stored credentials const isZoweKeyRingAvailable = await ConvertV1Profiles.checkZoweKeyRingAvailable(); if (isZoweKeyRingAvailable) { + let deleteMsgFormat: any = ConvertMsgFmt.PARAGRAPH; const knownServices = [ConvertV1Profiles.builtInCredMgrNm, "@brightside/core", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; for (const service of knownServices) { const accounts = await ConvertV1Profiles.findOldSecureProps(service); for (const account of accounts) { if (!account.includes("secure_config_props")) { const success = await ConvertV1Profiles.deleteOldSecureProps(service, account); - const errMsgTrailer = `secure value for "${service}/${account}".`; + const errMsgTrailer = `obsolete secure value ${service}/${account}`; if (success) { ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, + ConvertMsgFmt.REPORT_LINE | deleteMsgFormat, `Deleted ${errMsgTrailer}.` ); } else { ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, + ConvertMsgFmt.ERROR_LINE | deleteMsgFormat, `Failed to delete ${errMsgTrailer}.` ); } + + // only start a new paragraph on our first delete message + deleteMsgFormat = 0; } } } @@ -542,7 +564,6 @@ export class ConvertV1Profiles { * overrides - List of overrides to replace in app settings */ private static getOldPluginInfo(): IOldPluginInfo { - const oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; const pluginInfo: IOldPluginInfo = { plugins: [], overrides: [] @@ -565,7 +586,9 @@ export class ConvertV1Profiles { AppSettings.initialize(settingsFile, defaultSettings); } catch(error) { currCredMgr = null; - ConvertV1Profiles.addExceptionToConvertMsgs(`Failed to initialize AppSettings overrides from '${settingsFile}'.`, error); + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed to initialize AppSettings overrides from ${settingsFile}.`, error + ); } } @@ -574,14 +597,19 @@ export class ConvertV1Profiles { currCredMgr = AppSettings.instance.get("overrides", ConvertV1Profiles.credMgrKey); } catch(error) { currCredMgr = null; - ConvertV1Profiles.addExceptionToConvertMsgs(`Failed trying to read '${ConvertV1Profiles.credMgrKey}' overrides.`, error); + ConvertV1Profiles.addExceptionToConvertMsgs( + `Failed trying to read '${ConvertV1Profiles.credMgrKey}' overrides.`, error + ); } // we leave the 'false' indicator unchanged to allow for the use of no credMgr if (typeof currCredMgr === "string") { // if any of the old SCS credMgr names are found, record that we want to replace the credMgr - for (const oldOverrideName of [oldScsPluginNm, "KeytarCredentialManager", "Zowe-Plugin", "Broadcom-Plugin"]) { + for (const oldOverrideName of [ + ConvertV1Profiles.oldScsPluginNm, "KeytarCredentialManager", "Zowe-Plugin", "Broadcom-Plugin"]) + { if (currCredMgr.includes(oldOverrideName)) { + ConvertV1Profiles.oldScsPluginWasConfigured = true; pluginInfo.overrides.push(ConvertV1Profiles.credMgrKey); break; } @@ -590,8 +618,8 @@ export class ConvertV1Profiles { try { // Only record the need to uninstall the SCS plug-in if it is currently installed - if ( ConvertV1Profiles.isPluginInstalled(oldScsPluginNm)) { - pluginInfo.plugins.push(oldScsPluginNm); + if (ConvertV1Profiles.isPluginInstalled(ConvertV1Profiles.oldScsPluginNm)) { + pluginInfo.plugins.push(ConvertV1Profiles.oldScsPluginNm); } } catch (error) { // report all errors except the absence of the plugins.json file @@ -617,7 +645,7 @@ export class ConvertV1Profiles { } } catch (ioErr) { - ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file '${pluginsFileNm}'`, ioErr); + ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file ${pluginsFileNm}`, ioErr); } return false; } 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 24f3d8898b..943bf806df 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 @@ -62,13 +62,13 @@ export default class ConvertProfilesHandler implements ICommandHandler { uninstallPlugin(convertResult.v1ScsPluginName); const newMsg = new ConvertMsg( ConvertMsgFmt.REPORT_LINE | ConvertMsgFmt.PARAGRAPH, - `Uninstalled plug-in "${convertResult.v1ScsPluginName}"` + `Successfully uninstalled plug-in ${convertResult.v1ScsPluginName}.` ); convertResult.msgs.push(newMsg); } catch (error) { let newMsg = new ConvertMsg( ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - `Failed to uninstall plug-in "${convertResult.v1ScsPluginName}"` + `Failed to uninstall plug-in ${convertResult.v1ScsPluginName}.` ); convertResult.msgs.push(newMsg); From 4250900ad4b0b8a48f52920907e6833dc0068564 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Mon, 24 Jun 2024 15:48:22 -0400 Subject: [PATCH 05/16] Pass profileInfo into convert to init credMgr Signed-off-by: Gene Johnston --- .../src/config/src/ConvertV1Profiles.ts | 52 ++++++++++--------- .../src/config/src/doc/IConvertV1Profiles.ts | 6 +++ 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index 624ca57fe0..1896e1f492 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -27,6 +27,7 @@ import { ImperativeConfig } from "../../utilities"; import { CredentialManagerOverride } from "../../security/src/CredentialManagerOverride"; import { OverridesLoader } from "../../imperative/src/OverridesLoader"; import { ConfigSchema } from "./ConfigSchema"; +import { ProfileInfo } from "./ProfileInfo"; import { Logger } from "../../logger"; interface IOldPluginInfo { @@ -46,7 +47,7 @@ export class ConvertV1Profiles { private static readonly oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; private static readonly builtInCredMgrNm: string = "@zowe/cli"; - private static callerIsExternalApp: boolean = false; + private static profileInfo: ProfileInfo = null; private static oldScsPluginWasConfigured: boolean = false; private static convertOpts: IConvertV1ProfOpts = null; private static convertResult: IConvertV1ProfResult = null; @@ -76,7 +77,7 @@ export class ConvertV1Profiles { * @returns Result object into which messages and stats are stored. */ public static async convert(convertOpts: IConvertV1ProfOpts): Promise { - ConvertV1Profiles.callerIsExternalApp = false; + ConvertV1Profiles.profileInfo = convertOpts.profileInfo; // initialize our result, which will be used by our utility functions, and returned by us ConvertV1Profiles.convertResult = { @@ -109,13 +110,13 @@ export class ConvertV1Profiles { // Report if the old SCS plugin should be uninstalled if (ConvertV1Profiles.convertResult.v1ScsPluginName != null) { let verb = "will"; - if (ConvertV1Profiles.callerIsExternalApp) { + if (ConvertV1Profiles.profileInfo) { verb = "should"; } let uninstallMsg: string = `The obsolete plug-in ${ConvertV1Profiles.convertResult.v1ScsPluginName} ` + `${verb} be uninstalled because the SCS is now embedded within the Zowe clients.`; - if (ConvertV1Profiles.callerIsExternalApp) { + if (ConvertV1Profiles.profileInfo) { uninstallMsg += ` Zowe CLI plugins can only be uninstalled by the CLI. Use the command ` + `'zowe plugins uninstall ${ConvertV1Profiles.convertResult.v1ScsPluginName}'.`; } @@ -137,7 +138,6 @@ export class ConvertV1Profiles { if (ImperativeConfig.instance.config == null) { // Initialization for VSCode extensions does not create the config property, so create it now. - ConvertV1Profiles.callerIsExternalApp = true; ImperativeConfig.instance.config = await Config.load( ImperativeConfig.instance.loadedConfig.name, { @@ -237,25 +237,15 @@ export class ConvertV1Profiles { } else { // we must initialize credMgr to get and store credentials try { - // Initialize CredMgr with default values. - await CredentialManagerFactory.initialize({ - service: null, - Manager: null, - displayName: null, - invalidOnFailure: null - }); - - // OverridesLoader crashes unless the overrides property exists. - if (!ImperativeConfig.instance.loadedConfig?.overrides?.CredentialManager) { - ImperativeConfig.instance.loadedConfig.overrides = {}; + if (ConvertV1Profiles.profileInfo) { + // Initialize CredMgr using the profileInfo object supplied by a VS Code extension + await ConvertV1Profiles.profileInfo.readProfilesFromDisk(); + } else { + // Initialize CredMgr using CLI techniques. + await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, + ImperativeConfig.instance.callerPackageJson + ); } - - // The only thing that OverridesLoader wants from package.json is the name. - const callerPackageJson = { - name: ConvertV1Profiles.builtInCredMgrNm, - }; - - await OverridesLoader.load(ImperativeConfig.instance.loadedConfig, callerPackageJson); } catch (error) { ConvertV1Profiles.convertResult.credsWereMigrated = false; ConvertV1Profiles.addExceptionToConvertMsgs("Failed to initialize CredentialManager", error); @@ -358,6 +348,21 @@ export class ConvertV1Profiles { * @returns string - Path name to the newly created config file. */ private static async createNewConfigFile(convertedConfig: IConfig): Promise { + if (typeof (ImperativeConfig.instance.config.mVault) === "undefined" || + ImperativeConfig.instance.config.mVault === null || + Object.keys(ImperativeConfig.instance.config.mVault).length == 0 + ) { + // Either the vault does not exist or it is empty. So create a vault. + ImperativeConfig.instance.config.mVault = { + load: ((key: string): Promise => { + return CredentialManagerFactory.manager.load(key, true); + }), + save: ((key: string, value: any): Promise => { + return CredentialManagerFactory.manager.save(key, value); + }) + }; + } + const newConfig = ImperativeConfig.instance.config; newConfig.api.layers.activate(false, true); newConfig.api.layers.merge(convertedConfig); @@ -396,7 +401,6 @@ export class ConvertV1Profiles { private static loadV1Schemas(): void { if (!Object.hasOwn(ImperativeConfig.instance.loadedConfig, "profiles")) { // since no schemas are loaded, we read them from the V1 profiles directory - ConvertV1Profiles.callerIsExternalApp = true; ImperativeConfig.instance.loadedConfig.profiles = []; const v1ProfileTypes = fs.existsSync(ConvertV1Profiles.profilesRootDir) ? V1ProfileRead.getAllProfileDirectories(ConvertV1Profiles.profilesRootDir) : []; diff --git a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts index ec30b70dee..2c463df139 100644 --- a/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/doc/IConvertV1Profiles.ts @@ -9,12 +9,18 @@ * */ +import { ProfileInfo } from "../ProfileInfo"; + /** * This is the structure of the input options to be supplied to ConvertV1Profiles.convert. */ export interface IConvertV1ProfOpts { // Should V1 profiles be deleted after conversion? deleteV1Profs: boolean; + + // The ProfileInfo object that the API will use to initialize the Secure Credential Manager. + // This property should be supplied by a VSCode application. + profileInfo?: ProfileInfo } /** From 87eb1f611d95bddf367a716500cd0abe23b31d71 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Tue, 25 Jun 2024 14:19:23 -0400 Subject: [PATCH 06/16] Directly import zowe secrets instead of resolve Signed-off-by: Gene Johnston --- .../src/config/src/ConvertV1Profiles.ts | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index 1896e1f492..57509a8052 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -348,7 +348,7 @@ export class ConvertV1Profiles { * @returns string - Path name to the newly created config file. */ private static async createNewConfigFile(convertedConfig: IConfig): Promise { - if (typeof (ImperativeConfig.instance.config.mVault) === "undefined" || + if (typeof ImperativeConfig.instance.config.mVault === "undefined" || ImperativeConfig.instance.config.mVault === null || Object.keys(ImperativeConfig.instance.config.mVault).length == 0 ) { @@ -464,8 +464,7 @@ export class ConvertV1Profiles { } // Delete the securely stored credentials - const isZoweKeyRingAvailable = await ConvertV1Profiles.checkZoweKeyRingAvailable(); - if (isZoweKeyRingAvailable) { + if (await ConvertV1Profiles.isZoweKeyRingAvailable()) { let deleteMsgFormat: any = ConvertMsgFmt.PARAGRAPH; const knownServices = [ConvertV1Profiles.builtInCredMgrNm, "@brightside/core", "Zowe-Plugin", "Broadcom-Plugin", "Zowe"]; for (const service of knownServices) { @@ -491,11 +490,6 @@ export class ConvertV1Profiles { } } } - } else { - ConvertV1Profiles.addToConvertMsgs( - ConvertMsgFmt.ERROR_LINE | ConvertMsgFmt.PARAGRAPH, - "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values." - ); } } @@ -671,16 +665,17 @@ export class ConvertV1Profiles { } /** - * Lazy load zoweKeyRing, and verify that the credential vault is able to be accessed, - * or whether there is a problem. + * Verify that the credential vault is accessible, or whether there is a problem. * @returns true if credential vault is available, false if it is not */ - private static async checkZoweKeyRingAvailable(): Promise { + private static async isZoweKeyRingAvailable(): Promise { try { - const zoweSecretsPath = require.resolve("@zowe/secrets-for-zowe-sdk"); - ConvertV1Profiles.zoweKeyRing = (await import(zoweSecretsPath)).keyring; + ConvertV1Profiles.zoweKeyRing = (await import("@zowe/secrets-for-zowe-sdk")).keyring; await ConvertV1Profiles.zoweKeyRing.findCredentials(CredentialManagerOverride.DEFAULT_CRED_MGR_NAME); - } catch (err) { + } catch (error) { + ConvertV1Profiles.addExceptionToConvertMsgs( + "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values.", error + ); return false; } return true; From f367da43867b15f13e74c2ea37f132ac9583ea17 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Wed, 26 Jun 2024 12:28:10 -0400 Subject: [PATCH 07/16] Add changelog entry Signed-off-by: Gene Johnston --- packages/imperative/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index a3ce11fe12..0a3d20ccb8 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes + +- BugFix: V3 Breaking: Modified the ConvertV1Profiles.convert API to accept a new ProfileInfo option and initialize components sufficiently to enable VSCode apps to convert V1 profiles. [#2170](https://github.com/zowe/zowe-cli/issues/2170) + + ## `8.0.0-next.202406201950` - Enhancement: Added `ProfileInfo.profileManagerWillLoad` function to verify the credential manager can load. [#2111](https://github.com/zowe/zowe-cli/issues/2111) From 0429c55948fb804ca6c90ac0e203a190e2bc21c8 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Fri, 28 Jun 2024 10:33:42 -0400 Subject: [PATCH 08/16] Existing unit tests updated to pass Signed-off-by: Gene Johnston --- .../__tests__/ConvertV1Profiles.unit.test.ts | 639 +++++++++++------- .../src/config/src/ConvertV1Profiles.ts | 3 +- 2 files changed, 396 insertions(+), 246 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index 9ca07a18f2..fa5eba040c 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -9,10 +9,12 @@ * */ +jest.mock("jsonfile"); + import * as fs from "fs"; +import * as path from "path"; import * as fsExtra from "fs-extra"; -import { PluginIssues } from "../../imperative/src/plugins/utilities/PluginIssues"; -import { OverridesLoader } from "../../imperative/src/OverridesLoader"; +import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../.."; import { ConvertV1Profiles } from "../"; import { ConvertMsgFmt } from "../src/doc/IConvertV1Profiles"; @@ -21,14 +23,16 @@ import { ImperativeError } from "../../error/src/ImperativeError"; import { keyring } from "@zowe/secrets-for-zowe-sdk"; import { Logger } from "../../logger/src/Logger"; import { V1ProfileRead } from "../../profiles"; -import { Config } from "../../config/src/Config"; import { ConfigSchema } from "../../config/src/ConfigSchema"; import { AppSettings } from "../../settings/src/AppSettings"; import { CredentialManagerOverride } from "../../security/src/CredentialManagerOverride"; +import { ProfileInfo } from "../src/ProfileInfo"; +import { OverridesLoader } from "../../imperative/src/OverridesLoader"; jest.mock("../../imperative/src/OverridesLoader"); describe("ConvertV1Profiles tests", () => { + const oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; beforeAll(() => { // do not attempt to actually log any errors @@ -39,15 +43,17 @@ describe("ConvertV1Profiles tests", () => { describe("convert", () => { let isConversionNeededSpy: any; + let replaceOldCredMgrOverrideSpy: any; + let initCredMgrSpy: any; let moveV1ProfilesToConfigFileSpy: any; - let removeOldOverridesSpy: any; let deleteV1ProfilesSpy: any; beforeAll(() => { // use "any" so that we can call private functions isConversionNeededSpy = jest.spyOn(ConvertV1Profiles as any, "isConversionNeeded"); + replaceOldCredMgrOverrideSpy = jest.spyOn(ConvertV1Profiles as any, "replaceOldCredMgrOverride"); + initCredMgrSpy = jest.spyOn(ConvertV1Profiles as any, "initCredMgr"); moveV1ProfilesToConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "moveV1ProfilesToConfigFile"); - removeOldOverridesSpy = jest.spyOn(ConvertV1Profiles as any, "removeOldOverrides"); deleteV1ProfilesSpy = jest.spyOn(ConvertV1Profiles as any, "deleteV1Profiles"); // cliHome is a getter property, so mock the property. @@ -59,15 +65,20 @@ describe("ConvertV1Profiles tests", () => { }); }); + beforeEach(() => { + // functions called by convert which we just want to confirm have been called. + replaceOldCredMgrOverrideSpy.mockReturnValue(void 0); + initCredMgrSpy.mockResolvedValue(Promise.resolve()); + moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); + deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); + }); + afterEach(() => { jest.clearAllMocks(); // clear our spies usage counts }); it("should complete a conversion when all utility functions work", async () => { isConversionNeededSpy.mockReturnValueOnce(true); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -75,16 +86,57 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); + expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); + expect(deleteV1ProfilesSpy).toHaveBeenCalled(); + }); + + it("should report that CLI must uninstall plugin when called with ProfileInfo", async () => { + isConversionNeededSpy.mockReturnValueOnce(true); + + // Ensure that the old SCS plugin name is populated in the convert result + replaceOldCredMgrOverrideSpy.mockImplementation(() => { + ConvertV1Profiles["convertResult"].v1ScsPluginName = oldScsPluginNm; + }); + + // call the function that we want to test + const profInfo = new ProfileInfo("zowe"); + await ConvertV1Profiles.convert({ + deleteV1Profs: true, + profileInfo : profInfo + }); + + expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); - expect(removeOldOverridesSpy).toHaveBeenCalled(); expect(deleteV1ProfilesSpy).toHaveBeenCalled(); + + /* The following line is a swell debug tool when a code block, + * which is trying to match lines of result messages (like the block below), + * does not get the results that it expects. + * + console.log("convertResult:\n " + JSON.stringify(ConvertV1Profiles["convertResult"], null, 2)); + */ + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE && + nextMsg.msgText.includes( + `The obsolete plug-in ${oldScsPluginNm} should be uninstalled because the SCS is now ` + + `embedded within the Zowe clients. Zowe CLI plugins can only be uninstalled by the CLI. ` + + `Use the command 'zowe plugins uninstall ${oldScsPluginNm}'.` + ) + ) { + numMsgsFound++; + } + } + expect(numMsgsFound).toEqual(1); }); it("should not delete profiles when asked not to delete", async () => { isConversionNeededSpy.mockReturnValueOnce(true); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -92,16 +144,14 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).toHaveBeenCalled(); + expect(initCredMgrSpy).toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).toHaveBeenCalled(); - expect(removeOldOverridesSpy).toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); }); it("should skip conversion and not delete profiles", async () => { isConversionNeededSpy.mockReturnValueOnce(false); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -109,16 +159,14 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); }); it("should skip conversion but still delete profiles", async () => { isConversionNeededSpy.mockReturnValueOnce(false); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -126,8 +174,9 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).toHaveBeenCalled(); }); @@ -136,9 +185,6 @@ describe("ConvertV1Profiles tests", () => { isConversionNeededSpy.mockImplementation(() => { throw new Error(fakeErrMsg); }); - moveV1ProfilesToConfigFileSpy.mockResolvedValue(Promise.resolve()); - removeOldOverridesSpy.mockImplementation(() => { }); - deleteV1ProfilesSpy.mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles.convert({ @@ -146,17 +192,11 @@ describe("ConvertV1Profiles tests", () => { }); expect(isConversionNeededSpy).toHaveBeenCalled(); + expect(replaceOldCredMgrOverrideSpy).not.toHaveBeenCalled(); + expect(initCredMgrSpy).not.toHaveBeenCalled(); expect(moveV1ProfilesToConfigFileSpy).not.toHaveBeenCalled(); - expect(removeOldOverridesSpy).not.toHaveBeenCalled(); expect(deleteV1ProfilesSpy).not.toHaveBeenCalled(); - /* The following line is a swell debug tool when a code block, - * which is trying to match lines of result messages (like the block below), - * does not get the results that it expects. - * - console.log("convertResult:\n " + JSON.stringify(ConvertV1Profiles["convertResult"], null, 2)); - */ - let numErrMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE && @@ -166,11 +206,12 @@ describe("ConvertV1Profiles tests", () => { numErrMsgsFound++; } } - expect(numErrMsgsFound).toEqual(2); + expect(numErrMsgsFound).toEqual(3); }); }); // end convert describe("private functions", () => { + let loggerSpy: any; let mockSecureLoad: any; function setCredMgrState(desiredState: string): void { if (desiredState == "works") { @@ -188,7 +229,7 @@ describe("ConvertV1Profiles tests", () => { jest.restoreAllMocks(); // put spies back to original app implementation // do not attempt to actually log any errors - jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ + loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ error: jest.fn() } as any); }); @@ -198,7 +239,7 @@ describe("ConvertV1Profiles tests", () => { ConvertV1Profiles["convertResult"] = { msgs: [], v1ScsPluginName: null, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles["noCfgFilePathNm"], numProfilesFound: 0, profilesConverted: {}, @@ -211,7 +252,7 @@ describe("ConvertV1Profiles tests", () => { }); describe("isConversionNeeded", () => { - it("should return false if a client config exists", () => { + it("should return false if a client config exists", async () => { // Pretend that we have a zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -224,7 +265,7 @@ describe("ConvertV1Profiles tests", () => { // call the function that we want to test // using class["name"] notation because it is a private static function - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(convNeeded).toEqual(false); let numErrMsgsFound = 0; @@ -240,7 +281,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(1); }); - it("should return false if we find no V1 profiles", () => { + it("should return false if we find no V1 profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -257,7 +298,7 @@ describe("ConvertV1Profiles tests", () => { .mockReturnValueOnce(0); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); @@ -273,7 +314,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(1); }); - it("should return false if no profiles directory exists", () => { + it("should return false if no profiles directory exists", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -294,24 +335,24 @@ describe("ConvertV1Profiles tests", () => { .mockImplementationOnce(() => { throw noDirError; }); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); - let numErrMsgsFound = 0; + let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE && nextMsg.msgText.includes(`Did not convert any V1 profiles because ` + - `no V1 profiles were found at "${profileDir}"`) + `no V1 profiles were found at ${profileDir}`) ) { - numErrMsgsFound++; + numMsgsFound++; } } - expect(numErrMsgsFound).toEqual(1); + expect(numMsgsFound).toEqual(1); }); - it("should return false if an IO error occurs while reading profiles", () => { + it("should return false if an IO error occurs while reading profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -333,7 +374,7 @@ describe("ConvertV1Profiles tests", () => { .mockImplementationOnce(() => { throw ioError; }); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(false); @@ -352,7 +393,7 @@ describe("ConvertV1Profiles tests", () => { expect(numErrMsgsFound).toEqual(2); }); - it("should return true if we find some V1 profiles", () => { + it("should return true if we find some V1 profiles", async () => { // Pretend that we have no zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { configurable: true, @@ -369,7 +410,7 @@ describe("ConvertV1Profiles tests", () => { .mockReturnValueOnce(6); // call the function that we want to test - const convNeeded = ConvertV1Profiles["isConversionNeeded"](); + const convNeeded = await ConvertV1Profiles["isConversionNeeded"](); expect(getOldProfileCountSpy).toHaveBeenCalled(); expect(convNeeded).toEqual(true); @@ -521,6 +562,7 @@ describe("ConvertV1Profiles tests", () => { autoStore: true }; + let loadV1SchemasSpy:any; let activateSpy: any; let mergeSpy: any; let saveSpy: any; @@ -542,8 +584,8 @@ describe("ConvertV1Profiles tests", () => { properties: null as any, }); - updateSchemaSpy = jest.spyOn(ConfigSchema, "updateSchema") - .mockReturnValue(0 as any); + loadV1SchemasSpy = jest.spyOn(ConvertV1Profiles as any, "loadV1Schemas").mockReturnValue(void 0); + updateSchemaSpy = jest.spyOn(ConfigSchema, "updateSchema").mockReturnValue(0 as any); jest.spyOn(ImperativeConfig.instance, "config", "get").mockReturnValue({ api: { @@ -572,6 +614,7 @@ describe("ConvertV1Profiles tests", () => { expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -579,11 +622,11 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes("Your old V1 profiles have been moved") && - nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion") + if ((nextMsg.msgText.includes("Your old V1 profiles have been moved") && + nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion")) || - nextMsg.msgText.includes("Your new profiles have been saved") && - nextMsg.msgText.includes("To change your configuration, update that file in your text editor") + (nextMsg.msgText.includes("Your new profiles have been saved") && + nextMsg.msgText.includes("To change your configuration, update that file in your text editor")) ) { numMsgsFound++; } @@ -607,6 +650,7 @@ describe("ConvertV1Profiles tests", () => { expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -649,6 +693,7 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(activateSpy).toHaveBeenCalled(); expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); expect(updateSchemaSpy).toHaveBeenCalled(); expect(saveSpy).toHaveBeenCalled(); expect(renameSpy).toHaveBeenCalled(); @@ -663,13 +708,14 @@ describe("ConvertV1Profiles tests", () => { } } else { if (nextMsg.msgText.includes("Failed to rename profiles directory") || - nextMsg.msgText.includes(renameError) + nextMsg.msgText.includes(`Reason: ${renameError}`) || + nextMsg.msgText.includes(`Error: ${renameError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(3); + expect(numMsgsFound).toEqual(4); }); }); // end createNewConfigFile @@ -730,7 +776,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes(`Deleted the old profiles directory '${oldProfileDir}'`)) { + if (nextMsg.msgText.includes(`Deleted the old profiles directory ${oldProfileDir}`)) { numMsgsFound++; } } @@ -756,7 +802,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes(`The old profiles directory '${oldProfileDir}' did not exist.`)) { + if (nextMsg.msgText.includes(`The old profiles directory ${oldProfileDir} did not exist.`)) { numMsgsFound++; } } @@ -803,12 +849,13 @@ describe("ConvertV1Profiles tests", () => { it("should also delete credentials stored by old SCS plugin", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") - .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])); + .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) + .mockResolvedValue(Promise.resolve([])); jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") .mockResolvedValue(Promise.resolve(true)); @@ -826,7 +873,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes("Deleted secure value for ") && + if (nextMsg.msgText.includes("Deleted obsolete secure value ") && ( nextMsg.msgText.includes("secureUser") || nextMsg.msgText.includes("securePassword") @@ -841,12 +888,13 @@ describe("ConvertV1Profiles tests", () => { it("should report an error when we fail to delete secure credentials", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") - .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])); + .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) + .mockResolvedValue(Promise.resolve([])); // pretend that secure credential deletion failed jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") @@ -864,7 +912,7 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes("Failed to delete secure value") && + if (nextMsg.msgText.includes("Failed to delete obsolete secure value") && ( nextMsg.msgText.includes("secureUser") || nextMsg.msgText.includes("securePassword") @@ -877,9 +925,9 @@ describe("ConvertV1Profiles tests", () => { expect(numMsgsFound).toEqual(2); }); - it("should report an error when the zowe keyring is unavailable", async () => { + it("should only report directory deletion when zowe keyring is unavailable", async () => { // pretend that the zowe keyring is unavailable - const checkKeyRingSpy = jest.spyOn(ConvertV1Profiles as any, "checkZoweKeyRingAvailable") + const checkKeyRingSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists @@ -892,22 +940,20 @@ describe("ConvertV1Profiles tests", () => { await ConvertV1Profiles["deleteV1Profiles"](); expect(removeSyncSpy).toHaveBeenCalled(); - let numMsgsFound = 0; + let numDirDelMsgs = 0; + let numCredDelMsgs = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { - if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes( - "Zowe keyring or the credential vault are unavailable. Unable to delete old secure values")) - { - numMsgsFound++; - } + if (nextMsg.msgText.includes(`Deleted the old profiles directory ${oldProfileDir}`)) { + numDirDelMsgs++; } } - expect(numMsgsFound).toEqual(1); + expect(ConvertV1Profiles["convertResult"].msgs.length).toEqual(1); + expect(numDirDelMsgs).toEqual(1); checkKeyRingSpy.mockRestore(); // restore original app implementation }); }); // end deleteV1Profiles - describe("removeOldOverrides", () => { + describe("replaceOldCredMgrOverride", () => { it("should do nothing if there are no overrides", () => { // pretend that no overrides exist @@ -919,7 +965,7 @@ describe("ConvertV1Profiles tests", () => { // call the function that we want to test let caughtErr: any; try { - ConvertV1Profiles["removeOldOverrides"](); + ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -948,29 +994,10 @@ describe("ConvertV1Profiles tests", () => { } } as any); - // mock getter properties, so that we avoid real ImperativeConfig actions - Object.defineProperty(ImperativeConfig.instance, "cliHome", { - configurable: true, - get: jest.fn(() => { - return "/fake/cliHome"; - }) - }); - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - set: jest.fn() - }); - - // avoid running the real Config.load and OverridesLoader.load - const configLoadSpy = jest.spyOn(Config, "load").mockResolvedValue(Config.empty() as any); - const overridesLoaderLoadSpy = jest.spyOn(OverridesLoader, "load").mockResolvedValue(Promise.resolve()); - - // Avoid using the real secure credMgr. Pretend it works. - setCredMgrState("works"); - // call the function that we want to test let caughtErr: any; try { - await ConvertV1Profiles["removeOldOverrides"](); + await ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -979,61 +1006,30 @@ describe("ConvertV1Profiles tests", () => { expect(appSettingsSetSpy).toHaveBeenCalledWith( "overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME ); - expect(configLoadSpy).toHaveBeenCalled(); - expect(overridesLoaderLoadSpy).toHaveBeenCalled(); expect(ConvertV1Profiles["convertResult"].v1ScsPluginName).toEqual(fakeV1ScsPlugin); - expect(ConvertV1Profiles["convertResult"].reInitCredMgr).toEqual(false); + expect(ConvertV1Profiles["convertResult"].credsWereMigrated).toEqual(true); }); - it("should report when credMgr must be re-initialized", async () => { + it("should catch and report an error thrown by AppSettings.instance.set", () => { // pretend that we found an old credential manager const fakeV1ScsPlugin = "FakeScsPlugin"; jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( { plugins: [fakeV1ScsPlugin], overrides: ["CredentialManager"] } ); - // pretend that we set the credMgr - const appSettingsSetSpy = jest.fn(); + // pretend that AppSettings.set() throws an exception + const appSettingsCrashErrMsg = "A fake exception from AppSettings.instance.set"; + const appSettingsSetSpy = jest.fn().mockImplementation(() => { + throw new Error(appSettingsCrashErrMsg); + }); jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ set: appSettingsSetSpy } as any); - // pretend that our loadedConfig has a credMgr override - jest.spyOn(ImperativeConfig.instance, "loadedConfig", "get").mockReturnValue({ - overrides: { - CredentialManager: "CfgMgrOverride" - } - } as any); - - // mock getter properties, so that we avoid real ImperativeConfig actions - Object.defineProperty(ImperativeConfig.instance, "cliHome", { - configurable: true, - get: jest.fn(() => { - return "/fake/cliHome"; - }) - }); - Object.defineProperty(ImperativeConfig.instance, "config", { - configurable: true, - set: jest.fn() - }); - - // avoid running the real Config.load and OverridesLoader.load - const configLoadSpy = jest.spyOn(Config, "load").mockResolvedValue(Config.empty() as any); - const overridesLoaderLoadSpy = jest.spyOn(OverridesLoader, "load").mockResolvedValue(Promise.resolve()); - - // Avoid using the real secure credMgr. Pretend it works. - setCredMgrState("works"); - - // pretend that the CredMgr has been initialized - Object.defineProperty(CredentialManagerFactory, "initialized", { - configurable: true, - get: jest.fn(() => true) - }); - // call the function that we want to test let caughtErr: any; try { - await ConvertV1Profiles["removeOldOverrides"](); + ConvertV1Profiles["replaceOldCredMgrOverride"](); } catch (err) { caughtErr = err; } @@ -1043,64 +1039,32 @@ describe("ConvertV1Profiles tests", () => { "overrides", "CredentialManager", CredentialManagerOverride.DEFAULT_CRED_MGR_NAME ); expect(ConvertV1Profiles["convertResult"].v1ScsPluginName).toEqual(fakeV1ScsPlugin); - expect(configLoadSpy).not.toHaveBeenCalled(); - expect(overridesLoaderLoadSpy).not.toHaveBeenCalled(); - expect(ConvertV1Profiles["convertResult"].reInitCredMgr).toEqual(true); - let numMsgsFound = 0; - for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { - if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if (nextMsg.msgText.includes( - "The following plug-ins will be removed because they are now part of the core CLI and are no longer needed") || - fakeV1ScsPlugin - ) { - numMsgsFound++; - } - } - } - expect(numMsgsFound).toEqual(2); - }); - - it("should catch and report an error thrown by AppSettings.instance.set", () => { - // pretend that we found an old credential manager - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( - { plugins: [], overrides: ["CredentialManager"] } - ); - - // pretend that AppSettings.instance.set throws an exception - const fakeErrMsg = "A fake exception from AppSettings.instance.set"; - const appSettingsSetSpy = jest.fn().mockImplementation(() => { - throw new Error(fakeErrMsg); - }); - jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ - set: appSettingsSetSpy - } as any); - - // call the function that we want to test - let caughtErr: any; - try { - ConvertV1Profiles["removeOldOverrides"](); - } catch (err) { - caughtErr = err; - } - - expect(appSettingsSetSpy).toHaveBeenCalled(); - expect(caughtErr).not.toBeDefined(); - + expect(ConvertV1Profiles["convertResult"].credsWereMigrated).toEqual(false); let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes("Failed to replace credential manager override setting") || - nextMsg.msgText.includes(fakeErrMsg) + nextMsg.msgText.includes(`Reason: ${appSettingsCrashErrMsg}`) || + nextMsg.msgText.includes(`Error: ${appSettingsCrashErrMsg}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); - }); // end removeOldOverrides + }); // end replaceOldCredMgrOverride describe("getOldPluginInfo", () => { + let isPluginInstalledSpy: any; + + beforeEach(() => { + isPluginInstalledSpy = jest.spyOn(ConvertV1Profiles as any, "isPluginInstalled"); + }); + + afterAll(() => { + isPluginInstalledSpy.mockRestore(); // restore original app implementation + }); it("should retrieve old credMgr override and old plugin", () => { // pretend that we find the old SCS CredMgr name @@ -1110,14 +1074,8 @@ describe("ConvertV1Profiles tests", () => { get: appSettingsGetSpy } as any); - // pretend that PluginIssues.instance.getInstalledPlugins returns the name of the old SCS - const getPluginsSpy = jest.fn().mockReturnValue({ - [oldScsName]: {}, - "AnIrrelevantPluginName": {} - } as any); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); // call the function that we want to test let pluginInfo: any; @@ -1130,7 +1088,6 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); let numItemsFound = 0; for (const nextOverride of pluginInfo.overrides) { @@ -1159,11 +1116,8 @@ describe("ConvertV1Profiles tests", () => { get: appSettingsGetSpy } as any); - // pretend that PluginIssues.instance.getInstalledPlugins returns no plugins - const getPluginsSpy = jest.fn().mockReturnValue({} as any); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); // call the function that we want to test let pluginInfo: any; @@ -1176,75 +1130,135 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); expect(pluginInfo.overrides.length).toEqual(0); - expect(pluginInfo.plugins.length).toEqual(0); + expect(pluginInfo.plugins.length).toEqual(1); let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes("Failed trying to read 'CredentialManager' overrides.") || - nextMsg.msgText.includes(fakeErrMsg) + nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); + }); // end getOldPluginInfo - it("should catch exception from PluginIssues.instance.getInstalledPlugins and record error", () => { - // pretend that we find the old SCS CredMgr name - const oldScsName = "@zowe/secure-credential-store-for-zowe-cli"; - const appSettingsGetSpy = jest.fn().mockReturnValue(oldScsName); - jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ - get: appSettingsGetSpy - } as any); + describe("isPluginInstalled", () => { + const readFileSyncSpy = jest.spyOn(jsonfile, "readFileSync"); - // pretend that PluginIssues.instance.getInstalledPlugins crashes - const fakeErrMsg = "A fake exception from PluginIssues.instance.getInstalledPlugins"; - const getPluginsSpy = jest.fn().mockImplementation(() => { - throw new Error(fakeErrMsg); + beforeAll(() => { + // cliHome is a getter property, so mock the property. + Object.defineProperty(ImperativeConfig.instance, "cliHome", { + configurable: true, + get: jest.fn(() => { + return "/fake/cliHome"; + }) + }); + }); + + afterEach(() => { + readFileSyncSpy.mockRestore(); // restore original app implementation + }); + + it("should return true if plugin name is in the plugins file", () => { + // make readFileSync return some fake data + const pluginName = "FakePluginName"; + const fakePluginsJson = JSON.parse(`{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "${pluginName}": { + "package": "@zowe/${pluginName}-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "1.2.3" + } + }`); + readFileSyncSpy.mockImplementation(() => { + return fakePluginsJson; }); - jest.spyOn(PluginIssues, "instance", "get").mockReturnValue({ - getInstalledPlugins: getPluginsSpy - } as any); // call the function that we want to test - let pluginInfo: any; + let pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"](pluginName); + expect(pluginInstResult).toEqual(true); + }); + + it("should return false if plugin name is NOT in the plugins file", () => { + // make readFileSync return some fake data + const fakePluginsJson = JSON.parse(`{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "@zowe/not-your-plugin": { + "package": "@zowe/these-are-not-the-droids-you-are-looking-for@zowe-v2-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + } + }`); + readFileSyncSpy.mockImplementation(() => { + return fakePluginsJson; + }); + + // call the function that we want to test + let pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"]("PluginNameNotInstalled"); + expect(pluginInstResult).toEqual(false); + }); + + + it("should catch exception from readFileSync and record error", () => { + // pretend that readFileSync throws an error + const readFileErrMsg = "readFileSync threw some horrible exception"; + readFileSyncSpy.mockImplementation(jest.fn(() => { + throw new Error(readFileErrMsg); + })); + + // call the function that we want to test + const pluginName = "FakePluginName"; + let pluginInstResult: boolean = false; let caughtErr: any; try { - pluginInfo = ConvertV1Profiles["getOldPluginInfo"](); + pluginInstResult = ConvertV1Profiles["isPluginInstalled"](pluginName); } catch (err) { caughtErr = err; } expect(caughtErr).not.toBeDefined(); - expect(appSettingsGetSpy).toHaveBeenCalled(); - expect(getPluginsSpy).toHaveBeenCalled(); - expect(pluginInfo.plugins.length).toEqual(0); - - let numItemsFound = 0; - for (const nextOverride of pluginInfo.overrides) { - if (nextOverride === "CredentialManager") { - numItemsFound++; - } - } - expect(numItemsFound).toEqual(1); + expect(pluginInstResult).toEqual(false); - numItemsFound = 0; + let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { - if (nextMsg.msgText.includes("Failed trying to get the set of installed plugins") || - nextMsg.msgText.includes(fakeErrMsg) + if (nextMsg.msgText.includes("Cannot read plugins file") && nextMsg.msgText.includes("plugins.json") ) { + numMsgsFound++; + } + if ((nextMsg.msgText.includes("Reason: ") || nextMsg.msgText.includes("Error: ")) && + nextMsg.msgText.includes(readFileErrMsg) ) { - numItemsFound++; + numMsgsFound++; } } } - expect(numItemsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); }); - }); // end getOldPluginInfo + }); // end isPluginInstalled describe("getOldProfileCount", () => { @@ -1265,7 +1279,142 @@ describe("ConvertV1Profiles tests", () => { }); }); // end getOldProfileCount - describe("checkZoweKeyRingAvailable", () => { + describe("initCredMgr", () => { + let logMsg: string; + + beforeAll(() => { + // change logger spy to record the message + loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockImplementation(() => { + return { + error: jest.fn((errMsg) => { + logMsg = errMsg; + }) + } as any; + }); + }); + + beforeEach(() => { + // Reset the messages that have been logged or reported + logMsg = "Nothing logged"; + ConvertV1Profiles["convertResult"].msgs = []; + }); + + afterEach(() => { + jest.clearAllMocks(); // clear the mock counters + }); + + afterAll(() => { + // restore the logger spy back to doing nothing + loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ + error: jest.fn() + } as any); + }); + + it("should detect when credMgr has already been initialized", async () => { + // pretend that credMgr has been initialized. + let initializedWasCalled = false; + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + initializedWasCalled = true; + return true; + }) + }); + + // pretend that the SCS plugin was configured as the credMgr + ConvertV1Profiles["oldScsPluginWasConfigured"] = true; + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + + expect(initializedWasCalled).toEqual(true); + expect(logMsg).toContain( + `Credential manager has already been initialized with the old SCS plugin ${oldScsPluginNm}. ` + + `Old credentials cannot be migrated` + ); + }); + + it("should read profiles from disk when ProfileInfo is supplied", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // do not actually read any ProfileInfo from disk + ConvertV1Profiles["profileInfo"] = new ProfileInfo("zowe"); + const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") + .mockResolvedValue(Promise.resolve()); + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + expect(readFromDiskSpy).toHaveBeenCalled(); + }); + + it("should call overridesLoader when ProfileInfo is NOT supplied", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // do not actually load the overrides + ConvertV1Profiles["profileInfo"] = null as any; + const overridesLoaderSpy = jest.spyOn(OverridesLoader, "load"); + + // call the function that we want to test + await ConvertV1Profiles["initCredMgr"](); + expect(overridesLoaderSpy).toHaveBeenCalled(); + }); + + it("should catch an exception and report the error", async () => { + // pretend that credMgr has NOT been initialized. + Object.defineProperty(CredentialManagerFactory, "initialized", { + configurable: true, + get: jest.fn(() => { + return false; + }) + }); + + // do not actually read any ProfileInfo from disk + ConvertV1Profiles["profileInfo"] = new ProfileInfo("zowe"); + const fakeErrMsg = "A fake exception from findCredentials"; + const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") + .mockImplementation(() => { + throw new Error(fakeErrMsg); + }); + + // call the function that we want to test + let caughtErr: any; + try { + await ConvertV1Profiles["initCredMgr"](); + } catch (err) { + caughtErr = err; + } + + expect(readFromDiskSpy).toHaveBeenCalled(); + expect(caughtErr).not.toBeDefined(); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes(`Failed to initialize CredentialManager`) || + nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(3); + }); + }); // end initCredMgr + + describe("isZoweKeyRingAvailable", () => { it("should return true if it finds credentials in the vault", async () => { // pretend that findCredentials found a bunch of accounts and passwords @@ -1281,7 +1430,7 @@ describe("ConvertV1Profiles tests", () => { } as any; // call the function that we want to test - const result = await ConvertV1Profiles["checkZoweKeyRingAvailable"](); + const result = await ConvertV1Profiles["isZoweKeyRingAvailable"](); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; expect(findCredentialsSpy).toHaveBeenCalledWith("@zowe/cli"); @@ -1303,7 +1452,7 @@ describe("ConvertV1Profiles tests", () => { let caughtErr: any; let checkKeyRingResult: boolean = true; try { - checkKeyRingResult = await ConvertV1Profiles["checkZoweKeyRingAvailable"](); + checkKeyRingResult = await ConvertV1Profiles["isZoweKeyRingAvailable"](); } catch (err) { caughtErr = err; } @@ -1313,7 +1462,7 @@ describe("ConvertV1Profiles tests", () => { expect(caughtErr).not.toBeDefined(); expect(checkKeyRingResult).toEqual(false); }); - }); // end checkZoweKeyRingAvailable + }); // end isZoweKeyRingAvailable describe("findOldSecureProps", () => { @@ -1366,15 +1515,15 @@ describe("ConvertV1Profiles tests", () => { for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes(`Encountered an error while gathering secure properties ` + - `for service '${fakeServiceName}':`) - || - nextMsg.msgText.includes(fakeFindCredError) + `for service '${fakeServiceName}':`) || + nextMsg.msgText.includes(`Reason: ${fakeFindCredError}`) || + nextMsg.msgText.includes(`Error: ${fakeFindCredError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; }); }); // end findOldSecureProps @@ -1421,15 +1570,15 @@ describe("ConvertV1Profiles tests", () => { if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { if (nextMsg.msgText.includes( `Encountered an error while deleting secure data for ` + - `service '${fakeAcct}/${fakeProp}':`) - || - nextMsg.msgText.includes(fakeDelPassError) + `service '${fakeAcct}/${fakeProp}':`) || + nextMsg.msgText.includes(`Reason: ${fakeDelPassError}`) || + nextMsg.msgText.includes(`Error: ${fakeDelPassError}`) ) { numMsgsFound++; } } } - expect(numMsgsFound).toEqual(2); + expect(numMsgsFound).toEqual(3); ConvertV1Profiles["zoweKeyRing"] = origZoweKeyRing; }); }); // end deleteOldSecureProps diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index cbd71b9030..76a89ae394 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -635,8 +635,9 @@ export class ConvertV1Profiles { * @returns True if plugin is installed. False otherwise. */ private static isPluginInstalled(pluginName: string): boolean { - const pluginsFileNm = path.join(ImperativeConfig.instance.cliHome, "plugins", "plugins.json"); + let pluginsFileNm: string; try { + pluginsFileNm = path.join(ImperativeConfig.instance.cliHome, "plugins", "plugins.json"); const pluginsFileJson = readFileSync(pluginsFileNm); if (Object.hasOwn(pluginsFileJson, pluginName)) { return true; From 10aab3c72167682207c20a655d57ec0b8049fbd4 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Fri, 28 Jun 2024 18:33:08 -0400 Subject: [PATCH 09/16] Add tests to cover new functions Signed-off-by: Gene Johnston --- .../__tests__/ConvertV1Profiles.unit.test.ts | 303 +++++++++++++++--- 1 file changed, 258 insertions(+), 45 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index fa5eba040c..0dacf07039 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -12,7 +12,6 @@ jest.mock("jsonfile"); import * as fs from "fs"; -import * as path from "path"; import * as fsExtra from "fs-extra"; import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../.."; @@ -33,6 +32,8 @@ jest.mock("../../imperative/src/OverridesLoader"); describe("ConvertV1Profiles tests", () => { const oldScsPluginNm = "@zowe/secure-credential-store-for-zowe-cli"; + const profileDir = "/fake/path/to/profiles/"; + const appName = "zowe"; beforeAll(() => { // do not attempt to actually log any errors @@ -101,7 +102,7 @@ describe("ConvertV1Profiles tests", () => { }); // call the function that we want to test - const profInfo = new ProfileInfo("zowe"); + const profInfo = new ProfileInfo(appName); await ConvertV1Profiles.convert({ deleteV1Profs: true, profileInfo : profInfo @@ -326,7 +327,6 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that an error occurred because the profiles directory did not exist - const profileDir = "/fake/path/to/profiles/"; ConvertV1Profiles["profilesRootDir"] = profileDir; const noDirError = new ImperativeError({ additionalDetails: { code: 'ENOENT' } @@ -364,7 +364,6 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that got an I/O error - const profileDir = "/fake/path/to/profiles/"; ConvertV1Profiles["profilesRootDir"] = profileDir; const ioErrMsg = "Fake I/O error occurred"; const ioError = new ImperativeError({ @@ -599,6 +598,18 @@ describe("ConvertV1Profiles tests", () => { } as any); }); + beforeEach(() => { + // reset usage counts + loadV1SchemasSpy.mockClear(); + updateSchemaSpy.mockClear(); + }); + + afterAll(() => { + // restore original app implementations + loadV1SchemasSpy.mockRestore(); + updateSchemaSpy.mockRestore(); + }); + it("should create a config file and report movement of old profiles", async () => { // we report movement when we do not delete ConvertV1Profiles["convertOpts"] = { @@ -622,11 +633,11 @@ describe("ConvertV1Profiles tests", () => { let numMsgsFound = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgFormat & ConvertMsgFmt.REPORT_LINE) { - if ((nextMsg.msgText.includes("Your old V1 profiles have been moved") && - nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion")) + if (nextMsg.msgText.includes("Your old V1 profiles have been moved") && + nextMsg.msgText.includes("Delete them by re-running this operation and requesting deletion") || - (nextMsg.msgText.includes("Your new profiles have been saved") && - nextMsg.msgText.includes("To change your configuration, update that file in your text editor")) + nextMsg.msgText.includes("Your new profiles have been saved") && + nextMsg.msgText.includes("To change your configuration, update that file in your text editor") ) { numMsgsFound++; } @@ -670,6 +681,48 @@ describe("ConvertV1Profiles tests", () => { expect(numMsgsFound).toEqual(1); }); + it("should create a config vault if needed", async () => { + // make the vault non-existent + const configObj = ImperativeConfig.instance.config; + configObj["mVault"] = null as any; + + // request that we do not delete profiles + ConvertV1Profiles["convertOpts"] = { + deleteV1Profs: false + }; + + // pretend that rename worked + const renameSpy = jest.spyOn(fs, "renameSync") + .mockReturnValue(0 as any); + + // call the function that we want to test + await ConvertV1Profiles["createNewConfigFile"](testConfig); + + expect(ImperativeConfig.instance.config["mVault"]).not.toEqual(null); + expect(activateSpy).toHaveBeenCalled(); + expect(mergeSpy).toHaveBeenCalled(); + expect(loadV1SchemasSpy).toHaveBeenCalled(); + expect(updateSchemaSpy).toHaveBeenCalled(); + expect(saveSpy).toHaveBeenCalled(); + expect(renameSpy).toHaveBeenCalled(); + + // prevent calling the real underlying credentialManager factory load and save functions + Object.defineProperty(CredentialManagerFactory, "manager", { + configurable: true, + get: jest.fn(() => { + return { + configurable: true, + load: jest.fn(() => { }), + save: jest.fn(() => { }) + }; + }) + }); + + // get coverage of the load and save functions of the vault + await ImperativeConfig.instance.config.mVault.load(appName); + await ImperativeConfig.instance.config.mVault.save("name", "value"); + }); + it("should catch and report a problem when rename throws an error", async () => { // we were asked to delete ConvertV1Profiles["convertOpts"] = { @@ -747,15 +800,28 @@ describe("ConvertV1Profiles tests", () => { describe("deleteV1Profiles", () => { const oldProfileDir = "/fake/path/to/profiles-old"; - let existsSyncSpy: any = jest.fn(); - let removeSyncSpy: any = jest.fn(); + let existsSyncSpy: any; + let removeSyncSpy: any; + + beforeAll(() => { + ConvertV1Profiles["oldProfilesDir"] = oldProfileDir; + existsSyncSpy = jest.spyOn(fs, "existsSync"); + removeSyncSpy = jest.spyOn(fsExtra, "removeSync"); + }); beforeEach(() => { + // pretend that remove works + removeSyncSpy.mockReturnValue(0 as any); + + // reset usage counts + existsSyncSpy.mockClear(); + removeSyncSpy.mockClear(); + }); + + afterAll(() => { // restore original app implementations existsSyncSpy.mockRestore(); removeSyncSpy.mockRestore(); - - ConvertV1Profiles["oldProfilesDir"] = oldProfileDir; }); it("should delete the old v1 profiles directory", async () => { @@ -764,10 +830,7 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -790,10 +853,7 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory not exist - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(false); - - // pretend that remove works - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(false); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -816,11 +876,11 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); + existsSyncSpy.mockReturnValue(true); // pretend that remove crashed const removeError = "fsExtra.removeSync threw a horrible error"; - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockImplementation(() => { + removeSyncSpy.mockImplementation(() => { throw new Error(removeError); }); @@ -861,10 +921,7 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve(true)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -901,10 +958,7 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); @@ -931,17 +985,13 @@ describe("ConvertV1Profiles tests", () => { .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists - existsSyncSpy = jest.spyOn(fs, "existsSync").mockReturnValue(true); - - // pretend that remove worked - removeSyncSpy = jest.spyOn(fsExtra, "removeSync").mockReturnValue(0 as any); + existsSyncSpy.mockReturnValue(true); // call the function that we want to test await ConvertV1Profiles["deleteV1Profiles"](); expect(removeSyncSpy).toHaveBeenCalled(); let numDirDelMsgs = 0; - let numCredDelMsgs = 0; for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { if (nextMsg.msgText.includes(`Deleted the old profiles directory ${oldProfileDir}`)) { numDirDelMsgs++; @@ -954,11 +1004,20 @@ describe("ConvertV1Profiles tests", () => { }); // end deleteV1Profiles describe("replaceOldCredMgrOverride", () => { + let getOldPluginInfoSpy: any; + + beforeAll(() => { + getOldPluginInfoSpy = jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo"); + }); + + afterAll(() => { + // restore original app implementations + getOldPluginInfoSpy.mockRestore(); + }); it("should do nothing if there are no overrides", () => { // pretend that no overrides exist - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo") - .mockReturnValueOnce({ plugins: [], overrides: [] }); + getOldPluginInfoSpy.mockReturnValueOnce({ plugins: [], overrides: [] }); const appSettingsGetSpy = jest.spyOn(AppSettings, "instance", "get"); @@ -977,7 +1036,7 @@ describe("ConvertV1Profiles tests", () => { it("should replace a v1 SCS credential manager and report a v1 SCS plugin", async () => { // pretend that we found an old credential manager const fakeV1ScsPlugin = "FakeScsPlugin"; - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( + getOldPluginInfoSpy.mockReturnValueOnce( { plugins: [fakeV1ScsPlugin], overrides: ["CredentialManager"] } ); @@ -1013,7 +1072,7 @@ describe("ConvertV1Profiles tests", () => { it("should catch and report an error thrown by AppSettings.instance.set", () => { // pretend that we found an old credential manager const fakeV1ScsPlugin = "FakeScsPlugin"; - jest.spyOn(ConvertV1Profiles as any, "getOldPluginInfo").mockReturnValueOnce( + getOldPluginInfoSpy.mockReturnValueOnce( { plugins: [fakeV1ScsPlugin], overrides: ["CredentialManager"] } ); @@ -1106,6 +1165,31 @@ describe("ConvertV1Profiles tests", () => { expect(numItemsFound).toEqual(1); }); + it("should initialize appSettings when AppSettings.instance fails", () => { + // pretend that AppSettings.instance.get crashes + const appSettingsGetSpy = jest.spyOn(AppSettings, "instance", "get").mockImplementation(() => { + throw new Error("Error does not matter"); + }); + + // pretend that the old zowe SCS plugin is installed + isPluginInstalledSpy.mockReturnValue(true); + + // call the function that we want to test + let pluginInfo: any; + let caughtErr: any; + try { + pluginInfo = ConvertV1Profiles["getOldPluginInfo"](); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(isPluginInstalledSpy).toHaveBeenCalled(); + expect(appSettingsGetSpy).toHaveBeenCalled(); + expect(pluginInfo.overrides.length).toEqual(0); + expect(pluginInfo.plugins.length).toEqual(1); + }); + it("should catch exception from AppSettings.instance.get and record error", () => { // pretend that AppSettings.instance.get crashes const fakeErrMsg = "A fake exception from AppSettings.instance.get"; @@ -1146,12 +1230,53 @@ describe("ConvertV1Profiles tests", () => { } expect(numMsgsFound).toEqual(3); }); + + it("should catch exception from isPluginInstalled and record error", () => { + // pretend that we find the old SCS CredMgr name + const oldScsName = "@zowe/secure-credential-store-for-zowe-cli"; + const appSettingsGetSpy = jest.fn().mockReturnValue(oldScsName); + jest.spyOn(AppSettings, "instance", "get").mockReturnValue({ + get: appSettingsGetSpy + } as any); + + // pretend that isPluginInstalled throws an error + const caughtErrMsg = "isPluginInstalled threw a horrible exception"; + isPluginInstalledSpy.mockImplementation(jest.fn(() => { + throw new Error(caughtErrMsg); + })); + + // call the function that we want to test + let pluginInfo: any; + let caughtErr: any; + try { + pluginInfo = ConvertV1Profiles["getOldPluginInfo"](); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Failed trying to get the set of installed plugins") || + nextMsg.msgText.includes(`Reason: ${caughtErrMsg}`) || + nextMsg.msgText.includes(`Error: ${caughtErrMsg}`) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(3); + }); }); // end getOldPluginInfo describe("isPluginInstalled", () => { - const readFileSyncSpy = jest.spyOn(jsonfile, "readFileSync"); + let readFileSyncSpy: any; beforeAll(() => { + readFileSyncSpy = jest.spyOn(jsonfile, "readFileSync"); + // cliHome is a getter property, so mock the property. Object.defineProperty(ImperativeConfig.instance, "cliHome", { configurable: true, @@ -1161,7 +1286,7 @@ describe("ConvertV1Profiles tests", () => { }); }); - afterEach(() => { + afterAll(() => { readFileSyncSpy.mockRestore(); // restore original app implementation }); @@ -1190,7 +1315,7 @@ describe("ConvertV1Profiles tests", () => { }); // call the function that we want to test - let pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"](pluginName); + const pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"](pluginName); expect(pluginInstResult).toEqual(true); }); @@ -1218,7 +1343,7 @@ describe("ConvertV1Profiles tests", () => { }); // call the function that we want to test - let pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"]("PluginNameNotInstalled"); + const pluginInstResult: boolean = ConvertV1Profiles["isPluginInstalled"]("PluginNameNotInstalled"); expect(pluginInstResult).toEqual(false); }); @@ -1344,7 +1469,7 @@ describe("ConvertV1Profiles tests", () => { }); // do not actually read any ProfileInfo from disk - ConvertV1Profiles["profileInfo"] = new ProfileInfo("zowe"); + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") .mockResolvedValue(Promise.resolve()); @@ -1381,7 +1506,7 @@ describe("ConvertV1Profiles tests", () => { }); // do not actually read any ProfileInfo from disk - ConvertV1Profiles["profileInfo"] = new ProfileInfo("zowe"); + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); const fakeErrMsg = "A fake exception from findCredentials"; const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") .mockImplementation(() => { @@ -1414,6 +1539,94 @@ describe("ConvertV1Profiles tests", () => { }); }); // end initCredMgr + describe("loadV1Schemas", () => { + let existsSyncSpy: any; + + beforeAll(() => { + existsSyncSpy = jest.spyOn(fs, "existsSync"); + }); + + beforeEach(() => { + existsSyncSpy.mockClear(); // reset usage counts + + // pretend that our loadedConfig has no schemas in it + jest.spyOn(ImperativeConfig.instance, "loadedConfig", "get").mockReturnValue({} as any); + }); + + afterAll(() => { + // restore original app implementations + existsSyncSpy.mockRestore(); + }); + + it("should load schemas when none exist in ImperativeConfig loadedConfig", () => { + // pretend that the profiles root directory and schema file (xxx_meta.yaml) exist + existsSyncSpy.mockReturnValue(true); + + // pretend that we have profiles and they have schemas + const getAllProfileDirectoriesSpy = jest.spyOn(V1ProfileRead, "getAllProfileDirectories") + .mockReturnValue(["base", "cics", "zosmf"]); + + const readMetaFileSpy = jest.spyOn(V1ProfileRead, "readMetaFile") + .mockReturnValueOnce({ defaultProfile: "base", configuration: "baseSchema" as any }) + .mockReturnValueOnce({ defaultProfile: "cics", configuration: "cicsSchema" as any }) + .mockReturnValueOnce({ defaultProfile: "zosmf", configuration: "zosmfSchema" as any}); + + // call the function that we want to test + ConvertV1Profiles["loadV1Schemas"](); + + expect(existsSyncSpy).toHaveBeenCalled(); + expect(getAllProfileDirectoriesSpy).toHaveBeenCalledTimes(1); + expect(readMetaFileSpy).toHaveBeenCalledTimes(3); + expect(ImperativeConfig.instance.loadedConfig.profiles).toEqual(["baseSchema", "cicsSchema", "zosmfSchema"]); + }); + + it("should catch and report error thrown by readMetaFile", () => { + // pretend that the profiles root directory and schema file (xxx_meta.yaml) exist + existsSyncSpy.mockReturnValue(true); + + // pretend that we have profiles and they have schemas + const getAllProfileDirectoriesSpy = jest.spyOn(V1ProfileRead, "getAllProfileDirectories") + .mockReturnValue(["base", "cics", "zosmf"]); + + const fakeErrMsg = "A fake exception from readMetaFile"; + const readMetaFileSpy = jest.spyOn(V1ProfileRead, "readMetaFile").mockImplementation(() => { + throw new Error(fakeErrMsg); + }); + + // call the function that we want to test + let caughtErr: any; + try { + ConvertV1Profiles["loadV1Schemas"](); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(existsSyncSpy).toHaveBeenCalled(); + expect(getAllProfileDirectoriesSpy).toHaveBeenCalled(); + expect(readMetaFileSpy).toHaveBeenCalledTimes(3); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Failed to load schema for profile type base") || + nextMsg.msgText.includes("Failed to load schema for profile type cics") || + nextMsg.msgText.includes("Failed to load schema for profile type zosmf") + ) { + numMsgsFound++; + } + if (nextMsg.msgText.includes(`Reason: ${fakeErrMsg}`) || + nextMsg.msgText.includes(`Error: ${fakeErrMsg}`) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(9); + + }); + }); // end loadV1Schemas + describe("isZoweKeyRingAvailable", () => { it("should return true if it finds credentials in the vault", async () => { From a00d53d2beb8c16eeb2dbf50e878f0dfa0012740 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Mon, 1 Jul 2024 10:07:26 -0400 Subject: [PATCH 10/16] Mock out configureLogger Signed-off-by: Gene Johnston --- packages/imperative/CHANGELOG.md | 1 - .../src/config/__tests__/ConvertV1Profiles.unit.test.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 0a3d20ccb8..5d12a7692c 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -6,7 +6,6 @@ All notable changes to the Imperative package will be documented in this file. - BugFix: V3 Breaking: Modified the ConvertV1Profiles.convert API to accept a new ProfileInfo option and initialize components sufficiently to enable VSCode apps to convert V1 profiles. [#2170](https://github.com/zowe/zowe-cli/issues/2170) - ## `8.0.0-next.202406201950` - Enhancement: Added `ProfileInfo.profileManagerWillLoad` function to verify the credential manager can load. [#2111](https://github.com/zowe/zowe-cli/issues/2111) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index 0dacf07039..288bcecc09 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -21,6 +21,7 @@ import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error/src/ImperativeError"; import { keyring } from "@zowe/secrets-for-zowe-sdk"; import { Logger } from "../../logger/src/Logger"; +import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { V1ProfileRead } from "../../profiles"; import { ConfigSchema } from "../../config/src/ConfigSchema"; import { AppSettings } from "../../settings/src/AppSettings"; @@ -1416,6 +1417,9 @@ describe("ConvertV1Profiles tests", () => { }) } as any; }); + + // do not attempt to do any logging configuration + LoggingConfigurer.configureLogger = jest.fn(); }); beforeEach(() => { From 742e0662756a5ca6dc47a4c9f907881ede19effa Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Mon, 1 Jul 2024 10:18:44 -0400 Subject: [PATCH 11/16] Update for modified output Signed-off-by: Gene Johnston --- .../convert-profiles/convert-profiles.handler.unit.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 040e44720b..7df77d6800 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 @@ -59,7 +59,7 @@ describe("Configuration Convert Profiles command handler", () => { { msgFormat: 2, msgText: "Error Msg 2" } ], v1ScsPluginName: null as any, - reInitCredMgr: false, + credsWereMigrated: true, cfgFilePathNm: ConvertV1Profiles["noCfgFilePathNm"], numProfilesFound: 0, profilesConverted: { @@ -160,7 +160,7 @@ describe("Configuration Convert Profiles command handler", () => { expect(uninstallSpy).toHaveBeenCalled(); expect(stdout).toContain("Report Msg 1"); expect(stdout).toContain("Report Msg 2"); - expect(stdout).toContain('Uninstalled plug-in "fakeScsPluginName"'); + expect(stdout).toContain('Successfully uninstalled plug-in fakeScsPluginName'); expect(stderr).toContain("Error Msg 1"); expect(stderr).toContain("Error Msg 2"); }); @@ -186,7 +186,7 @@ describe("Configuration Convert Profiles command handler", () => { expect(stdout).toContain("Report Msg 2"); expect(stderr).toContain("Error Msg 1"); expect(stderr).toContain("Error Msg 2"); - expect(stderr).toContain('Failed to uninstall plug-in "fakeScsPluginName"'); + expect(stderr).toContain('Failed to uninstall plug-in fakeScsPluginName'); expect(stderr).toContain(fakeUninstallErr); }); }); From bc95adb6456a7b3551c4b6932f60bea285f4bbdf Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Mon, 1 Jul 2024 10:23:55 -0400 Subject: [PATCH 12/16] Mock out Logger.initLogger Signed-off-by: Gene Johnston --- .../src/config/__tests__/ConvertV1Profiles.unit.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index 288bcecc09..e489cec199 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -1419,6 +1419,7 @@ describe("ConvertV1Profiles tests", () => { }); // do not attempt to do any logging configuration + Logger.initLogger = jest.fn(); LoggingConfigurer.configureLogger = jest.fn(); }); From 0c77d01272b60aa3b4b39ddcd6f1e8fab2063bd8 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Mon, 1 Jul 2024 13:18:13 -0400 Subject: [PATCH 13/16] Create plugins.json during test setup Signed-off-by: Gene Johnston --- .../config/__resources__/plugins/plugins.json | 22 +++++++++++++++++++ ...ig.convert-profiles.integration.subtest.ts | 1 + 2 files changed, 23 insertions(+) create mode 100644 packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json new file mode 100644 index 0000000000..e17d2382f9 --- /dev/null +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/plugins/plugins.json @@ -0,0 +1,22 @@ +{ + "@zowe/secure-credential-store-for-zowe-cli": { + "package": "@zowe/secure-credential-store-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.1.12" + }, + "@zowe/cics-for-zowe-cli": { + "package": "@zowe/cics-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "4.0.11" + }, + "@broadcom/jclcheck-for-zowe-cli": { + "package": "@broadcom/jclcheck-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "1.1.2" + }, + "@broadcom/endevor-for-zowe-cli": { + "package": "@broadcom/endevor-for-zowe-cli@zowe-v1-lts", + "registry": "https://registry.npmjs.org/", + "version": "5.7.6" + } +} diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts index 643ca94ac6..6263057a56 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts @@ -34,6 +34,7 @@ describe("imperative-test-cli config convert-profiles", () => { beforeEach(() => { fsExtra.copySync(__dirname + "/../../config/__resources__/profiles_secured_and_base", TEST_ENVIRONMENT.workingDir + "/profiles"); + fsExtra.copySync(__dirname + "/../../config/__resources__/plugins", TEST_ENVIRONMENT.workingDir + "/plugins"); }); afterEach(() => { From 8ed2deee1ec02233aa4a41c74cdeb3089e72d55c Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Tue, 2 Jul 2024 13:14:53 -0400 Subject: [PATCH 14/16] Remove error on missing plugins dir for VSCode app Signed-off-by: Gene Johnston --- .../__tests__/ConvertV1Profiles.unit.test.ts | 42 ++++++++++++++++++- .../src/config/src/ConvertV1Profiles.ts | 7 +++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index e489cec199..c6d5aed7ff 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -1348,8 +1348,7 @@ describe("ConvertV1Profiles tests", () => { expect(pluginInstResult).toEqual(false); }); - - it("should catch exception from readFileSync and record error", () => { + it("should catch exception from readFileSync and record error for CLI", () => { // pretend that readFileSync throws an error const readFileErrMsg = "readFileSync threw some horrible exception"; readFileSyncSpy.mockImplementation(jest.fn(() => { @@ -1384,6 +1383,45 @@ describe("ConvertV1Profiles tests", () => { } expect(numMsgsFound).toEqual(3); }); + + it("should catch exception from readFileSync but not record error for VSCode app", () => { + // pretend that readFileSync throws an error + const readFileErrMsg = "readFileSync threw some horrible exception"; + readFileSyncSpy.mockImplementation(jest.fn(() => { + throw new Error(readFileErrMsg); + })); + + // pretend that we were called by a VSCode app + ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); + + // call the function that we want to test + const pluginName = "FakePluginName"; + let pluginInstResult: boolean = false; + let caughtErr: any; + try { + pluginInstResult = ConvertV1Profiles["isPluginInstalled"](pluginName); + } catch (err) { + caughtErr = err; + } + + expect(caughtErr).not.toBeDefined(); + expect(pluginInstResult).toEqual(false); + + let numMsgsFound = 0; + for (const nextMsg of ConvertV1Profiles["convertResult"].msgs) { + if (nextMsg.msgFormat & ConvertMsgFmt.ERROR_LINE) { + if (nextMsg.msgText.includes("Cannot read plugins file") && nextMsg.msgText.includes("plugins.json")) { + numMsgsFound++; + } + if ((nextMsg.msgText.includes("Reason: ") || nextMsg.msgText.includes("Error: ")) && + nextMsg.msgText.includes(readFileErrMsg) + ) { + numMsgsFound++; + } + } + } + expect(numMsgsFound).toEqual(0); + }); }); // end isPluginInstalled describe("getOldProfileCount", () => { diff --git a/packages/imperative/src/config/src/ConvertV1Profiles.ts b/packages/imperative/src/config/src/ConvertV1Profiles.ts index 76a89ae394..4669d2f8dd 100644 --- a/packages/imperative/src/config/src/ConvertV1Profiles.ts +++ b/packages/imperative/src/config/src/ConvertV1Profiles.ts @@ -644,7 +644,12 @@ export class ConvertV1Profiles { } } catch (ioErr) { - ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file ${pluginsFileNm}`, ioErr); + // A VSCode extension may legitimately not have any plugins directory. + // However, something is wrong if the CLI does not have the plugins + // directory and file, so display the error. + if (!ConvertV1Profiles.profileInfo) { + ConvertV1Profiles.addExceptionToConvertMsgs(`Cannot read plugins file ${pluginsFileNm}`, ioErr); + } } return false; } From af327842a9b9e5bbcb047feb73734fd90799bd23 Mon Sep 17 00:00:00 2001 From: Gene Johnston Date: Tue, 2 Jul 2024 16:56:17 -0400 Subject: [PATCH 15/16] Reorg spies and mockRestores for timing on different OSes Signed-off-by: Gene Johnston --- .../__tests__/ConvertV1Profiles.unit.test.ts | 167 ++++++++++-------- 1 file changed, 91 insertions(+), 76 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts index c6d5aed7ff..a842f4e953 100644 --- a/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConvertV1Profiles.unit.test.ts @@ -37,18 +37,24 @@ describe("ConvertV1Profiles tests", () => { const appName = "zowe"; beforeAll(() => { - // do not attempt to actually log any errors + // do not attempt to do any logging configuration + Logger.initLogger = jest.fn(); + LoggingConfigurer.configureLogger = jest.fn(); + }); + + beforeEach(() => { + // do not actually log any errors jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ error: jest.fn() } as any); }); describe("convert", () => { - let isConversionNeededSpy: any; - let replaceOldCredMgrOverrideSpy: any; - let initCredMgrSpy: any; - let moveV1ProfilesToConfigFileSpy: any; - let deleteV1ProfilesSpy: any; + let isConversionNeededSpy: any = jest.fn(); + let replaceOldCredMgrOverrideSpy: any = jest.fn(); + let initCredMgrSpy: any = jest.fn(); + let moveV1ProfilesToConfigFileSpy: any = jest.fn(); + let deleteV1ProfilesSpy: any = jest.fn(); beforeAll(() => { // use "any" so that we can call private functions @@ -79,6 +85,15 @@ describe("ConvertV1Profiles tests", () => { jest.clearAllMocks(); // clear our spies usage counts }); + afterAll(() => { + // restore original app implementations + isConversionNeededSpy.mockRestore(); + replaceOldCredMgrOverrideSpy.mockRestore(); + initCredMgrSpy.mockRestore(); + moveV1ProfilesToConfigFileSpy.mockRestore(); + deleteV1ProfilesSpy.mockRestore(); + }); + it("should complete a conversion when all utility functions work", async () => { isConversionNeededSpy.mockReturnValueOnce(true); @@ -213,7 +228,6 @@ describe("ConvertV1Profiles tests", () => { }); // end convert describe("private functions", () => { - let loggerSpy: any; let mockSecureLoad: any; function setCredMgrState(desiredState: string): void { if (desiredState == "works") { @@ -227,15 +241,6 @@ describe("ConvertV1Profiles tests", () => { } as any); } - beforeAll(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - - // do not attempt to actually log any errors - loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ - error: jest.fn() - } as any); - }); - beforeEach(() => { // create the result normally created by the public function convert() ConvertV1Profiles["convertResult"] = { @@ -254,6 +259,12 @@ describe("ConvertV1Profiles tests", () => { }); describe("isConversionNeeded", () => { + let getOldProfileCountSpy: any; + + afterAll(() => { + getOldProfileCountSpy.mockRestore(); // restore original app implementation + }); + it("should return false if a client config exists", async () => { // Pretend that we have a zowe config. Object.defineProperty(ImperativeConfig.instance, "config", { @@ -295,7 +306,7 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that we have no old V1 profiles - const getOldProfileCountSpy = jest.spyOn( + getOldProfileCountSpy = jest.spyOn( ConvertV1Profiles as any, "getOldProfileCount") .mockReturnValueOnce(0); @@ -332,7 +343,7 @@ describe("ConvertV1Profiles tests", () => { const noDirError = new ImperativeError({ additionalDetails: { code: 'ENOENT' } } as any); - const getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") + getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") .mockImplementationOnce(() => { throw noDirError; }); // call the function that we want to test @@ -370,7 +381,7 @@ describe("ConvertV1Profiles tests", () => { const ioError = new ImperativeError({ msg: ioErrMsg }); - const getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") + getOldProfileCountSpy = jest.spyOn(ConvertV1Profiles as any, "getOldProfileCount") .mockImplementationOnce(() => { throw ioError; }); // call the function that we want to test @@ -405,7 +416,7 @@ describe("ConvertV1Profiles tests", () => { }); // pretend that we have 6 old V1 profiles - const getOldProfileCountSpy = jest.spyOn( + getOldProfileCountSpy = jest.spyOn( ConvertV1Profiles as any, "getOldProfileCount") .mockReturnValueOnce(6); @@ -418,6 +429,14 @@ describe("ConvertV1Profiles tests", () => { }); // end isConversionNeeded describe("moveV1ProfilesToConfigFile", () => { + let convertPropNamesSpy: any = jest.fn(); + let createNewConfigFileSpy: any = jest.fn(); + + afterAll(() => { + // restore original app implementations + convertPropNamesSpy.mockRestore(); + createNewConfigFileSpy.mockRestore(); + }); it("should successfully move multiple v1 profiles to a config file", async () => { jest.spyOn(V1ProfileRead, "getAllProfileDirectories").mockReturnValueOnce(["fruit", "nut"]); @@ -434,9 +453,9 @@ describe("ConvertV1Profiles tests", () => { .mockReturnValueOnce({ unitPrice: 1 }) .mockReturnValueOnce({ unitPrice: 5 }) .mockReturnValueOnce({ unitPrice: 2 }); - jest.spyOn(ConvertV1Profiles as any, "convertPropNames") + convertPropNamesSpy = jest.spyOn(ConvertV1Profiles as any, "convertPropNames") .mockImplementation(jest.fn()); - jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") + createNewConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") .mockImplementation(jest.fn()); // Avoid using the real secure credMgr. Pretend it works. @@ -474,9 +493,9 @@ describe("ConvertV1Profiles tests", () => { .mockImplementationOnce(() => ({ color: "green", secret: "managed by A" })) .mockImplementationOnce(() => { throw profileError; }) .mockImplementationOnce(() => ({ color: "brown", secret: "managed by C" })); - jest.spyOn(ConvertV1Profiles as any, "convertPropNames") + convertPropNamesSpy = jest.spyOn(ConvertV1Profiles as any, "convertPropNames") .mockImplementation(jest.fn()); - jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") + createNewConfigFileSpy = jest.spyOn(ConvertV1Profiles as any, "createNewConfigFile") .mockImplementation(jest.fn()); // Avoid using the real secure credMgr. Pretend it fails. @@ -529,7 +548,6 @@ describe("ConvertV1Profiles tests", () => { }, autoStore: true }; - jest.restoreAllMocks(); // put spies back to original app implementation // call the function that we want to test ConvertV1Profiles["convertPropNames"](testConfig); @@ -562,16 +580,14 @@ describe("ConvertV1Profiles tests", () => { autoStore: true }; - let loadV1SchemasSpy:any; + let loadV1SchemasSpy: any = jest.fn(); + let updateSchemaSpy: any = jest.fn(); let activateSpy: any; let mergeSpy: any; let saveSpy: any; - let updateSchemaSpy: any; let layerActiveSpy: any; beforeAll(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - // Pretend that our utility functions work. activateSpy = jest.fn(); mergeSpy = jest.fn(); @@ -800,6 +816,17 @@ describe("ConvertV1Profiles tests", () => { }); // end putCfgFileNmInResult describe("deleteV1Profiles", () => { + let isZoweKeyRingAvailableSpy: any = jest.fn(); + let findOldSecurePropsSpy: any = jest.fn(); + let deleteOldSecurePropsSpy: any = jest.fn(); + + afterAll(() => { + // restore original app implementations + isZoweKeyRingAvailableSpy.mockRestore(); + findOldSecurePropsSpy.mockRestore(); + deleteOldSecurePropsSpy.mockRestore(); + }); + const oldProfileDir = "/fake/path/to/profiles-old"; let existsSyncSpy: any; let removeSyncSpy: any; @@ -827,7 +854,7 @@ describe("ConvertV1Profiles tests", () => { it("should delete the old v1 profiles directory", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists @@ -850,7 +877,7 @@ describe("ConvertV1Profiles tests", () => { it("should report that the old v1 profiles directory does not exist", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory not exist @@ -873,7 +900,7 @@ describe("ConvertV1Profiles tests", () => { it("should catch and report a problem when remove throws an error", async () => { // pretend that we found no secure property names under any old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValue(Promise.resolve([])); // pretend that the profiles directory exists @@ -910,15 +937,15 @@ describe("ConvertV1Profiles tests", () => { it("should also delete credentials stored by old SCS plugin", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) .mockResolvedValue(Promise.resolve([])); - jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") + deleteOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") .mockResolvedValue(Promise.resolve(true)); // pretend that the profiles directory exists @@ -946,16 +973,16 @@ describe("ConvertV1Profiles tests", () => { it("should report an error when we fail to delete secure credentials", async () => { // pretend that the zowe keyring is available - jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(true)); // pretend that we found secure property names under one old-school service - jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") + findOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "findOldSecureProps") .mockResolvedValueOnce(Promise.resolve(["secureUser", "securePassword"])) .mockResolvedValue(Promise.resolve([])); // pretend that secure credential deletion failed - jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") + deleteOldSecurePropsSpy = jest.spyOn(ConvertV1Profiles as any, "deleteOldSecureProps") .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists @@ -982,7 +1009,7 @@ describe("ConvertV1Profiles tests", () => { it("should only report directory deletion when zowe keyring is unavailable", async () => { // pretend that the zowe keyring is unavailable - const checkKeyRingSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") + isZoweKeyRingAvailableSpy = jest.spyOn(ConvertV1Profiles as any, "isZoweKeyRingAvailable") .mockResolvedValue(Promise.resolve(false)); // pretend that the profiles directory exists @@ -1000,7 +1027,6 @@ describe("ConvertV1Profiles tests", () => { } expect(ConvertV1Profiles["convertResult"].msgs.length).toEqual(1); expect(numDirDelMsgs).toEqual(1); - checkKeyRingSpy.mockRestore(); // restore original app implementation }); }); // end deleteV1Profiles @@ -1444,26 +1470,10 @@ describe("ConvertV1Profiles tests", () => { }); // end getOldProfileCount describe("initCredMgr", () => { - let logMsg: string; - - beforeAll(() => { - // change logger spy to record the message - loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockImplementation(() => { - return { - error: jest.fn((errMsg) => { - logMsg = errMsg; - }) - } as any; - }); - - // do not attempt to do any logging configuration - Logger.initLogger = jest.fn(); - LoggingConfigurer.configureLogger = jest.fn(); - }); + let readProfilesFromDiskSpy: any = jest.fn(); beforeEach(() => { - // Reset the messages that have been logged or reported - logMsg = "Nothing logged"; + // Reset the messages that have been reported ConvertV1Profiles["convertResult"].msgs = []; }); @@ -1472,13 +1482,21 @@ describe("ConvertV1Profiles tests", () => { }); afterAll(() => { - // restore the logger spy back to doing nothing - loggerSpy = jest.spyOn(Logger, "getImperativeLogger").mockReturnValue({ - error: jest.fn() - } as any); + // restore original app implementations + readProfilesFromDiskSpy.mockRestore(); }); it("should detect when credMgr has already been initialized", async () => { + // change logger spy to record the message + let logMsg: string = "Nothing logged"; + jest.spyOn(Logger, "getImperativeLogger").mockImplementation(() => { + return { + error: jest.fn((errMsg) => { + logMsg = errMsg; + }) + } as any; + }); + // pretend that credMgr has been initialized. let initializedWasCalled = false; Object.defineProperty(CredentialManagerFactory, "initialized", { @@ -1511,14 +1529,19 @@ describe("ConvertV1Profiles tests", () => { }) }); - // do not actually read any ProfileInfo from disk + // pretend that the SCS plugin was configured as the credMgr + ConvertV1Profiles["oldScsPluginWasConfigured"] = true; + + // pretend that our caller supplied ProfileInfo ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); - const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") - .mockResolvedValue(Promise.resolve()); + + // do not actually read any ProfileInfo from disk + readProfilesFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], + "readProfilesFromDisk").mockResolvedValue(Promise.resolve()); // call the function that we want to test await ConvertV1Profiles["initCredMgr"](); - expect(readFromDiskSpy).toHaveBeenCalled(); + expect(readProfilesFromDiskSpy).toHaveBeenCalled(); }); it("should call overridesLoader when ProfileInfo is NOT supplied", async () => { @@ -1551,7 +1574,7 @@ describe("ConvertV1Profiles tests", () => { // do not actually read any ProfileInfo from disk ConvertV1Profiles["profileInfo"] = new ProfileInfo(appName); const fakeErrMsg = "A fake exception from findCredentials"; - const readFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") + readProfilesFromDiskSpy = jest.spyOn(ConvertV1Profiles["profileInfo"], "readProfilesFromDisk") .mockImplementation(() => { throw new Error(fakeErrMsg); }); @@ -1564,7 +1587,7 @@ describe("ConvertV1Profiles tests", () => { caughtErr = err; } - expect(readFromDiskSpy).toHaveBeenCalled(); + expect(readProfilesFromDiskSpy).toHaveBeenCalled(); expect(caughtErr).not.toBeDefined(); let numMsgsFound = 0; @@ -1722,10 +1745,6 @@ describe("ConvertV1Profiles tests", () => { describe("findOldSecureProps", () => { - beforeEach(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - }); - it("should find old secure properties", async () => { // pretend that findCredentials found a bunch of accounts and passwords const origZoweKeyRing = ConvertV1Profiles["zoweKeyRing"]; @@ -1786,10 +1805,6 @@ describe("ConvertV1Profiles tests", () => { describe("deleteOldSecureProps", () => { - beforeEach(() => { - jest.restoreAllMocks(); // put spies back to original app implementation - }); - it("should delete the specified secure property", async () => { // pretend that we successfully deleted the secure property ConvertV1Profiles["zoweKeyRing"] = { From 8a4726208a3e150a1ab0d148a8cd53650e8217be Mon Sep 17 00:00:00 2001 From: zowe-robot Date: Fri, 5 Jul 2024 17:17:34 +0000 Subject: [PATCH 16/16] Bump version to 8.0.0-next.202407051717 [ci skip] Signed-off-by: zowe-robot --- .../__packages__/cli-test-utils/package.json | 4 +- lerna.json | 2 +- npm-shrinkwrap.json | 122 +++++++++--------- packages/cli/package.json | 28 ++-- packages/core/package.json | 6 +- packages/imperative/CHANGELOG.md | 2 +- packages/imperative/package.json | 4 +- packages/provisioning/package.json | 8 +- packages/secrets/package.json | 2 +- packages/workflows/package.json | 10 +- packages/zosconsole/package.json | 8 +- packages/zosfiles/package.json | 10 +- packages/zosjobs/package.json | 10 +- packages/zoslogs/package.json | 8 +- packages/zosmf/package.json | 8 +- packages/zostso/package.json | 10 +- packages/zosuss/package.json | 6 +- 17 files changed, 124 insertions(+), 124 deletions(-) diff --git a/__tests__/__packages__/cli-test-utils/package.json b/__tests__/__packages__/cli-test-utils/package.json index 4e72cab7f0..a83dddbe8d 100644 --- a/__tests__/__packages__/cli-test-utils/package.json +++ b/__tests__/__packages__/cli-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Test utilities package for Zowe CLI plug-ins", "author": "Zowe", "license": "EPL-2.0", @@ -43,7 +43,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/lerna.json b/lerna.json index 8849da3e0f..5c27a61411 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "command": { "publish": { "ignoreChanges": [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d0a4f6123e..83710a78e3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -52,7 +52,7 @@ }, "__tests__/__packages__/cli-test-utils": { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "find-up": "^5.0.0", @@ -63,7 +63,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" @@ -16341,21 +16341,21 @@ }, "packages/cli": { "name": "@zowe/cli", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -16368,7 +16368,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", + "@zowe/cli-test-utils": "8.0.0-next.202407051717", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" @@ -16377,7 +16377,7 @@ "node": ">=18.12.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" } }, "packages/cli/node_modules/brace-expansion": { @@ -16424,15 +16424,15 @@ }, "packages/core": { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "comment-json": "~4.2.3", "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16443,7 +16443,7 @@ }, "packages/imperative": { "name": "@zowe/imperative", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "@types/yargs": "^17.0.32", @@ -16497,7 +16497,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", @@ -16646,16 +16646,16 @@ }, "packages/provisioning": { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "js-yaml": "^4.1.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16667,7 +16667,7 @@ }, "packages/secrets": { "name": "@zowe/secrets-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "hasInstallScript": true, "license": "EPL-2.0", "devDependencies": { @@ -16680,15 +16680,15 @@ }, "packages/workflows": { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16700,12 +16700,12 @@ }, "packages/zosconsole": { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16717,16 +16717,16 @@ }, "packages/zosfiles": { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16758,15 +16758,15 @@ }, "packages/zosjobs": { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16778,12 +16778,12 @@ }, "packages/zoslogs": { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16795,12 +16795,12 @@ }, "packages/zosmf": { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16812,15 +16812,15 @@ }, "packages/zostso": { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" @@ -16832,15 +16832,15 @@ }, "packages/zosuss": { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "license": "EPL-2.0", "dependencies": { "ssh2": "^1.15.0" }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index aef839e916..5762a5938d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "zoweVersion": "V3", "description": "Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.", "author": "Zowe", @@ -58,17 +58,17 @@ "preshrinkwrap": "node ../../scripts/rewriteShrinkwrap.js" }, "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -78,13 +78,13 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", + "@zowe/cli-test-utils": "8.0.0-next.202407051717", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" }, "engines": { "node": ">=18.12.0" diff --git a/packages/core/package.json b/packages/core/package.json index cac9554a65..1c07861c3a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Core libraries shared by Zowe SDK packages", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 0753528924..54a106171d 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the Imperative package will be documented in this file. -## Recent Changes +## `8.0.0-next.202407051717` - BugFix: V3 Breaking: Modified the ConvertV1Profiles.convert API to accept a new ProfileInfo option and initialize components sufficiently to enable VSCode apps to convert V1 profiles. [#2170](https://github.com/zowe/zowe-cli/issues/2170) diff --git a/packages/imperative/package.json b/packages/imperative/package.json index e91ad5c51a..0376a3b00f 100644 --- a/packages/imperative/package.json +++ b/packages/imperative/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/imperative", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "framework for building configurable CLIs", "author": "Zowe", "license": "EPL-2.0", @@ -94,7 +94,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407021516", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", diff --git a/packages/provisioning/package.json b/packages/provisioning/package.json index d5ab141ff5..86888b30d8 100644 --- a/packages/provisioning/package.json +++ b/packages/provisioning/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS provisioning APIs", "author": "Zowe", "license": "EPL-2.0", @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/secrets/package.json b/packages/secrets/package.json index 697c0ed9a3..25fdb5d971 100644 --- a/packages/secrets/package.json +++ b/packages/secrets/package.json @@ -3,7 +3,7 @@ "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", "repository": "https://github.com/zowe/zowe-cli.git", "author": "Zowe", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", "bugs": { "url": "https://github.com/zowe/zowe-cli/issues" diff --git a/packages/workflows/package.json b/packages/workflows/package.json index 8d798d4078..6756a80d57 100644 --- a/packages/workflows/package.json +++ b/packages/workflows/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS workflows APIs", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosconsole/package.json b/packages/zosconsole/package.json index 98e56a3df5..abf58e59db 100644 --- a/packages/zosconsole/package.json +++ b/packages/zosconsole/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS console", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosfiles/package.json b/packages/zosfiles/package.json index ebcc2d0e17..2c4574d1e0 100644 --- a/packages/zosfiles/package.json +++ b/packages/zosfiles/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with files and data sets on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,10 +49,10 @@ "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosjobs/package.json b/packages/zosjobs/package.json index 9c6f893a54..455536a3a7 100644 --- a/packages/zosjobs/package.json +++ b/packages/zosjobs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with jobs on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -46,12 +46,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zoslogs/package.json b/packages/zoslogs/package.json index a4d9efc176..f1b7da5a79 100644 --- a/packages/zoslogs/package.json +++ b/packages/zoslogs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS logs", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosmf/package.json b/packages/zosmf/package.json index 17b31fe51c..eb6dd8c5cc 100644 --- a/packages/zosmf/package.json +++ b/packages/zosmf/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with the z/OS Management Facility", "author": "Zowe", "license": "EPL-2.0", @@ -44,9 +44,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zostso/package.json b/packages/zostso/package.json index 2df023715f..829fcd438b 100644 --- a/packages/zostso/package.json +++ b/packages/zostso/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with TSO on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407021516" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosuss/package.json b/packages/zosuss/package.json index 8882f5b1af..5a6790754c 100644 --- a/packages/zosuss/package.json +++ b/packages/zosuss/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407021516", + "version": "8.0.0-next.202407051717", "description": "Zowe SDK to interact with USS on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407021516", - "@zowe/imperative": "8.0.0-next.202407021516" + "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/imperative": "8.0.0-next.202407051717" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next"