diff --git a/packages/zowe-explorer-api/__tests__/__unit__/profiles/AuthHandler.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/profiles/AuthHandler.unit.test.ts index 90a2323ad8..472e123472 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/profiles/AuthHandler.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/profiles/AuthHandler.unit.test.ts @@ -72,66 +72,12 @@ describe("AuthHandler.lockProfile", () => { }); }); -describe("AuthHandler.updateTreeProvidersWithProfile", () => { - function getBlockMocks(nodeInChildren: boolean = false) { - const setProfileToChoice = jest.fn(); - const dummyNode = nodeInChildren - ? { - label: "lpar.zosmf", - setProfileToChoice, - } - : undefined; - const children = [dummyNode].filter(Boolean); - const getChildren = jest.fn().mockReturnValue(children); - const treeProviders = { - ds: { - getChildren, - }, - uss: { - getChildren, - }, - job: { - getChildren, - }, - }; - return { - children, - dummyNode, - executeCommand: jest.spyOn(commands, "executeCommand").mockResolvedValueOnce(treeProviders), - getChildren, - setProfileToChoice, - treeProviders, - }; - } - it("calls node.setProfileToChoice on matching profile nodes in each provider", async () => { - const blockMocks = getBlockMocks(true); - const profile: imperative.IProfileLoaded = { name: "lpar.zosmf", type: "zosmf", message: "", failNotFound: true }; - await (AuthHandler as any).updateTreeProvidersWithProfile(profile); - // getChildren called for each tree provider - expect(blockMocks.getChildren).toHaveBeenCalledTimes(3); - // setProfileToChoice called once for each matching profile node per provider (3 providers, 3 calls) - expect(blockMocks.setProfileToChoice).toHaveBeenCalledTimes(3); - expect(blockMocks.setProfileToChoice).toHaveBeenCalledWith(profile); - }); - - it("does nothing if the profile node does not exist for a provider", async () => { - const blockMocks = getBlockMocks(); - const profile: imperative.IProfileLoaded = { name: "lpar.zosmf", type: "zosmf", message: "", failNotFound: true }; - await (AuthHandler as any).updateTreeProvidersWithProfile(profile); - // getChildren called for each tree provider - expect(blockMocks.getChildren).toHaveBeenCalledTimes(3); - // if no matching nodes were found, setProfileToChoice should never be called - expect(blockMocks.setProfileToChoice).not.toHaveBeenCalled(); - }); -}); - describe("AuthHandler.promptForAuthentication", () => { it("handles a token-based authentication error - login successful, profile is string", async () => { const tokenNotValidMsg = "Token is not valid or expired."; const impError = new ImperativeError({ additionalDetails: tokenNotValidMsg, msg: tokenNotValidMsg }); const ssoLogin = jest.fn().mockResolvedValue(true); const promptCredentials = jest.fn(); - const updateTreeProvidersWithProfileMock = jest.spyOn(AuthHandler as any, "updateTreeProvidersWithProfile").mockImplementation(); const showMessageMock = jest.spyOn(Gui, "showMessage").mockResolvedValueOnce("Log in to Authentication Service"); const unlockProfileSpy = jest.spyOn(AuthHandler, "unlockProfile"); await expect(AuthHandler.promptForAuthentication(impError, "lpar.zosmf", { promptCredentials, ssoLogin })).resolves.toBe(true); @@ -141,14 +87,12 @@ describe("AuthHandler.promptForAuthentication", () => { expect(unlockProfileSpy).toHaveBeenCalledTimes(1); expect(unlockProfileSpy).toHaveBeenCalledWith("lpar.zosmf", true); expect(showMessageMock).toHaveBeenCalledTimes(1); - expect(updateTreeProvidersWithProfileMock).not.toHaveBeenCalled(); }); it("handles a standard authentication error - credentials provided, profile is string", async () => { const tokenNotValidMsg = "Invalid credentials"; const impError = new ImperativeError({ additionalDetails: tokenNotValidMsg, msg: tokenNotValidMsg }); const ssoLogin = jest.fn().mockResolvedValue(true); - const updateTreeProvidersWithProfileMock = jest.spyOn(AuthHandler as any, "updateTreeProvidersWithProfile").mockImplementation(); const promptCredentials = jest.fn().mockResolvedValue(["us3r", "p4ssw0rd"]); const errorMessageMock = jest.spyOn(Gui, "errorMessage").mockResolvedValueOnce("Update Credentials"); const unlockProfileSpy = jest.spyOn(AuthHandler, "unlockProfile").mockClear(); @@ -159,7 +103,6 @@ describe("AuthHandler.promptForAuthentication", () => { expect(errorMessageMock).toHaveBeenCalledTimes(1); expect(promptCredentials).toHaveBeenCalledTimes(1); expect(promptCredentials).toHaveBeenCalledWith("lpar.zosmf", true); - expect(updateTreeProvidersWithProfileMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/zowe-explorer-api/src/profiles/AuthHandler.ts b/packages/zowe-explorer-api/src/profiles/AuthHandler.ts index d997e77a34..d3307f0561 100644 --- a/packages/zowe-explorer-api/src/profiles/AuthHandler.ts +++ b/packages/zowe-explorer-api/src/profiles/AuthHandler.ts @@ -12,9 +12,9 @@ import { Gui } from "../globals"; import { CorrelatedError, FileManagement } from "../utils"; import * as imperative from "@zowe/imperative"; -import { IZoweTree, IZoweTreeNode } from "../tree"; -import { commands } from "vscode"; +import { IZoweTreeNode } from "../tree"; import { Mutex } from "async-mutex"; +import { ZoweVsCodeExtension } from "../vscode/ZoweVsCodeExtension"; export interface IAuthMethods { /* Method for establishing SSO login with a given profile name */ @@ -76,16 +76,6 @@ export class AuthHandler { } } - private static async updateTreeProvidersWithProfile(profile: imperative.IProfileLoaded): Promise { - // TODO: If we can access extender tree providers (e.g. CICS), it would help to propagate profile updates here. - // For now we will propagate profile changes to core providers (Data Sets, USS, Jobs) - const treeProviders = (await commands.executeCommand("zowe.getTreeProviders")) as any; - for (const provider of [treeProviders.ds, treeProviders.uss, treeProviders.job]) { - const node = (await (provider as IZoweTree).getChildren()).find((n) => n.label === profile?.name); - node?.setProfileToChoice?.(profile); - } - } - /** * Prompts the user to authenticate over SSO or a credential prompt in the event of an error. * @param imperativeError The authentication error that was encountered @@ -110,10 +100,11 @@ export class AuthHandler { }); if (userResp === message) { if (await opts.ssoLogin(null, profileName)) { + // SSO login was successful, propagate new profile properties to other tree providers if (typeof profile !== "string") { - await AuthHandler.updateTreeProvidersWithProfile(profile); + ZoweVsCodeExtension.onProfileUpdatedEmitter.fire(profile); } - // SSO login was successful, unlock profile so it can be used again + // Unlock profile so it can be used again AuthHandler.unlockProfile(profileName, true); return true; } @@ -135,9 +126,9 @@ export class AuthHandler { }); if (creds != null) { - // New creds were set, directly propagate new profile to other tree providers. + // New creds were set, propagate new profile properties to other tree providers. if (typeof profile !== "string") { - await AuthHandler.updateTreeProvidersWithProfile(profile); + ZoweVsCodeExtension.onProfileUpdatedEmitter.fire(profile); } // Unlock profile so it can be used again AuthHandler.unlockProfile(profileName, true); diff --git a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts index 6d39ddd41b..bac56b7867 100644 --- a/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts +++ b/packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts @@ -15,9 +15,9 @@ import { ProfilesCache } from "../profiles/ProfilesCache"; import { Login, Logout } from "@zowe/core-for-zowe-sdk"; import * as imperative from "@zowe/imperative"; import { Gui } from "../globals/Gui"; -import { PromptCredentialsOptions } from "./doc/PromptCredentials"; +import type { PromptCredentialsOptions } from "./doc/PromptCredentials"; import { Types } from "../Types"; -import { BaseProfileAuthOptions } from "./doc/BaseProfileAuth"; +import type { BaseProfileAuthOptions } from "./doc/BaseProfileAuth"; /** * Collection of utility functions for writing Zowe Explorer VS Code extensions. @@ -27,6 +27,9 @@ export class ZoweVsCodeExtension { return vscode.workspace.workspaceFolders?.find((f) => f.uri.scheme === "file"); } + public static onProfileUpdatedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + public static readonly onProfileUpdated = ZoweVsCodeExtension.onProfileUpdatedEmitter.event; + /** * @internal */ diff --git a/packages/zowe-explorer/__tests__/__common__/testUtils.ts b/packages/zowe-explorer/__tests__/__common__/testUtils.ts index 861ff1cbcc..61352adea7 100644 --- a/packages/zowe-explorer/__tests__/__common__/testUtils.ts +++ b/packages/zowe-explorer/__tests__/__common__/testUtils.ts @@ -46,7 +46,10 @@ export function processSubscriptions(subscriptions: IJestIt[], test: ITestContex spyOnSubscription(sub); it(sub.title ?? `Test: ${sub.name}`, async () => { const parms = sub.parm ?? [test.value]; - await test.context.subscriptions.find((s) => Object.keys(s)[0] === getName(sub.name))?.[getName(sub.name)](...parms); + await test.context.subscriptions + .filter(Boolean) + .find((s) => Object.keys(s)[0] === getName(sub.name)) + ?.[getName(sub.name)](...parms); sub.mock.forEach((mock) => { expect(mock.spy).toHaveBeenCalledWith(...mock.arg); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts index 55b33ca2e7..9220e057a1 100644 --- a/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/extension.unit.test.ts @@ -17,7 +17,7 @@ import * as fsextra from "fs-extra"; import * as extension from "../../src/extension"; import * as zosfiles from "@zowe/zos-files-for-zowe-sdk"; import * as zosmf from "@zowe/zosmf-for-zowe-sdk"; -import { imperative, Gui, Validation, ProfilesCache, FileManagement } from "@zowe/zowe-explorer-api"; +import { imperative, Gui, Validation, ProfilesCache, FileManagement, ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; import { createGetConfigMock, createInstanceOfProfileInfo, createIProfile, createTreeView } from "../__mocks__/mockCreators/shared"; import { Constants } from "../../src/configuration/Constants"; import { Profiles } from "../../src/configuration/Profiles"; @@ -245,11 +245,11 @@ async function createGlobalMocks() { "zowe.copyExternalLink", "zowe.revealOutputChannel", "zowe.troubleshootError", - "zowe.getTreeProviders", "zowe.placeholderCommand", ], }; + jest.replaceProperty(ZoweVsCodeExtension, "onProfileUpdated", jest.fn()); Object.defineProperty(fs, "mkdirSync", { value: globalMocks.mockMkdirSync, configurable: true }); Object.defineProperty(vscode.window, "createTreeView", { value: globalMocks.mockCreateTreeView, diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts index d7794961a5..03eae51c60 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedInit.unit.test.ts @@ -26,7 +26,7 @@ import { UnixCommandHandler } from "../../../../src/commands/UnixCommandHandler" import { SharedTreeProviders } from "../../../../src/trees/shared/SharedTreeProviders"; import { SharedContext } from "../../../../src/trees/shared/SharedContext"; import * as certWizard from "../../../../src/utils/CertificateWizard"; -import { Gui, imperative, ZoweScheme } from "@zowe/zowe-explorer-api"; +import { Gui, imperative, ZoweScheme, ZoweVsCodeExtension } from "@zowe/zowe-explorer-api"; import { MockedProperty } from "../../../__mocks__/mockUtils"; import { DatasetFSProvider } from "../../../../src/trees/dataset/DatasetFSProvider"; import { UssFSProvider } from "../../../../src/trees/uss/UssFSProvider"; @@ -64,6 +64,8 @@ describe("Test src/shared/extension", () => { ssoLogin: jest.fn(), ssoLogout: jest.fn(), }; + jest.replaceProperty(ZoweVsCodeExtension, "onProfileUpdated", jest.fn()); + const commands: IJestIt[] = [ { name: "zowe.updateSecureCredentials", diff --git a/packages/zowe-explorer/src/configuration/Constants.ts b/packages/zowe-explorer/src/configuration/Constants.ts index b2edd1c351..e55309adfb 100644 --- a/packages/zowe-explorer/src/configuration/Constants.ts +++ b/packages/zowe-explorer/src/configuration/Constants.ts @@ -16,7 +16,7 @@ import { imperative, PersistenceSchemaEnum } from "@zowe/zowe-explorer-api"; import type { Profiles } from "./Profiles"; export class Constants { - public static readonly COMMAND_COUNT = 106; + public static readonly COMMAND_COUNT = 105; public static readonly MAX_SEARCH_HISTORY = 5; public static readonly MAX_FILE_HISTORY = 10; public static readonly MS_PER_SEC = 1000; diff --git a/packages/zowe-explorer/src/trees/shared/SharedInit.ts b/packages/zowe-explorer/src/trees/shared/SharedInit.ts index 9733254159..8999928f5b 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedInit.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedInit.ts @@ -141,6 +141,23 @@ export class SharedInit { }) ); + context.subscriptions.push( + ZoweVsCodeExtension.onProfileUpdated(async (profile) => { + const providers = Object.values(SharedTreeProviders.providers); + for (const provider of providers) { + try { + const node = (await provider.getChildren()).find((n) => n.label === profile?.name); + node?.setProfileToChoice?.(profile); + } catch (err) { + if (err instanceof Error) { + ZoweLogger.error(err.message); + } + return; + } + } + }) + ); + if (providers.ds || providers.uss) { context.subscriptions.push( vscode.commands.registerCommand("zowe.openRecentMember", () => SharedActions.openRecentMemberPrompt(providers.ds, providers.uss)) @@ -292,7 +309,6 @@ export class SharedInit { (error: CorrelatedError, stackTrace?: string) => new TroubleshootError(context, { error, stackTrace }) ) ); - context.subscriptions.push(vscode.commands.registerCommand("zowe.getTreeProviders", () => SharedTreeProviders)); context.subscriptions.push(vscode.window.registerUriHandler(ZoweUriHandler.getInstance())); context.subscriptions.push( vscode.commands.registerCommand("zowe.placeholderCommand", () => {