diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 63e3cae2c0..2037208b9f 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -12,7 +12,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed issue where creating a new team configuration file could cause Zowe Explorer to crash, resulting in all sessions disappearing from trees. [#2906](https://github.com/zowe/zowe-explorer-vscode/issues/2906) - Fixed data set not opening when the token has expired. [#3001](https://github.com/zowe/zowe-explorer-vscode/issues/3001) -- Fixed JSON errors being ignored when `zowe.config.json` files change on disk and are reloaded. [#3066](https://github.com/zowe/zowe-explorer-vscode/issues/3066) +- Fixed JSON errors being ignored when `zowe.config.json` files change on disk and are reloaded. [#3066](https://github.com/zowe/zowe-explorer-vscode/issues/3066) [#3074](https://github.com/zowe/zowe-explorer-vscode/issues/3074) ## `2.17.0` diff --git a/packages/zowe-explorer/__mocks__/vscode.ts b/packages/zowe-explorer/__mocks__/vscode.ts index 2641a29c51..992a04f39e 100644 --- a/packages/zowe-explorer/__mocks__/vscode.ts +++ b/packages/zowe-explorer/__mocks__/vscode.ts @@ -809,3 +809,5 @@ export enum DiagnosticSeverity { Information = 2, Hint = 3, } + +export class Selection {} diff --git a/packages/zowe-explorer/__tests__/__unit__/Profiles.extended.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/Profiles.extended.unit.test.ts index a6120b90a8..cf4cfd5252 100644 --- a/packages/zowe-explorer/__tests__/__unit__/Profiles.extended.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/Profiles.extended.unit.test.ts @@ -137,10 +137,6 @@ async function createGlobalMocks() { Object.defineProperty(ZoweLogger, "trace", { value: jest.fn(), configurable: true }); newMocks.mockProfileInstance = new Profiles(newMocks.log); - Object.defineProperty(Profiles, "CreateInstance", { - value: () => newMocks.mockProfileInstance, - configurable: true, - }); Object.defineProperty(Profiles, "getInstance", { value: () => newMocks.mockProfileInstance, configurable: true, @@ -250,6 +246,17 @@ describe("Profiles Unit Test - Function createInstance", () => { expect(mockWorkspaceFolders).toHaveBeenCalledTimes(1); expect(profilesInstance.cwd).toBe("fakePath"); }); + + it("Tests that createInstance catches error and logs it", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(Profiles.prototype, "refresh").mockRejectedValueOnce(new Error("test error")); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); + await expect(Profiles.createInstance(globalMocks.log)).resolves.not.toThrow(); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); + }); }); describe("Profiles Unit Tests - Function createNewConnection for v1 Profiles", () => { @@ -583,6 +590,7 @@ describe("Profiles Unit Tests - Function createZoweSession", () => { jest.spyOn(Gui, "resolveQuickPick").mockResolvedValueOnce(new utils.FilterDescriptor("Test1")); jest.spyOn(Gui, "resolveQuickPick").mockResolvedValueOnce(new utils.FilterDescriptor("Test2")); jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({ + getAllProfiles: jest.fn().mockReturnValue([]), usingTeamConfig: false, } as any); jest.spyOn(Gui, "showInputBox").mockResolvedValue("test"); @@ -617,7 +625,7 @@ describe("Profiles Unit Tests - Function createZoweSession", () => { spyInfo.mockClear(); }); - it("Tests that createZoweSession catches error and log warning", async () => { + it("Tests that createZoweSession catches error and logs it", async () => { const globalMocks = await createGlobalMocks(); jest.spyOn(Gui, "createQuickPick").mockReturnValue({ show: jest.fn(), @@ -626,11 +634,12 @@ describe("Profiles Unit Tests - Function createZoweSession", () => { } as any); jest.spyOn(Gui, "resolveQuickPick").mockResolvedValueOnce(new utils.FilterDescriptor("Test")); jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockRejectedValueOnce(new Error("test error")); - const warnSpy = jest.spyOn(ZoweLogger, "warn"); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); await expect(Profiles.getInstance().createZoweSession(globalMocks.testUSSTree)).resolves.not.toThrow(); - expect(warnSpy).toBeCalledTimes(1); - expect(warnSpy).toBeCalledWith(Error("test error")); - warnSpy.mockClear(); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); }); }); @@ -1099,6 +1108,17 @@ describe("Profiles Unit Tests - function promptCredentials", () => { jest.spyOn(Profiles.getInstance(), "updateProfilesArrays").mockImplementation(); await expect(Profiles.getInstance().promptCredentials("secure_config_props")).resolves.toEqual(["test", "12345", "encodedAuth"]); }); + + it("Tests that promptCredentials catches error and logs it", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockRejectedValueOnce(new Error("test error")); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); + await expect(Profiles.getInstance().promptCredentials(globalMocks.testProfile)).resolves.not.toThrow(); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); + }); }); describe("Profiles Unit Tests - function getDeleteProfile", () => { @@ -1342,6 +1362,24 @@ describe("Profiles Unit Tests - function deleteProfile", () => { await expect(Profiles.getInstance().deleteProfile(datasetTree, ussTree, jobsTree, testNode)).resolves.not.toThrow(); }); + + it("Tests that deleteProfile catches error and logs it", async () => { + const globalMocks = await createGlobalMocks(); + const datasetSessionNode = createDatasetSessionNode(globalMocks.testSession, globalMocks.testProfile); + const datasetTree = createDatasetTree(datasetSessionNode, globalMocks.testProfile); + const ussSessionNode = [createUSSSessionNode(globalMocks.testSession, globalMocks.testProfile)]; + const ussTree = createUSSTree([], ussSessionNode); + const jobsTree = createJobsTree(globalMocks.testSession, createIJobObject(), globalMocks.testProfile, createTreeView()); + + jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockRejectedValueOnce(new Error("test error")); + jest.spyOn(Profiles.getInstance(), "getDeleteProfile").mockResolvedValue(globalMocks.testProfile); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); + await expect(Profiles.getInstance().deleteProfile(datasetTree, ussTree, jobsTree)).resolves.not.toThrow(); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); + }); }); describe("Profiles Unit Tests - function checkCurrentProfile", () => { @@ -1415,13 +1453,23 @@ describe("Profiles Unit Tests - function checkCurrentProfile", () => { setupProfilesCheck(globalMocks); await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "inactive" }); }); - it("should throw an error if using token auth and is logged out or has expired token", async () => { + it("should show as unverified if using token auth and is logged out or has expired token", async () => { const globalMocks = await createGlobalMocks(); jest.spyOn(utils, "errorHandling").mockImplementation(); - jest.spyOn(utils.ProfilesUtils, "isUsingTokenAuth").mockResolvedValue(true); + jest.spyOn(utils.ProfilesUtils, "isUsingTokenAuth").mockResolvedValueOnce(true); setupProfilesCheck(globalMocks); await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "unverified" }); }); + it("should show as unverified if profiles fail to load", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockRejectedValueOnce(new Error("test error")); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); + await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "unverified" }); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); + }); }); describe("Profiles Unit Tests - function editSession", () => { @@ -1491,6 +1539,17 @@ describe("Profiles Unit Tests - function editSession", () => { base64EncodedAuth: "base64Auth", }); }); + + it("Tests that editSession catches error and logs it", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockRejectedValueOnce(new Error("test error")); + jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce(""); + const errorSpy = jest.spyOn(ZoweLogger, "error"); + await expect(Profiles.getInstance().editSession(globalMocks.testProfile, globalMocks.testProfile.name)).resolves.not.toThrow(); + expect(errorSpy).toBeCalledTimes(1); + expect(errorSpy).toBeCalledWith(Error("test error")); + errorSpy.mockClear(); + }); }); describe("Profiles Unit Tests - function getProfileSetting", () => { diff --git a/packages/zowe-explorer/__tests__/__unit__/ZoweExplorerExtender.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/ZoweExplorerExtender.unit.test.ts index b1dffd1eb9..d81f948e75 100644 --- a/packages/zowe-explorer/__tests__/__unit__/ZoweExplorerExtender.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/ZoweExplorerExtender.unit.test.ts @@ -148,7 +148,7 @@ describe("ZoweExplorerExtender unit tests", () => { ZoweExplorerExtender.createInstance(); Object.defineProperty(vscode.Uri, "file", { value: jest.fn(), configurable: true }); - Object.defineProperty(Gui, "showTextDocument", { value: jest.fn(), configurable: true }); + const showTextDocumentSpy = jest.spyOn(Gui, "showTextDocument").mockResolvedValue({} as any); const zoweDir = getZoweDir(); const userInputs = [ @@ -167,8 +167,9 @@ describe("ZoweExplorerExtender unit tests", () => { }, { choice: "Show Config", - configError: `Error parsing JSON in the file '${path.join(zoweDir, "zowe.config.user.json")}'`, + configError: `Error parsing JSON in the file '${path.join(zoweDir, "zowe.config.user.json")}' at Line 4, Column 0`, shouldFail: false, + shouldNavigate: true, fileChecks: ["zowe.config.user.json"], mockExistsSync: blockMocks.mockExistsSync.mockImplementationOnce, }, @@ -176,7 +177,7 @@ describe("ZoweExplorerExtender unit tests", () => { choice: "Show Config", configError: "Error parsing JSON", fileChecks: ["zowe.config.user.json", "zowe.config.json"], - shouldFail: false, + shouldFail: true, mockExistsSync: blockMocks.mockExistsSync.mockImplementationOnce, }, { @@ -188,18 +189,20 @@ describe("ZoweExplorerExtender unit tests", () => { }, ]; for (const userInput of userInputs) { + showTextDocumentSpy.mockClear(); blockMocks.mockErrorMessage.mockImplementationOnce((_msg, ..._items) => Promise.resolve(userInput.choice)); if (userInput.fileChecks.length > 1) { userInput.mockExistsSync((_path) => false); } - await ZoweExplorerExtender.showZoweConfigError(userInput.configError); + ZoweExplorerExtender.showZoweConfigError(userInput.configError); + await new Promise((resolve) => process.nextTick(() => resolve())); expect(blockMocks.mockErrorMessage).toHaveBeenCalledWith( 'Error encountered when loading your Zowe config. Click "Show Config" for more details.', undefined, "Show Config" ); if (userInput.choice == null) { - expect(Gui.showTextDocument).not.toHaveBeenCalled(); + expect(showTextDocumentSpy).not.toHaveBeenCalled(); } else { if (userInput.v1) { expect(vscode.Uri.file).toHaveBeenCalledWith(path.join(zoweDir, "profiles", "exampleType", "exampleType_meta.yaml")); @@ -209,9 +212,12 @@ describe("ZoweExplorerExtender unit tests", () => { } } if (userInput.shouldFail) { - expect(Gui.showTextDocument).not.toHaveBeenCalled(); + expect(showTextDocumentSpy).not.toHaveBeenCalled(); } else { - expect(Gui.showTextDocument).toHaveBeenCalled(); + expect(showTextDocumentSpy).toHaveBeenCalled(); + if (userInput.shouldNavigate) { + expect((await showTextDocumentSpy.mock.results[0].value).selection).toBeDefined(); + } } } } diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index 7e98d33e44..88eec5a8a1 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -825,7 +825,7 @@ describe("ProfilesUtils unit tests", () => { expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(1); }); - it("should throw new error if error is not an instance of ProfInfoErr", async () => { + it("should ignore error if it is not an instance of ProfInfoErr", async () => { const expectedErrorMsg = "Another error unrelated to credential management"; getDirectValueSpy.mockReturnValueOnce(false); getCredentialManagerOverrideSpy.mockReturnValue("@zowe/cli"); @@ -836,7 +836,7 @@ describe("ProfilesUtils unit tests", () => { readProfilesFromDiskSpy.mockImplementation(() => { throw new Error(expectedErrorMsg); }); - await expect(profUtils.ProfilesUtils.getProfileInfo(false)).rejects.toThrow(expectedErrorMsg); + await expect(profUtils.ProfilesUtils.getProfileInfo(false)).resolves.not.toThrow(); expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/zowe-explorer/src/Profiles.ts b/packages/zowe-explorer/src/Profiles.ts index 6e48ab8e59..1b3058c31b 100644 --- a/packages/zowe-explorer/src/Profiles.ts +++ b/packages/zowe-explorer/src/Profiles.ts @@ -53,7 +53,12 @@ export class Profiles extends ProfilesCache { // Processing stops if there are no profiles detected public static async createInstance(log: zowe.imperative.Logger): Promise { Profiles.loader = new Profiles(log, vscode.workspace.workspaceFolders?.[0]?.uri.fsPath); - await Profiles.loader.refresh(ZoweExplorerApiRegister.getInstance()); + try { + await Profiles.loader.refresh(ZoweExplorerApiRegister.getInstance()); + } catch (err) { + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + } return Profiles.loader; } @@ -99,7 +104,14 @@ export class Profiles extends ProfilesCache { public async checkCurrentProfile(theProfile: zowe.imperative.IProfileLoaded): Promise { ZoweLogger.trace("Profiles.checkCurrentProfile called."); let profileStatus: IProfileValidation; - const usingTokenAuth = await ProfilesUtils.isUsingTokenAuth(theProfile.name); + let usingTokenAuth: boolean; + try { + usingTokenAuth = await ProfilesUtils.isUsingTokenAuth(theProfile.name); + } catch (err) { + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + return { name: theProfile.name, status: "unverified" }; + } if (usingTokenAuth && !theProfile.profile.tokenType) { const error = new zowe.imperative.ImperativeError({ @@ -337,13 +349,15 @@ export class Profiles extends ProfilesCache { let mProfileInfo: zowe.imperative.ProfileInfo; try { mProfileInfo = await this.getProfileInfo(); - const profAllAttrs = mProfileInfo.getAllProfiles(); - for (const pName of profileNamesList) { - const osLocInfo = mProfileInfo.getOsLocInfo(profAllAttrs.find((p) => p.profName === pName)); - items.push(new FilterItem({ text: pName, icon: this.getProfileIcon(osLocInfo)[0] })); - } } catch (err) { - ZoweLogger.warn(err); + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + return; + } + const profAllAttrs = mProfileInfo.getAllProfiles(); + for (const pName of profileNamesList) { + const osLocInfo = mProfileInfo.getOsLocInfo(profAllAttrs.find((p) => p.profName === pName)); + items.push(new FilterItem({ text: pName, icon: this.getProfileIcon(osLocInfo)[0] })); } const quickpick = Gui.createQuickPick(); @@ -406,6 +420,7 @@ export class Profiles extends ProfilesCache { } catch (error) { ZoweLogger.error(error); ZoweExplorerExtender.showZoweConfigError(error.message); + return; } if (config.usingTeamConfig) { const profiles = config.getAllProfiles(); @@ -468,7 +483,15 @@ export class Profiles extends ProfilesCache { public async editSession(profileLoaded: zowe.imperative.IProfileLoaded, profileName: string): Promise { ZoweLogger.trace("Profiles.editSession called."); - if ((await this.getProfileInfo()).usingTeamConfig) { + let usingTeamConfig: boolean; + try { + usingTeamConfig = (await this.getProfileInfo()).usingTeamConfig; + } catch (err) { + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + return; + } + if (usingTeamConfig) { const currentProfile = await this.getProfileFromConfig(profileLoaded.name); const filePath = currentProfile.profLoc.osLoc[0]; await this.openConfigFile(filePath); @@ -926,13 +949,21 @@ export class Profiles extends ProfilesCache { placeHolder: localize("promptCredentials.passwordInputBoxOptions.placeholder", "Password"), prompt: localize("promptCredentials.passwordInputBoxOptions.prompt", "Enter the password for the connection. Leave blank to not store."), }; + let mProfileInfo: zowe.imperative.ProfileInfo; + try { + mProfileInfo = await this.getProfileInfo(); + } catch (err) { + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + return; + } const promptInfo = await ZoweVsCodeExtension.updateCredentials( { profile: typeof profile === "string" ? undefined : profile, sessionName: typeof profile === "string" ? profile : undefined, rePrompt, - secure: (await this.getProfileInfo()).isSecured(), + secure: mProfileInfo.isSecured(), userInputBoxOptions, passwordInputBoxOptions, }, @@ -993,8 +1024,16 @@ export class Profiles extends ProfilesCache { } const deleteLabel = deletedProfile.name; + let usingTeamConfig: boolean; + try { + usingTeamConfig = (await this.getProfileInfo()).usingTeamConfig; + } catch (err) { + ZoweLogger.error(err); + ZoweExplorerExtender.showZoweConfigError(err.message); + return; + } - if ((await this.getProfileInfo()).usingTeamConfig) { + if (usingTeamConfig) { const currentProfile = await this.getProfileFromConfig(deleteLabel); const filePath = currentProfile.profLoc.osLoc[0]; await this.openConfigFile(filePath); diff --git a/packages/zowe-explorer/src/ZoweExplorerExtender.ts b/packages/zowe-explorer/src/ZoweExplorerExtender.ts index d60dbef8b2..659b3f96e9 100644 --- a/packages/zowe-explorer/src/ZoweExplorerExtender.ts +++ b/packages/zowe-explorer/src/ZoweExplorerExtender.ts @@ -82,7 +82,14 @@ export class ZoweExplorerExtender implements ZoweExplorerApi.IApiExplorerExtende return; } } - Gui.showTextDocument(vscode.Uri.file(configPath)); + + Gui.showTextDocument(vscode.Uri.file(configPath)).then((editor) => { + const errorLocation = errorDetails.match(/Line (\d+), Column (\d+)/); + if (errorLocation != null) { + const position = new vscode.Position(+errorLocation[1] - 1, +errorLocation[2]); + editor.selection = new vscode.Selection(position, position); + } + }); }); } diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index db9c6d3a13..11a94b5a39 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -375,13 +375,10 @@ export class ProfilesUtils { await profileInfo.readProfilesFromDisk(); return profileInfo; } catch (err) { - if (err instanceof imperative.ProfInfoErr) { - if (err.errorCode === imperative.ProfInfoErr.LOAD_CRED_MGR_FAILED) { - await ProfilesUtils.promptAndDisableCredentialManagement(); - return; - } + if (err instanceof imperative.ProfInfoErr && err.errorCode === imperative.ProfInfoErr.LOAD_CRED_MGR_FAILED) { + await ProfilesUtils.promptAndDisableCredentialManagement(); } - throw err; + // Ignore other types of errors since they will be handled later } }