Skip to content

Commit

Permalink
refactor: use onProfileUpdate event to update tree providers
Browse files Browse the repository at this point in the history
Signed-off-by: Trae Yelovich <[email protected]>
  • Loading branch information
traeok committed Dec 27, 2024
1 parent fd55a0f commit e354184
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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();
});
});

Expand Down
23 changes: 7 additions & 16 deletions packages/zowe-explorer-api/src/profiles/AuthHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -76,16 +76,6 @@ export class AuthHandler {
}
}

private static async updateTreeProvidersWithProfile(profile: imperative.IProfileLoaded): Promise<void> {
// 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<IZoweTreeNode>).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
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions packages/zowe-explorer-api/src/vscode/ZoweVsCodeExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -27,6 +27,9 @@ export class ZoweVsCodeExtension {
return vscode.workspace.workspaceFolders?.find((f) => f.uri.scheme === "file");
}

public static onProfileUpdatedEmitter: vscode.EventEmitter<imperative.IProfileLoaded> = new vscode.EventEmitter();
public static readonly onProfileUpdated = ZoweVsCodeExtension.onProfileUpdatedEmitter.event;

/**
* @internal
*/
Expand Down
5 changes: 4 additions & 1 deletion packages/zowe-explorer/__tests__/__common__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/src/configuration/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 17 additions & 1 deletion packages/zowe-explorer/src/trees/shared/SharedInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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", () => {
Expand Down

0 comments on commit e354184

Please sign in to comment.