From 39c4812f36b45a2c4a72c09f89f398cd6a940316 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 27 Dec 2024 14:29:46 -0500 Subject: [PATCH] Port fixes for invalid credential handling Signed-off-by: Timothy Johnson --- packages/zowe-explorer/CHANGELOG.md | 2 + .../__tests__/__unit__/ZoweNode.unit.test.ts | 488 ----------------- .../abstract/ZoweTreeProvider.unit.test.ts | 5 +- .../__unit__/dataset/DatasetTree.unit.test.ts | 72 +-- .../dataset/ZoweDatasetNode.unit.test.ts | 517 +++++++++++++++++- .../__unit__/job/ZoweJobNode.unit.test.ts | 50 +- .../__unit__/shared/init.unit.test.ts | 20 +- .../__unit__/shared/utils.unit.test.ts | 81 ++- .../__unit__/uss/ZoweUSSNode.unit.test.ts | 231 ++++---- .../__unit__/utils/ProfilesUtils.unit.test.ts | 2 +- .../sample/src/dataset/DatasetTree.i18n.json | 1 - .../src/dataset/ZoweDatasetNode.i18n.json | 1 + .../i18n/sample/src/job/ZoweJobNode.i18n.json | 5 +- .../i18n/sample/src/uss/ZoweUSSNode.i18n.json | 10 +- packages/zowe-explorer/src/Profiles.ts | 9 +- .../src/abstract/ZoweTreeProvider.ts | 4 +- .../zowe-explorer/src/dataset/DatasetTree.ts | 43 +- .../src/dataset/ZoweDatasetNode.ts | 101 ++-- packages/zowe-explorer/src/globals.ts | 6 +- .../zowe-explorer/src/job/ZosJobsProvider.ts | 2 +- packages/zowe-explorer/src/job/ZoweJobNode.ts | 76 ++- .../zowe-explorer/src/shared/IZoweTreeOpts.ts | 2 +- .../zowe-explorer/src/shared/TreeProviders.ts | 16 +- packages/zowe-explorer/src/shared/context.ts | 11 +- packages/zowe-explorer/src/shared/init.ts | 14 +- packages/zowe-explorer/src/shared/refresh.ts | 2 +- packages/zowe-explorer/src/uss/USSTree.ts | 15 +- packages/zowe-explorer/src/uss/ZoweUSSNode.ts | 59 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 79 +-- 29 files changed, 1020 insertions(+), 904 deletions(-) delete mode 100644 packages/zowe-explorer/__tests__/__unit__/ZoweNode.unit.test.ts diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 8e9441547c..62b9566625 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -20,6 +20,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue where clicking on a file in the Unix System Services tree caused the tree to abruptly change focus to the selected item. [#2486](https://github.com/zowe/zowe-explorer-vscode/issues/2486) - Fixed an issue where binary USS files were not fetched using the "Pull from Mainframe" context menu option. [#3355](https://github.com/zowe/zowe-explorer-vscode/issues/3355) - Fixed an issue with Auto Save where a failed UNIX file or data set save operation caused an infinite loop of save requests. [#2406](https://github.com/zowe/zowe-explorer-vscode/issues/2406), [#2627](https://github.com/zowe/zowe-explorer-vscode/issues/2627) +- Fixed an issue where invalid credentials could trigger "Update Credentials" prompt twice in new VS Code session. [#3376](https://github.com/zowe/zowe-explorer-vscode/pull/3376) +- Fixed an issue where expanding job node to show spool doesn't trigger "Update Credentials" prompt when credentials are invalid. [#3376](https://github.com/zowe/zowe-explorer-vscode/pull/3376) ## `2.18.0` diff --git a/packages/zowe-explorer/__tests__/__unit__/ZoweNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/ZoweNode.unit.test.ts deleted file mode 100644 index a0ac523a4e..0000000000 --- a/packages/zowe-explorer/__tests__/__unit__/ZoweNode.unit.test.ts +++ /dev/null @@ -1,488 +0,0 @@ -/** - * 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. - * - */ - -jest.mock("vscode"); -jest.mock("@zowe/cli"); -jest.mock("Session"); -import * as vscode from "vscode"; -import { ZoweDatasetNode } from "../../src/dataset/ZoweDatasetNode"; -import { List, imperative } from "@zowe/cli"; -import { Profiles } from "../../src/Profiles"; -import * as globals from "../../src/globals"; -import { ZoweLogger } from "../../src/utils/LoggerUtils"; -import { DatasetSortOpts, SortDirection } from "@zowe/zowe-explorer-api"; - -describe("Unit Tests (Jest)", () => { - // Globals - const session = new imperative.Session({ - user: "fake", - password: "fake", - hostname: "fake", - protocol: "https", - type: "basic", - }); - const profileOne: imperative.IProfileLoaded = { - name: "profile1", - profile: {}, - type: "zosmf", - message: "", - failNotFound: false, - }; - const ProgressLocation = jest.fn().mockImplementation(() => { - return { - Notification: 15, - }; - }); - - const withProgress = jest.fn().mockImplementation((progLocation, callback) => { - return callback(); - }); - - Object.defineProperty(globals, "LOG", { value: jest.fn(), configurable: true }); - Object.defineProperty(globals.LOG, "error", { value: jest.fn(), configurable: true }); - Object.defineProperty(vscode, "ProgressLocation", { value: ProgressLocation }); - Object.defineProperty(vscode.window, "withProgress", { value: withProgress }); - Object.defineProperty(ZoweLogger, "error", { value: jest.fn(), configurable: true }); - Object.defineProperty(ZoweLogger, "trace", { value: jest.fn(), configurable: true }); - - beforeEach(() => { - withProgress.mockImplementation((progLocation, callback) => { - return callback(); - }); - }); - - const showErrorMessage = jest.fn(); - Object.defineProperty(vscode.window, "showErrorMessage", { value: showErrorMessage }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - /************************************************************************************************************* - * Creates an ZoweDatasetNode and checks that its members are all initialized by the constructor - *************************************************************************************************************/ - it("Testing that the ZoweDatasetNode is defined", async () => { - const testNode = new ZoweDatasetNode({ label: "BRTVS99", collapsibleState: vscode.TreeItemCollapsibleState.None, session }); - testNode.contextValue = globals.DS_SESSION_CONTEXT; - - expect(testNode.label).toBeDefined(); - expect(testNode.collapsibleState).toBeDefined(); - expect(testNode.label).toBeDefined(); - expect(testNode.getParent()).toBeUndefined(); - expect(testNode.getSession()).toBeDefined(); - }); - - /************************************************************************************************************* - * Creates sample ZoweDatasetNode list and checks that getChildren() returns the correct array - *************************************************************************************************************/ - it("Testing that getChildren returns the correct Thenable", async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode.dirty = true; - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - rootNode.pattern = "SAMPLE, SAMPLE.PUBLIC, SAMPLE"; - let rootChildren = await rootNode.getChildren(); - - // Creating structure of files and folders under BRTVS99 profile - const sampleChildren: ZoweDatasetNode[] = [ - new ZoweDatasetNode({ - label: "BRTVS99", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: rootNode, - profile: profileOne, - }), - new ZoweDatasetNode({ - label: "BRTVS99.CA10", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: rootNode, - profile: profileOne, - contextOverride: globals.DS_MIGRATED_FILE_CONTEXT, - }), - new ZoweDatasetNode({ - label: "BRTVS99.CA11.SPFTEMP0.CNTL", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: profileOne, - }), - new ZoweDatasetNode({ - label: "BRTVS99.DDIR", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: profileOne, - }), - new ZoweDatasetNode({ - label: "BRTVS99.VS1", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: rootNode, - profile: profileOne, - contextOverride: globals.VSAM_CONTEXT, - }), - ]; - sampleChildren[0].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[0]] }; - - // Checking that the rootChildren are what they are expected to be - expect(rootChildren).toEqual(sampleChildren); - - rootNode.dirty = true; - // Check the dirty and children variable have been set - rootChildren = await rootNode.getChildren(); - - // Checking that the rootChildren are what they are expected to be - expect(rootChildren).toEqual(sampleChildren); - - // Check that error is thrown when label is blank - const errorNode = new ZoweDatasetNode({ - label: "", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - errorNode.dirty = true; - await expect(errorNode.getChildren()).rejects.toEqual(Error("Invalid node")); - - // Check that label is different when label contains a [] - const rootNode2 = new ZoweDatasetNode({ - label: "root[test]", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode2.dirty = true; - rootChildren = await rootNode2.getChildren(); - }); - - /************************************************************************************************************* - * Creates sample ZoweDatasetNode list and checks that getChildren() returns the correct array for a PO - *************************************************************************************************************/ - it("Testing that getChildren returns the correct Thenable for a PO", async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ label: "root", collapsibleState: vscode.TreeItemCollapsibleState.None, session, profile: profileOne }); - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - rootNode.dirty = true; - const subNode = new ZoweDatasetNode({ - label: "sub", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: profileOne, - }); - subNode.dirty = true; - const subChildren = await subNode.getChildren(); - - // Creating structure of files and folders under BRTVS99 profile - const sampleChildren: ZoweDatasetNode[] = [ - new ZoweDatasetNode({ - label: "BRTVS99", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: subNode, - profile: profileOne, - }), - new ZoweDatasetNode({ - label: "BRTVS99.DDIR", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: subNode, - profile: profileOne, - }), - ]; - - sampleChildren[0].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[0]] }; - sampleChildren[1].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[1]] }; - // Checking that the rootChildren are what they are expected to be - expect(subChildren).toEqual(sampleChildren); - }); - - /************************************************************************************************************* - * Checks that returning an unsuccessful response results in an error being thrown and caught - *************************************************************************************************************/ - it( - "Checks that when bright.List.dataSet/allMembers() returns an unsuccessful response, " + "it returns a label of 'No data sets found'", - async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - rootNode.dirty = true; - const subNode = new ZoweDatasetNode({ - label: "Response Fail", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: profileOne, - }); - jest.spyOn(subNode as any, "getDatasets").mockReturnValueOnce([ - { - success: true, - apiResponse: { - items: [], - }, - }, - ]); - subNode.dirty = true; - const response = await subNode.getChildren(); - expect(response[0].label).toBe("No data sets found"); - } - ); - - /************************************************************************************************************* - * Checks that passing a session node that is not dirty ignores the getChildren() method - *************************************************************************************************************/ - it("Checks that passing a session node that is not dirty the getChildren() method is exited early", async () => { - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - const infoChild = new ZoweDatasetNode({ - label: "Use the search button to display data sets", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: rootNode, - profile: profileOne, - contextOverride: globals.INFORMATION_CONTEXT, - }); - - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - rootNode.dirty = false; - await expect(await rootNode.getChildren()).toEqual([infoChild]); - }); - - /************************************************************************************************************* - * Checks that passing a session node with no hlq ignores the getChildren() method - *************************************************************************************************************/ - it("Checks that passing a session node with no hlq the getChildren() method is exited early", async () => { - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - const infoChild = new ZoweDatasetNode({ - label: "Use the search button to display data sets", - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: rootNode, - profile: profileOne, - contextOverride: globals.INFORMATION_CONTEXT, - }); - - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - await expect(await rootNode.getChildren()).toEqual([infoChild]); - }); - - /************************************************************************************************************* - * Checks that when getSession() is called on a memeber it returns the proper session - *************************************************************************************************************/ - it("Checks that a member can reach its session properly", async () => { - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode.contextValue = globals.DS_SESSION_CONTEXT; - const subNode = new ZoweDatasetNode({ - label: globals.DS_PDS_CONTEXT, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: profileOne, - }); - const member = new ZoweDatasetNode({ - label: globals.DS_MEMBER_CONTEXT, - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: subNode, - profile: profileOne, - }); - await expect(member.getSession()).toBeDefined(); - }); - /************************************************************************************************************* - * Tests that certain types can't have children - *************************************************************************************************************/ - it("Testing that certain types can't have children", async () => { - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode.dirty = true; - rootNode.contextValue = globals.DS_DS_CONTEXT; - expect(await rootNode.getChildren()).toHaveLength(0); - rootNode.contextValue = globals.DS_MEMBER_CONTEXT; - expect(await rootNode.getChildren()).toHaveLength(0); - rootNode.contextValue = globals.INFORMATION_CONTEXT; - expect(await rootNode.getChildren()).toHaveLength(0); - }); - /************************************************************************************************************* - * Tests that we shouldn't be updating children - *************************************************************************************************************/ - it("Tests that we shouldn't be updating children", async () => { - // Creating a rootNode - const rootNode = new ZoweDatasetNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - rootNode.children = [ - new ZoweDatasetNode({ label: "onestep", collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile: profileOne }), - ]; - rootNode.dirty = false; - rootNode.contextValue = globals.DS_PDS_CONTEXT; - expect((await rootNode.getChildren())[0].label).toEqual("onestep"); - }); - - /************************************************************************************************************* - * Run with a favorite - *************************************************************************************************************/ - it("Testing Run with a favorite", async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - // Creating a rootNode - const pds = new ZoweDatasetNode({ - label: "[root]: something", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - pds.dirty = true; - pds.contextValue = globals.DS_PDS_CONTEXT; - expect((await pds.getChildren())[0].label).toEqual("BRTVS99"); - }); - - /************************************************************************************************************* - * Multiple member names returned - *************************************************************************************************************/ - it("Testing what happens when response has multiple members", async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - const sessionNode = { - encodingMap: {}, - getSessionNode: jest.fn(), - sort: { method: DatasetSortOpts.Name, direction: SortDirection.Ascending }, - } as unknown as ZoweDatasetNode; - const getSessionNodeSpy = jest.spyOn(ZoweDatasetNode.prototype, "getSessionNode").mockReturnValue(sessionNode); - // Creating a rootNode - const pds = new ZoweDatasetNode({ - label: "[root]: something", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: sessionNode, - session, - profile: profileOne, - }); - pds.dirty = true; - pds.contextValue = globals.DS_PDS_CONTEXT; - const allMembers = jest.fn(); - allMembers.mockImplementationOnce(() => { - return { - success: true, - apiResponse: { - items: [{ member: "MEMBER1" }], - returnedRows: 3, - }, - }; - }); - Object.defineProperty(List, "allMembers", { value: allMembers }); - const pdsChildren = await pds.getChildren(); - expect(pdsChildren[0].label).toEqual("MEMBER1"); - expect(pdsChildren[0].contextValue).toEqual(globals.DS_MEMBER_CONTEXT); - expect(pdsChildren[1].label).toEqual("2 members with errors"); - expect(pdsChildren[1].contextValue).toEqual(globals.DS_FILE_ERROR_MEMBER_CONTEXT); - getSessionNodeSpy.mockRestore(); - }); - - /************************************************************************************************************* - * No values returned - *************************************************************************************************************/ - it("Testing what happens when response has no members", async () => { - Object.defineProperty(Profiles, "getInstance", { - value: jest.fn(() => { - return { - loadNamedProfile: jest.fn().mockReturnValue(profileOne), - }; - }), - }); - // Creating a rootNode - const pds = new ZoweDatasetNode({ - label: "[root]: something", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session, - profile: profileOne, - }); - pds.dirty = true; - pds.contextValue = globals.DS_PDS_CONTEXT; - const allMembers = jest.fn(); - allMembers.mockImplementationOnce(() => { - return { - success: true, - apiResponse: { - items: [], - }, - }; - }); - Object.defineProperty(List, "allMembers", { value: allMembers }); - expect((await pds.getChildren())[0].label).toEqual("No data sets found"); - }); -}); diff --git a/packages/zowe-explorer/__tests__/__unit__/abstract/ZoweTreeProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/abstract/ZoweTreeProvider.unit.test.ts index 2005f73583..fe815a749a 100644 --- a/packages/zowe-explorer/__tests__/__unit__/abstract/ZoweTreeProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/abstract/ZoweTreeProvider.unit.test.ts @@ -277,7 +277,7 @@ describe("Tree Provider unit tests, function getTreeItem", () => { }); }); -describe("Tree Provider unit tests, function getTreeItem", () => { +describe("Tree Provider unit tests, function flipState", () => { it("Testing that expand tree is executed successfully", async () => { const globalMocks = await createGlobalMocks(); const spy = jest.spyOn(ZoweLogger, "trace"); @@ -288,14 +288,17 @@ describe("Tree Provider unit tests, function getTreeItem", () => { session: globalMocks.testSession, }); folder.contextValue = globals.USS_DIR_CONTEXT; + folder.dirty = false; // Testing flipState to open await globalMocks.testUSSTree.flipState(folder, true); expect(JSON.stringify(folder.iconPath)).toContain("folder-open.svg"); + expect(folder.dirty).toBe(false); // Testing flipState to closed await globalMocks.testUSSTree.flipState(folder, false); expect(JSON.stringify(folder.iconPath)).toContain("folder-closed.svg"); + expect(folder.dirty).toBe(true); expect(spy).toBeCalled(); spy.mockClear(); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts index 524451e4b1..a4cb835c57 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/DatasetTree.unit.test.ts @@ -573,26 +573,6 @@ describe("Dataset Tree Unit Tests - Function getChildren", () => { expect(children).toEqual(sampleChildren); }); - it("Checking function for return if element.getChildren is undefined", async () => { - createGlobalMocks(); - const blockMocks = createBlockMocks(); - - mocked(Profiles.getInstance).mockReturnValue(blockMocks.profile); - mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView); - const testTree = new DatasetTree(); - testTree.mSessionNodes.push(blockMocks.datasetSessionNode); - const parent = new ZoweDatasetNode({ - label: "BRTVS99.PUBLIC", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: testTree.mSessionNodes[1], - }); - parent.dirty = true; - jest.spyOn(parent, "getChildren").mockResolvedValueOnce(undefined as any); - - const children = await testTree.getChildren(parent); - - expect(children).not.toBeDefined(); - }); }); describe("Dataset Tree Unit Tests - Function loadProfilesForFavorites", () => { function createBlockMocks() { @@ -1043,8 +1023,8 @@ describe("USSTree Unit Tests - Function addSingleSession", () => { } it("Tests that addSingleSession doesn't add the session again, if it was already added", async () => { - await createGlobalMocks(); - const blockMocks = await createBlockMocks(); + createGlobalMocks(); + const blockMocks = createBlockMocks(); await blockMocks.testTree.addSingleSession(blockMocks.testProfile); @@ -1052,8 +1032,8 @@ describe("USSTree Unit Tests - Function addSingleSession", () => { }); it("Tests that addSingleSession successfully adds a session", async () => { - await createGlobalMocks(); - const blockMocks = await createBlockMocks(); + createGlobalMocks(); + const blockMocks = createBlockMocks(); blockMocks.testTree.mSessionNodes.pop(); blockMocks.testSession.ISession.tokenType = blockMocks.testBaseProfile.profile.tokenType; @@ -1520,7 +1500,7 @@ describe("Dataset Tree Unit Tests - Function flipState", () => { }); }); describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { log: zowe.imperative.Logger.getAppLogger(), session: createISession(), @@ -1556,7 +1536,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { it("Checking adding of new filter - Theia", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); globalMocks.isTheia.mockReturnValue(true); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); @@ -1572,7 +1552,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking cancelled attempt to add a filter - Theia", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); globalMocks.isTheia.mockReturnValue(true); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); @@ -1587,7 +1567,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking usage of existing filter - Theia", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); globalMocks.isTheia.mockReturnValue(true); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("HLQ.PROD1.STUFF")); @@ -1603,7 +1583,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking cancelling of filter prompt with available filters - Theia", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); globalMocks.isTheia.mockReturnValue(true); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(undefined); @@ -1618,7 +1598,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking function on favorites", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView); const testTree = new DatasetTree(); @@ -1646,7 +1626,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking adding of new filter", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD1.STUFF"); @@ -1671,7 +1651,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking adding of new filter of multiple ds search", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD(STUF*),HLQ.PROD1*"); @@ -1699,7 +1679,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking adding of new filter with data set member", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD1(MEMBER)"); @@ -1712,7 +1692,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking adding of new filter with Unverified profile", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); Object.defineProperty(Profiles, "getInstance", { value: jest.fn(() => { return { @@ -1740,7 +1720,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking cancelled attempt to add a filter", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce(undefined); @@ -1754,7 +1734,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking usage of existing filter from filterPrompt", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); const quickPickItem = new utils.FilterDescriptor("HLQ.PROD1.STUFF"); mocked(vscode.window.createQuickPick).mockReturnValueOnce( @@ -1775,7 +1755,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking cancelling of filter prompt with available filters", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); const quickPickItem = undefined; mocked(vscode.window.createQuickPick).mockReturnValueOnce(createQuickPickContent("HLQ.PROD1.STUFF", quickPickItem, blockMocks.qpPlaceholder)); @@ -1793,7 +1773,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking adding of new filter error is caught on getChildren", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD1.STUFF"); @@ -1815,14 +1795,14 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking function for return if getChildren is undefined", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD1.STUFF"); mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView); const testTree = new DatasetTree(); testTree.mSessionNodes.push(blockMocks.datasetSessionNode); - Object.defineProperty(testTree.mSessionNodes[1], "getChildren", { + Object.defineProperty(testTree, "getChildren", { value: jest.fn(() => { return; }), @@ -1837,7 +1817,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking function for return if element.getChildren calls error handling for success: false", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); const errorSpy = jest.spyOn(utils, "errorHandling"); const debugSpy = jest.spyOn(ZoweLogger, "debug"); @@ -1866,7 +1846,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); it("Checking function for return if element.getChildren returns undefined", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); mocked(vscode.window.showQuickPick).mockResolvedValueOnce(new utils.FilterDescriptor("\uFF0B " + "Create a new filter")); mocked(vscode.window.showInputBox).mockResolvedValueOnce("HLQ.PROD1.STUFF"); @@ -1883,7 +1863,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { it("updates stats with modified date and user ID if provided in API", async () => { const globalMocks = createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const blockMocks = createBlockMocks(globalMocks); const testTree = new DatasetTree(); testTree.mSessionNodes.push(blockMocks.datasetSessionNode); @@ -1923,7 +1903,7 @@ describe("Dataset Tree Unit Tests - Function datasetFilterPrompt", () => { }); }); describe("Dataset Tree Unit Tests - Function editSession", () => { - async function createBlockMocks() { + function createBlockMocks() { const newMocks = { log: zowe.imperative.Logger.getAppLogger(), session: createISession(), @@ -1937,7 +1917,7 @@ describe("Dataset Tree Unit Tests - Function editSession", () => { mockCheckCurrentProfile: jest.fn(), }; - newMocks.datasetSessionNode = await createDatasetSessionNode(newMocks.session, newMocks.imperativeProfile); + newMocks.datasetSessionNode = createDatasetSessionNode(newMocks.session, newMocks.imperativeProfile); newMocks.profile = createInstanceOfProfile(newMocks.imperativeProfile); Object.defineProperty(Profiles, "getInstance", { @@ -1966,7 +1946,7 @@ describe("Dataset Tree Unit Tests - Function editSession", () => { it("Checking common run of function", async () => { createGlobalMocks(); - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); mocked(vscode.window.createTreeView).mockReturnValueOnce(blockMocks.treeView); const testTree = new DatasetTree(); diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/ZoweDatasetNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/ZoweDatasetNode.unit.test.ts index cd76da246a..ea29bb138d 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/ZoweDatasetNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/ZoweDatasetNode.unit.test.ts @@ -10,7 +10,7 @@ */ import * as vscode from "vscode"; -import { Gui, ValidProfileEnum } from "@zowe/zowe-explorer-api"; +import { imperative, Gui, ValidProfileEnum, DatasetSortOpts, SortDirection } from "@zowe/zowe-explorer-api"; import { createSessCfgFromArgs, createInstanceOfProfile, @@ -30,6 +30,7 @@ import { Profiles } from "../../../src/Profiles"; import { ZoweLogger } from "../../../src/utils/LoggerUtils"; import { LocalFileManagement } from "../../../src/utils/LocalFileManagement"; import { getIconById, IconId } from "../../../src/generators/icons"; +import { List } from "@zowe/cli"; // Missing the definition of path module, because I need the original logic for tests jest.mock("fs"); @@ -70,6 +71,518 @@ function createGlobalMocks() { return newMocks; } +describe("ZoweDatasetNode Unit Tests", () => { + // Globals + const session = new imperative.Session({ + user: "fake", + password: "fake", + hostname: "fake", + protocol: "https", + type: "basic", + }); + const profileOne: imperative.IProfileLoaded = { + name: "profile1", + profile: {}, + type: "zosmf", + message: "", + failNotFound: false, + }; + const ProgressLocation = jest.fn().mockImplementation(() => { + return { + Notification: 15, + }; + }); + + const withProgress = jest.fn().mockImplementation((progLocation, callback) => { + return callback(); + }); + + Object.defineProperty(globals, "LOG", { value: jest.fn(), configurable: true }); + Object.defineProperty(globals.LOG, "error", { value: jest.fn(), configurable: true }); + Object.defineProperty(vscode, "ProgressLocation", { value: ProgressLocation }); + Object.defineProperty(vscode.window, "withProgress", { value: withProgress }); + Object.defineProperty(ZoweLogger, "error", { value: jest.fn(), configurable: true }); + Object.defineProperty(ZoweLogger, "trace", { value: jest.fn(), configurable: true }); + + beforeEach(() => { + withProgress.mockImplementation((progLocation, callback) => { + return callback(); + }); + }); + + const showErrorMessage = jest.fn(); + Object.defineProperty(vscode.window, "showErrorMessage", { value: showErrorMessage }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + /************************************************************************************************************* + * Creates an ZoweDatasetNode and checks that its members are all initialized by the constructor + *************************************************************************************************************/ + it("Testing that the ZoweDatasetNode is defined", async () => { + const testNode = new ZoweDatasetNode({ label: "BRTVS99", collapsibleState: vscode.TreeItemCollapsibleState.None, session }); + testNode.contextValue = globals.DS_SESSION_CONTEXT; + + expect(testNode.label).toBeDefined(); + expect(testNode.collapsibleState).toBeDefined(); + expect(testNode.label).toBeDefined(); + expect(testNode.getParent()).toBeUndefined(); + expect(testNode.getSession()).toBeDefined(); + }); + + /************************************************************************************************************* + * Creates sample ZoweDatasetNode list and checks that getChildren() returns the correct array + *************************************************************************************************************/ + it("Testing that getChildren returns the correct Thenable", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + rootNode.dirty = true; + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + rootNode.pattern = "SAMPLE, SAMPLE.PUBLIC, SAMPLE"; + let rootChildren = await rootNode.getChildren(); + + // Creating structure of files and folders under BRTVS99 profile + const sampleChildren: ZoweDatasetNode[] = [ + new ZoweDatasetNode({ + label: "BRTVS99", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: profileOne, + }), + new ZoweDatasetNode({ + label: "BRTVS99.CA10", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: profileOne, + contextOverride: globals.DS_MIGRATED_FILE_CONTEXT, + }), + new ZoweDatasetNode({ + label: "BRTVS99.CA11.SPFTEMP0.CNTL", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }), + new ZoweDatasetNode({ + label: "BRTVS99.DDIR", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }), + new ZoweDatasetNode({ + label: "BRTVS99.VS1", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: profileOne, + contextOverride: globals.VSAM_CONTEXT, + }), + ]; + sampleChildren[0].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[0]] }; + + // Checking that the rootChildren are what they are expected to be + expect(rootChildren).toEqual(sampleChildren); + + rootNode.dirty = true; + // Check the dirty and children variable have been set + rootChildren = await rootNode.getChildren(); + + // Checking that the rootChildren are what they are expected to be + expect(rootChildren).toEqual(sampleChildren); + + // Check that error is thrown when label is blank + const errorNode = new ZoweDatasetNode({ + label: "", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + errorNode.dirty = true; + await expect(errorNode.getChildren()).rejects.toEqual(Error("Invalid node")); + + // Check that label is different when label contains a [] + const rootNode2 = new ZoweDatasetNode({ + label: "root[test]", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + rootNode2.dirty = true; + rootChildren = await rootNode2.getChildren(); + }); + + /************************************************************************************************************* + * Creates sample ZoweDatasetNode list and checks that getChildren() returns the correct array for a PO + *************************************************************************************************************/ + it("Testing that getChildren returns the correct Thenable for a PO", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ label: "root", collapsibleState: vscode.TreeItemCollapsibleState.None, session, profile: profileOne }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + rootNode.dirty = true; + const subNode = new ZoweDatasetNode({ + label: "sub", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }); + subNode.dirty = true; + const subChildren = await subNode.getChildren(); + + // Creating structure of files and folders under BRTVS99 profile + const sampleChildren: ZoweDatasetNode[] = [ + new ZoweDatasetNode({ + label: "BRTVS99", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: subNode, + profile: profileOne, + }), + new ZoweDatasetNode({ + label: "BRTVS99.DDIR", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: subNode, + profile: profileOne, + }), + ]; + + sampleChildren[0].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[0]] }; + sampleChildren[1].command = { command: "zowe.ds.ZoweNode.openPS", title: "", arguments: [sampleChildren[1]] }; + // Checking that the rootChildren are what they are expected to be + expect(subChildren).toEqual(sampleChildren); + }); + + /************************************************************************************************************* + * Checks that returning an unsuccessful response results in an error being thrown and caught + *************************************************************************************************************/ + it("Checks that when List.dataSet/allMembers() throws an error, it returns an empty list", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + contextOverride: globals.DS_SESSION_CONTEXT, + }); + rootNode.dirty = true; + const subNode = new ZoweDatasetNode({ + label: "Response Fail", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }); + jest.spyOn(List, "allMembers").mockRejectedValueOnce(new Error(subNode.label as string)); + // Populate node with children from previous search to ensure they are removed + subNode.children = [ + new ZoweDatasetNode({ label: "old", collapsibleState: vscode.TreeItemCollapsibleState.None, session, profile: profileOne }), + ]; + subNode.dirty = true; + const response = await subNode.getChildren(); + expect(response).toEqual([]); + }); + + it("Checks that when List.dataSet/allMembers() returns an empty response, it returns a label of 'No data sets found'", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + contextOverride: globals.DS_SESSION_CONTEXT, + }); + rootNode.dirty = true; + const subNode = new ZoweDatasetNode({ + label: "Response Fail", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }); + jest.spyOn(subNode as any, "getDatasets").mockResolvedValueOnce([ + { + success: true, + apiResponse: { + items: [], + }, + }, + ]); + subNode.dirty = true; + const response = await subNode.getChildren(); + expect(response[0].label).toBe("No data sets found"); + }); + + /************************************************************************************************************* + * Checks that passing a session node that is not dirty ignores the getChildren() method + *************************************************************************************************************/ + it("Checks that passing a session node that is not dirty the getChildren() method is exited early", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + contextOverride: globals.DS_SESSION_CONTEXT, + }); + const infoChild = new ZoweDatasetNode({ + label: "Use the search button to display data sets", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + contextOverride: globals.INFORMATION_CONTEXT, + }); + rootNode.dirty = false; + expect(await rootNode.getChildren()).toEqual([infoChild]); + }); + + /************************************************************************************************************* + * Checks that passing a session node with no hlq ignores the getChildren() method + *************************************************************************************************************/ + it("Checks that passing a session node with no hlq the getChildren() method is exited early", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + contextOverride: globals.DS_SESSION_CONTEXT, + }); + const infoChild = new ZoweDatasetNode({ + label: "Use the search button to display data sets", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + contextOverride: globals.INFORMATION_CONTEXT, + }); + expect(await rootNode.getChildren()).toEqual([infoChild]); + }); + + /************************************************************************************************************* + * Checks that when getSession() is called on a memeber it returns the proper session + *************************************************************************************************************/ + it("Checks that a member can reach its session properly", async () => { + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + const subNode = new ZoweDatasetNode({ + label: globals.DS_PDS_CONTEXT, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: rootNode, + profile: profileOne, + }); + const member = new ZoweDatasetNode({ + label: globals.DS_MEMBER_CONTEXT, + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: subNode, + profile: profileOne, + }); + expect(member.getSession()).toBeDefined(); + }); + /************************************************************************************************************* + * Tests that certain types can't have children + *************************************************************************************************************/ + it("Testing that certain types can't have children", async () => { + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + rootNode.dirty = true; + rootNode.contextValue = globals.DS_DS_CONTEXT; + expect(await rootNode.getChildren()).toHaveLength(0); + rootNode.contextValue = globals.DS_MEMBER_CONTEXT; + expect(await rootNode.getChildren()).toHaveLength(0); + rootNode.contextValue = globals.INFORMATION_CONTEXT; + expect(await rootNode.getChildren()).toHaveLength(0); + }); + /************************************************************************************************************* + * Tests that we shouldn't be updating children + *************************************************************************************************************/ + it("Tests that we shouldn't be updating children", async () => { + // Creating a rootNode + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + rootNode.children = [ + new ZoweDatasetNode({ label: "onestep", collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, session, profile: profileOne }), + ]; + rootNode.dirty = false; + rootNode.contextValue = globals.DS_PDS_CONTEXT; + expect((await rootNode.getChildren())[0].label).toEqual("onestep"); + }); + + /************************************************************************************************************* + * Multiple member names returned + *************************************************************************************************************/ + it("Testing what happens when response has multiple members", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + const sessionNode = { + encodingMap: {}, + getSessionNode: jest.fn(), + sort: { method: DatasetSortOpts.Name, direction: SortDirection.Ascending }, + } as unknown as ZoweDatasetNode; + const getSessionNodeSpy = jest.spyOn(ZoweDatasetNode.prototype, "getSessionNode").mockReturnValue(sessionNode); + // Creating a rootNode + const pds = new ZoweDatasetNode({ + label: "[root]: something", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: sessionNode, + session, + profile: profileOne, + contextOverride: globals.DS_PDS_CONTEXT, + }); + pds.dirty = true; + const allMembers = jest.fn(); + allMembers.mockImplementationOnce(() => { + return { + success: true, + apiResponse: { + items: [{ member: "MEMBER1" }], + returnedRows: 3, + }, + }; + }); + Object.defineProperty(List, "allMembers", { value: allMembers }); + const pdsChildren = await pds.getChildren(); + expect(pdsChildren[0].label).toEqual("MEMBER1"); + expect(pdsChildren[0].contextValue).toEqual(globals.DS_MEMBER_CONTEXT); + expect(pdsChildren[1].label).toEqual("2 members with errors"); + expect(pdsChildren[1].contextValue).toEqual(globals.DS_FILE_ERROR_MEMBER_CONTEXT); + getSessionNodeSpy.mockRestore(); + }); + it("Testing what happens when response has multiple members and member pattern is set", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + const sessionNode = { + encodingMap: {}, + getSessionNode: jest.fn(), + sort: { method: DatasetSortOpts.Name, direction: SortDirection.Ascending }, + } as unknown as ZoweDatasetNode; + const getSessionNodeSpy = jest.spyOn(ZoweDatasetNode.prototype, "getSessionNode").mockReturnValue(sessionNode); + // Creating a rootNode + const pds = new ZoweDatasetNode({ + label: "[root]: something", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + parentNode: sessionNode, + session, + profile: profileOne, + contextOverride: globals.DS_PDS_CONTEXT, + }); + pds.dirty = true; + pds.memberPattern = "MEM*"; + const allMembers = jest.fn(); + allMembers.mockImplementationOnce(() => { + return { + success: true, + apiResponse: { + items: [{ member: "MEMBER1" }], + returnedRows: 1, + }, + }; + }); + Object.defineProperty(List, "allMembers", { value: allMembers }); + const pdsChildren = await pds.getChildren(); + expect(pdsChildren[0].label).toEqual("MEMBER1"); + expect(pdsChildren[0].contextValue).toEqual(globals.DS_MEMBER_CONTEXT); + expect(allMembers).toHaveBeenCalledWith(expect.any(imperative.Session), pds.label, expect.objectContaining({ pattern: pds.memberPattern })); + getSessionNodeSpy.mockRestore(); + }); + + /************************************************************************************************************* + * No values returned + *************************************************************************************************************/ + it("Testing what happens when response has no members", async () => { + Object.defineProperty(Profiles, "getInstance", { + value: jest.fn(() => { + return { + loadNamedProfile: jest.fn().mockReturnValue(profileOne), + }; + }), + }); + // Creating a rootNode + const pds = new ZoweDatasetNode({ + label: "[root]: something", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session, + profile: profileOne, + }); + pds.dirty = true; + pds.contextValue = globals.DS_PDS_CONTEXT; + const allMembers = jest.fn(); + allMembers.mockImplementationOnce(() => { + return { + success: true, + apiResponse: { + items: [], + }, + }; + }); + Object.defineProperty(List, "allMembers", { value: allMembers }); + expect((await pds.getChildren())[0].label).toEqual("No data sets found"); + }); +}); + describe("ZoweDatasetNode Unit Tests - Function node.openDs()", () => { function createBlockMocks() { const session = createISession(); @@ -353,7 +866,7 @@ describe("ZoweDatasetNode Unit Tests - Function node.openDs()", () => { contextOverride: globals.DS_MEMBER_CONTEXT, }); const showErrorMessageSpy = jest.spyOn(Gui, "errorMessage"); - const logErrorSpy = jest.spyOn(ZoweLogger, "error"); + const logErrorSpy = jest.spyOn(ZoweLogger, "error").mockClear(); try { await node.openDs(false, true, blockMocks.testDatasetTree); diff --git a/packages/zowe-explorer/__tests__/__unit__/job/ZoweJobNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/job/ZoweJobNode.unit.test.ts index b698fe2027..967d9c6194 100644 --- a/packages/zowe-explorer/__tests__/__unit__/job/ZoweJobNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/job/ZoweJobNode.unit.test.ts @@ -15,7 +15,7 @@ import * as vscode from "vscode"; import * as zowe from "@zowe/cli"; import * as globals from "../../../src/globals"; import { createIJobFile, createIJobObject, createJobSessionNode } from "../../../__mocks__/mockCreators/jobs"; -import { ZoweJobNode } from "../../../src/job/ZoweJobNode"; +import { ZoweJobNode, ZoweSpoolNode } from "../../../src/job/ZoweJobNode"; import { IZoweJobTreeNode, ProfilesCache, Gui, JobSortOpts, SortDirection } from "@zowe/zowe-explorer-api"; import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister"; import { Profiles } from "../../../src/Profiles"; @@ -378,7 +378,26 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { globalMocks.testJobNode.dirty = true; const spoolFilesAfter = await globalMocks.testJobNode.getChildren(); expect(spoolFilesAfter.length).toBe(1); - expect(spoolFilesAfter[0].label).toEqual("There are no JES spool messages to display"); + expect(spoolFilesAfter[0].label).toEqual("No spool files found"); + }); + + it("Tests that getChildren returns empty list if there is error retrieving spool files", async () => { + const globalMocks = await createGlobalMocks(); + jest.spyOn(ZoweExplorerApiRegister, "getJesApi").mockReturnValueOnce({ + getSpoolFiles: jest.fn().mockResolvedValue(new Error("Response Fail")), + } as any); + // Populate node with children from previous search to ensure they are removed + globalMocks.testJobNode.children = [ + new ZoweSpoolNode({ + label: "old", + collapsibleState: vscode.TreeItemCollapsibleState.None, + session: globalMocks.testSession, + profile: globalMocks.testProfile, + }), + ]; + globalMocks.testJobNode.dirty = true; + const spools = await globalMocks.testJobNode.getChildren(); + expect(spools).toEqual([]); }); it("Tests that getChildren returns the spool files if user/owner is not defined", async () => { @@ -394,12 +413,13 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { expect(spoolFiles[0].owner).toEqual("*"); }); - it("should return a new job if not owner and is a session", async () => { + it("should return placeholder node if session node expanded without search params", async () => { const globalMocks = await createGlobalMocks(); const expectedJob = new ZoweJobNode({ label: "Use the search button to display jobs", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: globalMocks.testJobNode, + contextOverride: globals.INFORMATION_CONTEXT, }); globalMocks.testJobNode._owner = null; @@ -414,10 +434,9 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { label: "No jobs found", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: globalMocks.testJobsProvider.mSessionNodes[1], + contextOverride: globals.INFORMATION_CONTEXT, }), ]; - expectedJob[0].iconPath = null; - expectedJob[0].contextValue = "information"; await globalMocks.testJobsProvider.addSession("fake"); globalMocks.testJobsProvider.mSessionNodes[1].filtered = true; jest.spyOn(globalMocks.testJobsProvider.mSessionNodes[1], "getJobs").mockResolvedValue([]); @@ -425,6 +444,27 @@ describe("ZoweJobNode unit tests - Function getChildren", () => { expect(jobs).toEqual(expectedJob); }); + it("should return empty list if there is error retrieving jobs", async () => { + const globalMocks = await createGlobalMocks(); + await globalMocks.testJobsProvider.addSession("fake"); + // Populate node with children from previous search to ensure they are removed + globalMocks.testJobsProvider.mSessionNodes[1].children = [ + new ZoweJobNode({ + label: "old", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.testSession, + profile: globalMocks.testProfile, + }), + ]; + globalMocks.testJobsProvider.mSessionNodes[1].filtered = true; + globalMocks.mockGetJobsByParameters.mockRejectedValue(new Error("Response Fail")); + jest.spyOn(ZoweExplorerApiRegister, "getJesApi").mockReturnValueOnce({ + getSession: jest.fn().mockReturnValue(globalMocks.testSession), + } as any); + const jobs = await globalMocks.testJobsProvider.mSessionNodes[1].getChildren(); + expect(jobs).toEqual([]); + }); + it("To check smfid field in Jobs Tree View", async () => { const globalMocks = await createGlobalMocks(); diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts index 85acd51746..5c6a90b9c1 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/init.unit.test.ts @@ -311,7 +311,8 @@ describe("Test src/shared/extension", () => { describe("watchConfigProfile", () => { let context: any; let watcherPromise: any; - const spyReadFile = jest.fn().mockReturnValue("test"); + const fakeUri = { fsPath: "fsPath" }; + const spyReadFile = jest.fn().mockReturnValue(Buffer.from("test")); const mockEmitter = jest.fn(); const watcher: any = { onDidCreate: jest.fn(), @@ -321,9 +322,12 @@ describe("Test src/shared/extension", () => { beforeEach(() => { context = { subscriptions: [] }; jest.clearAllMocks(); - Object.defineProperty(vscode.workspace, "workspaceFolders", { value: [{ uri: { fsPath: "fsPath" } }], configurable: true }); + Object.defineProperty(vscode.workspace, "workspaceFolders", { value: [{ uri: fakeUri }], configurable: true }); Object.defineProperty(vscode.workspace, "fs", { value: { readFile: spyReadFile }, configurable: true }); - Object.defineProperty(globals, "SAVED_PROFILE_CONTENTS", { value: "test", configurable: true }); + Object.defineProperty(globals, "SAVED_PROFILE_CONTENTS", { + value: new Map(Object.entries({ [fakeUri.fsPath]: Buffer.from("test") })), + configurable: true, + }); jest.spyOn(vscode.workspace, "createFileSystemWatcher").mockReturnValue(watcher); jest.spyOn(ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter, "fire").mockImplementation(mockEmitter); }); @@ -354,23 +358,23 @@ describe("Test src/shared/extension", () => { it("should be able to trigger onDidChange listener", async () => { const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); - watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun(fakeUri))); sharedExtension.watchConfigProfile(context); await watcherPromise; expect(context.subscriptions).toContain(watcher); - expect(spyReadFile).toHaveBeenCalledWith("uri"); + expect(spyReadFile).toHaveBeenCalledWith(fakeUri); expect(spyRefreshAll).not.toHaveBeenCalled(); expect(mockEmitter).not.toHaveBeenCalled(); }); it("should be able to trigger onDidChange listener with changes", async () => { const spyRefreshAll = jest.spyOn(refreshActions, "refreshAll").mockImplementation(); - spyReadFile.mockReturnValueOnce("other"); - watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun("uri"))); + spyReadFile.mockReturnValueOnce(Buffer.from("other")); + watcher.onDidChange.mockImplementationOnce((fun) => (watcherPromise = fun(fakeUri))); sharedExtension.watchConfigProfile(context); await watcherPromise; expect(context.subscriptions).toContain(watcher); - expect(spyReadFile).toHaveBeenCalledWith("uri"); + expect(spyReadFile).toHaveBeenCalledWith(fakeUri); expect(spyRefreshAll).toHaveBeenCalledTimes(1); expect(mockEmitter).toHaveBeenCalledTimes(1); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts index 3a86769159..09c7669e8a 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts @@ -33,10 +33,11 @@ import { Gui, IZoweDatasetTreeNode, IZoweTreeNode, IZoweUSSTreeNode, ProfilesCac import { ZoweLogger } from "../../../src/utils/LoggerUtils"; import { LocalStorageKey, ZoweLocalStorage } from "../../../src/utils/ZoweLocalStorage"; import { LocalFileManagement } from "../../../src/utils/LocalFileManagement"; +import { TreeProviders } from "../../../src/shared/TreeProviders"; jest.mock("fs"); -async function createGlobalMocks() { +function createGlobalMocks() { const newMocks = { session: createISession(), profileOne: createIProfile(), @@ -85,7 +86,7 @@ async function createGlobalMocks() { describe("Shared Utils Unit Tests - Function node.concatChildNodes()", () => { it("Checks that concatChildNodes returns the proper array of children", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const rootNode = new ZoweUSSNode({ label: "root", collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, @@ -124,17 +125,45 @@ describe("syncSessionNode shared util function", () => { const sessionNode = createDatasetSessionNode(undefined, serviceProfile); it("should update a session and a profile in the provided node", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // given Object.defineProperty(globalMocks.mockProfilesCache, "loadNamedProfile", { value: jest.fn().mockReturnValue(createIProfile()), }); const expectedSession = new imperative.Session({}); - const sessionForProfile = () => new imperative.Session({}); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const sessionForProfile = (_profile) => + ({ + getSession: () => new imperative.Session({}), + } as any); // when - await utils.syncSessionNode(Profiles.getInstance())(sessionForProfile)(sessionNode); - expect(await sessionNode.getSession()).toEqual(expectedSession); - expect(await sessionNode.getProfile()).toEqual(createIProfile()); + utils.syncSessionNode(sessionForProfile, sessionNode); + expect(sessionNode.getSession()).toEqual(expectedSession); + expect(sessionNode.getProfile()).toEqual(createIProfile()); + }); + it("should update session node and refresh tree node if provided", async () => { + const globalMocks = createGlobalMocks(); + // given + Object.defineProperty(globalMocks.mockProfilesCache, "loadNamedProfile", { + value: jest.fn().mockReturnValue(createIProfile()), + }); + const getChildrenSpy = jest.spyOn(sessionNode, "getChildren").mockResolvedValueOnce([]); + const refreshElementMock = jest.fn(); + jest.spyOn(TreeProviders, "getProviderForNode").mockReturnValueOnce({ + refreshElement: refreshElementMock, + } as any); + const getSessionMock = jest.fn().mockReturnValue(new imperative.Session({})); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const sessionForProfile = (_profile) => + ({ + getSession: getSessionMock, + } as any); + // when + utils.syncSessionNode(sessionForProfile, sessionNode, sessionNode); + expect(getSessionMock).toHaveBeenCalled(); + expect(sessionNode.dirty).toBe(true); + expect(await getChildrenSpy).toHaveBeenCalled(); + expect(refreshElementMock).toHaveBeenCalledWith(sessionNode); }); it("should do nothing, if there is no profile from provided node in the file system", async () => { const profiles = createInstanceOfProfile(serviceProfile); @@ -145,8 +174,12 @@ describe("syncSessionNode shared util function", () => { ); profiles.getBaseProfile = jest.fn(() => undefined); // when - const dummyFn = () => new imperative.Session({}); - await utils.syncSessionNode(profiles)(dummyFn)(sessionNode); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const dummyFn = (_profile) => + ({ + getSession: () => new imperative.Session({}), + } as any); + utils.syncSessionNode(dummyFn, sessionNode); // then const initialSession = sessionNode.getSession(); const initialProfile = sessionNode.getProfile(); @@ -307,7 +340,7 @@ describe("Test uploadContent", () => { }); describe("Test force upload", () => { - async function createBlockMocks() { + function createBlockMocks() { const newVariables = { dsNode: new ZoweDatasetNode({ label: "", collapsibleState: vscode.TreeItemCollapsibleState.None }), ussNode: new ZoweUSSNode({ label: "", collapsibleState: vscode.TreeItemCollapsibleState.None }), @@ -372,7 +405,7 @@ describe("Test force upload", () => { }); it("should successfully call upload for a USS file if user clicks 'Yes'", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); blockMocks.showInformationMessage.mockResolvedValueOnce("Yes"); blockMocks.withProgress.mockResolvedValueOnce(blockMocks.fileResponse); await sharedUtils.willForceUpload(blockMocks.ussNode, blockMocks.mockDoc, null); @@ -387,7 +420,7 @@ describe("Test force upload", () => { }); it("should successfully call upload for a data set if user clicks 'Yes'", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); blockMocks.showInformationMessage.mockResolvedValueOnce("Yes"); blockMocks.withProgress.mockResolvedValueOnce(blockMocks.fileResponse); await sharedUtils.willForceUpload(blockMocks.dsNode, blockMocks.mockDoc, null); @@ -402,14 +435,14 @@ describe("Test force upload", () => { }); it("should cancel upload if user clicks 'No'", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); blockMocks.showInformationMessage.mockResolvedValueOnce("No"); await sharedUtils.willForceUpload(blockMocks.dsNode, blockMocks.mockDoc, null); expect(blockMocks.showInformationMessage.mock.calls[1][0]).toBe("Upload cancelled."); }); it("should display specific message if Theia is detected", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); Object.defineProperty(globals, "ISTHEIA", { value: true }); blockMocks.showInformationMessage.mockResolvedValueOnce("No"); await sharedUtils.willForceUpload(blockMocks.dsNode, blockMocks.mockDoc, null); @@ -419,7 +452,7 @@ describe("Test force upload", () => { }); it("should show error message if file fails to upload", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); blockMocks.showInformationMessage.mockResolvedValueOnce("Yes"); blockMocks.withProgress.mockResolvedValueOnce({ ...blockMocks.fileResponse, success: false }); await sharedUtils.willForceUpload(blockMocks.ussNode, blockMocks.mockDoc, null); @@ -434,7 +467,7 @@ describe("Test force upload", () => { }); it("should show error message if upload throws an error", async () => { - const blockMocks = await createBlockMocks(); + const blockMocks = createBlockMocks(); blockMocks.showInformationMessage.mockResolvedValueOnce("Yes"); const testError = new Error("Task failed successfully"); blockMocks.withProgress.mockRejectedValueOnce(testError); @@ -1160,7 +1193,7 @@ describe("Shared utils unit tests - function confirmForUnsavedDoc", () => { }); describe("Shared utils unit tests - function initializeFileOpening", () => { it("successfully handles binary data sets that should be re-downloaded", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockRejectedValue("Test error!"); jest.spyOn(Gui, "errorMessage").mockResolvedValue("Re-download"); @@ -1185,7 +1218,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles binary data sets that should be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // Creating a test node const rootNode = new ZoweDatasetNode({ @@ -1208,7 +1241,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles text data sets that should be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); @@ -1232,7 +1265,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles text data sets that shouldn't be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); @@ -1256,7 +1289,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles binary USS files that should be re-downloaded", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockRejectedValue("Test error!"); jest.spyOn(Gui, "errorMessage").mockResolvedValue("Re-download"); @@ -1282,7 +1315,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles binary USS files that should be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // Creating a test node const rootNode = new ZoweUSSNode({ @@ -1306,7 +1339,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles text USS files that should be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); @@ -1331,7 +1364,7 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { }); it("successfully handles text USS files that shouldn't be previewed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); diff --git a/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts index 0cb8185826..199491b812 100644 --- a/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts @@ -25,7 +25,7 @@ import { createInstanceOfProfile, createValidIProfile, } from "../../../__mocks__/mockCreators/shared"; -import { createUSSTree } from "../../../__mocks__/mockCreators/uss"; +import { createUSSNode, createUSSTree } from "../../../__mocks__/mockCreators/uss"; import * as fs from "fs"; import * as path from "path"; import * as workspaceUtils from "../../../src/utils/workspace"; @@ -38,7 +38,7 @@ import { LocalFileManagement } from "../../../src/utils/LocalFileManagement"; jest.mock("fs"); jest.mock("path"); -async function createGlobalMocks() { +function createGlobalMocks() { const globalMocks = { ussFile: jest.fn(), Download: jest.fn(), @@ -187,7 +187,7 @@ async function createGlobalMocks() { describe("ZoweUSSNode Unit Tests - Initialization of class", () => { it("Checks that the ZoweUSSNode structure matches the snapshot", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); globalMocks.withProgress.mockImplementation((progLocation, callback) => { return callback(); @@ -223,7 +223,7 @@ describe("ZoweUSSNode Unit Tests - Initialization of class", () => { }); it("Tests that creating a new USS node initializes all methods and properties", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); globalMocks.withProgress.mockImplementation((progLocation, callback) => { return callback(); @@ -244,7 +244,7 @@ describe("ZoweUSSNode Unit Tests - Initialization of class", () => { }); it("Tests that creating a new binary USS node initializes all methods and properties", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); globalMocks.withProgress.mockImplementation((progLocation, callback) => { return callback(); @@ -267,7 +267,7 @@ describe("ZoweUSSNode Unit Tests - Initialization of class", () => { describe("ZoweUSSNode Unit Tests - Function node.getSession()", () => { it("Tests that node.getSession() returns the proper globalMocks.session", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // Creating a rootNode const rootNode = new ZoweUSSNode({ @@ -297,7 +297,7 @@ describe("ZoweUSSNode Unit Tests - Function node.getSession()", () => { }); describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { node: null, testUSSTree: null, @@ -344,8 +344,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { } it("Tests that node.refreshUSS() works correctly for dirty file state, when user didn't cancel file save", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.ussFile.mockResolvedValue(globalMocks.response); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(true); @@ -360,8 +360,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { }); it("Tests that node.refreshUSS() works correctly for dirty file state, when user cancelled file save", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.ussFile.mockResolvedValueOnce(globalMocks.response); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(true); @@ -376,8 +376,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { }); it("Tests that node.refreshUSS() works correctly for not dirty file state", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.ussFile.mockResolvedValueOnce(globalMocks.response); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(false); @@ -392,8 +392,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { }); it("Tests that node.refreshUSS() works correctly with exception thrown in process", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.ussFile.mockRejectedValueOnce(Error("")); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(true); @@ -407,8 +407,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { expect(blockMocks.node.downloaded).toBe(false); }); it("Tests that node.refreshUSS() throws an error when context value is invalid", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const badContextValueParent = new ZoweUSSNode({ label: "test-parent", collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, @@ -428,8 +428,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { expect(showErrorMessageSpy).toBeCalledTimes(1); }); it("Tests that node.refreshUSS() works correctly for files under directories", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.ussNode.contextValue = globals.USS_DIR_CONTEXT; globalMocks.ussFile.mockResolvedValueOnce(globalMocks.response); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(false); @@ -443,8 +443,8 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { expect(blockMocks.node.downloaded).toBe(true); }); it("Tests that node.refreshUSS() works correctly for favorited files/directories", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.ussNode.contextValue = globals.FAV_PROFILE_CONTEXT; globalMocks.ussFile.mockResolvedValueOnce(globalMocks.response); globalMocks.mockIsDirtyInEditor.mockReturnValueOnce(false); @@ -461,7 +461,7 @@ describe("ZoweUSSNode Unit Tests - Function node.refreshUSS()", () => { describe("ZoweUSSNode Unit Tests - Function node.getEtag()", () => { it("Tests that getEtag() returns a value", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const rootNode = new ZoweUSSNode({ label: "gappy", @@ -475,7 +475,7 @@ describe("ZoweUSSNode Unit Tests - Function node.getEtag()", () => { describe("ZoweUSSNode Unit Tests - Function node.setEtag()", () => { it("Tests that setEtag() assigns a value", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const rootNode = new ZoweUSSNode({ label: "gappy", @@ -490,7 +490,7 @@ describe("ZoweUSSNode Unit Tests - Function node.setEtag()", () => { }); describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { ussDir: new ZoweUSSNode({ label: "usstest", @@ -509,8 +509,8 @@ describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { return newMocks; } it("Tests that rename updates and refreshes the UI components of the node", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const newFullPath = "/u/user/newName"; await blockMocks.ussDir.rename(newFullPath); @@ -526,8 +526,8 @@ describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { blockMocks.providerSpy.mockClear(); }); it("Tests that rename updates and refreshes the UI components of any loaded children for a node", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); // Child dir of blockMocks.ussDir const ussSubDir = new ZoweUSSNode({ label: "ussSubDir", @@ -568,7 +568,7 @@ describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { describe("ZoweUSSNode Unit Tests - Function node.reopen()", () => { it("Tests that reopen works for a file with closed tab", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const hasClosedTab = true; const ussFile = new ZoweUSSNode({ label: "usstest", @@ -587,7 +587,7 @@ describe("ZoweUSSNode Unit Tests - Function node.reopen()", () => { }); it("Tests that reopen() opens a file if asked to refresh a closed file", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const vscodeCommandSpy = jest.spyOn(vscode.commands, "executeCommand"); @@ -608,7 +608,7 @@ describe("ZoweUSSNode Unit Tests - Function node.reopen()", () => { describe("ZoweUSSNode Unit Tests - Function node.setBinary()", () => { it("Tests that node.setBinary() works", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const rootNode = new ZoweUSSNode({ label: "favProfileNode", @@ -698,7 +698,7 @@ describe("ZoweUSSNode Unit Tests - Function node.setEncoding()", () => { }); describe("ZoweUSSNode Unit Tests - Function node.deleteUSSNode()", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { ussNode: null, testUSSTree: null, @@ -728,32 +728,32 @@ describe("ZoweUSSNode Unit Tests - Function node.deleteUSSNode()", () => { } it("Tests that node is deleted if user verified", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowWarningMessage.mockResolvedValueOnce("Delete"); await blockMocks.ussNode.deleteUSSNode(blockMocks.testUSSTree, "", false); expect(blockMocks.testUSSTree.refresh).toHaveBeenCalled(); }); it("Tests that node is not deleted if user did not verify", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowWarningMessage.mockResolvedValueOnce("Cancel"); await blockMocks.ussNode.deleteUSSNode(blockMocks.testUSSTree, "", true); expect(blockMocks.testUSSTree.refresh).not.toHaveBeenCalled(); }); it("Tests that node is not deleted if user cancelled", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowWarningMessage.mockResolvedValueOnce(undefined); await blockMocks.ussNode.deleteUSSNode(blockMocks.testUSSTree, "", true); expect(blockMocks.testUSSTree.refresh).not.toHaveBeenCalled(); }); it("Tests that node is not deleted if an error thrown", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowWarningMessage.mockResolvedValueOnce("Delete"); globalMocks.ussFile.mockImplementationOnce(() => { throw Error("testError"); @@ -771,7 +771,7 @@ describe("ZoweUSSNode Unit Tests - Function node.deleteUSSNode()", () => { }); describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { rootNode: new ZoweUSSNode({ label: "/u", @@ -797,8 +797,8 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { } it("Tests that node.getChildren() returns the correct Thenable", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.rootNode.contextValue = globals.USS_DIR_CONTEXT; blockMocks.rootNode.dirty = true; @@ -836,8 +836,8 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { }); it("Tests that node.getChildren() returns no children if none exist", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const nodeNoChildren = new ZoweUSSNode({ label: "aDir", @@ -856,8 +856,8 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { it("Tests that only children with parent paths matching the current fullPath are returned as existing children", async () => { // This tests functionality that prevents children of previous searches from appearing in new searches with different filepaths, // especially if file or folder names (labels) are shared between the different filepaths. - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const oldPath = "/u/oldUser"; const newPath = "/u/newUser"; @@ -900,9 +900,9 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { expect(newChildren[1].fullPath).toContain(newPath); }); - it("Tests that error is thrown when node label is blank", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + it("Tests that error is thrown when node label is blank", () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.rootNode.label = ""; blockMocks.rootNode.dirty = true; @@ -911,73 +911,54 @@ describe("ZoweUSSNode Unit Tests - Function node.getChildren()", () => { expect(blockMocks.rootNode.getChildren()).rejects.toEqual(Error("Invalid node")); }); - it( - "Tests that when zowe.List. causes an error on the zowe call, " + "node.getChildren() throws an error and the catch block is reached", - async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); - - blockMocks.childNode.contextValue = globals.USS_SESSION_CONTEXT; - blockMocks.childNode.fullPath = "Throw Error"; - blockMocks.childNode.dirty = true; - blockMocks.childNode.profile = globalMocks.profileOne; + it("Tests that when List.fileList throws an error, node.getChildren() throws an error and the catch block is reached", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); - await blockMocks.childNode.getChildren(); - expect(globalMocks.showErrorMessage.mock.calls.length).toEqual(1); - expect(globalMocks.showErrorMessage.mock.calls[0][0]).toEqual( - "Retrieving response from uss-file-list Error: Throwing an error to check error handling for unit tests!" - ); - } - ); - - it( - "Tests that when bright.List returns an unsuccessful response, " + "node.getChildren() throws an error and the catch block is reached", - async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); - - blockMocks.childNode.contextValue = globals.USS_SESSION_CONTEXT; - blockMocks.childNode.dirty = true; - blockMocks.childNode.profile = globalMocks.profileOne; - const subNode = new ZoweUSSNode({ - label: "Response Fail", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: blockMocks.childNode, - profile: globalMocks.profileOne, - }); - subNode.fullPath = "THROW ERROR"; - subNode.dirty = true; - - await subNode.getChildren(); - expect(globalMocks.showErrorMessage.mock.calls.length).toEqual(1); - expect(globalMocks.showErrorMessage.mock.calls[0][0]).toEqual( - "Retrieving response from uss-file-list Error: Throwing an error to check error handling for unit tests!" - ); - } - ); + // Populate node with children from previous search to ensure they are removed + blockMocks.childNode.children = [createUSSNode(globalMocks.session, globalMocks.profileOne)]; + blockMocks.childNode.contextValue = globals.USS_SESSION_CONTEXT; + blockMocks.childNode.fullPath = "Throw Error"; + blockMocks.childNode.dirty = true; + blockMocks.childNode.profile = globalMocks.profileOne; + + const response = await blockMocks.childNode.getChildren(); + expect(response).toEqual([]); + expect(globalMocks.showErrorMessage.mock.calls.length).toEqual(1); + expect(globalMocks.showErrorMessage.mock.calls[0][0]).toEqual( + "Retrieving response from USS list API Error: Throwing an error to check error handling for unit tests!" + ); + }); - it("Tests that when passing a globalMocks.session node that is not dirty the node.getChildren() method is exited early", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + it("Tests that when passing a session node that is not dirty the node.getChildren() method is exited early", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.rootNode.contextValue = globals.USS_SESSION_CONTEXT; blockMocks.rootNode.dirty = false; + blockMocks.rootNode.fullPath = "/some/path"; expect(await blockMocks.rootNode.getChildren()).toEqual([]); }); - it("Tests that when passing a globalMocks.session node with no hlq the node.getChildren() method is exited early", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + it("Tests that when passing a session node without path the node.getChildren() method is exited early", async () => { + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); blockMocks.rootNode.contextValue = globals.USS_SESSION_CONTEXT; + const expectedNode = new ZoweUSSNode({ + label: "Use the search button to list USS files", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: blockMocks.rootNode, + contextOverride: globals.INFORMATION_CONTEXT, + }); - expect(await blockMocks.rootNode.getChildren()).toEqual([]); + expect(await blockMocks.rootNode.getChildren()).toEqual([expectedNode]); }); }); describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { - async function createBlockMocks(globalMocks) { + function createBlockMocks(globalMocks) { const newMocks = { testUSSTree: null, dsNode: null, @@ -1047,8 +1028,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { } it("Tests that node.openUSS() is executed successfully", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const node = new ZoweUSSNode({ label: "node", @@ -1079,8 +1060,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() is executed successfully with Unverified profile", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); Object.defineProperty(Profiles, "getInstance", { value: jest.fn(() => { @@ -1122,8 +1103,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() fails when an error is thrown", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const parent = new ZoweUSSNode({ label: "parent", @@ -1159,8 +1140,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() executes successfully for favorited file", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); // Set up mock favorite globalMocks.session const favoriteSession = new ZoweUSSNode({ @@ -1198,8 +1179,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() executes successfully for child file of favorited directory", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); // Set up mock favorite globalMocks.session const favoriteSession = new ZoweUSSNode({ @@ -1234,8 +1215,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() is executed successfully when chtag says binary", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); globalMocks.isFileTagBinOrAscii.mockResolvedValue(true); globalMocks.existsSync.mockReturnValue(null); @@ -1263,8 +1244,8 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { }); it("Tests that node.openUSS() fails when passed an invalid node", async () => { - const globalMocks = await createGlobalMocks(); - const blockMocks = await createBlockMocks(globalMocks); + const globalMocks = createGlobalMocks(); + const blockMocks = createBlockMocks(globalMocks); const badParent = new ZoweUSSNode({ label: "parent", @@ -1289,7 +1270,7 @@ describe("ZoweUSSNode Unit Tests - Function node.openUSS()", () => { describe("ZoweUSSNode Unit Tests - Function node.isDirtyInEditor()", () => { it("Tests that node.isDirtyInEditor() returns true if the file is open", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // Creating a test node const rootNode = new ZoweUSSNode({ @@ -1312,7 +1293,7 @@ describe("ZoweUSSNode Unit Tests - Function node.isDirtyInEditor()", () => { }); it("Tests that node.isDirtyInEditor() returns false if the file is not open", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); globalMocks.mockTextDocuments.pop(); @@ -1339,7 +1320,7 @@ describe("ZoweUSSNode Unit Tests - Function node.isDirtyInEditor()", () => { describe("ZoweUSSNode Unit Tests - Function node.openedDocumentInstance()", () => { it("Tests that node.openedDocumentInstance() returns the document if it is open", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); // Creating a test node const rootNode = new ZoweUSSNode({ @@ -1362,7 +1343,7 @@ describe("ZoweUSSNode Unit Tests - Function node.openedDocumentInstance()", () = }); it("Tests that node.openedDocumentInstance() returns null if the file is not open", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); globalMocks.mockTextDocuments.pop(); @@ -1488,7 +1469,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { } it("Tests node.pasteUssTree() reads clipboard contents and uploads files successfully", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); blockMocks.pasteSpy.mockClear(); @@ -1536,7 +1517,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { }); it("paste renames duplicate files before copying when needed", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); blockMocks.pasteSpy.mockClear(); @@ -1563,7 +1544,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { }); it("Tests node.pasteUssTree() reads clipboard contents finds same file name on destination directory", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); jest.spyOn(blockMocks.mockUssApi, "fileList").mockResolvedValueOnce(blockMocks.fileResponseSame); @@ -1574,7 +1555,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { }); it("Tests node.pasteUssTree() could not retrieve fileList api response", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); jest.spyOn(blockMocks.mockUssApi, "fileList").mockResolvedValueOnce(blockMocks.fileResponseEmpty); @@ -1589,7 +1570,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { await expect(vscode.env.clipboard.readText()).resolves.not.toThrow(); }); it("Tests node.pasteUssTree() reads clipboard contents and returns early if nothing is in the clipboard", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); globalMocks.readText.mockResolvedValueOnce(""); @@ -1603,7 +1584,7 @@ describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { }); it("Tests node.pasteUssTree() reads clipboard contents and fails to upload directory & file", async () => { - const globalMocks = await createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); jest.spyOn(blockMocks.mockUssApi, "fileList").mockResolvedValueOnce(blockMocks.fileResponse); 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 34d55863bb..ca24f40f51 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -333,7 +333,7 @@ describe("ProfilesUtils unit tests", () => { const profInfoSpy = jest.spyOn(profUtils.ProfilesUtils, "getProfileInfo").mockReturnValue({ readProfilesFromDisk: mockReadProfilesFromDisk, usingTeamConfig: true, - getTeamConfig: () => [], + getTeamConfig: () => ({ exists: true, layers: [] }), } as never); Object.defineProperty(globals.LOG, "debug", { value: jest.fn(), diff --git a/packages/zowe-explorer/i18n/sample/src/dataset/DatasetTree.i18n.json b/packages/zowe-explorer/i18n/sample/src/dataset/DatasetTree.i18n.json index 8c690ed9a6..f0b9b8041f 100644 --- a/packages/zowe-explorer/i18n/sample/src/dataset/DatasetTree.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/dataset/DatasetTree.i18n.json @@ -1,6 +1,5 @@ { "Favorites": "Favorites", - "getChildren.noDataset": "No data sets found", "initializeFavorites.log.debug": "Initializing profiles with data set favorites.", "initializeFavorites.no.favorites": "No data set favorites found.", "initializeFavorites.invalidDsFavorite1": "Invalid Data Sets favorite: {0}.", diff --git a/packages/zowe-explorer/i18n/sample/src/dataset/ZoweDatasetNode.i18n.json b/packages/zowe-explorer/i18n/sample/src/dataset/ZoweDatasetNode.i18n.json index 2a25c1284c..6427978503 100644 --- a/packages/zowe-explorer/i18n/sample/src/dataset/ZoweDatasetNode.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/dataset/ZoweDatasetNode.i18n.json @@ -6,6 +6,7 @@ "getChildren.noDataset": "No data sets found", "getDataSets.error.sessionMissing": "Profile auth error", "getDataSets.error.additionalDetails": "Profile is not authenticated, please log in to continue", + "getDatasets.error": "Retrieving response from MVS list API", "dataSet.opening": "$(sync~spin) Opening data set...", "openDs.error": "Invalid data set or member.", "openDs.openDataSet": "Opening {0}" diff --git a/packages/zowe-explorer/i18n/sample/src/job/ZoweJobNode.i18n.json b/packages/zowe-explorer/i18n/sample/src/job/ZoweJobNode.i18n.json index 9e9e04a0f1..3b71196df2 100644 --- a/packages/zowe-explorer/i18n/sample/src/job/ZoweJobNode.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/job/ZoweJobNode.i18n.json @@ -1,9 +1,10 @@ { "getChildren.search": "Use the search button to display jobs", - "getChildren.noSpoolFiles": "There are no JES spool messages to display", + "getChildren.noSpoolFiles": "No spool files found", "getChildren.noJobs": "No jobs found", "getJobs.status.not.supported": "Filtering by job status is not yet supported with this profile type. Will show jobs with all statuses.", "getJobs.error.sessionMissing": "Profile auth error", "getJobs.error.additionalDetails": "Profile is not authenticated, please log in to continue", - "getChildren.error.response": "Retrieving response from " + "getJobs.error": "Retrieving response from JES list API", + "getSpoolFiles.error": "Retrieving response from JES list API" } diff --git a/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json b/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json index 692ba86a65..596bd1bde0 100644 --- a/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json @@ -1,9 +1,6 @@ { + "getChildren.search": "Use the search button to list USS files", "getChildren.error.invalidNode": "Invalid node", - "getChildren.error.sessionMissing": "Profile auth error", - "getChildren.error.additionalDetails": "Profile is not authenticated, please log in to continue", - "getChildren.responses.error.response": "The response from Zowe CLI was not successful", - "getChildren.error.response": "Retrieving response from ", "getChildren.responses.open": "Open", "deleteUssPrompt.deleteCancelled": "Delete action was cancelled.", "deleteUSSNode.error.node": "Unable to delete node: ", @@ -17,5 +14,8 @@ "refreshUSS.file2": " was probably deleted.", "paste.missingApis": "Required API functions for pasting (fileList, copy and/or putContent) were not found.", "uploadFile.putContents": "Uploading USS files...", - "copyUssFile.error": "Error uploading files" + "copyUssFile.error": "Error uploading files", + "getChildren.error.sessionMissing": "Profile auth error", + "getChildren.error.additionalDetails": "Profile is not authenticated, please log in to continue", + "getUssFiles.error": "Retrieving response from USS list API" } diff --git a/packages/zowe-explorer/src/Profiles.ts b/packages/zowe-explorer/src/Profiles.ts index 1b3058c31b..29a6d20c64 100644 --- a/packages/zowe-explorer/src/Profiles.ts +++ b/packages/zowe-explorer/src/Profiles.ts @@ -1235,7 +1235,7 @@ export class Profiles extends ProfilesCache { return filteredProfile; } - public async ssoLogin(node?: IZoweNodeType, label?: string): Promise { + public async ssoLogin(node?: IZoweNodeType, label?: string): Promise { ZoweLogger.trace("Profiles.ssoLogin called."); let loginTokenType: string; let serviceProfile: zowe.imperative.IProfileLoaded; @@ -1249,7 +1249,7 @@ export class Profiles extends ProfilesCache { Gui.showMessage( localize("ssoAuth.usingBasicAuth", "This profile is using basic authentication and does not support token authentication.") ); - return; + return false; } const zeInstance = ZoweExplorerApiRegister.getInstance(); @@ -1258,7 +1258,7 @@ export class Profiles extends ProfilesCache { } catch (error) { ZoweLogger.warn(error); Gui.showMessage(localize("ssoLogin.tokenType.error", "Error getting supported tokenType value for profile {0}", serviceProfile.name)); - return; + return false; } try { let loginOk = false; @@ -1273,11 +1273,12 @@ export class Profiles extends ProfilesCache { } else { Gui.showMessage(this.profilesOpCancelled); } + return loginOk; } catch (err) { const message = localize("ssoLogin.error", "Unable to log in with {0}. {1}", serviceProfile.name, err?.message); ZoweLogger.error(message); Gui.errorMessage(message); - return; + return false; } } diff --git a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts index 9d2c64cd98..2256fcaab6 100644 --- a/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts +++ b/packages/zowe-explorer/src/abstract/ZoweTreeProvider.ts @@ -131,9 +131,11 @@ export class ZoweTreeProvider { if (icon) { element.iconPath = icon.path; } - element.dirty = true; if (isOpen) { this.mOnDidChangeTreeData.fire(element); + } else { + // Don't mark as dirty when expanded to avoid duplicate refresh + element.dirty = true; } } diff --git a/packages/zowe-explorer/src/dataset/DatasetTree.ts b/packages/zowe-explorer/src/dataset/DatasetTree.ts index 715e8de838..6d3ff6e20e 100644 --- a/packages/zowe-explorer/src/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/dataset/DatasetTree.ts @@ -179,19 +179,11 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree { ZoweLogger.trace("DatasetTree.datasetFilterPrompt called."); - ZoweLogger.debug(localize("enterPattern.log.debug.prompt", "Prompting the user for a data set pattern")); let pattern: string; await this.checkCurrentProfile(node); let nonFaveNode; if (Profiles.getInstance().validProfile !== ValidProfileEnum.INVALID) { if (contextually.isSessionNotFav(node)) { + ZoweLogger.debug(localize("enterPattern.log.debug.prompt", "Prompting the user for a data set pattern")); nonFaveNode = node; if (this.mHistory.getSearchHistory().length > 0) { const createPick = new FilterDescriptor(DatasetTree.defaultDialogText); @@ -999,9 +986,8 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree ZoweExplorerApiRegister.getMvsApi(profileValue).getSession())(nonFaveNode); + syncSessionNode((profileValue) => ZoweExplorerApiRegister.getMvsApi(profileValue), nonFaveNode); let dataSet: IDataSet; const dsSets: (IDataSet & { memberPattern?: string })[] = []; const dsNames = pattern.split(","); @@ -1055,8 +1041,7 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree} */ public async getChildren(): Promise { - ZoweLogger.trace("ZoweDatasetNode.getChildren called."); + ZoweLogger.trace(`ZoweDatasetNode.getChildren called for ${this.label as string}.`); if (!this.pattern && contextually.isSessionNotFav(this)) { - return [ - new ZoweDatasetNode({ - label: localize("getChildren.search", "Use the search button to display data sets"), - collapsibleState: vscode.TreeItemCollapsibleState.None, - parentNode: this, - contextOverride: globals.INFORMATION_CONTEXT, - }), - ]; + const placeholder = new ZoweDatasetNode({ + label: localize("getChildren.search", "Use the search button to display data sets"), + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: this, + contextOverride: globals.INFORMATION_CONTEXT, + }); + return (this.children = [placeholder]); } if (contextually.isDocument(this) || contextually.isInformation(this)) { return []; @@ -209,9 +210,10 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod } // Gets the datasets from the pattern or members of the dataset and displays any thrown errors - const responses = await this.getDatasets(); - if (responses.length === 0) { - return; + const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + const responses = await this.getDatasets(cachedProfile); + if (responses == null) { + return []; } // push nodes to an object with property names to avoid duplicates @@ -221,7 +223,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod // The dataSetsMatchingPattern API may return success=false and apiResponse=[] when no data sets found if (!response.success && !(Array.isArray(response.apiResponse) && response.apiResponse.length === 0)) { await errorHandling(localize("getChildren.responses.error", "The response from Zowe CLI was not successful")); - return; + return []; } // Loops through all the returned dataset members and creates nodes for them @@ -524,46 +526,51 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod LocalFileManagement.storeFileInfo(this); } - private async getDatasets(): Promise { + private async getDatasets(profile: imperative.IProfileLoaded): Promise { ZoweLogger.trace("ZoweDatasetNode.getDatasets called."); const responses: zowe.IZosFilesResponse[] = []; - const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); const options: zowe.IListOptions = { attributes: true, - responseTimeout: cachedProfile.profile.responseTimeout, + responseTimeout: profile.profile.responseTimeout, }; - if (contextually.isSessionNotFav(this)) { - const dsPatterns = [ - ...new Set( - this.pattern - .toUpperCase() - .split(",") - .map((p) => p.trim()) - ), - ]; - const mvsApi = ZoweExplorerApiRegister.getMvsApi(cachedProfile); - if (!mvsApi.getSession(cachedProfile)) { - throw new zowe.imperative.ImperativeError({ - msg: localize("getDataSets.error.sessionMissing", "Profile auth error"), - additionalDetails: localize("getDataSets.error.additionalDetails", "Profile is not authenticated, please log in to continue"), - errorCode: `${zowe.imperative.RestConstants.HTTP_STATUS_401}`, - }); - } - if (mvsApi.dataSetsMatchingPattern) { - responses.push(await mvsApi.dataSetsMatchingPattern(dsPatterns)); - } else { - for (const dsp of dsPatterns) { - responses.push(await mvsApi.dataSet(dsp)); + try { + if (contextually.isSessionNotFav(this)) { + const dsPatterns = [ + ...new Set( + this.pattern + .toUpperCase() + .split(",") + .map((p) => p.trim()) + ), + ]; + const mvsApi = ZoweExplorerApiRegister.getMvsApi(profile); + if (!mvsApi.getSession(profile)) { + throw new zowe.imperative.ImperativeError({ + msg: localize("getDataSets.error.sessionMissing", "Profile auth error"), + additionalDetails: localize("getDataSets.error.additionalDetails", "Profile is not authenticated, please log in to continue"), + errorCode: `${zowe.imperative.RestConstants.HTTP_STATUS_401}`, + }); } + if (mvsApi.dataSetsMatchingPattern) { + responses.push(await mvsApi.dataSetsMatchingPattern(dsPatterns)); + } else { + for (const dsp of dsPatterns) { + responses.push(await mvsApi.dataSet(dsp)); + } + } + } else if (this.memberPattern) { + this.memberPattern = this.memberPattern.toUpperCase(); + for (const memPattern of this.memberPattern.split(",")) { + options.pattern = memPattern; + responses.push(await ZoweExplorerApiRegister.getMvsApi(profile).allMembers(this.label as string, options)); + } + } else { + responses.push(await ZoweExplorerApiRegister.getMvsApi(profile).allMembers(this.label as string, options)); } - } else if (this.memberPattern) { - this.memberPattern = this.memberPattern.toUpperCase(); - for (const memPattern of this.memberPattern.split(",")) { - options.pattern = memPattern; - responses.push(await ZoweExplorerApiRegister.getMvsApi(cachedProfile).allMembers(this.label as string, options)); - } - } else { - responses.push(await ZoweExplorerApiRegister.getMvsApi(cachedProfile).allMembers(this.label as string, options)); + } catch (error) { + const updated = await errorHandling(error, this.getProfileName(), localize("getDatasets.error", "Retrieving response from MVS list API")); + syncSessionNode((prof) => ZoweExplorerApiRegister.getMvsApi(prof), this.getSessionNode(), updated && this); + return; } return responses; } diff --git a/packages/zowe-explorer/src/globals.ts b/packages/zowe-explorer/src/globals.ts index 76d64e8edd..fcf2575442 100644 --- a/packages/zowe-explorer/src/globals.ts +++ b/packages/zowe-explorer/src/globals.ts @@ -107,7 +107,7 @@ export const DS_NAME_REGEX_CHECK = /^[a-zA-Z#@$][a-zA-Z0-9#@$-]{0,7}(\.[a-zA-Z#@ export const MEMBER_NAME_REGEX_CHECK = /^[a-zA-Z#@$][a-zA-Z0-9#@$]{0,7}$/; export let ACTIVATED = false; export let PROFILE_SECURITY: string | boolean = ZOWE_CLI_SCM; -export let SAVED_PROFILE_CONTENTS = new Uint8Array(); +export let SAVED_PROFILE_CONTENTS = new Map(); export const JOBS_MAX_PREFIX = 8; export const ZE_EXT_NAME = "zowe.vscode-extension-for-zowe"; @@ -369,10 +369,6 @@ export function setActivated(value: boolean): void { ACTIVATED = value; } -export function setSavedProfileContents(value: Uint8Array): void { - SAVED_PROFILE_CONTENTS = value; -} - export async function setGlobalSecurityValue(credentialManager?: string): Promise { const settingEnabled: boolean = SettingsConfig.getDirectValue(this.SETTINGS_SECURE_CREDENTIALS_ENABLED); if (settingEnabled && credentialManager) { diff --git a/packages/zowe-explorer/src/job/ZosJobsProvider.ts b/packages/zowe-explorer/src/job/ZosJobsProvider.ts index 492fcb301a..09b89f1d3e 100644 --- a/packages/zowe-explorer/src/job/ZosJobsProvider.ts +++ b/packages/zowe-explorer/src/job/ZosJobsProvider.ts @@ -256,8 +256,8 @@ export class ZosJobsProvider extends ZoweTreeProvider implements IZoweTree} */ public async getChildren(): Promise { - const thisSessionNode = this.getSessionNode(); - ZoweLogger.trace(`ZoweJobNode.getChildren called for ${String(thisSessionNode.label)}.`); - if (this?.filter !== undefined) { - return this.children; - } + ZoweLogger.trace(`ZoweJobNode.getChildren called for ${this.label as string}.`); if (contextually.isSession(this) && !this.filtered && !contextually.isFavorite(this)) { return [ new ZoweJobNode({ label: localize("getChildren.search", "Use the search button to display jobs"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, + contextOverride: globals.INFORMATION_CONTEXT, }), ]; } - if (!this.dirty) { + if (!this.dirty || this.filter !== undefined) { return this.children; } const elementChildren: Record = {}; if (contextually.isJob(this)) { // Fetch spool files under job node - const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); - const spools: zowe.IJobFile[] = ( - (await ZoweExplorerApiRegister.getJesApi(cachedProfile).getSpoolFiles(this.job.jobname, this.job.jobid)) ?? [] - ) - // filter out all the objects which do not seem to be correct Job File Document types - // see an issue #845 for the details - .filter((item) => !(item.id === undefined && item.ddname === undefined && item.stepname === undefined)); - if (!spools.length) { + const spools = await this.getSpoolFiles(this.job); + if (spools == null) { + return []; + } else if (!spools.length) { const noSpoolNode = new ZoweSpoolNode({ - label: localize("getChildren.noSpoolFiles", "There are no JES spool messages to display"), + label: localize("getChildren.noSpoolFiles", "No spool files found"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, + contextOverride: globals.INFORMATION_CONTEXT, }); - noSpoolNode.iconPath = null; - return [noSpoolNode]; + return (this.children = [noSpoolNode]); } - const refreshTimestamp = Date.now(); spools.forEach((spool) => { const sessionName = this.getProfileName(); const procstep = spool.procstep ? spool.procstep : undefined; @@ -185,15 +183,16 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { } else { // Fetch jobs under session node const jobs = await this.getJobs(this._owner, this._prefix, this._searchId, this._jobStatus); - if (jobs.length === 0) { + if (jobs == null) { + return []; + } else if (jobs.length === 0) { const noJobsNode = new ZoweJobNode({ label: localize("getChildren.noJobs", "No jobs found"), collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: this, + contextOverride: globals.INFORMATION_CONTEXT, }); - noJobsNode.contextValue = globals.INFORMATION_CONTEXT; - noJobsNode.iconPath = null; - return [noJobsNode]; + return (this.children = [noJobsNode]); } jobs.forEach((job) => { let nodeTitle: string; @@ -354,10 +353,10 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { } } - private async getJobs(owner: string, prefix: string, searchId: string, status: string): Promise { + private async getJobs(owner: string, prefix: string, searchId: string, status: string): Promise { ZoweLogger.trace("ZoweJobNode.getJobs called."); - let jobsInternal: zowe.IJob[] = []; const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + let jobsInternal: zowe.IJob[] = []; try { if (this.searchId.length > 0) { jobsInternal.push(await ZoweExplorerApiRegister.getJesApi(cachedProfile).getJob(searchId)); @@ -398,14 +397,33 @@ export class ZoweJobNode extends ZoweTreeNode implements IZoweJobTreeNode { }, []); } } catch (error) { - ZoweLogger.trace("Error getting jobs from Rest API."); - await errorHandling(error, cachedProfile.name, localize("getChildren.error.response", "Retrieving response from ") + `zowe.GetJobs`); - syncSessionNode(Profiles.getInstance())((profileValue) => ZoweExplorerApiRegister.getJesApi(profileValue).getSession())( - this.getSessionNode() - ); + const updated = await errorHandling(error, this.getProfileName(), localize("getJobs.error", "Retrieving response from JES list API")); + syncSessionNode((profile) => ZoweExplorerApiRegister.getJesApi(profile), this.getSessionNode(), updated && this); + return; } return jobsInternal; } + + private async getSpoolFiles(job: zowe.IJob = this.job): Promise { + ZoweLogger.trace("ZoweJobNode.getSpoolFiles called."); + const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + let spools: zowe.IJobFile[] = []; + try { + spools = (await ZoweExplorerApiRegister.getJesApi(cachedProfile).getSpoolFiles(this.job.jobname, this.job.jobid)) ?? []; + // filter out all the objects which do not seem to be correct Job File Document types + // see an issue #845 for the details + spools = spools.filter((item) => !(item.id === undefined && item.ddname === undefined && item.stepname === undefined)); + } catch (error) { + const updated = await errorHandling( + error, + this.getProfileName(), + localize("getSpoolFiles.error", "Retrieving response from JES list API") + ); + syncSessionNode((profile) => ZoweExplorerApiRegister.getJesApi(profile), this.getSessionNode(), updated && this); + return; + } + return spools; + } } export class ZoweSpoolNode extends ZoweJobNode { diff --git a/packages/zowe-explorer/src/shared/IZoweTreeOpts.ts b/packages/zowe-explorer/src/shared/IZoweTreeOpts.ts index 2a1816b397..962d33aded 100644 --- a/packages/zowe-explorer/src/shared/IZoweTreeOpts.ts +++ b/packages/zowe-explorer/src/shared/IZoweTreeOpts.ts @@ -19,10 +19,10 @@ export interface IZoweTreeOpts { parentNode?: IZoweTreeNode; session?: imperative.Session; profile?: imperative.IProfileLoaded; + contextOverride?: string; } export interface IZoweDatasetTreeOpts extends IZoweTreeOpts { - contextOverride?: string; encoding?: ZosEncoding; etag?: string; } diff --git a/packages/zowe-explorer/src/shared/TreeProviders.ts b/packages/zowe-explorer/src/shared/TreeProviders.ts index a5192ea858..bcbf0e9657 100644 --- a/packages/zowe-explorer/src/shared/TreeProviders.ts +++ b/packages/zowe-explorer/src/shared/TreeProviders.ts @@ -14,8 +14,8 @@ import { IZoweProviders } from "./init"; import { DatasetTree } from "../dataset/DatasetTree"; import { USSTree } from "../uss/USSTree"; import { ZosJobsProvider } from "../job/ZosJobsProvider"; -import { IZoweNodeType } from "@zowe/zowe-explorer-api"; -import { getSessionType } from "./context"; +import { IZoweNodeType, IZoweTree } from "@zowe/zowe-explorer-api"; +import * as contextually from "./context"; type ProviderFunctions = { ds: (context: vscode.ExtensionContext) => Promise; @@ -75,8 +75,18 @@ export class TreeProviders { public static contextValueExistsAcrossTrees(node: IZoweNodeType, contextValue: string): boolean { const sessions = this.getSessionForAllTrees(node.getLabel().toString()); const sessionContextInOtherTree = sessions.find( - (session) => session.contextValue.includes(contextValue) && getSessionType(session) !== getSessionType(node) + (session) => session.contextValue.includes(contextValue) && contextually.getSessionType(session) !== contextually.getSessionType(node) ); return sessionContextInOtherTree !== undefined; } + + public static getProviderForNode(node: IZoweNodeType): IZoweTree { + if (contextually.isTypeDsTreeNode(node) || contextually.isDsSession(node)) { + return TreeProviders.ds; + } else if (contextually.isTypeUssTreeNode(node) || contextually.isUssSession(node)) { + return TreeProviders.uss; + } else { + return TreeProviders.job; + } + } } diff --git a/packages/zowe-explorer/src/shared/context.ts b/packages/zowe-explorer/src/shared/context.ts index b6c2f9e799..333b436ae7 100644 --- a/packages/zowe-explorer/src/shared/context.ts +++ b/packages/zowe-explorer/src/shared/context.ts @@ -11,7 +11,7 @@ import * as globals from "../globals"; import { TreeItem } from "vscode"; -import { IZoweTreeNode, IZoweUSSTreeNode } from "@zowe/zowe-explorer-api"; +import { IZoweDatasetTreeNode, IZoweTreeNode, IZoweUSSTreeNode } from "@zowe/zowe-explorer-api"; import { imperative } from "@zowe/cli"; /** @@ -474,6 +474,15 @@ export function isJobsSession(node: TreeItem): boolean { return new RegExp("^(" + globals.JOBS_SESSION_CONTEXT + ")").test(node.contextValue); } +/** + * Helper function which identifies if the node is part of the Dataset tree view + * @param node + * @return true if part of the Dataset tree, false otherwise + */ +export function isTypeDsTreeNode(node): node is IZoweDatasetTreeNode { + return (node as IZoweDatasetTreeNode).getDsDocumentFilePath !== undefined; +} + /** * Helper function which identifies if the node is part of the USS tree view * @param node diff --git a/packages/zowe-explorer/src/shared/init.ts b/packages/zowe-explorer/src/shared/init.ts index e59de77dcb..c608726581 100644 --- a/packages/zowe-explorer/src/shared/init.ts +++ b/packages/zowe-explorer/src/shared/init.ts @@ -255,11 +255,11 @@ export function watchConfigProfile(context: vscode.ExtensionContext): void { }); watcher.onDidChange(async (uri: vscode.Uri) => { ZoweLogger.info(localize("watchConfigProfile.update", "Team config file updated.")); - const newProfileContents = await vscode.workspace.fs.readFile(uri); - if (newProfileContents.toString() === globals.SAVED_PROFILE_CONTENTS.toString()) { + const newProfileContents = Buffer.from(await vscode.workspace.fs.readFile(uri)); + if (globals.SAVED_PROFILE_CONTENTS.get(uri.fsPath)?.equals(newProfileContents)) { return; } - globals.setSavedProfileContents(newProfileContents); + globals.SAVED_PROFILE_CONTENTS.set(uri.fsPath, newProfileContents); void refreshActions.refreshAll(); ZoweExplorerApiRegister.getInstance().onProfilesUpdateEmitter.fire(EventTypes.UPDATE); }); @@ -276,11 +276,11 @@ export function initSubscribers(context: vscode.ExtensionContext, theProvider: I const theTreeView = theProvider.getTreeView(); context.subscriptions.push(theTreeView); if (!globals.ISTHEIA) { - theTreeView.onDidCollapseElement(async (e) => { - await theProvider.flipState(e.element, false); + theTreeView.onDidCollapseElement((e) => { + theProvider.flipState(e.element, false); }); - theTreeView.onDidExpandElement(async (e) => { - await theProvider.flipState(e.element, true); + theTreeView.onDidExpandElement((e) => { + theProvider.flipState(e.element, true); }); } } diff --git a/packages/zowe-explorer/src/shared/refresh.ts b/packages/zowe-explorer/src/shared/refresh.ts index 92202791e4..e7ce9dbaca 100644 --- a/packages/zowe-explorer/src/shared/refresh.ts +++ b/packages/zowe-explorer/src/shared/refresh.ts @@ -49,7 +49,7 @@ export async function refreshAll(treeProvider?: IZoweTree): Promi if (contextually.isSessionNotFav(sessNode)) { sessNode.dirty = true; returnIconState(sessNode); - syncSessionNode(Profiles.getInstance())((profileValue) => ZoweExplorerApiRegister.getCommonApi(profileValue).getSession())(sessNode); + syncSessionNode((profileValue) => ZoweExplorerApiRegister.getCommonApi(profileValue), sessNode); } } else { await removeSession(treeProvider, sessNode.label.toString().trim()); diff --git a/packages/zowe-explorer/src/uss/USSTree.ts b/packages/zowe-explorer/src/uss/USSTree.ts index 2ca0c0c6af..9a89119f8d 100644 --- a/packages/zowe-explorer/src/uss/USSTree.ts +++ b/packages/zowe-explorer/src/uss/USSTree.ts @@ -346,8 +346,8 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree { ZoweLogger.trace("USSTree.getAllLoadedItems called."); - if (this.log) { - ZoweLogger.debug(localize("enterPattern.log.debug.prompt", "Prompting the user to choose a member from the filtered list")); - } + ZoweLogger.debug(localize("enterPattern.log.debug.prompt", "Prompting the user to choose a member from the filtered list")); const loadedNodes: IZoweUSSTreeNode[] = []; const sessions = await this.getChildren(); @@ -564,14 +562,12 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree { ZoweLogger.trace("USSTree.filterPrompt called."); - if (this.log) { - ZoweLogger.debug(localize("filterPrompt.log.debug.promptUSSPath", "Prompting the user for a USS path")); - } await this.checkCurrentProfile(node); if (Profiles.getInstance().validProfile !== ValidProfileEnum.INVALID) { let sessionNode; let remotepath: string; if (contextually.isSessionNotFav(node)) { + ZoweLogger.debug(localize("filterPrompt.log.debug.promptUSSPath", "Prompting the user for a USS path")); sessionNode = node; if (this.mHistory.getSearchHistory().length > 0) { const createPick = new FilterDescriptor(USSTree.defaultDialogText); @@ -632,7 +628,7 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree ZoweExplorerApiRegister.getUssApi(profileValue).getSession())(node); + syncSessionNode((profileValue) => ZoweExplorerApiRegister.getUssApi(profileValue), node); // Sanitization: Replace multiple forward slashes with just one forward slash const sanitizedPath = remotepath.replace(/\/+/g, "/").replace(/(\/*)$/, ""); sessionNode.tooltip = sessionNode.fullPath = sanitizedPath; @@ -793,9 +789,8 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree(); } @@ -131,9 +138,15 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { * @returns {Promise} */ public async getChildren(): Promise { - ZoweLogger.trace("ZoweUSSNode.getChildren called."); + ZoweLogger.trace(`ZoweUSSNode.getChildren called for ${this.label as string}.`); if ((!this.fullPath && contextually.isSession(this)) || contextually.isDocument(this)) { - return []; + const placeholder = new ZoweUSSNode({ + label: localize("getChildren.search", "Use the search button to list USS files"), + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: this, + contextOverride: globals.INFORMATION_CONTEXT, + }); + return (this.children = [placeholder]); } if (!this.dirty) { @@ -149,27 +162,10 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { } // Get the directories from the fullPath and display any thrown errors - let response: IZosFilesResponse; - const sessNode = this.getSessionNode(); - try { - const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); - if (!ZoweExplorerApiRegister.getUssApi(cachedProfile).getSession(cachedProfile)) { - throw new imperative.ImperativeError({ - msg: localize("getChildren.error.sessionMissing", "Profile auth error"), - additionalDetails: localize("getChildren.error.additionalDetails", "Profile is not authenticated, please log in to continue"), - errorCode: `${imperative.RestConstants.HTTP_STATUS_401}`, - }); - } - response = await ZoweExplorerApiRegister.getUssApi(cachedProfile).fileList(this.fullPath); - - // Throws reject if the Zowe command does not throw an error but does not succeed - if (!response.success) { - throw Error(localize("getChildren.responses.error.response", "The response from Zowe CLI was not successful")); - } - } catch (err) { - await errorHandling(err, this.label.toString(), localize("getChildren.error.response", "Retrieving response from ") + `uss-file-list`); - syncSessionNode(Profiles.getInstance())((profileValue) => ZoweExplorerApiRegister.getUssApi(profileValue).getSession())(sessNode); - return this.children; + const cachedProfile = Profiles.getInstance().loadNamedProfile(this.getProfileName()); + const response = await this.getUssFiles(cachedProfile); + if (!response.success) { + return []; } // If search path has changed, invalidate all children @@ -739,6 +735,23 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { await errorHandling(error, this.label.toString(), localize("copyUssFile.error", "Error uploading files")); } } + + private async getUssFiles(profile: imperative.IProfileLoaded): Promise { + try { + if (!ZoweExplorerApiRegister.getUssApi(profile).getSession(profile)) { + throw new imperative.ImperativeError({ + msg: localize("getChildren.error.sessionMissing", "Profile auth error"), + additionalDetails: localize("getChildren.error.additionalDetails", "Profile is not authenticated, please log in to continue"), + errorCode: `${imperative.RestConstants.HTTP_STATUS_401}`, + }); + } + return await ZoweExplorerApiRegister.getUssApi(profile).fileList(this.fullPath); + } catch (error) { + const updated = await errorHandling(error, this.getProfileName(), localize("getUssFiles.error", "Retrieving response from USS list API")); + syncSessionNode((prof) => ZoweExplorerApiRegister.getUssApi(prof), this.getSessionNode(), updated && this); + return { success: false, commandResponse: null }; + } + } } let wasSavedRecently = false; diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 4a7dee995e..0e842d2474 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -16,7 +16,7 @@ import * as globals from "../globals"; import * as path from "path"; import * as fs from "fs"; import * as util from "util"; -import { IZoweTreeNode, ZoweTreeNode, getZoweDir, getFullPath, Gui, ProfilesCache, EventTypes } from "@zowe/zowe-explorer-api"; +import { IZoweTreeNode, ZoweTreeNode, getZoweDir, getFullPath, Gui, ProfilesCache, EventTypes, ZoweExplorerApi } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import * as nls from "vscode-nls"; import { imperative, getImperativeConfig } from "@zowe/cli"; @@ -24,6 +24,7 @@ import { ZoweExplorerExtender } from "../ZoweExplorerExtender"; import { ZoweLogger } from "./LoggerUtils"; import { SettingsConfig } from "./SettingsConfig"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; +import { TreeProviders } from "../shared/TreeProviders"; // Set up localization nls.config({ @@ -38,7 +39,7 @@ const localize: nls.LocalizeFunc = nls.loadMessageBundle(); * @param {label} - additional information such as profile name, credentials, messageID etc * @param {moreInfo} - additional/customized error messages *************************************************************************************************************/ -export async function errorHandling(errorDetails: Error | string, label?: string, moreInfo?: string): Promise { +export async function errorHandling(errorDetails: Error | string, label?: string, moreInfo?: string): Promise { // Use util.inspect instead of JSON.stringify to handle circular references // eslint-disable-next-line @typescript-eslint/restrict-template-expressions ZoweLogger.error(`${errorDetails.toString()}\n` + util.inspect({ errorDetails, label, moreInfo }, { depth: null })); @@ -55,7 +56,7 @@ export async function errorHandling(errorDetails: Error | string, label?: string if (prof.profName === label.trim()) { const filePath = prof.profLoc.osLoc[0]; await Profiles.getInstance().openConfigFile(filePath); - return; + return false; } } } @@ -81,25 +82,24 @@ export async function errorHandling(errorDetails: Error | string, label?: string if (tokenError.includes("Token is not valid or expired.") || isTokenAuth) { if (globals.ISTHEIA) { Gui.errorMessage(errToken); - await Profiles.getInstance().ssoLogin(null, label); - return; + return Profiles.getInstance().ssoLogin(null, label); } const message = localize("errorHandling.authentication.login", "Log in to Authentication Service"); - Gui.showMessage(errToken, { items: [message] }).then(async (selection) => { + const success = Gui.showMessage(errToken, { items: [message] }).then(async (selection) => { if (selection) { - await Profiles.getInstance().ssoLogin(null, label); + return Profiles.getInstance().ssoLogin(null, label); } }); - return; + return success; } } if (globals.ISTHEIA) { Gui.errorMessage(errMsg); - return; + return false; } const checkCredsButton = localize("errorHandling.checkCredentials.button", "Update Credentials"); - await Gui.errorMessage(errMsg, { + const creds = await Gui.errorMessage(errMsg, { items: [checkCredsButton], vsCodeOpts: { modal: true }, }).then(async (selection) => { @@ -107,9 +107,9 @@ export async function errorHandling(errorDetails: Error | string, label?: string Gui.showMessage(localize("errorHandling.checkCredentials.cancelled", "Operation Cancelled")); return; } - await Profiles.getInstance().promptCredentials(label.trim(), true); + return Profiles.getInstance().promptCredentials(label.trim(), true); }); - return; + return creds != null ? true : false; } } @@ -120,6 +120,7 @@ export async function errorHandling(errorDetails: Error | string, label?: string } // Try to keep message readable since VS Code doesn't support newlines in error messages Gui.errorMessage(moreInfo + errorDetails.toString().replace(/\n/g, " | ")); + return false; } /** @@ -128,27 +129,31 @@ export async function errorHandling(errorDetails: Error | string, label?: string * @param getSessionForProfile is a function to build a valid specific session based on provided profile * @param sessionNode is a tree node, containing session information */ -type SessionForProfile = (_profile: imperative.IProfileLoaded) => imperative.Session; -export const syncSessionNode = - (_profiles: Profiles) => - (getSessionForProfile: SessionForProfile) => - (sessionNode: IZoweTreeNode): void => { - ZoweLogger.trace("ProfilesUtils.syncSessionNode called."); - - const profileType = sessionNode.getProfile()?.type; - const profileName = sessionNode.getProfileName(); - - let profile: imperative.IProfileLoaded; - try { - profile = Profiles.getInstance().loadNamedProfile(profileName, profileType); - } catch (e) { - ZoweLogger.warn(e); - return; - } - sessionNode.setProfileToChoice(profile); - const session = getSessionForProfile(profile); - sessionNode.setSessionToChoice(session); - }; +export function syncSessionNode( + getCommonApi: (profile: imperative.IProfileLoaded) => ZoweExplorerApi.ICommon, + sessionNode: IZoweTreeNode, + nodeToRefresh?: IZoweTreeNode +): void { + ZoweLogger.trace("ProfilesUtils.syncSessionNode called."); + + const profileType = sessionNode.getProfile()?.type; + const profileName = sessionNode.getProfileName(); + + let profile: imperative.IProfileLoaded; + try { + profile = Profiles.getInstance().loadNamedProfile(profileName, profileType); + } catch (e) { + ZoweLogger.warn(e); + return; + } + sessionNode.setProfileToChoice(profile); + const session = getCommonApi(profile).getSession(); + sessionNode.setSessionToChoice(session); + if (nodeToRefresh) { + nodeToRefresh.dirty = true; + void nodeToRefresh.getChildren().then(() => TreeProviders.getProviderForNode(nodeToRefresh).refreshElement(nodeToRefresh)); + } +} /** * @deprecated Use `Gui.resolveQuickPick` instead @@ -477,7 +482,7 @@ export class ProfilesUtils { ); } - public static async getProfileInfo(envTheia: boolean): Promise { + public static async getProfileInfo(_envTheia: boolean): Promise { ZoweLogger.trace("ProfilesUtils.getProfileInfo called."); const hasSecureCredentialManagerEnabled: boolean = SettingsConfig.getDirectValue(globals.SETTINGS_SECURE_CREDENTIALS_ENABLED); @@ -523,6 +528,12 @@ export class ProfilesUtils { ZoweLogger.warn(schemaWarning); } globals.setConfigPath(rootPath); + globals.SAVED_PROFILE_CONTENTS.clear(); + for (const layer of mProfileInfo.getTeamConfig().layers) { + if (layer.exists) { + globals.SAVED_PROFILE_CONTENTS.set(vscode.Uri.file(layer.path).fsPath, fs.readFileSync(layer.path)); + } + } ZoweLogger.info(`Zowe Explorer is using the team configuration file "${mProfileInfo.getTeamConfig().configName}"`); const layers = mProfileInfo.getTeamConfig().layers || []; const layerSummary = layers.map(