Skip to content

Commit

Permalink
Merge pull request #2866 from zowe/unixCmd-ext-no-ssh
Browse files Browse the repository at this point in the history
Issue Unix Command API changes
  • Loading branch information
JillieBeanSim authored Apr 23, 2024
2 parents 505e6c4 + 8fa0582 commit 494f759
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 217 deletions.
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t
### Bug fixes

- Fixed an issue where the `ProfilesCache` class would retain old service profiles, even if they were removed from the team config. [#2395](https://github.com/zowe/zowe-explorer-vscode/issues/2395)
- **Breaking:** issueUnixCommand API now takes sshSession as a optional parameter. [#2866](https://github.com/zowe/zowe-explorer-vscode/pull/2866)

## `3.0.0-next.202403051607`

Expand Down
18 changes: 13 additions & 5 deletions packages/zowe-explorer-api/src/extend/MainframeInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,13 +512,21 @@ export namespace MainframeInteraction {
/**
* Issues a Unix Command and returns a Console Command API response.
*
* @param {string} command
* @param {string} cwd
* @param {boolean} flag
* @returns {Promise<string>}
* @param {string} command - UNIX command
* @param {string} cwd - UNIX working directory for command to be issued
* @param {zosuss.SshSession} sshSession - Optional Parameter, passed to extender if sshProfileRequired returns true
* @returns {string} - UNIX command output string
* @memberof ICommand
*/
issueUnixCommand?(command: string, cwd: string, sshSession?: zosuss.SshSession): Promise<string>;

/**
* Extender will require this API and return true if Zowe SSH profile is to be used for issuing UNIX commands.
* Zowe Explorer will prepare the ssh session and pass it to extender with issueUnixCommand()
*
* @returns {boolean} - true if SSH profile is to be used for issuing UNIX commands
* @memberof ICommand
*/
issueUnixCommand?(sshSession: zosuss.SshSession, command: string, cwd: string, flag: boolean): Promise<string>;
sshProfileRequired?(): boolean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -388,9 +388,9 @@ export namespace ZoweExplorerZosmf {
return zosconsole.IssueCommand.issueSimple(this.getSession(), command);
}

public async issueUnixCommand(sshSession: zosuss.SshSession, command: string, cwd: string, flag: boolean): Promise<string> {
public async issueUnixCommand(command: string, cwd: string, sshSession: zosuss.SshSession): Promise<string> {
let stdout = "";
if (flag) {
if (cwd) {
await zosuss.Shell.executeSshCwd(sshSession, command, '"' + cwd + '"', (data: string) => {
stdout += data;
});
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
### Bug fixes

- Fixed issue where "Allocate Like" input box placeholder was showing a localization ID instead of the intended message ("Enter a name for the new data set"). [#2759](https://github.com/zowe/vscode-extension-for-zowe/issues/2759)
- Fix concerns regarding Unix command handling work. [#2866](https://github.com/zowe/zowe-explorer-vscode/pull/2866)

## `3.0.0-next.202403051607`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,27 @@ describe("UnixCommand Actions Unit Testing", () => {
});
expect(showInputBox.mock.calls.length).toBe(2);
expect(appendLine.mock.calls.length).toBe(2);
expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ d iplinfo1");
expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ /d iplinfo1");
expect(appendLine.mock.calls[1][0]["commandResponse"]).toBe("iplinfo1");
expect(showInformationMessage.mock.calls.length).toBe(0);
});

it("tests the selectSshProfile function with quickpick", async () => {
showQuickPick.mockReturnValueOnce("test1" as any);
await expect(
(unixActions as any).selectSshProfile([
{
name: "test1",
},
{
name: "test2",
},
])
).resolves.toEqual({
name: "test1",
});
});

it("tests the issueUnixCommand function user selects a history item", async () => {
const mockUssApi = await apiRegisterInstance.getUssApi(profileOne);
const getUssApiMock = jest.fn();
Expand Down Expand Up @@ -271,24 +287,47 @@ describe("UnixCommand Actions Unit Testing", () => {
});
expect(showInputBox.mock.calls.length).toBe(1);
expect(appendLine.mock.calls.length).toBe(2);
expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ d iplinfo0");
expect(appendLine.mock.calls[0][0]).toBe("> firstName@ssh:/u/directorypath$ /d iplinfo0");
expect(appendLine.mock.calls[1][0]["commandResponse"]).toBe("iplinfo0");
expect(showInformationMessage.mock.calls.length).toBe(0);
});

it("tests the issueUnixCommand function - issueUnixCommand throws an error", async () => {
it("tests the issueUnixCommand function user escapes the quick pick box", async () => {
showQuickPick.mockReturnValueOnce("firstName");
showInputBox.mockReturnValueOnce("/u/directorypath");
showInputBox.mockReturnValueOnce("/d iplinfo3");
withProgress.mockRejectedValueOnce(Error("fake testError"));

const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne);
const getCommandApiMock = jest.fn();
getCommandApiMock.mockReturnValue(mockCommandApi);
apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance);

jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(undefined));

await unixActions.issueUnixCommand();

expect(showQuickPick.mock.calls.length).toBe(1);
expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]);
expect(showQuickPick.mock.calls[0][1]).toEqual({
canPickMany: false,
ignoreFocusOut: true,
placeHolder: "Select the Profile to use to submit the Unix command",
});
expect(showInputBox.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
});

it("tests the issueUnixCommand function user escapes the commandbox", async () => {
showQuickPick.mockReturnValueOnce("firstName");
showInputBox.mockReturnValueOnce("/directorypath");
showInputBox.mockReturnValueOnce(undefined);

const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne);
const getCommandApiMock = jest.fn();
getCommandApiMock.mockReturnValue(mockCommandApi);
apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance);

jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem));
jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValue("iplinfo3" as any);

await unixActions.issueUnixCommand();

Expand All @@ -300,20 +339,23 @@ describe("UnixCommand Actions Unit Testing", () => {
placeHolder: "Select the Profile to use to submit the Unix command",
});
expect(showInputBox.mock.calls.length).toBe(2);
expect(showErrorMessage.mock.calls.length).toBe(1);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Error: fake testError");
expect(showInformationMessage.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
});

it("tests the issueUnixCommand function user escapes the quick pick box", async () => {
it("tests the issueUnixCommand function - issueUnixCommand throws an error", async () => {
showQuickPick.mockReturnValueOnce("firstName");
showInputBox.mockReturnValueOnce("/u/directorypath");
showInputBox.mockReturnValueOnce("/d iplinfo3");
withProgress.mockRejectedValueOnce(Error("fake testError"));

const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne);
const getCommandApiMock = jest.fn();
getCommandApiMock.mockReturnValue(mockCommandApi);
apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance);

jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(undefined));
jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem));
jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValue("iplinfo3" as any);

await unixActions.issueUnixCommand();

Expand All @@ -324,9 +366,9 @@ describe("UnixCommand Actions Unit Testing", () => {
ignoreFocusOut: true,
placeHolder: "Select the Profile to use to submit the Unix command",
});
expect(showInputBox.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
expect(showInputBox.mock.calls.length).toBe(2);
expect(showErrorMessage.mock.calls.length).toBe(1);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Error: fake testError");
});

it("If nothing is entered in the inputbox of path", async () => {
Expand All @@ -350,32 +392,6 @@ describe("UnixCommand Actions Unit Testing", () => {
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
});

it("tests the issueUnixCommand function user escapes the commandbox", async () => {
showQuickPick.mockReturnValueOnce("firstName");
showInputBox.mockReturnValueOnce("/directorypath");
showInputBox.mockReturnValueOnce(undefined);

const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne);
const getCommandApiMock = jest.fn();
getCommandApiMock.mockReturnValue(mockCommandApi);
apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance);

jest.spyOn(Gui, "resolveQuickPick").mockImplementation(() => Promise.resolve(qpItem));

await unixActions.issueUnixCommand();

expect(showQuickPick.mock.calls.length).toBe(1);
expect(showQuickPick.mock.calls[0][0]).toEqual(["firstName", "secondName"]);
expect(showQuickPick.mock.calls[0][1]).toEqual({
canPickMany: false,
ignoreFocusOut: true,
placeHolder: "Select the Profile to use to submit the Unix command",
});
expect(showInputBox.mock.calls.length).toBe(2);
expect(showInformationMessage.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls[0][0]).toEqual("No command entered.");
});

it("tests the issueUnixCommand function user starts typing a value in quick pick", async () => {
createQuickPick.mockReturnValueOnce({
placeholder: 'Choose "Create new..." to define a new profile or select an existing profile to add to the Data Set Explorer',
Expand Down Expand Up @@ -417,15 +433,11 @@ describe("UnixCommand Actions Unit Testing", () => {
expect(showInputBox.mock.calls.length).toBe(1);
});

it("tests the issueUnixCommand error in prompt credentials", async () => {
showQuickPick.mockReturnValueOnce("firstName");

await unixActions.issueUnixCommand();

expect(showInformationMessage.mock.calls.length).toBe(1);
});

it("tests the issueUnixCommand function user does not select a profile", async () => {
Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", {
value: jest.fn().mockReturnValue(["firstName"]),
configurable: true,
});
showQuickPick.mockReturnValueOnce(undefined);

await unixActions.issueUnixCommand();
Expand All @@ -440,22 +452,7 @@ describe("UnixCommand Actions Unit Testing", () => {
configurable: true,
});
await unixActions.issueUnixCommand();
expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available");
});

it("tests the issueUnixCommand function from a session", async () => {
jest.spyOn(unixActions, "checkCurrentProfile").mockReturnValue(undefined as any);
const mockCommandApi = await apiRegisterInstance.getCommandApi(profileOne);
const getCommandApiMock = jest.fn();
getCommandApiMock.mockReturnValue(mockCommandApi);
apiRegisterInstance.getCommandApi = getCommandApiMock.bind(apiRegisterInstance);

showInputBox.mockReturnValueOnce("/d iplinfo1");
jest.spyOn(mockCommandApi, "issueUnixCommand").mockReturnValueOnce("iplinfo1" as any);

await unixActions.issueUnixCommand(session, null as any, testNode);

expect(showInformationMessage.mock.calls.length).toBe(0);
expect(showInformationMessage.mock.calls[0][0]).toEqual("No profiles available.");
});

it("ssh profile not found", async () => {
Expand Down Expand Up @@ -501,16 +498,12 @@ describe("UnixCommand Actions Unit Testing", () => {
expect(showErrorMessage.mock.calls[0][0]).toEqual("SSH profile missing connection details. Please update.");
});

it("tests the selectSshProfile function", async () => {
showQuickPick.mockReturnValueOnce("test1" as any);
it("tests the selectSshProfile function-1", async () => {
await expect(
(unixActions as any).selectSshProfile([
{
name: "test1",
},
{
name: "test2",
},
])
).resolves.toEqual({
name: "test1",
Expand All @@ -532,19 +525,19 @@ describe("UnixCommand Actions Unit Testing", () => {
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
});

it("Not able to issue the command", async () => {
it("getCommand API is not implemented", async () => {
Object.defineProperty(ZoweExplorerApiRegister, "getInstance", {
value: jest.fn(() => {
return {
getCommandApi: jest.fn(() => undefined),
};
}),
});
await unixActions.issueUnixCommand(session, null as any, testNode);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing Commands is not supported for this profile.");
await unixActions.issueUnixCommand(testNode, null as any);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing commands is not supported for this profile type, undefined.");
});

it("Not yet implemented for specific profile", async () => {
it("issueUnixCommand API is not yet implemented", async () => {
Object.defineProperty(ZoweExplorerApiRegister, "getInstance", {
value: jest.fn(() => {
return {
Expand All @@ -556,7 +549,20 @@ describe("UnixCommand Actions Unit Testing", () => {
};
}),
});
await unixActions.issueUnixCommand(session, null as any, testNode);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Not implemented yet for profile of type: zosmf");
await unixActions.issueUnixCommand(testNode, null as any);
expect(showErrorMessage.mock.calls[0][0]).toEqual("Issuing UNIX commands is not supported for this profile type, zosmf.");
});

it("tests the issueUnixCommand function user does not select a profile - userSelectProfile", async () => {
Object.defineProperty(ProfileManagement, "getRegisteredProfileNameList", {
value: jest.fn().mockReturnValue(["firstName"]),
configurable: true,
});
showQuickPick.mockReturnValueOnce(undefined);

await (unixActions as any).userSelectProfile();

expect(showInformationMessage.mock.calls.length).toBe(1);
expect(showInformationMessage.mock.calls[0][0]).toEqual("Operation Cancelled");
});
});
27 changes: 21 additions & 6 deletions packages/zowe-explorer/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -955,19 +955,34 @@
"The partitioned (PO) data set already exists.\nDo you want to merge them while replacing any existing members?": "The partitioned (PO) data set already exists.\nDo you want to merge them while replacing any existing members?",
"Unable to copy data set.": "Unable to copy data set.",
"$(plus) Create a new Unix command": "$(plus) Create a new Unix command",
"Issuing commands is not supported for this profile type, {0}./Profile type": {
"message": "Issuing commands is not supported for this profile type, {0}.",
"comment": [
"Profile type"
]
},
"Issuing UNIX commands is not supported for this profile type, {0}./Profile type": {
"message": "Issuing UNIX commands is not supported for this profile type, {0}.",
"comment": [
"Profile type"
]
},
"Error preparring SSH connection for issueing UNIX commands, please check SSH profile for correctness.": "Error preparring SSH connection for issueing UNIX commands, please check SSH profile for correctness.",
"Redirecting to Home Directory": "Redirecting to Home Directory",
"No SSH profile found. Please create an SSH profile.": "No SSH profile found. Please create an SSH profile.",
"SSH profile missing connection details. Please update.": "SSH profile missing connection details. Please update.",
"No profiles available.": "No profiles available.",
"Zowe Unix Command": "Zowe Unix Command",
"Issuing Commands is not supported for this profile.": "Issuing Commands is not supported for this profile.",
"An SSH profile will be used for issuing UNIX commands with the profile {0}.": "An SSH profile will be used for issuing UNIX commands with the profile {0}.",
"Error checking if SSH profile type required for issueing UNIX commands, setting requirement to false for profile {0}.": "Error checking if SSH profile type required for issueing UNIX commands, setting requirement to false for profile {0}.",
"Enter the path of the directory in order to execute the command": "Enter the path of the directory in order to execute the command",
"Not implemented yet for profile of type: {0}/Profile type": {
"message": "Not implemented yet for profile of type: {0}",
"comment": [
"Profile type"
]
},
"Enter the path of the directory in order to execute the command": "Enter the path of the directory in order to execute the command",
"Redirecting to Home Directory": "Redirecting to Home Directory",
"Select the ssh Profile.": "Select the ssh Profile.",
"No SSH profile found. Please create an SSH profile.": "No SSH profile found. Please create an SSH profile.",
"SSH profile missing connection details. Please update.": "SSH profile missing connection details. Please update.",
"Select the Profile to use to submit the Unix command": "Select the Profile to use to submit the Unix command",
"Select a Unix command to run against {0} (An option to edit will follow)/Current work directory": {
"message": "Select a Unix command to run against {0} (An option to edit will follow)",
Expand All @@ -982,7 +997,6 @@
]
},
"Enter or update the Unix command": "Enter or update the Unix command",
"No command entered.": "No command entered.",
"Unix command submitted.": "Unix command submitted.",
"$(plus) Create a new TSO command": "$(plus) Create a new TSO command",
"Zowe TSO Command": "Zowe TSO Command",
Expand All @@ -1001,6 +1015,7 @@
]
},
"Enter or update the TSO command": "Enter or update the TSO command",
"No command entered.": "No command entered.",
"TSO command submitted.": "TSO command submitted.",
"No account number was supplied.": "No account number was supplied.",
"Select the TSO Profile to use for account number.": "Select the TSO Profile to use for account number.",
Expand Down
Loading

0 comments on commit 494f759

Please sign in to comment.