Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile management #2472

Merged
merged 41 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ea8fd30
initial work for profile management
JillieBeanSim Sep 21, 2023
82c1974
add token auth part
JillieBeanSim Sep 21, 2023
723e375
remove other commands similar to new profile management options
JillieBeanSim Sep 21, 2023
041bd1f
add commands back without when clause as to not be breaking change
JillieBeanSim Sep 21, 2023
7c49e51
run localization scripts
JillieBeanSim Sep 21, 2023
36e3120
add items back to package.nls.json
JillieBeanSim Sep 21, 2023
f6010fc
realign for easier comparison
JillieBeanSim Sep 21, 2023
d6346a7
handle case of no authentication method shown for profile
JillieBeanSim Sep 21, 2023
84cf3b5
add changelog
JillieBeanSim Sep 21, 2023
fb680bb
update strings in different scenarios
JillieBeanSim Sep 22, 2023
6a4d068
separate the work
JillieBeanSim Sep 22, 2023
61a021a
update check and strings in Profiles.ssoLogin
JillieBeanSim Sep 22, 2023
0245bb7
clean up and add some unit tests
JillieBeanSim Sep 22, 2023
3444400
fix qp labels I broke
JillieBeanSim Sep 22, 2023
b5139a5
add unit tests
JillieBeanSim Sep 22, 2023
29987e6
cut down on duplication of test code
JillieBeanSim Sep 22, 2023
4484235
clear more duplication
JillieBeanSim Sep 22, 2023
8479821
cleanup
JillieBeanSim Sep 22, 2023
c046daa
clean duplication unit testing
JillieBeanSim Sep 22, 2023
4d4637c
this should fix high duplication failure
JillieBeanSim Sep 22, 2023
c22dbc2
don't think I can condense anything else from file
JillieBeanSim Sep 22, 2023
39194d0
add delete to management options and group left profile items
JillieBeanSim Sep 22, 2023
8daf411
should fix theia tests for delete
JillieBeanSim Sep 23, 2023
6d491d8
about as clean as test file can get
JillieBeanSim Sep 23, 2023
eaeec46
add message for manual editing with opening of config
JillieBeanSim Sep 23, 2023
7ff9223
missed one
JillieBeanSim Sep 23, 2023
b9cd91c
address feedback
JillieBeanSim Sep 26, 2023
2c5eba7
fix unit tests
JillieBeanSim Sep 26, 2023
6006d3c
address feedback
JillieBeanSim Sep 27, 2023
3990e7a
Merge remote-tracking branch 'origin/main' into profile-management
JillieBeanSim Sep 27, 2023
5b57f4c
update log string
JillieBeanSim Sep 27, 2023
c5dd06c
address code smell
JillieBeanSim Sep 27, 2023
af7e90c
Merge branch 'main' into profile-management
JillieBeanSim Sep 28, 2023
fdace6e
address login feedback
JillieBeanSim Oct 2, 2023
71cd777
move enable, disable, and delete to quickpick
JillieBeanSim Oct 2, 2023
f4b0e0c
Merge branch 'main' into profile-management
JillieBeanSim Oct 2, 2023
ccc9223
Merge branch 'main' into profile-management
JillieBeanSim Oct 2, 2023
00af763
add unit tests
JillieBeanSim Oct 2, 2023
742a90e
address feedback
JillieBeanSim Oct 6, 2023
b55efdc
Merge branch 'main' into profile-management
JillieBeanSim Oct 9, 2023
60e3c9d
Merge branch 'main' into profile-management
JillieBeanSim Oct 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
### New features and enhancements

- Added "Sort Jobs" feature for job nodes in Jobs tree view. [#2257](https://github.com/zowe/vscode-extension-for-zowe/issues/2251)
- Introduce a new user interface for managing profiles via right-click action "Manage Profile".
- Added new edit feature on `Edit Attributes` view for changing file tags on USS [#2113](https://github.com/zowe/vscode-extension-for-zowe/issues/2113)

### Bug fixes
Expand Down
34 changes: 34 additions & 0 deletions packages/zowe-explorer/__mocks__/mockCreators/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,40 @@ export function createValidIProfile(): imperative.IProfileLoaded {
};
}

export function createTokenAuthIProfile(): imperative.IProfileLoaded {
return {
name: "sestest",
profile: {
type: "zosmf",
host: "test",
port: 1443,
rejectUnauthorized: false,
tokenType: "apimlAuthenticationToken",
tokenValue: "stringofletters",
name: "testName",
},
type: "zosmf",
message: "",
failNotFound: false,
};
}

export function createNoAuthIProfile(): imperative.IProfileLoaded {
return {
name: "sestest",
profile: {
type: "zosmf",
host: null,
port: 1443,
rejectUnauthorized: false,
name: "testName",
},
type: "zosmf",
message: "",
failNotFound: false,
};
}

export function createAltTypeIProfile(): imperative.IProfileLoaded {
return {
name: "altTypeProfile",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const DatasetsLocators = {
favoriteProfileInDatasetXpath: "(//div[contains(@id,'Favorites') and contains(@id,'TestSeleniumProfile')])",
addToFavoriteOptionXpath: "//li[@data-command='zowe.ds.saveSearch']",
removeFavoriteProfileFromDatasetsOptionXpath: "//li[@data-command='zowe.ds.removeFavProfile']",
deleteProfileFromDatasetsXpath: "(//li[@data-command='zowe.ds.deleteProfile'])",
manageProfileFromDatasetsXpath: "(//li[@data-command='zowe.profileManagement'])",
};

export const UssLocators = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,23 +170,29 @@ export async function verifyProfileIsHideInJobs() {
export async function deleteDefaultProfileInDatasets() {
const profileName = await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.defaultDatasetsProfileXpath)), WAITTIME);
await driverChrome.actions().click(profileName, Button.RIGHT).perform();
await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.deleteProfileFromDatasetsXpath)), WAITTIME).click();
await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.manageProfileFromDatasetsXpath)), WAITTIME).click();
await driverChrome.sleep(SHORTSLEEPTIME);
const manageProfile = driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.emptyInputBoxXpath)), WAITTIME);
manageProfile.sendKeys("Delete Profile");
manageProfile.sendKeys(Key.ENTER);
await driverChrome.sleep(SHORTSLEEPTIME);
const deleteProfile = driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.emptyInputBoxXpath)), WAITTIME);
deleteProfile.sendKeys("Delete");
deleteProfile.sendKeys(Key.ENTER);
return;
}

export async function deleteProfileInDatasets() {
const favprofile = await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.secondDatasetProfileXpath)), WAITTIME);
await driverChrome.actions().click(favprofile, Button.RIGHT).perform();
await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.deleteProfileFromDatasetsXpath)), WAITTIME).click();
await driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.manageProfileFromDatasetsXpath)), WAITTIME).click();
await driverChrome.sleep(SHORTSLEEPTIME);
const manageProfile = driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.emptyInputBoxXpath)), WAITTIME);
manageProfile.sendKeys("Delete Profile");
manageProfile.sendKeys(Key.ENTER);
await driverChrome.sleep(SHORTSLEEPTIME);
const deleteProfile = driverChrome.wait(until.elementLocated(By.xpath(DatasetsLocators.emptyInputBoxXpath)), WAITTIME);
deleteProfile.sendKeys("Delete");
deleteProfile.sendKeys(Key.ENTER);
return;
}

export async function verifyRemovedFavoriteProfileInDatasets() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,7 @@ describe("Profiles Unit Tests - function checkCurrentProfile", () => {
it("should throw an error 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, "isUsingTokenAuth").mockResolvedValue(true);
jest.spyOn(utils.ProfilesUtils, "isUsingTokenAuth").mockResolvedValue(true);
setupProfilesCheck(globalMocks);
await expect(Profiles.getInstance().checkCurrentProfile(globalMocks.testProfile)).resolves.toEqual({ name: "sestest", status: "unverified" });
});
Expand Down Expand Up @@ -1569,6 +1569,7 @@ describe("Profiles Unit Tests - function ssoLogin", () => {
],
configurable: true,
});
Object.defineProperty(utils.ProfilesUtils, "isProfileUsingBasicAuth", { value: jest.fn(), configurable: true });
jest.spyOn(Gui, "showMessage").mockImplementation();
});
it("should perform an SSOLogin successfully while fetching the base profile", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ async function createGlobalMocks() {
"zowe.manualPoll",
"zowe.updateSecureCredentials",
"zowe.promptCredentials",
"zowe.profileManagement",
"zowe.openRecentMember",
"zowe.searchInAllLoadedItems",
"zowe.ds.deleteProfile",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/

import { ZoweDatasetNode } from "../../../src/dataset/ZoweDatasetNode";
import * as sharedMock from "../../../__mocks__/mockCreators/shared";
import * as dsMock from "../../../__mocks__/mockCreators/datasets";
import * as profUtils from "../../../src/utils/ProfilesUtils";
import { ProfileManagement } from "../../../src/utils/ProfileManagement";
import { Gui } from "@zowe/zowe-explorer-api";
import { ZoweLogger } from "../../../src/utils/LoggerUtils";
import { Profiles } from "../../../src/Profiles";
import * as vscode from "vscode";
import { imperative } from "@zowe/cli";

jest.mock("fs");
jest.mock("vscode");

describe("ProfileManagement unit tests", () => {
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
function createGlobalMocks(): any {
const newMocks = {
mockSession: sharedMock.createISession(),
mockBasicAuthProfile: sharedMock.createValidIProfile(),
mockTokenAuthProfile: sharedMock.createTokenAuthIProfile(),
mockNoAuthProfile: sharedMock.createNoAuthIProfile(),
opCancelledSpy: jest.spyOn(Gui, "infoMessage"),
mockDsSessionNode: ZoweDatasetNode,
mockResolveQp: jest.fn(),
mockCreateQp: jest.fn(),
mockUpdateChosen: ProfileManagement.basicAuthUpdateQpItems[ProfileManagement.AuthQpLabels.update],
mockAddBasicChosen: ProfileManagement.basicAuthAddQpItems[ProfileManagement.AuthQpLabels.add],
mockLoginChosen: ProfileManagement.tokenAuthLoginQpItem[ProfileManagement.AuthQpLabels.login],
mockLogoutChosen: ProfileManagement.tokenAuthLogoutQpItem[ProfileManagement.AuthQpLabels.logout],
mockEditProfChosen: ProfileManagement.editProfileQpItems[ProfileManagement.AuthQpLabels.edit],
mockDeleteProfChosen: ProfileManagement.deleteProfileQpItem[ProfileManagement.AuthQpLabels.delete],
mockProfileInfo: { usingTeamConfig: true },
mockProfileInstance: null as any,
debugLogSpy: null as any,
promptSpy: null as any,
editSpy: null as any,
loginSpy: null as any,
logoutSpy: null as any,
logMsg: null as any,
};
Object.defineProperty(profUtils.ProfilesUtils, "promptCredentials", { value: jest.fn(), configurable: true });
newMocks.promptSpy = jest.spyOn(profUtils.ProfilesUtils, "promptCredentials");
Object.defineProperty(ZoweLogger, "debug", { value: jest.fn(), configurable: true });
newMocks.debugLogSpy = jest.spyOn(ZoweLogger, "debug");
Object.defineProperty(Gui, "resolveQuickPick", { value: newMocks.mockResolveQp, configurable: true });
newMocks.mockCreateQp.mockReturnValue({
show: jest.fn(() => {
return {};
}),
hide: jest.fn(() => {
return {};
}),
onDidAccept: jest.fn(() => {
return {};
}),
});
Object.defineProperty(Gui, "createQuickPick", { value: newMocks.mockCreateQp, configurable: true });
newMocks.mockDsSessionNode = dsMock.createDatasetSessionNode(newMocks.mockSession, newMocks.mockBasicAuthProfile) as any;
newMocks.mockProfileInstance = sharedMock.createInstanceOfProfile(newMocks.mockBasicAuthProfile);
Object.defineProperty(Profiles, "getInstance", {
value: jest.fn().mockReturnValue(newMocks.mockProfileInstance),
configurable: true,
});
Object.defineProperty(newMocks.mockProfileInstance, "editSession", { value: jest.fn(), configurable: true });
newMocks.editSpy = jest.spyOn(newMocks.mockProfileInstance, "editSession");
Object.defineProperty(newMocks.mockProfileInstance, "ssoLogin", { value: jest.fn(), configurable: true });
newMocks.loginSpy = jest.spyOn(newMocks.mockProfileInstance, "ssoLogin");
Object.defineProperty(newMocks.mockProfileInstance, "ssoLogout", { value: jest.fn(), configurable: true });
newMocks.logoutSpy = jest.spyOn(newMocks.mockProfileInstance, "ssoLogout");

return newMocks;
}

describe("unit tests around basic auth selections", () => {
function createBlockMocks(globalMocks): any {
globalMocks.logMsg = `Profile ${globalMocks.mockBasicAuthProfile.name} is using basic authentication.`;
globalMocks.mockDsSessionNode.getProfile = jest.fn().mockReturnValue(globalMocks.mockBasicAuthProfile);
Object.defineProperty(vscode.commands, "executeCommand", { value: jest.fn(), configurable: true });
return globalMocks;
}
it("profile using basic authentication should see Operation Cancelled when escaping quick pick", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(undefined);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.opCancelledSpy).toBeCalledWith("Operation Cancelled");
});
it("profile using basic authentication should see promptCredentials called when Update Credentials chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockUpdateChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.promptSpy).toBeCalled();
});
it("profile using basic authentication should see editSession called when Edit Profile chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockEditProfChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.editSpy).toBeCalled();
});
it("profile using basic authentication should see editSession called when Delete Profile chosen with v2 profile", async () => {
const mocks = createBlockMocks(createGlobalMocks());
Object.defineProperty(mocks.mockProfileInstance, "getProfileInfo", {
value: jest.fn().mockResolvedValue(mocks.mockProfileInfo as imperative.ProfileInfo),
configurable: true,
});
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockDeleteProfChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.editSpy).toBeCalled();
});
it("profile using basic authentication should see delete commands called when Delete Profile chosen with v1 profile", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockDeleteProfChosen);
mocks.mockProfileInfo.usingTeamConfig = false;
const commandSpy = jest.spyOn(vscode.commands, "executeCommand");
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.editSpy).not.toBeCalled();
expect(commandSpy).toBeCalled();
});
});
describe("unit tests around token auth selections", () => {
function createBlockMocks(globalMocks): any {
globalMocks.logMsg = `Profile ${globalMocks.mockTokenAuthProfile.name} is using token authentication.`;
Object.defineProperty(profUtils.ProfilesUtils, "isUsingTokenAuth", { value: jest.fn().mockResolvedValueOnce(true), configurable: true });
globalMocks.mockDsSessionNode.getProfile = jest.fn().mockReturnValue(globalMocks.mockTokenAuthProfile);
return globalMocks;
}
it("profile using token authentication should see Operation Cancelled when escaping quick pick", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(undefined);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.opCancelledSpy).toBeCalledWith("Operation Cancelled");
});
it("profile using token authentication should see ssoLogin called when Log in to authentication service chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockLoginChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.loginSpy).toBeCalled();
});
it("profile using token authentication should see ssoLogout called when Log out from authentication service chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockLogoutChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.logoutSpy).toBeCalled();
});
});
describe("unit tests around no auth declared selections", () => {
function createBlockMocks(globalMocks): any {
globalMocks.logMsg = `Profile ${globalMocks.mockNoAuthProfile.name} authentication method is unkown.`;
Object.defineProperty(profUtils.ProfilesUtils, "isUsingTokenAuth", { value: jest.fn().mockResolvedValueOnce(false), configurable: true });
globalMocks.mockDsSessionNode.getProfile = jest.fn().mockReturnValue(globalMocks.mockNoAuthProfile);
return globalMocks;
}
it("profile with no authentication method should see Operation Cancelled when escaping quick pick", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(undefined);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.opCancelledSpy).toBeCalledWith("Operation Cancelled");
});
it("profile with no authentication method should see promptCredentials called when Add Basic Credentials chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockAddBasicChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.promptSpy).toBeCalled();
});
it("profile with no authentication method should see ssoLogin called when Log in to authentication service chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockLoginChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.loginSpy).toBeCalled();
});
it("profile with no authentication method should see editSession called when Edit Profile chosen", async () => {
const mocks = createBlockMocks(createGlobalMocks());
mocks.mockResolveQp.mockResolvedValueOnce(mocks.mockEditProfChosen);
await ProfileManagement.manageProfile(mocks.mockDsSessionNode);
expect(mocks.debugLogSpy).toBeCalledWith(mocks.logMsg);
expect(mocks.editSpy).toBeCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ describe("ProfilesUtils unit tests", () => {
jest.spyOn(Profiles.getInstance(), "getDefaultProfile").mockReturnValueOnce({} as any);
jest.spyOn(Profiles.getInstance(), "getLoadedProfConfig").mockResolvedValue({ type: "test" } as any);
jest.spyOn(Profiles.getInstance(), "getSecurePropsForProfile").mockResolvedValue([]);
await expect(profUtils.isUsingTokenAuth("test")).resolves.toEqual(false);
await expect(profUtils.ProfilesUtils.isUsingTokenAuth("test")).resolves.toEqual(false);
});
});
});
1 change: 1 addition & 0 deletions packages/zowe-explorer/i18n/sample/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"description": "VS Code extension, powered by Zowe CLI, that streamlines interaction with mainframe data sets, USS files, and jobs",
"viewsContainers.activitybar": "Zowe Explorer",
"zowe.promptCredentials": "Update Credentials",
"zowe.profileManagement": "Manage Profile",
"zowe.extRefresh": "Refresh Zowe Explorer",
"zowe.ds.explorer": "Data Sets",
"zowe.uss.explorer": "Unix System Services (USS)",
Expand Down
Loading