From 8957a6429c4de5487a2d800616c92228b67a7493 Mon Sep 17 00:00:00 2001 From: benjamin-t-santos <115251181+benjamin-t-santos@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:46:14 -0400 Subject: [PATCH 01/14] DatasetFSprovider fetchDataset() - fix behavior with non-existent datasets and members (#3255) * Fix PDS members in fetchDataset Signed-off-by: Benjamin Santos * Update DatasetFSProvider.unit.test.ts Signed-off-by: Benjamin Santos * Update CHANGELOG.md Signed-off-by: Benjamin Santos * Merge branch 'main' into datasetfsprovider-remote-lookup-fix Signed-off-by: Benjamin Santos * move changelog update Signed-off-by: Benjamin Santos * improve logic Signed-off-by: Benjamin Santos * add ? after checking dsorg Signed-off-by: Benjamin Santos * do not fetch attributes for members Signed-off-by: Benjamin Santos --------- Signed-off-by: Benjamin Santos --- packages/zowe-explorer/CHANGELOG.md | 1 + .../dataset/DatasetFSProvider.unit.test.ts | 116 +++++++++++++++++- .../src/trees/dataset/DatasetFSProvider.ts | 27 ++-- 3 files changed, 135 insertions(+), 9 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 41e7bfbfd..f23d62b1d 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue where the contents of an editor did not update when polling spool content or using the "Pull from Mainframe" action with jobs. [#3249](https://github.com/zowe/zowe-explorer-vscode/pull/3249) - Fixed an issue where Zowe Explorer sometimes prompted the user to convert V1 profiles when changes were made to a team configuration after initialization. [#3246](https://github.com/zowe/zowe-explorer-vscode/pull/3246) - Fixed an issue where the encoding of a USS file was not automatically detected when opened for the first time. [#3253](https://github.com/zowe/zowe-explorer-vscode/pull/3253) +- `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) ## `3.0.1` diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts index 2af16a725..a48da93bb 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts @@ -734,6 +734,33 @@ describe("fetchEntriesForDataset", () => { describe("fetchDataset", () => { describe("calls dataSet to verify that the data set exists on the mainframe", () => { describe("PS", () => { + it("non-existent PS URI", async () => { + const dataSetMock = jest.fn().mockResolvedValue({ + success: true, + apiResponse: { + items: [], + }, + commandResponse: "", + }); + const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValue({ + dataSet: dataSetMock, + } as any); + try { + await (DatasetFSProvider.instance as any).fetchDataset(testUris.ps, { + isRoot: false, + slashAfterProfilePos: testUris.ps.path.indexOf("/", 1), + profileName: "sestest", + profile: testProfile, + }); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toBe(testUris.ps.toString(true)); + } + expect(dataSetMock).toHaveBeenCalled(); + mvsApiMock.mockRestore(); + }); + it("non-existent URI", async () => { const dataSetMock = jest.fn().mockResolvedValue({ success: true, @@ -747,7 +774,7 @@ describe("fetchDataset", () => { } as any); await (DatasetFSProvider.instance as any).fetchDataset(testUris.ps, { isRoot: false, - slashAfterProfilePos: testUris.pds.path.indexOf("/", 1), + slashAfterProfilePos: testUris.ps.path.indexOf("/", 1), profileName: "sestest", profile: testProfile, }); @@ -772,6 +799,36 @@ describe("fetchDataset", () => { }); describe("PDS", () => { + it("non-existent PDS URI", async () => { + const dataSetMock = jest.fn().mockResolvedValue({ + success: true, + apiResponse: { + items: [], + }, + commandResponse: "", + }); + const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValue({ + dataSet: dataSetMock, + } as any); + const lookupMock = jest.spyOn(DatasetFSProvider.instance as any, "lookup"); + lookupMock.mockImplementation(() => { + throw FileSystemError.FileNotFound(testUris.pds); + }); + try { + await (DatasetFSProvider.instance as any).fetchDataset(testUris.pds, { + isRoot: false, + slashAfterProfilePos: testUris.pds.path.indexOf("/", 1), + profileName: "sestest", + profile: testProfile, + }); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toBe(testUris.pds.toString(true)); + } + lookupMock.mockRestore(); + mvsApiMock.mockRestore(); + }); it("non-existent URI", async () => { const dataSetMock = jest.fn().mockResolvedValue({ success: true, @@ -807,6 +864,63 @@ describe("fetchDataset", () => { fetchEntriesForDatasetMock.mockRestore(); }); }); + + describe("PDS member", () => { + it("non-existent member URI", async () => { + const allMembersMockNoMatch = jest.fn().mockResolvedValue({ + success: true, + apiResponse: { + items: [ + { + member: "NOMATCH", + }, + ], + }, + commandResponse: "", + }); + const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValue({ + allMembers: allMembersMockNoMatch, + } as any); + try { + await (DatasetFSProvider.instance as any).fetchDataset(testUris.pdsMember, { + isRoot: false, + slashAfterProfilePos: testUris.pds.path.indexOf("/", 1), + profileName: "sestest", + profile: testProfile, + }); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toBe(testUris.pdsMember.toString(true)); + } + expect(allMembersMockNoMatch).toHaveBeenCalledWith("USER.DATA.PDS"); + mvsApiMock.mockRestore(); + }); + it("existing member URI", async () => { + const allMembersMock = jest.fn().mockResolvedValue({ + success: true, + apiResponse: { + items: [ + { + member: "MEMBER1", + }, + ], + }, + commandResponse: "", + }); + const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValue({ + allMembers: allMembersMock, + } as any); + await (DatasetFSProvider.instance as any).fetchDataset(testUris.pdsMember, { + isRoot: false, + slashAfterProfilePos: testUris.pdsMember.path.indexOf("/", 1), + profileName: "sestest", + profile: testProfile, + }); + expect(allMembersMock).toHaveBeenCalledWith("USER.DATA.PDS"); + mvsApiMock.mockRestore(); + }); + }); }); }); diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index fc9512b33..a853471ad 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -203,17 +203,28 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem const uriPath = uri.path.substring(uriInfo.slashAfterProfilePos + 1).split("/"); const pdsMember = uriPath.length === 2; if (!entryExists) { - const resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(entryIsDir ? uriPath[0] : path.parse(uriPath[0]).name, { - attributes: true, - }); - if (resp.success) { - const dsorg: string = resp.apiResponse?.items?.[0]?.dsorg; - entryIsDir = pdsMember ? false : dsorg?.startsWith("PO") ?? false; + if (pdsMember) { + const resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).allMembers(uriPath[0]); + entryIsDir = false; + const memberName = path.parse(uriPath[1]).name; + if ( + !resp.success || + resp.apiResponse?.items?.length < 1 || + !resp.apiResponse.items.find((respItem) => respItem.member === memberName) + ) { + throw vscode.FileSystemError.FileNotFound(uri); + } } else { - throw vscode.FileSystemError.FileNotFound(uri); + const resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(uriPath[0], { + attributes: true, + }); + if (resp.success && resp.apiResponse?.items?.length > 0) { + entryIsDir = resp.apiResponse.items[0].dsorg?.startsWith("PO"); + } else { + throw vscode.FileSystemError.FileNotFound(uri); + } } } - if (entryIsDir) { if (!entryExists) { this.createDirectory(uri); From 712370df89c19a3f9cd08b26dc5cc589ba540c4e Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 22 Oct 2024 11:07:02 -0400 Subject: [PATCH 02/14] fix: use `vscode.workspace.fs` for `delete` and `rename` (#3261) * fix: use vscode.workspace.fs to rename URIs Signed-off-by: Trae Yelovich * chore: update ZE changelog Signed-off-by: Trae Yelovich * update mocks, remaining use of delete/rename Signed-off-by: Trae Yelovich --------- Signed-off-by: Trae Yelovich --- packages/zowe-explorer/CHANGELOG.md | 4 +++- .../__tests__/__mocks__/vscode.ts | 5 +++++ .../trees/dataset/DatasetActions.unit.test.ts | 21 +++++++++---------- .../trees/dataset/DatasetTree.unit.test.ts | 4 ++-- .../trees/job/JobFSProvider.unit.test.ts | 4 ++-- .../__unit__/trees/job/JobTree.unit.test.ts | 2 +- .../__unit__/trees/uss/USSTree.unit.test.ts | 4 ++-- .../trees/uss/ZoweUSSNode.unit.test.ts | 8 +++---- .../src/trees/dataset/DatasetActions.ts | 2 +- .../src/trees/dataset/DatasetTree.ts | 4 ++-- .../src/trees/job/JobFSProvider.ts | 7 ++----- .../zowe-explorer/src/trees/job/JobTree.ts | 2 +- .../zowe-explorer/src/trees/uss/USSTree.ts | 4 ++-- .../src/trees/uss/ZoweUSSNode.ts | 4 ++-- 14 files changed, 39 insertions(+), 36 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index f23d62b1d..6c318baaa 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### Bug fixes +- `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) +- Fixed an issue where renaming or deleting a USS file or data set did not update the opened editor. [#3260](https://github.com/zowe/zowe-explorer-vscode/issues/3260) + ## `3.0.2` ### New features and enhancements @@ -20,7 +23,6 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue where the contents of an editor did not update when polling spool content or using the "Pull from Mainframe" action with jobs. [#3249](https://github.com/zowe/zowe-explorer-vscode/pull/3249) - Fixed an issue where Zowe Explorer sometimes prompted the user to convert V1 profiles when changes were made to a team configuration after initialization. [#3246](https://github.com/zowe/zowe-explorer-vscode/pull/3246) - Fixed an issue where the encoding of a USS file was not automatically detected when opened for the first time. [#3253](https://github.com/zowe/zowe-explorer-vscode/pull/3253) -- `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) ## `3.0.1` diff --git a/packages/zowe-explorer/__tests__/__mocks__/vscode.ts b/packages/zowe-explorer/__tests__/__mocks__/vscode.ts index 8ab63f761..5c32ad980 100644 --- a/packages/zowe-explorer/__tests__/__mocks__/vscode.ts +++ b/packages/zowe-explorer/__tests__/__mocks__/vscode.ts @@ -1498,6 +1498,11 @@ export namespace workspace { } } +// We need to do this since "delete" is a reserved keyword and cannot be defined as a function name. +Object.defineProperty(workspace.fs, "delete", { + value: jest.fn(), +}); + export interface InputBoxOptions { placeholder?: string; } diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts index dcfa23e15..9352efd1f 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetActions.unit.test.ts @@ -66,7 +66,7 @@ function createGlobalMocks() { testFavoritesNode: createDatasetFavoritesNode(), testDatasetTree: null, getContentsSpy: null, - fspDelete: jest.spyOn(DatasetFSProvider.prototype, "delete").mockImplementation(), + fspDelete: jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(), statusBarMsgSpy: null, mvsApi: null, mockShowWarningMessage: jest.fn(), @@ -96,7 +96,6 @@ function createGlobalMocks() { value: newMocks.mockShowWarningMessage, configurable: true, }); - Object.defineProperty(vscode.workspace.fs, "delete", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.window, "showInputBox", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.workspace, "openTextDocument", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.workspace, "getConfiguration", { value: jest.fn(), configurable: true }); @@ -487,7 +486,7 @@ describe("Dataset Actions Unit Tests - Function deleteDatasetPrompt", () => { blockMocks.testDatasetTree.getTreeView.mockReturnValueOnce(treeView); globalMocks.mockShowWarningMessage.mockResolvedValueOnce("Delete"); - jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); await DatasetActions.deleteDatasetPrompt(blockMocks.testDatasetTree); expect(mocked(Gui.showMessage)).toHaveBeenCalledWith( @@ -721,7 +720,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); await DatasetActions.deleteDataset(node, blockMocks.testDatasetTree); expect(deleteSpy).toHaveBeenCalledWith(node.resourceUri, { recursive: false }); }); @@ -737,7 +736,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); await DatasetActions.deleteDataset(node, blockMocks.testDatasetTree); expect(deleteSpy).toHaveBeenCalledWith(node.resourceUri, { recursive: false }); }); @@ -753,7 +752,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - jest.spyOn(DatasetFSProvider.instance, "delete").mockRejectedValueOnce(Error("not found")); + jest.spyOn(vscode.workspace.fs, "delete").mockRejectedValueOnce(Error("not found")); await expect(DatasetActions.deleteDataset(node, blockMocks.testDatasetTree)).rejects.toThrow("not found"); expect(mocked(Gui.showMessage)).toHaveBeenCalledWith("Unable to find file " + node.label?.toString()); }); @@ -769,7 +768,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - jest.spyOn(DatasetFSProvider.instance, "delete").mockRejectedValueOnce(Error("")); + jest.spyOn(vscode.workspace.fs, "delete").mockRejectedValueOnce(Error("")); await expect(DatasetActions.deleteDataset(node, blockMocks.testDatasetTree)).rejects.toThrow(""); expect(mocked(Gui.errorMessage)).toHaveBeenCalledWith("Error"); }); @@ -792,7 +791,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { node.contextValue = Constants.DS_PDS_CONTEXT + Constants.FAV_SUFFIX; mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete"); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete"); await DatasetActions.deleteDataset(node, blockMocks.testDatasetTree); @@ -812,7 +811,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { const child = new ZoweDatasetNode({ label: "child", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: parent }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); await DatasetActions.deleteDataset(child, blockMocks.testDatasetTree); expect(deleteSpy).toHaveBeenCalledWith(child.resourceUri, { recursive: false }); @@ -841,7 +840,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { blockMocks.testDatasetTree.mFavorites[0].children.push(child); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); await DatasetActions.deleteDataset(child, blockMocks.testDatasetTree); expect(deleteSpy).toHaveBeenCalledWith(child.resourceUri, { recursive: false }); expect(blockMocks.testDatasetTree.removeFavorite).toHaveBeenCalledWith(child); @@ -864,7 +863,7 @@ describe("Dataset Actions Unit Tests - Function deleteDataset", () => { }); mocked(vscode.window.showQuickPick).mockResolvedValueOnce("Delete" as any); - const deleteSpy = jest.spyOn(DatasetFSProvider.instance, "delete").mockImplementation(); + const deleteSpy = jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(); deleteSpy.mockClear(); await expect(DatasetActions.deleteDataset(child, blockMocks.testDatasetTree)).rejects.toThrow("Cannot delete, item invalid."); expect(deleteSpy).not.toHaveBeenCalled(); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts index 1cb7e8149..2ea97e539 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetTree.unit.test.ts @@ -2232,7 +2232,7 @@ describe("Dataset Tree Unit Tests - Function rename", () => { mvsApi, profileInstance, mockCheckCurrentProfile, - rename: jest.spyOn(DatasetFSProvider.instance, "rename").mockImplementation(), + rename: jest.spyOn(vscode.workspace.fs, "rename").mockImplementation(), }; } @@ -2479,7 +2479,7 @@ describe("Dataset Tree Unit Tests - Function rename", () => { favProfileNode.children.push(favParent); testTree.mFavorites.push(favProfileNode); const renameDataSetMemberSpy = jest.spyOn((DatasetTree as any).prototype, "renameDataSetMember"); - const renameMock = jest.spyOn(DatasetFSProvider.instance, "rename").mockImplementation(); + const renameMock = jest.spyOn(vscode.workspace.fs, "rename").mockImplementation(); await testTree.rename(child); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobFSProvider.unit.test.ts index 4a8a58897..641b38c44 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobFSProvider.unit.test.ts @@ -321,7 +321,7 @@ describe("delete", () => { const lookupParentDirMock = jest .spyOn(JobFSProvider.instance as any, "_lookupParentDirectory") .mockReturnValueOnce({ ...testEntries.session }); - await JobFSProvider.instance.delete(testUris.job, { recursive: true, deleteRemote: true }); + await JobFSProvider.instance.delete(testUris.job, { recursive: true }); const jobInfo = testEntries.job.job; expect(jobInfo).not.toBeUndefined(); expect(mockUssApi.deleteJob).toHaveBeenCalledWith(jobInfo?.jobname || "TESTJOB", jobInfo?.jobid || "JOB12345"); @@ -341,7 +341,7 @@ describe("delete", () => { fakeJob.job = testEntries.job.job; const lookupMock = jest.spyOn(JobFSProvider.instance as any, "lookup").mockReturnValueOnce(fakeSpool); const lookupParentDirMock = jest.spyOn(JobFSProvider.instance as any, "_lookupParentDirectory").mockReturnValueOnce(fakeJob); - await JobFSProvider.instance.delete(testUris.spool, { recursive: true, deleteRemote: true }); + await JobFSProvider.instance.delete(testUris.spool, { recursive: true }); expect(mockUssApi.deleteJob).not.toHaveBeenCalled(); expect(lookupParentDirMock).not.toHaveBeenCalled(); ussApiMock.mockRestore(); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts index 5e573cdbe..3cbd4eba2 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/job/JobTree.unit.test.ts @@ -135,7 +135,7 @@ async function createGlobalMocks() { }; jest.spyOn(JobFSProvider.instance, "createDirectory").mockImplementation(globalMocks.FileSystemProvider.createDirectory); - jest.spyOn(JobFSProvider.instance, "delete").mockImplementation(globalMocks.FileSystemProvider.delete); + jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(globalMocks.FileSystemProvider.delete); jest.spyOn(Gui, "createTreeView").mockImplementation(globalMocks.createTreeView); Object.defineProperty(ProfilesCache, "getConfigInstance", { value: jest.fn(() => { diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts index 17cfac602..42b7e41da 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/USSTree.unit.test.ts @@ -103,7 +103,7 @@ function createGlobalMocks() { }; jest.spyOn(UssFSProvider.instance, "createDirectory").mockImplementation(globalMocks.FileSystemProvider.createDirectory); - jest.spyOn(UssFSProvider.instance, "rename").mockImplementation(globalMocks.FileSystemProvider.rename); + jest.spyOn(vscode.workspace.fs, "rename").mockImplementation(globalMocks.FileSystemProvider.rename); globalMocks.mockTextDocuments.push(globalMocks.mockTextDocumentDirty); globalMocks.mockTextDocuments.push(globalMocks.mockTextDocumentClean); @@ -1687,7 +1687,7 @@ describe("USSTree Unit Tests - Function crossLparMove", () => { ]; ussDirNode.dirty = false; - const deleteMock = jest.spyOn(UssFSProvider.instance, "delete").mockResolvedValue(undefined); + const deleteMock = jest.spyOn(vscode.workspace.fs, "delete").mockResolvedValue(undefined); const readFileMock = jest.spyOn(UssFSProvider.instance, "readFile").mockResolvedValue(new Uint8Array([1, 2, 3])); const writeFileMock = jest.spyOn(UssFSProvider.instance, "writeFile").mockResolvedValue(undefined); const existsMock = jest.spyOn(UssFSProvider.instance, "exists").mockReturnValueOnce(false); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts index 5d00e1ac6..56f052c5b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/ZoweUSSNode.unit.test.ts @@ -430,7 +430,7 @@ describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { uss: { addSingleSession: jest.fn(), mSessionNodes: [], refresh: jest.fn() } as any, job: { addSingleSession: jest.fn(), mSessionNodes: [], refresh: jest.fn() } as any, }), - renameSpy: jest.spyOn(UssFSProvider.instance, "rename").mockImplementation(), + renameSpy: jest.spyOn(vscode.workspace.fs, "rename").mockImplementation(), getEncodingForFile: jest.spyOn(UssFSProvider.instance as any, "getEncodingForFile").mockReturnValue(undefined), }; newMocks.ussDir.contextValue = Constants.USS_DIR_CONTEXT; @@ -443,7 +443,7 @@ describe("ZoweUSSNode Unit Tests - Function node.rename()", () => { const newFullPath = "/u/user/newName"; const errMessageMock = jest.spyOn(Gui, "errorMessage").mockImplementation(); - const renameMock = jest.spyOn(UssFSProvider.instance, "rename").mockRejectedValueOnce(new Error("Rename error: file is busy")); + const renameMock = jest.spyOn(vscode.workspace.fs, "rename").mockRejectedValueOnce(new Error("Rename error: file is busy")); await blockMocks.ussDir.rename(newFullPath); expect(errMessageMock).toHaveBeenCalledWith("Rename error: file is busy"); @@ -631,7 +631,7 @@ describe("ZoweUSSNode Unit Tests - Function node.deleteUSSNode()", () => { session: globalMocks.session, profile: globalMocks.profileOne, }), - fspDelete: jest.spyOn(UssFSProvider.instance, "delete").mockImplementation(), + fspDelete: jest.spyOn(vscode.workspace.fs, "delete").mockImplementation(), }; newMocks.ussNode = new ZoweUSSNode({ @@ -679,7 +679,7 @@ describe("ZoweUSSNode Unit Tests - Function node.deleteUSSNode()", () => { const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocks(globalMocks); globalMocks.mockShowWarningMessage.mockResolvedValueOnce("Delete"); - jest.spyOn(UssFSProvider.instance, "delete").mockImplementationOnce(() => { + jest.spyOn(vscode.workspace.fs, "delete").mockImplementationOnce(() => { throw Error("testError"); }); diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts b/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts index 6b469cd61..647ea46ce 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetActions.ts @@ -1171,7 +1171,7 @@ export class DatasetActions { } await datasetProvider.checkCurrentProfile(node); if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) { - await DatasetFSProvider.instance.delete(node.resourceUri, { recursive: false }); + await vscode.workspace.fs.delete(node.resourceUri, { recursive: false }); } else { return; } diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts b/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts index 236900202..5e18da8f3 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetTree.ts @@ -1167,7 +1167,7 @@ export class DatasetTree extends ZoweTreeProvider implemen const newUri = node.resourceUri.with({ path: path.posix.join(path.posix.dirname(node.resourceUri.path), afterMemberName), }); - await DatasetFSProvider.instance.rename(node.resourceUri, newUri, { overwrite: false }); + await vscode.workspace.fs.rename(node.resourceUri, newUri, { overwrite: false }); node.resourceUri = newUri; node.label = afterMemberName; node.tooltip = afterMemberName; @@ -1222,7 +1222,7 @@ export class DatasetTree extends ZoweTreeProvider implemen const newUri = node.resourceUri.with({ path: path.posix.join(path.posix.dirname(node.resourceUri.path), afterDataSetName), }); - await DatasetFSProvider.instance.rename(node.resourceUri, newUri, { overwrite: false }); + await vscode.workspace.fs.rename(node.resourceUri, newUri, { overwrite: false }); // Rename corresponding node in Sessions or Favorites section (whichever one Rename wasn't called from) if (SharedContext.isFavorite(node)) { diff --git a/packages/zowe-explorer/src/trees/job/JobFSProvider.ts b/packages/zowe-explorer/src/trees/job/JobFSProvider.ts index cb998c092..fc5dfaf2d 100644 --- a/packages/zowe-explorer/src/trees/job/JobFSProvider.ts +++ b/packages/zowe-explorer/src/trees/job/JobFSProvider.ts @@ -281,7 +281,7 @@ export class JobFSProvider extends BaseProvider implements vscode.FileSystemProv * @param options Options for deleting the spool file or job * - `deleteRemote` - Deletes the job from the remote system if set to true. */ - public async delete(uri: vscode.Uri, options: { readonly recursive: boolean; readonly deleteRemote: boolean }): Promise { + public async delete(uri: vscode.Uri, options: { readonly recursive: boolean }): Promise { const entry = this.lookup(uri, false); const isJob = FsJobsUtils.isJobEntry(entry); if (!isJob) { @@ -291,10 +291,7 @@ export class JobFSProvider extends BaseProvider implements vscode.FileSystemProv const parent = this._lookupParentDirectory(uri, false); const profInfo = FsAbstractUtils.getInfoForUri(uri, Profiles.getInstance()); - - if (options.deleteRemote) { - await ZoweExplorerApiRegister.getJesApi(profInfo.profile).deleteJob(entry.job.jobname, entry.job.jobid); - } + await ZoweExplorerApiRegister.getJesApi(profInfo.profile).deleteJob(entry.job.jobname, entry.job.jobid); parent.entries.delete(entry.name); this._fireSoon({ type: vscode.FileChangeType.Deleted, uri }); } diff --git a/packages/zowe-explorer/src/trees/job/JobTree.ts b/packages/zowe-explorer/src/trees/job/JobTree.ts index 3fa17e221..8a950c2ff 100644 --- a/packages/zowe-explorer/src/trees/job/JobTree.ts +++ b/packages/zowe-explorer/src/trees/job/JobTree.ts @@ -218,7 +218,7 @@ export class JobTree extends ZoweTreeProvider implements Types public async delete(node: IZoweJobTreeNode): Promise { ZoweLogger.trace("JobTree.delete called."); - await JobFSProvider.instance.delete(node.resourceUri, { recursive: false, deleteRemote: true }); + await vscode.workspace.fs.delete(node.resourceUri, { recursive: false }); const favNode = this.relabelFavoritedJob(node); favNode.contextValue = SharedContext.asFavorite(favNode); await this.removeFavorite(favNode); diff --git a/packages/zowe-explorer/src/trees/uss/USSTree.ts b/packages/zowe-explorer/src/trees/uss/USSTree.ts index 3e574829f..cc741e35a 100644 --- a/packages/zowe-explorer/src/trees/uss/USSTree.ts +++ b/packages/zowe-explorer/src/trees/uss/USSTree.ts @@ -126,7 +126,7 @@ export class USSTree extends ZoweTreeProvider implements Types true ); } - await UssFSProvider.instance.delete(sourceUri, { recursive: true }); + await vscode.workspace.fs.delete(sourceUri, { recursive: true }); } else { // create a file on the remote system for writing try { @@ -160,7 +160,7 @@ export class USSTree extends ZoweTreeProvider implements Types if (!recursiveCall) { // Delete any files from the selection on the source LPAR - await UssFSProvider.instance.delete(sourceNode.resourceUri, { recursive: false }); + await vscode.workspace.fs.delete(sourceNode.resourceUri, { recursive: false }); } } } diff --git a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts index f7f795113..364532e9a 100644 --- a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts +++ b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts @@ -364,7 +364,7 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { }); try { - await UssFSProvider.instance.rename(oldUri, newUri, { overwrite: false }); + await vscode.workspace.fs.rename(oldUri, newUri, { overwrite: false }); } catch (err) { Gui.errorMessage(err.message); return; @@ -416,7 +416,7 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { return; } try { - await UssFSProvider.instance.delete(this.resourceUri, { recursive: this.isFolder }); + await vscode.workspace.fs.delete(this.resourceUri, { recursive: this.isFolder }); } catch (err) { ZoweLogger.error(err); if (err instanceof Error) { From 86a82f62d296a3b16270bb63885c8c76ffb70c41 Mon Sep 17 00:00:00 2001 From: SanthoshiBoyina1 <142206957+SanthoshiBoyina1@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:03:23 +0530 Subject: [PATCH 03/14] Fix: To resolve error message upon switching the authentication methods (#3275) * To handle missing args in basicAuthClearSecureArray Signed-off-by: Santhoshi Boyina * To handle missing args in tokenAuthClearSecureArray Signed-off-by: Santhoshi Boyina * To add unit test case for missing arg's in basicAuthClearSecureArray Signed-off-by: Santhoshi Boyina * To add unit test case for missing arg's in tokenAuthClearSecureArray Signed-off-by: Santhoshi Boyina * To run pre-publish command Signed-off-by: Santhoshi Boyina * To update changelog Signed-off-by: Santhoshi Boyina * To modify tokenAuthClearSecureArray() function Signed-off-by: Santhoshi Boyina * To add additional test cases for tokenAuthClearSecureArray() Signed-off-by: Santhoshi Boyina --------- Signed-off-by: Santhoshi Boyina --- packages/zowe-explorer/CHANGELOG.md | 1 + .../configuration/Profiles.unit.test.ts | 167 ++++++++++++++++++ packages/zowe-explorer/l10n/bundle.l10n.json | 3 +- packages/zowe-explorer/l10n/poeditor.json | 3 +- .../src/configuration/Profiles.ts | 29 ++- 5 files changed, 193 insertions(+), 10 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 6c318baaa..28d3c0174 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) - Fixed an issue where renaming or deleting a USS file or data set did not update the opened editor. [#3260](https://github.com/zowe/zowe-explorer-vscode/issues/3260) +- Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) ## `3.0.2` diff --git a/packages/zowe-explorer/__tests__/__unit__/configuration/Profiles.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/configuration/Profiles.unit.test.ts index 813d265b7..8f82c2a62 100644 --- a/packages/zowe-explorer/__tests__/__unit__/configuration/Profiles.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/configuration/Profiles.unit.test.ts @@ -2281,4 +2281,171 @@ describe("Profiles Unit Tests - function basicAuthClearSecureArray", () => { getProfileInfoMock.mockRestore(); getProfileFromConfigMock.mockRestore(); }); + + it("does not call Config.delete when user and password arg's are missing in mergeArgsForProfile", async () => { + const teamCfgMock = { + delete: jest.fn(), + save: jest.fn(), + set: jest.fn(), + }; + const profAttrsMock = { + isDefaultProfile: false, + profName: "example_profile", + profType: "zosmf", + profLoc: { + jsonLoc: undefined, + }, + }; + const mergeArgsMock = { + knownArgs: [], + }; + const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({ + getTeamConfig: jest.fn().mockReturnValue(teamCfgMock), + mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock), + } as any); + const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock); + + await Profiles.getInstance().basicAuthClearSecureArray("example_profile"); + expect(teamCfgMock.delete).not.toHaveBeenCalled(); + expect(teamCfgMock.set).not.toHaveBeenCalled(); + expect(teamCfgMock.save).toHaveBeenCalled(); + getProfileInfoMock.mockRestore(); + getProfileFromConfigMock.mockRestore(); + }); +}); + +describe("Profiles Unit Tests - function tokenAuthClearSecureArray", () => { + it("calls Config APIs when profLoc.jsonLoc is valid, no loginTokenType provided", async () => { + const teamCfgMock = { + delete: jest.fn(), + save: jest.fn(), + set: jest.fn(), + }; + const profAttrsMock = { + isDefaultProfile: false, + profName: "example_profile", + profType: "zosmf", + profLoc: { + jsonLoc: "/user/path/to/zowe.config.json", + locType: imperative.ProfLocType.TEAM_CONFIG, + }, + }; + const mergeArgsMock = { + knownArgs: [ + { + argName: "tokenType", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenType", + }, + }, + { + argName: "tokenValue", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenValue", + }, + }, + { + argName: "tokenExpiration", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenExpiration", + }, + }, + ], + }; + const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({ + getTeamConfig: jest.fn().mockReturnValue(teamCfgMock), + mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock), + } as any); + const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock); + + await Profiles.getInstance().tokenAuthClearSecureArray("example_profile"); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[0].argLoc.jsonLoc); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[1].argLoc.jsonLoc); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[2].argLoc.jsonLoc); + expect(teamCfgMock.set).toHaveBeenCalledWith(`${profAttrsMock.profLoc.jsonLoc}.secure`, ["user", "password"]); + expect(teamCfgMock.save).toHaveBeenCalled(); + getProfileInfoMock.mockRestore(); + getProfileFromConfigMock.mockRestore(); + }); + it("calls Config APIs when profLoc.jsonLoc is valid, loginTokenType provided", async () => { + const teamCfgMock = { + delete: jest.fn(), + save: jest.fn(), + set: jest.fn(), + }; + const profAttrsMock = { + isDefaultProfile: false, + profName: "example_profile", + profType: "zosmf", + profLoc: { + jsonLoc: "/user/path/to/zowe.config.json", + locType: imperative.ProfLocType.TEAM_CONFIG, + }, + }; + const mergeArgsMock = { + knownArgs: [ + { + argName: "tokenType", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenType", + }, + }, + { + argName: "tokenValue", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenValue", + }, + }, + { + argName: "tokenExpiration", + argLoc: { + jsonLoc: "profiles.example_profile.properties.tokenExpiration", + }, + }, + ], + }; + const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({ + getTeamConfig: jest.fn().mockReturnValue(teamCfgMock), + mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock), + } as any); + const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock); + + await Profiles.getInstance().tokenAuthClearSecureArray("example_profile", "apimlAuthenticationToken"); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[0].argLoc.jsonLoc); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[1].argLoc.jsonLoc); + expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[2].argLoc.jsonLoc); + expect(teamCfgMock.set).toHaveBeenCalledWith(`${profAttrsMock.profLoc.jsonLoc}.secure`, []); + expect(teamCfgMock.save).toHaveBeenCalled(); + getProfileInfoMock.mockRestore(); + getProfileFromConfigMock.mockRestore(); + }); + it("does not call Config.delete when tokenType, tokenValue, tokenExpiration arg's are missing in mergeArgsForProfile", async () => { + const teamCfgMock = { + delete: jest.fn(), + save: jest.fn(), + set: jest.fn(), + }; + const profAttrsMock = { + isDefaultProfile: false, + profName: "example_profile", + profType: "zosmf", + profLoc: { + jsonLoc: undefined, + }, + }; + const mergeArgsMock = { + knownArgs: [], + }; + const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({ + getTeamConfig: jest.fn().mockReturnValue(teamCfgMock), + mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock), + } as any); + const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock); + await Profiles.getInstance().tokenAuthClearSecureArray("example_profile"); + expect(teamCfgMock.delete).not.toHaveBeenCalled(); + expect(teamCfgMock.set).not.toHaveBeenCalled(); + expect(teamCfgMock.save).toHaveBeenCalled(); + getProfileInfoMock.mockRestore(); + getProfileFromConfigMock.mockRestore(); + }); }); diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index ef955fbf6..423bed79a 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -3,13 +3,12 @@ "Page Size:": "Page Size:", "Page": "Page", "No": "No", + "items": "items", "item": "item", - "s": "s", "selected": "selected", "Data Sets": "Data Sets", "Unix System Services (USS)": "Unix System Services (USS)", "Jobs": "Jobs", - "DataPanelContext has to be used within ": "DataPanelContext has to be used within ", "Refresh": "Refresh", "Search History": "Search History", "Favorites": "Favorites", diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index c5ed995fc..d76c582e7 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -414,13 +414,12 @@ "Page Size:": "", "Page": "", "No": "", + "items": "", "item": "", - "s": "", "selected": "", "Data Sets": "", "Unix System Services (USS)": "", "Jobs": "", - "DataPanelContext has to be used within ": "", "Refresh": "", "Search History": "", "Favorites": "", diff --git a/packages/zowe-explorer/src/configuration/Profiles.ts b/packages/zowe-explorer/src/configuration/Profiles.ts index de3fd1b59..56c67aedc 100644 --- a/packages/zowe-explorer/src/configuration/Profiles.ts +++ b/packages/zowe-explorer/src/configuration/Profiles.ts @@ -834,8 +834,14 @@ export class Profiles extends ProfilesCache { if (profAttrs.profLoc.jsonLoc) { configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, loginTokenType?.startsWith("apimlAuthenticationToken") ? [] : ["tokenValue"]); } - configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "user")?.argLoc.jsonLoc); - configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "password")?.argLoc.jsonLoc); + const userArgJsonLoc = profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "user")?.argLoc.jsonLoc; + if (userArgJsonLoc) { + configApi.delete(userArgJsonLoc); + } + const passwordArgJsonLoc = profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "password")?.argLoc.jsonLoc; + if (passwordArgJsonLoc) { + configApi.delete(passwordArgJsonLoc); + } await configApi.save(); } @@ -848,11 +854,22 @@ export class Profiles extends ProfilesCache { // Otherwise, we want to keep `tokenValue` in the secure array of the parent profile to avoid disconnecting child profiles if (profAttrs?.profLoc.jsonLoc) { configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, usingApimlToken ? [] : ["user", "password"]); - configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenType")?.argLoc.jsonLoc); - configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenValue")?.argLoc.jsonLoc); - configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenExpiration")?.argLoc.jsonLoc); - await configApi.save(); + const tokenTypeArgJsonLoc = profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenType")?.argLoc.jsonLoc; + if (tokenTypeArgJsonLoc) { + configApi.delete(tokenTypeArgJsonLoc); + } + const tokenValueArgJsonLoc = profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenValue") + ?.argLoc.jsonLoc; + if (tokenValueArgJsonLoc) { + configApi.delete(tokenValueArgJsonLoc); + } + const tokenExpirationArgJsonLoc = profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenExpiration") + ?.argLoc.jsonLoc; + if (tokenExpirationArgJsonLoc) { + configApi.delete(tokenExpirationArgJsonLoc); + } } + await configApi.save(); } public async handleSwitchAuthentication(node: Types.IZoweNodeType): Promise { From 05c93bd0a7f91cc45ef0803619fc4a533d83cefb Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 24 Oct 2024 14:08:15 -0400 Subject: [PATCH 04/14] fix: "Show Config" button in error dialog does not work during initialization (#3274) * fix: check if profileInfo is nullish during v1 migration Signed-off-by: Trae Yelovich * chore: update ZE changelog Signed-off-by: Trae Yelovich * tests: integration test for broken config Signed-off-by: Trae Yelovich * add unit test for nullish profileinfo Signed-off-by: Trae Yelovich * fix transient failures in UpdateCredentials scenario Signed-off-by: Trae Yelovich * remove extra join import in wdio conf Signed-off-by: Trae Yelovich * make integration test more reliable Signed-off-by: Trae Yelovich * move getprofileinfo call into try/catch during profiles init Signed-off-by: Trae Yelovich * test: open notification center to check for dialog Signed-off-by: Trae Yelovich * add license header to test; add another null check Signed-off-by: Trae Yelovich * add typedoc to ProfilesUtils.getProfileInfo Signed-off-by: Trae Yelovich * setupDefaultCredentialManager: log err msgs, update typedoc Signed-off-by: Trae Yelovich * test: promptUserWithNoConfigs, nullish profileInfo case Signed-off-by: Trae Yelovich * refactor typedoc for setupDefaultCredentialManager Signed-off-by: Trae Yelovich --------- Signed-off-by: Trae Yelovich --- packages/zowe-explorer/CHANGELOG.md | 1 + .../dialogs/ShowConfigErrorDialog.feature | 7 +++ .../dialogs/ShowConfigErrorDialog.steps.ts | 54 +++++++++++++++++++ .../profiles/UpdateCredentials.steps.ts | 15 ++++-- .../__integration__/bdd/wdio.conf.ts | 19 +++++++ .../__unit__/utils/ProfilesUtils.unit.test.ts | 21 ++++++++ .../src/configuration/Profiles.ts | 2 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 22 ++++++-- 8 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 packages/zowe-explorer/__tests__/__integration__/bdd/features/dialogs/ShowConfigErrorDialog.feature create mode 100644 packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/dialogs/ShowConfigErrorDialog.steps.ts diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 28d3c0174..a371ad462 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) - Fixed an issue where renaming or deleting a USS file or data set did not update the opened editor. [#3260](https://github.com/zowe/zowe-explorer-vscode/issues/3260) +- Fixed an issue during initialization where a broken team configuration file caused the "Show Config" action in the error dialog to stop working. [#3273](https://github.com/zowe/zowe-explorer-vscode/issues/3273) - Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) ## `3.0.2` diff --git a/packages/zowe-explorer/__tests__/__integration__/bdd/features/dialogs/ShowConfigErrorDialog.feature b/packages/zowe-explorer/__tests__/__integration__/bdd/features/dialogs/ShowConfigErrorDialog.feature new file mode 100644 index 000000000..3a68bf267 --- /dev/null +++ b/packages/zowe-explorer/__tests__/__integration__/bdd/features/dialogs/ShowConfigErrorDialog.feature @@ -0,0 +1,7 @@ +Feature: Show Config Error Dialog + +Scenario: Initializing Zowe Explorer with a broken profile + When a user opens Zowe Explorer + Then the Show Config dialog should appear + When the user clicks on the "Show Config" button + Then the config should appear in the editor \ No newline at end of file diff --git a/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/dialogs/ShowConfigErrorDialog.steps.ts b/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/dialogs/ShowConfigErrorDialog.steps.ts new file mode 100644 index 000000000..6fc99ad51 --- /dev/null +++ b/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/dialogs/ShowConfigErrorDialog.steps.ts @@ -0,0 +1,54 @@ +/** + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +import { Then, When } from "@cucumber/cucumber"; +import { getZoweExplorerContainer } from "../../../../__common__/shared.wdio"; +import { Notification, Workbench } from "wdio-vscode-service"; + +When("a user opens Zowe Explorer", async function () { + this.zoweExplorerPane = await getZoweExplorerContainer(); + await expect(this.zoweExplorerPane).toBeDefined(); +}); + +Then("the Show Config dialog should appear", async function () { + this.workbench = await browser.getWorkbench(); + let notification: Notification; + const notificationCenter = await (this.workbench as Workbench).openNotificationsCenter(); + await notificationCenter.wait(60000); + await browser.waitUntil(async () => { + const notifications: Notification[] = await notificationCenter.getNotifications("error" as any); + for (const n of notifications) { + if ((await n.getMessage()).startsWith("Error encountered when loading your Zowe config.")) { + notification = n; + return true; + } + } + + return false; + }); + await expect(notification).toBeDefined(); + this.configErrorDialog = notification; + await (this.configErrorDialog as Notification).wait(); +}); + +When('the user clicks on the "Show Config" button', async function () { + const button = await this.configErrorDialog.elem.$("a[role='button']"); + await expect(button).toBeClickable(); + await button.click(); +}); + +Then("the config should appear in the editor", async function () { + const editorView = (this.workbench as Workbench).getEditorView(); + await editorView.wait(); + await browser.waitUntil(async () => (await editorView.getOpenEditorTitles()).length > 0); + const editorTitles = await editorView.getOpenEditorTitles(); + await expect(editorTitles.some((editorTitle) => editorTitle.includes("zowe.config.json"))).toBe(true); +}); diff --git a/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/profiles/UpdateCredentials.steps.ts b/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/profiles/UpdateCredentials.steps.ts index cb0cd76fb..c8b80ee65 100644 --- a/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/profiles/UpdateCredentials.steps.ts +++ b/packages/zowe-explorer/__tests__/__integration__/bdd/step_definitions/profiles/UpdateCredentials.steps.ts @@ -13,7 +13,6 @@ import * as fs from "fs"; import * as path from "path"; import { AfterAll, Then, When } from "@cucumber/cucumber"; import { paneDivForTree } from "../../../../__common__/shared.wdio"; -import { TreeItem } from "wdio-vscode-service"; import quickPick from "../../../../__pageobjects__/QuickPick"; import { Key } from "webdriverio"; @@ -54,9 +53,17 @@ When(/a user who has profile with (.*) auth in team config/, function (authType: }); When("the user has a profile in their Data Sets tree", async function () { this.treePane = await paneDivForTree("Data Sets"); - this.profileNode = (await this.treePane.getVisibleItems()).pop() as TreeItem; - await expect(this.profileNode).toBeDefined(); - await expect(await this.profileNode.getLabel()).toContain(this.authType); + await browser.waitUntil(async () => { + const visibleItems = await this.treePane.getVisibleItems(); + for (const item of visibleItems) { + if ((await item.getLabel()) === `zosmf_${this.authType as string}`) { + this.profileNode = item; + return true; + } + } + + return false; + }); }); When("a user clicks search button for the profile", async function () { await this.profileNode.elem.moveTo(); diff --git a/packages/zowe-explorer/__tests__/__integration__/bdd/wdio.conf.ts b/packages/zowe-explorer/__tests__/__integration__/bdd/wdio.conf.ts index f3aa0a4ca..a0ff1bcaa 100644 --- a/packages/zowe-explorer/__tests__/__integration__/bdd/wdio.conf.ts +++ b/packages/zowe-explorer/__tests__/__integration__/bdd/wdio.conf.ts @@ -13,6 +13,7 @@ import type { Options } from "@wdio/types"; import { join as joinPath, resolve as resolvePath } from "path"; import { emptyDirSync } from "fs-extra"; import { baseConfig } from "../../__common__/base.wdio.conf"; +import { renameSync, rmSync, writeFileSync } from "fs"; const dataDir = joinPath(__dirname, "..", "..", "__common__", ".wdio-vscode-service", "data"); const screenshotDir = joinPath(__dirname, "results", "screenshots"); @@ -152,6 +153,24 @@ export const config: Options.Testrunner = { emptyDirSync(screenshotDir); }, + beforeFeature: async function (uri, feature) { + if (feature.name === "Show Config Error Dialog") { + const configPath = joinPath(process.env["ZOWE_CLI_HOME"], "zowe.config.json"); + const backupConfigPath = joinPath(process.env["ZOWE_CLI_HOME"], "zowe.config.bkp"); + renameSync(configPath, backupConfigPath); + writeFileSync(configPath, "invalidjson"); + } + }, + + afterFeature: async function (uri, feature) { + if (feature.name === "Show Config Error Dialog") { + const backupConfigPath = joinPath(process.env["ZOWE_CLI_HOME"], "zowe.config.bkp"); + const configPath = joinPath(process.env["ZOWE_CLI_HOME"], "zowe.config.json"); + rmSync(configPath); + renameSync(backupConfigPath, configPath); + } + }, + afterStep: async function (step, scenario, result, context) { if (!result.passed) { await browser.saveScreenshot(joinPath(screenshotDir, `${scenario.name} - ${step.text}.png`)); 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 2f20c7d61..31916b427 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -1108,6 +1108,20 @@ describe("ProfilesUtils unit tests", () => { }; } + it("should return early if profileInfo is nullish", async () => { + const blockMocks = getBlockMocks(); + blockMocks.getValueMock.mockReturnValueOnce(Definitions.V1MigrationStatus.JustMigrated); + blockMocks.setValueMock.mockImplementation(); + const getProfInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue(undefined as any); + const onlyV1ProfilesExistMock = new MockedProperty(imperative.ProfileInfo, "onlyV1ProfilesExist", { get: () => true }); + await ProfilesUtils.handleV1MigrationStatus(); + expect(getProfInfoMock).toHaveBeenCalled(); + expect(onlyV1ProfilesExistMock.mock).not.toHaveBeenCalled(); + blockMocks.getValueMock.mockRestore(); + blockMocks.setValueMock.mockRestore(); + onlyV1ProfilesExistMock[Symbol.dispose](); + }); + it("should call executeCommand with zowe.ds.addSession if the migration status is CreateConfigSelected", async () => { const blockMocks = getBlockMocks(); const executeCommandMock = jest.spyOn(vscode.commands, "executeCommand").mockImplementation(); @@ -1256,6 +1270,13 @@ describe("ProfilesUtils unit tests", () => { }); describe("promptUserWithNoConfigs", () => { + it("returns early if profileInfo is nullish", async () => { + const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue(undefined as any); + const showMessageSpy = jest.spyOn(Gui, "showMessage"); + await ProfilesUtils.promptUserWithNoConfigs(); + expect(showMessageSpy).not.toHaveBeenCalled(); + profInfoMock.mockRestore(); + }); it("prompts the user if they don't have any Zowe client configs", async () => { const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue({ getTeamConfig: () => ({ exists: false }), diff --git a/packages/zowe-explorer/src/configuration/Profiles.ts b/packages/zowe-explorer/src/configuration/Profiles.ts index 56c67aedc..d657f7f55 100644 --- a/packages/zowe-explorer/src/configuration/Profiles.ts +++ b/packages/zowe-explorer/src/configuration/Profiles.ts @@ -44,11 +44,11 @@ export class Profiles extends ProfilesCache { Constants.PROFILES_CACHE = Profiles.loader; try { await Profiles.loader.refresh(ZoweExplorerApiRegister.getInstance()); + await Profiles.getInstance().getProfileInfo(); } catch (err) { ZoweLogger.error(err); ZoweExplorerExtender.showZoweConfigError(err.message); } - await Profiles.getInstance().getProfileInfo(); return Profiles.loader; } diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index a7369eaef..95fbc0c07 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -181,7 +181,8 @@ export class ProfilesUtils { /** * Use the default credential manager in Zowe Explorer and setup before use - * @returns Promise the object of profileInfo using the default credential manager + * @returns {imperative.ProfileInfo} a ProfileInfo instance using the default credential manager, + * or undefined if an error occurred unrelated to credential manager initialization */ public static async setupDefaultCredentialManager(): Promise { try { @@ -204,6 +205,9 @@ export class ProfilesUtils { if (err instanceof imperative.ProfInfoErr && err.errorCode === imperative.ProfInfoErr.LOAD_CRED_MGR_FAILED) { await ProfilesUtils.promptAndDisableCredentialManagement(); } + if (err instanceof Error) { + ZoweLogger.error(err.message); + } // Ignore other types of errors since they will be handled later } } @@ -295,6 +299,10 @@ export class ProfilesUtils { ); } + /** + * Creates an instance of ProfileInfo and calls `readProfilesFromDisk` to load profiles. + * @returns An instance of `ProfileInfo`, or `undefined` if there was an error. + */ public static async getProfileInfo(): Promise { ZoweLogger.trace("ProfilesUtils.getProfileInfo called."); const hasSecureCredentialManagerEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED); @@ -367,13 +375,17 @@ export class ProfilesUtils { // VS Code registers our updated TreeView IDs. Otherwise, VS Code's "Refresh Extensions" option will break v3 init. const ussPersistentSettings = vscode.workspace.getConfiguration("Zowe-USS-Persistent"); const upgradingFromV1 = ZoweLocalStorage.getValue(Definitions.LocalStorageKey.V1_MIGRATION_STATUS); - const mProfileInfo = await ProfilesUtils.getProfileInfo(); + const profileInfo = await ProfilesUtils.getProfileInfo(); + if (profileInfo == null) { + return; + } + if (ussPersistentSettings != null && upgradingFromV1 == null && imperative.ProfileInfo.onlyV1ProfilesExist) { await ZoweLocalStorage.setValue(Definitions.LocalStorageKey.V1_MIGRATION_STATUS, Definitions.V1MigrationStatus.JustMigrated); await vscode.commands.executeCommand("workbench.action.reloadWindow"); } - if (upgradingFromV1 == null || mProfileInfo.getTeamConfig().exists || !imperative.ProfileInfo.onlyV1ProfilesExist) { + if (upgradingFromV1 == null || profileInfo.getTeamConfig().exists || !imperative.ProfileInfo.onlyV1ProfilesExist) { return; } const userSelection = await this.v1ProfileOptions(); @@ -391,6 +403,10 @@ export class ProfilesUtils { */ public static async promptUserWithNoConfigs(): Promise { const profInfo = await ProfilesUtils.getProfileInfo(); + if (profInfo == null) { + return; + } + if (!profInfo.getTeamConfig().exists && !imperative.ProfileInfo.onlyV1ProfilesExist) { Gui.showMessage( vscode.l10n.t("No Zowe client configurations were detected. Click 'Create New' to create a new Zowe team configuration."), From bbcbb1ab7989318082f1c32f1ea737046e5681a9 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Mon, 28 Oct 2024 09:09:15 -0400 Subject: [PATCH 05/14] fix(ds): Call remoteLookupForResource when entry doesn't exist locally (#3268) Signed-off-by: Trae Yelovich Signed-off-by: Billie Simmons Co-authored-by: Billie Simmons --- packages/zowe-explorer/CHANGELOG.md | 1 + .../trees/dataset/DatasetFSProvider.unit.test.ts | 14 +++++--------- .../src/trees/dataset/DatasetFSProvider.ts | 7 ++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index a371ad462..1ab4212c5 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue where renaming or deleting a USS file or data set did not update the opened editor. [#3260](https://github.com/zowe/zowe-explorer-vscode/issues/3260) - Fixed an issue during initialization where a broken team configuration file caused the "Show Config" action in the error dialog to stop working. [#3273](https://github.com/zowe/zowe-explorer-vscode/issues/3273) - Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) +- Fixed an issue where calling `vscode.workspace.fs.readFile` with a PDS member URI would throw an error when the PDS already existed as a filesystem entry. [#3267](https://github.com/zowe/zowe-explorer-vscode/issues/3267) ## `3.0.2` diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts index a48da93bb..3bfacb4d3 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts @@ -348,7 +348,7 @@ describe("readFile", () => { _getInfoFromUriMock.mockRestore(); }); - it("checks if parent dir exists when lookup fails & calls remoteLookupForResource if parent dir doesn't exist", async () => { + it("calls remoteLookupForResource if entry does not exist locally", async () => { const _lookupAsFileMock = jest .spyOn(DatasetFSProvider.instance as any, "_lookupAsFile") .mockImplementationOnce(() => { @@ -357,7 +357,6 @@ describe("readFile", () => { .mockReturnValue(testEntries.pdsMember); const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockImplementation(); - const _lookupParentDirectoryMock = jest.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory").mockReturnValueOnce(null); const _getInfoFromUriMock = jest.spyOn(DatasetFSProvider.instance as any, "_getInfoFromUri").mockReturnValueOnce({ profile: testProfile, path: "/USER.DATA.PS", @@ -368,7 +367,6 @@ describe("readFile", () => { await DatasetFSProvider.instance.readFile(testUris.pdsMember); expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember); - expect(_lookupParentDirectoryMock).toHaveBeenCalledWith(testUris.pdsMember, true); expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember); expect(fetchDatasetAtUriMock).toHaveBeenCalledWith(testUris.pdsMember, { isConflict: false }); _getInfoFromUriMock.mockRestore(); @@ -378,9 +376,6 @@ describe("readFile", () => { const _lookupAsFileMock = jest.spyOn(DatasetFSProvider.instance as any, "_lookupAsFile").mockImplementationOnce(() => { throw FileSystemError.FileNotFound(testUris.pdsMember); }); - const _lookupParentDirectoryMock = jest - .spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory") - .mockReturnValueOnce(testEntries.pds); const _getInfoFromUriMock = jest.spyOn(DatasetFSProvider.instance as any, "_getInfoFromUri").mockReturnValueOnce({ profile: testProfile, path: "/USER.DATA.PS", @@ -388,13 +383,14 @@ describe("readFile", () => { const remoteLookupForResourceMock = jest .spyOn(DatasetFSProvider.instance, "remoteLookupForResource") .mockReset() - .mockResolvedValue(testEntries.pdsMember); + .mockResolvedValueOnce(null as any); await expect(DatasetFSProvider.instance.readFile(testUris.pdsMember)).rejects.toThrow(); expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember); - expect(_lookupParentDirectoryMock).toHaveBeenCalledWith(testUris.pdsMember, true); - expect(remoteLookupForResourceMock).not.toHaveBeenCalledWith(testUris.pdsMember); + expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember); _getInfoFromUriMock.mockRestore(); + _lookupAsFileMock.mockRestore(); + remoteLookupForResourceMock.mockRestore(); }); it("returns the data for an entry", async () => { diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index a853471ad..a9c3849bf 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -390,11 +390,8 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem throw err; } - // check if parent directory exists; if not, do a remote lookup - const parent = this._lookupParentDirectory(uri, true); - if (parent == null) { - ds = await this.remoteLookupForResource(uri); - } + // do a remote lookup if the entry does not yet exist locally + ds = await this.remoteLookupForResource(uri); } if (ds == null) { From 374ac0686aebd421c8f5183141fb435e8452fb0a Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 31 Oct 2024 10:36:04 -0400 Subject: [PATCH 06/14] fix: Only show "No configs detected" prompt if ZE opened (#3281) * fix: show 'No config detected' prompt once when ZE opened Signed-off-by: Trae Yelovich * chore: update ZE changelog Signed-off-by: Trae Yelovich * resolve failing tests Signed-off-by: Trae Yelovich * refactor: move event into static fn, add coverage Signed-off-by: Trae Yelovich * update changelog Signed-off-by: Trae Yelovich * patch coverage for ProfilesUtils.promptUserWithNoConfigs Signed-off-by: Trae Yelovich * move variable definition Signed-off-by: Trae Yelovich --------- Signed-off-by: Trae Yelovich Signed-off-by: Billie Simmons Co-authored-by: Billie Simmons Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- packages/zowe-explorer/CHANGELOG.md | 2 ++ .../trees/dataset/DatasetInit.unit.test.ts | 28 +++++++++++++++---- .../__unit__/utils/ProfilesUtils.unit.test.ts | 24 ++++++++++++++++ packages/zowe-explorer/src/extension.ts | 1 - .../src/trees/dataset/DatasetInit.ts | 9 ++++++ .../zowe-explorer/src/utils/ProfilesUtils.ts | 6 ++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 1ab4212c5..2ef3a25a5 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -13,6 +13,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue during initialization where a broken team configuration file caused the "Show Config" action in the error dialog to stop working. [#3273](https://github.com/zowe/zowe-explorer-vscode/issues/3273) - Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) - Fixed an issue where calling `vscode.workspace.fs.readFile` with a PDS member URI would throw an error when the PDS already existed as a filesystem entry. [#3267](https://github.com/zowe/zowe-explorer-vscode/issues/3267) +- Fixed issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280) +- Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278) ## `3.0.2` diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetInit.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetInit.unit.test.ts index 16a83c6c8..a0d97af00 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetInit.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetInit.unit.test.ts @@ -17,6 +17,7 @@ import { SharedContext } from "../../../../src/trees/shared/SharedContext"; import { DatasetActions } from "../../../../src/trees/dataset/DatasetActions"; import { DatasetInit } from "../../../../src/trees/dataset/DatasetInit"; import { SharedInit } from "../../../../src/trees/shared/SharedInit"; +import { ProfilesUtils } from "../../../../src/utils/ProfilesUtils"; describe("Test src/dataset/extension", () => { describe("initDatasetProvider", () => { @@ -39,7 +40,9 @@ describe("Test src/dataset/extension", () => { filterPrompt: jest.fn(), rename: jest.fn(), onDidChangeConfiguration: jest.fn(), - getTreeView: jest.fn(), + getTreeView: jest.fn().mockReturnValue({ + onDidChangeVisibility: jest.fn(), + }), sortPdsMembersDialog: jest.fn(), filterPdsMembersDialog: jest.fn(), openWithEncoding: jest.fn(), @@ -144,7 +147,11 @@ describe("Test src/dataset/extension", () => { name: "zowe.ds.pasteDataSets:1", parm: [false], mock: [ - { spy: jest.spyOn(dsProvider, "getTreeView"), arg: [], ret: { reveal: jest.fn(), selection: [test.value] } }, + { + spy: jest.spyOn(dsProvider, "getTreeView"), + arg: [], + ret: { reveal: jest.fn(), onDidChangeVisibility: jest.fn(), selection: [test.value] }, + }, { spy: jest.spyOn(DatasetActions, "pasteDataSetMembers"), arg: [dsProvider, test.value] }, { spy: jest.spyOn(DatasetActions, "refreshDataset"), arg: ["test", dsProvider] }, ], @@ -152,7 +159,11 @@ describe("Test src/dataset/extension", () => { { name: "zowe.ds.pasteDataSets:2", mock: [ - { spy: jest.spyOn(dsProvider, "getTreeView"), arg: [], ret: { reveal: jest.fn(), selection: [test.value] } }, + { + spy: jest.spyOn(dsProvider, "getTreeView"), + arg: [], + ret: { reveal: jest.fn(), onDidChangeVisibility: jest.fn(), selection: [test.value] }, + }, { spy: jest.spyOn(DatasetActions, "pasteDataSetMembers"), arg: [dsProvider, test.value] }, { spy: jest.spyOn(DatasetActions, "refreshDataset"), arg: ["test", dsProvider] }, ], @@ -204,12 +215,11 @@ describe("Test src/dataset/extension", () => { onDidChangeConfiguration = (fun: () => void) => { return { onDidChangeConfiguration: fun }; }; - spyCreateDatasetTree = jest.spyOn(DatasetInit, "createDatasetTree"); + spyCreateDatasetTree = jest.spyOn(DatasetInit, "createDatasetTree").mockResolvedValue(dsProvider as any); jest.spyOn(SharedInit, "initSubscribers").mockImplementation(jest.fn()); Object.defineProperty(vscode.commands, "registerCommand", { value: registerCommand }); Object.defineProperty(vscode.workspace, "onDidChangeConfiguration", { value: onDidChangeConfiguration }); - spyCreateDatasetTree.mockResolvedValue(dsProvider as any); await DatasetInit.initDatasetProvider(test.context); }); beforeEach(() => { @@ -227,4 +237,12 @@ describe("Test src/dataset/extension", () => { expect(myProvider).toBe(null); }); }); + + describe("datasetTreeVisibilityChanged", () => { + it("calls ProfilesUtils.promptUserWithNoConfigs if visible", async () => { + const promptUserWithNoConfigsMock = jest.spyOn(ProfilesUtils, "promptUserWithNoConfigs").mockImplementation(); + await DatasetInit.datasetTreeVisibilityChanged({ visible: true }); + expect(promptUserWithNoConfigsMock).toHaveBeenCalled(); + }); + }); }); 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 31916b427..cf99a6b09 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -1270,6 +1270,13 @@ describe("ProfilesUtils unit tests", () => { }); describe("promptUserWithNoConfigs", () => { + it("returns early if user was already prompted in this session", async () => { + const noConfigDialogShownMock = new MockedProperty(ProfilesUtils, "noConfigDialogShown", { value: true }); + const getProfInfoSpy = jest.spyOn(ProfilesUtils, "getProfileInfo"); + await ProfilesUtils.promptUserWithNoConfigs(); + expect(getProfInfoSpy).not.toHaveBeenCalled(); + noConfigDialogShownMock[Symbol.dispose](); + }); it("returns early if profileInfo is nullish", async () => { const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue(undefined as any); const showMessageSpy = jest.spyOn(Gui, "showMessage"); @@ -1278,6 +1285,7 @@ describe("ProfilesUtils unit tests", () => { profInfoMock.mockRestore(); }); it("prompts the user if they don't have any Zowe client configs", async () => { + const noConfigDialogShownMock = new MockedProperty(ProfilesUtils, "noConfigDialogShown", { value: false }); const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue({ getTeamConfig: () => ({ exists: false }), } as any); @@ -1294,11 +1302,16 @@ describe("ProfilesUtils unit tests", () => { expect(profInfoMock).toHaveBeenCalled(); profInfoMock.mockRestore(); onlyV1ProfsExistMock[Symbol.dispose](); + noConfigDialogShownMock[Symbol.dispose](); }); it("executes zowe.ds.addSession if the user selects 'Create New' in the prompt", async () => { const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue({ getTeamConfig: () => ({ exists: false }), } as any); + const noConfigDialogShownMock = new MockedProperty(ProfilesUtils, "noConfigDialogShown", { + configurable: true, + value: false, + }); const onlyV1ProfsExistMock = new MockedProperty(imperative.ProfileInfo, "onlyV1ProfilesExist", { configurable: true, get: () => false, @@ -1315,11 +1328,16 @@ describe("ProfilesUtils unit tests", () => { executeCommandMock.mockRestore(); profInfoMock.mockRestore(); onlyV1ProfsExistMock[Symbol.dispose](); + noConfigDialogShownMock[Symbol.dispose](); }); it("does not prompt the user if they have a Zowe team config", async () => { const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue({ getTeamConfig: () => ({ exists: true }), } as any); + const noConfigDialogShownMock = new MockedProperty(ProfilesUtils, "noConfigDialogShown", { + configurable: true, + value: false, + }); const onlyV1ProfsExistMock = new MockedProperty(imperative.ProfileInfo, "onlyV1ProfilesExist", { configurable: true, get: () => false, @@ -1333,11 +1351,16 @@ describe("ProfilesUtils unit tests", () => { expect(profInfoMock).toHaveBeenCalled(); profInfoMock.mockRestore(); onlyV1ProfsExistMock[Symbol.dispose](); + noConfigDialogShownMock[Symbol.dispose](); }); it("does not prompt the user if they have v1 profiles", async () => { const profInfoMock = jest.spyOn(ProfilesUtils, "getProfileInfo").mockResolvedValue({ getTeamConfig: () => ({ exists: false }), } as any); + const noConfigDialogShownMock = new MockedProperty(ProfilesUtils, "noConfigDialogShown", { + configurable: true, + value: false, + }); const onlyV1ProfsExistMock = new MockedProperty(imperative.ProfileInfo, "onlyV1ProfilesExist", { configurable: true, get: () => true, @@ -1351,6 +1374,7 @@ describe("ProfilesUtils unit tests", () => { expect(profInfoMock).toHaveBeenCalled(); profInfoMock.mockRestore(); onlyV1ProfsExistMock[Symbol.dispose](); + noConfigDialogShownMock[Symbol.dispose](); }); }); diff --git a/packages/zowe-explorer/src/extension.ts b/packages/zowe-explorer/src/extension.ts index 8176f3df8..8bc9d2b22 100644 --- a/packages/zowe-explorer/src/extension.ts +++ b/packages/zowe-explorer/src/extension.ts @@ -56,7 +56,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { @@ -30,6 +31,12 @@ export class DatasetInit { return tree; } + public static async datasetTreeVisibilityChanged(this: void, e: vscode.TreeViewVisibilityChangeEvent): Promise { + if (e.visible) { + await ProfilesUtils.promptUserWithNoConfigs(); + } + } + public static async initDatasetProvider(context: vscode.ExtensionContext): Promise { ZoweLogger.trace("DatasetInit.initDatasetProvider called."); context.subscriptions.push(vscode.workspace.registerFileSystemProvider(ZoweScheme.DS, DatasetFSProvider.instance, { isCaseSensitive: true })); @@ -38,6 +45,8 @@ export class DatasetInit { return null; } + datasetProvider.getTreeView().onDidChangeVisibility(DatasetInit.datasetTreeVisibilityChanged); + context.subscriptions.push( vscode.commands.registerCommand("zowe.all.config.init", async () => { await datasetProvider.createZoweSchema(datasetProvider); diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index 95fbc0c07..a4569a3c0 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -28,6 +28,7 @@ export enum ProfilesConvertStatus { export class ProfilesUtils { public static PROFILE_SECURITY: string | boolean = Constants.ZOWE_CLI_SCM; + private static noConfigDialogShown: boolean = false; /** * Check if the credential manager's vsix is installed for use @@ -402,6 +403,10 @@ export class ProfilesUtils { * This aims to help direct new Zowe Explorer users to create a new team configuration. */ public static async promptUserWithNoConfigs(): Promise { + if (ProfilesUtils.noConfigDialogShown) { + return; + } + const profInfo = await ProfilesUtils.getProfileInfo(); if (profInfo == null) { return; @@ -418,6 +423,7 @@ export class ProfilesUtils { await vscode.commands.executeCommand("zowe.ds.addSession"); } }); + ProfilesUtils.noConfigDialogShown = true; } } From 7f790bf65da15015cba067a9790eed888e1f55e1 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 5 Nov 2024 10:20:15 -0500 Subject: [PATCH 07/14] fix(v3): Pass `responseTimeout` in z/OSMF MVS and USS API calls (#3292) * fix: pass responseTimeout to API functions Signed-off-by: Trae Yelovich * refactor: remove fallback for spreading newOptions Signed-off-by: Trae Yelovich * refactor: use optional chaining; work on resolving tests Signed-off-by: Trae Yelovich * refactor: pass profile props to tests, fix types Signed-off-by: Trae Yelovich * tests: resolve failing cases in ZE Signed-off-by: Trae Yelovich * chore: update changelog entry for ZE API Signed-off-by: Trae Yelovich * refactor: remove fallback for spreading undefined options Signed-off-by: Trae Yelovich * fix: add missing functions to MvsApi test list Signed-off-by: Trae Yelovich --------- Signed-off-by: Trae Yelovich --- packages/zowe-explorer-api/CHANGELOG.md | 2 + .../ZoweExplorerZosmfApi.unit.test.ts | 138 ++++++++++-------- .../src/profiles/ZoweExplorerZosmfApi.ts | 107 ++++++++++---- .../ZoweExplorerZosmfApi.unit.test.ts | 8 +- .../ZoweExplorerZosmfApi.unit.test.ts.snap | 4 + 5 files changed, 175 insertions(+), 84 deletions(-) diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index f6452a0dd..61d5cb54a 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ### Bug fixes +- Fixed an issue where the `responseTimeout` profile property was ignored for z/OSMF MVS and USS API calls. [#3225](https://github.com/zowe/zowe-explorer-vscode/issues/3225) + ## `3.0.2` ### New features and enhancements diff --git a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts index ca00c2980..6882b7554 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts @@ -21,32 +21,37 @@ import { ZoweExplorerZosmf } from "../../../src/profiles/ZoweExplorerZosmfApi"; import { FileManagement } from "../../../src/utils/FileManagement"; import { MainframeInteraction } from "../../../src/extend"; +type ParametersWithProfileArgs = F extends (...args: infer P) => any ? [...Parameters, profileProperties?: object] : never; + type ITestApi = { [K in keyof T]: { name: K; spy: jest.SpyInstance; - args: jest.ArgsType; - transform?: (args: jest.ArgsType) => any[]; + args: ParametersWithProfileArgs; + transform?: (args: ParametersWithProfileArgs) => any[]; }; }[keyof T]; -type ITestProfile = { - host: string; - port: number; - basePath: string; - rejectUnauthorized: boolean; - user?: string; - password?: string; +const fakeProperties = { + responseTimeout: 60, }; -const fakeProfile: ITestProfile = { +const fakeProfile: imperative.IProfile = { host: "example.com", port: 443, basePath: "/api/v1", rejectUnauthorized: true, user: "admin", password: "123456", + ...fakeProperties, +}; +const loadedProfile: imperative.IProfileLoaded = { + profile: fakeProfile, + message: "", + type: "zosmf", + failNotFound: false, }; + const fakeSession = imperative.Session.createFromUrl(new URL("https://example.com")); const mISshSession: zosuss.ISshSession = { @@ -74,7 +79,7 @@ async function expectUnixCommandApiWithSshSession( async function expectApiWithSession({ name, spy, args, transform }: ITestApi, apiInstance: MainframeInteraction.ICommon): Promise { spy.mockClear().mockResolvedValue(undefined); const getSessionSpy = jest.spyOn(apiInstance, "getSession").mockReturnValue(fakeSession); - await apiInstance[name as string](...args); + await apiInstance[name as string](...Object.values(args)); expect(getSessionSpy).toHaveBeenCalledTimes(1); const params: unknown[] = transform ? transform(args) : args; expect(spy).toHaveBeenCalledWith(fakeSession, ...params); @@ -96,20 +101,22 @@ describe("ZosmfUssApi", () => { password: "password", protocol: "http", user: "aZosmfUser", + ...fakeProperties, }, } as imperative.IProfileLoaded; it("should include profile properties in the built session object", () => { - const api = new ZoweExplorerZosmf.UssApi(); + const api = new ZoweExplorerZosmf.UssApi(loadedProfile); - const transformedProps = { ...exampleProfile.profile, hostname: exampleProfile.profile?.host }; + const transformedProps: Record = { ...exampleProfile.profile, hostname: exampleProfile.profile?.host, ...fakeProperties }; delete transformedProps["host"]; + delete transformedProps["responseTimeout"]; expect((api as any)._getSession(exampleProfile).mISession).toMatchObject(transformedProps); }); }); describe("updateAttributes", () => { - const ussApi = new ZoweExplorerZosmf.UssApi(); + const ussApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const getSessionMock = jest.spyOn(ussApi, "getSession").mockReturnValue(fakeSession); const putUSSPayload = jest.spyOn(zosfiles.Utilities, "putUSSPayload").mockResolvedValue(Buffer.from("test")); @@ -203,20 +210,20 @@ describe("ZosmfUssApi", () => { it("uploads a file from buffer", async () => { const uploadFileSpy = jest.spyOn(zosfiles.Upload, "bufferToUssFile").mockImplementation(); - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const buf = Buffer.from("123abc"); await zosmfApi.uploadFromBuffer(buf, "/some/uss/path"); - expect(uploadFileSpy).toHaveBeenCalledWith(zosmfApi.getSession(), "/some/uss/path", buf, undefined); + expect(uploadFileSpy).toHaveBeenCalledWith(zosmfApi.getSession(), "/some/uss/path", buf, fakeProperties); }); it("constants should be unchanged", () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); expect(zosmfApi.getProfileTypeName()).toMatchSnapshot(); expect(zosmfApi.getTokenTypeName()).toMatchSnapshot(); }); it("getSessionFromCommandArgument should build session from arguments", () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const session = zosmfApi.getSessionFromCommandArgument(fakeProfile as unknown as imperative.ICommandArguments); expect(session).toBeDefined(); const sessCfg: imperative.ISession = { @@ -225,6 +232,7 @@ describe("ZosmfUssApi", () => { type: imperative.SessConstants.AUTH_TYPE_BASIC, }; delete sessCfg["host"]; + delete sessCfg["responseTimeout"]; expect(session.ISession).toMatchObject(sessCfg); }); @@ -234,17 +242,18 @@ describe("ZosmfUssApi", () => { } as unknown as imperative.IProfileLoaded); const session = zosmfApi.getSession(); expect(session).toBeDefined(); - const sessCfg: Partial & { hostname: string; type: string } = { + const sessCfg: Partial & { hostname: string; type: string } = { ...fakeProfile, hostname: fakeProfile.host, type: imperative.SessConstants.AUTH_TYPE_BASIC, }; delete sessCfg.host; + delete sessCfg["responseTimeout"]; expect(session.ISession).toMatchObject(sessCfg); }); it("getSession should build session from profile with token", () => { - const fakeProfileWithToken = { + const fakeProfileWithToken: imperative.IProfile = { ...fakeProfile, tokenType: imperative.SessConstants.TOKEN_TYPE_JWT, tokenValue: "fakeToken", @@ -256,12 +265,13 @@ describe("ZosmfUssApi", () => { } as unknown as imperative.IProfileLoaded); const session = zosmfApi.getSession(); expect(session).toBeDefined(); - const sessCfg: Partial & { hostname: string; type: string } = { + const sessCfg: Partial & { hostname: string; type: string } = { ...fakeProfileWithToken, hostname: fakeProfileWithToken.host, type: imperative.SessConstants.AUTH_TYPE_TOKEN, }; delete sessCfg.host; + delete sessCfg["responseTimeout"]; expect(session.ISession).toMatchObject(sessCfg); }); @@ -274,7 +284,7 @@ describe("ZosmfUssApi", () => { }); it("getStatus should validate active profile", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const checkStatusSpy = jest.spyOn(zosmf.CheckStatus, "getZosmfInfo").mockResolvedValue({}); const status = await zosmfApi.getStatus({ profile: fakeProfile } as unknown as imperative.IProfileLoaded, "zosmf"); expect(status).toBe("active"); @@ -282,7 +292,7 @@ describe("ZosmfUssApi", () => { }); it("getStatus should validate inactive profile", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const checkStatusSpy = jest.spyOn(zosmf.CheckStatus, "getZosmfInfo").mockResolvedValue(undefined as unknown as zosmf.IZosmfInfoResponse); const status = await zosmfApi.getStatus({ profile: fakeProfile } as unknown as imperative.IProfileLoaded, "zosmf"); expect(status).toBe("inactive"); @@ -290,7 +300,7 @@ describe("ZosmfUssApi", () => { }); it("should test that copy calls zowe.Utilities.putUSSPayload", async () => { - const api = new ZoweExplorerZosmf.UssApi(); + const api = new ZoweExplorerZosmf.UssApi(loadedProfile); api.getSession = jest.fn(); const response = Buffer.from("hello world!"); @@ -303,13 +313,13 @@ describe("ZosmfUssApi", () => { }); it("getStatus should validate unverified profile", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const status = await zosmfApi.getStatus({ profile: fakeProfile } as unknown as imperative.IProfileLoaded, "sample"); expect(status).toBe("unverified"); }); it("login and logout should call APIML endpoints", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const loginSpy = jest.spyOn(Login, "apimlLogin").mockResolvedValue(""); const logoutSpy = jest.spyOn(Logout, "apimlLogout").mockResolvedValue(); @@ -321,7 +331,7 @@ describe("ZosmfUssApi", () => { }); it("should retrieve the tag of a file", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); jest.spyOn(JSON, "parse").mockReturnValue({ stdout: ["-t UTF-8 tesfile.txt"], }); @@ -334,7 +344,7 @@ describe("ZosmfUssApi", () => { }); it("should update the tag attribute when passed in", async () => { - const zosmfApi = new ZoweExplorerZosmf.UssApi(); + const zosmfApi = new ZoweExplorerZosmf.UssApi(loadedProfile); const changeTagSpy = jest.fn(); Object.defineProperty(zosfiles.Utilities, "putUSSPayload", { value: changeTagSpy, @@ -345,7 +355,7 @@ describe("ZosmfUssApi", () => { }); it("calls putUSSPayload to move a directory from old path to new path", async () => { - const api = new ZoweExplorerZosmf.UssApi(); + const api = new ZoweExplorerZosmf.UssApi(loadedProfile); const putUssPayloadSpy = jest.fn(); Object.defineProperty(zosfiles.Utilities, "putUSSPayload", { value: putUssPayloadSpy, @@ -364,7 +374,7 @@ describe("ZosmfUssApi", () => { { name: "fileList", spy: jest.spyOn(zosfiles.List, "fileList"), - args: ["ussPath"], + args: ["ussPath", fakeProperties], }, { name: "isFileTagBinOrAscii", @@ -374,33 +384,33 @@ describe("ZosmfUssApi", () => { { name: "getContents", spy: jest.spyOn(zosfiles.Download, "ussFile"), - args: ["ussPath", {}], + args: ["ussPath", fakeProperties], }, { name: "putContent", spy: jest.spyOn(zosfiles.Upload, "fileToUssFile"), - args: ["localPath", "ussPath", {}], + args: ["localPath", "ussPath", fakeProperties], }, { name: "uploadDirectory", spy: jest.spyOn(zosfiles.Upload, "dirToUSSDirRecursive"), - args: ["localPath", "ussPath", {}], + args: ["localPath", "ussPath", fakeProperties], }, { name: "create", spy: jest.spyOn(zosfiles.Create, "uss"), - args: ["ussPath", "file", "777"], + args: ["ussPath", "file", "777", fakeProperties], }, { name: "delete", spy: jest.spyOn(zosfiles.Delete, "ussFile"), - args: ["/ussPath", false], - transform: (args) => [args[0].slice(1), args[1]], + args: ["/ussPath", false, fakeProperties], + transform: (args) => [args[0].slice(1), args[1], fakeProperties], }, { name: "delete", spy: jest.spyOn(zosfiles.Delete, "ussFile"), - args: ["ussPath", false], + args: ["ussPath", false, fakeProperties], }, { name: "rename", @@ -410,7 +420,7 @@ describe("ZosmfUssApi", () => { ]; ussApis.forEach((ussApi) => { it(`${ussApi?.name} should inject session into Zowe API`, async () => { - await expectApiWithSession(ussApi, new ZoweExplorerZosmf.UssApi()); + await expectApiWithSession(ussApi, new ZoweExplorerZosmf.UssApi(loadedProfile)); }); }); }); @@ -420,38 +430,38 @@ describe("ZosmfMvsApi", () => { { name: "dataSet", spy: jest.spyOn(zosfiles.List, "dataSet"), - args: ["dsname", {}], + args: ["dsname", fakeProperties], }, { name: "allMembers", spy: jest.spyOn(zosfiles.List, "allMembers"), - args: ["dsname", {}], + args: ["dsname", fakeProperties], }, { name: "getContents", spy: jest.spyOn(zosfiles.Download, "dataSet"), - args: ["dsname", {}], + args: ["dsname", fakeProperties], }, { name: "putContents", spy: jest.spyOn(zosfiles.Upload, "pathToDataSet"), - args: ["localPath", "dsname", {}], + args: ["localPath", "dsname", fakeProperties], }, { name: "createDataSet", spy: jest.spyOn(zosfiles.Create, "dataSet"), - args: [0, "dsname", {}], + args: [0, "dsname", fakeProperties], }, { name: "createDataSetMember", spy: jest.spyOn(zosfiles.Upload, "bufferToDataSet"), - args: ["dsname", {}], + args: ["dsname", fakeProperties], transform: (args) => [Buffer.from(""), ...args], }, { name: "allocateLikeDataSet", spy: jest.spyOn(zosfiles.Create, "dataSetLike"), - args: ["dsname1", "dsname2"], + args: ["dsname1", "dsname2", fakeProperties], }, { name: "copyDataSetMember", @@ -459,15 +469,18 @@ describe("ZosmfMvsApi", () => { args: [ { dsn: "dsname1", member: "member1" }, { dsn: "dsname2", member: "member2" }, - { "from-dataset": { dsn: "dsname1", member: "member1" } }, + { "from-dataset": { dsn: "dsname1", member: "member1" }, ...fakeProperties }, ], transform: (args) => [args[1], args[2]], }, { name: "copyDataSetMember", spy: jest.spyOn(zosfiles.Copy, "dataSet"), - args: [{ dsn: "dsname1", member: "member1" }, { dsn: "dsname2", member: "member2" }, {} as any], - transform: (args) => [args[1], { "from-dataset": args[0] }], + args: [ + { dsn: "dsname1", member: "member1" }, + { dsn: "dsname2", member: "member2" }, + ], + transform: (args) => [args[1], { "from-dataset": args[0], ...fakeProperties }], }, { name: "copyDataSetMember", @@ -476,46 +489,57 @@ describe("ZosmfMvsApi", () => { { dsn: "dsname1", member: "member1" }, { dsn: "dsname2", member: "member2" }, ], - transform: (args) => [args[1], { "from-dataset": args[0] }], + transform: (args) => [args[1], { "from-dataset": args[0], ...fakeProperties }], }, { name: "renameDataSet", spy: jest.spyOn(zosfiles.Rename, "dataSet"), - args: ["dsname1", "dsname2"], + args: ["dsname1", "dsname2", fakeProperties], }, { name: "renameDataSetMember", spy: jest.spyOn(zosfiles.Rename, "dataSetMember"), - args: ["dsname", "member1", "member2"], + args: ["dsname", "member1", "member2", fakeProperties], }, { name: "hMigrateDataSet", spy: jest.spyOn(zosfiles.HMigrate, "dataSet"), - args: ["dsname"], + args: ["dsname", fakeProperties], }, { name: "hRecallDataSet", spy: jest.spyOn(zosfiles.HRecall, "dataSet"), - args: ["dsname"], + args: ["dsname", fakeProperties], }, { name: "deleteDataSet", spy: jest.spyOn(zosfiles.Delete, "dataSet"), - args: ["dsname", {}], + args: ["dsname", fakeProperties], + }, + { + name: "dataSetsMatchingPattern", + spy: jest.spyOn(zosfiles.List, "dataSetsMatchingPattern"), + args: [["SAMPLE.A*", "SAMPLE.B*"], fakeProperties], + }, + { + name: "copyDataSet", + spy: jest.spyOn(zosfiles.Copy, "dataSet"), + args: ["FROM.NAME", "TO.NAME", undefined, undefined, fakeProperties], + transform: (args) => [{ dsn: args[1] }, { enq: undefined, "from-dataset": { dsn: args[0] }, replace: undefined, ...fakeProperties }], }, ]; mvsApis.forEach((mvsApi) => { it(`${mvsApi?.name} should inject session into Zowe API`, async () => { - await expectApiWithSession(mvsApi, new ZoweExplorerZosmf.MvsApi()); + await expectApiWithSession(mvsApi, new ZoweExplorerZosmf.MvsApi(loadedProfile)); }); }); it("uploads a data set from buffer", async () => { const uploadFileSpy = jest.spyOn(zosfiles.Upload, "bufferToDataSet").mockImplementation(); - const zosmfApi = new ZoweExplorerZosmf.MvsApi(); + const zosmfApi = new ZoweExplorerZosmf.MvsApi(loadedProfile); const buf = Buffer.from("123abc"); await zosmfApi.uploadFromBuffer(buf, "SOME.DS(MEMB)"); - expect(uploadFileSpy).toHaveBeenCalledWith(zosmfApi.getSession(), buf, "SOME.DS(MEMB)", undefined); + expect(uploadFileSpy).toHaveBeenCalledWith(zosmfApi.getSession(), buf, "SOME.DS(MEMB)", fakeProperties); }); }); diff --git a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts index c2fad2367..340da8f7b 100644 --- a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts +++ b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts @@ -119,7 +119,7 @@ export namespace ZoweExplorerZosmf { */ export class UssApi extends CommonApi implements MainframeInteraction.IUss { public fileList(ussFilePath: string): Promise { - return zosfiles.List.fileList(this.getSession(), ussFilePath); + return zosfiles.List.fileList(this.getSession(), ussFilePath, { responseTimeout: this.profile?.profile?.responseTimeout }); } public isFileTagBinOrAscii(ussFilePath: string): Promise { @@ -127,11 +127,14 @@ export namespace ZoweExplorerZosmf { } public getContents(inputFilePath: string, options: zosfiles.IDownloadSingleOptions): Promise { - return zosfiles.Download.ussFile(this.getSession(), inputFilePath, options); + return zosfiles.Download.ussFile(this.getSession(), inputFilePath, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public copy(outputPath: string, options?: Omit): Promise { - return zosfiles.Utilities.putUSSPayload(this.getSession(), outputPath, { ...(options ?? {}), request: "copy" }); + return zosfiles.Utilities.putUSSPayload(this.getSession(), outputPath, { ...options, request: "copy" }); } public async move(oldPath: string, newPath: string): Promise { @@ -142,11 +145,17 @@ export namespace ZoweExplorerZosmf { } public uploadFromBuffer(buffer: Buffer, filePath: string, options?: zosfiles.IUploadOptions): Promise { - return zosfiles.Upload.bufferToUssFile(this.getSession(), filePath, buffer, options); + return zosfiles.Upload.bufferToUssFile(this.getSession(), filePath, buffer, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public putContent(inputFilePath: string, ussFilePath: string, options: zosfiles.IUploadOptions): Promise { - return zosfiles.Upload.fileToUssFile(this.getSession(), inputFilePath, ussFilePath, options); + return zosfiles.Upload.fileToUssFile(this.getSession(), inputFilePath, ussFilePath, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public async updateAttributes(ussPath: string, attributes: Partial): Promise { @@ -199,17 +208,20 @@ export namespace ZoweExplorerZosmf { ussDirectoryPath: string, options?: zosfiles.IUploadOptions ): Promise { - return zosfiles.Upload.dirToUSSDirRecursive(this.getSession(), inputDirectoryPath, ussDirectoryPath, options); + return zosfiles.Upload.dirToUSSDirRecursive(this.getSession(), inputDirectoryPath, ussDirectoryPath, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public create(ussPath: string, type: string, mode?: string): Promise { - return zosfiles.Create.uss(this.getSession(), ussPath, type, mode); + return zosfiles.Create.uss(this.getSession(), ussPath, type, mode, { responseTimeout: this.profile?.profile?.responseTimeout }); } public delete(ussPath: string, recursive?: boolean): Promise { // handle zosmf api issue with file paths const fixedName = ussPath.startsWith("/") ? ussPath.substring(1) : ussPath; - return zosfiles.Delete.ussFile(this.getSession(), fixedName, recursive); + return zosfiles.Delete.ussFile(this.getSession(), fixedName, recursive, { responseTimeout: this.profile?.profile?.responseTimeout }); } public async rename(currentUssPath: string, newUssPath: string): Promise { @@ -235,23 +247,35 @@ export namespace ZoweExplorerZosmf { */ export class MvsApi extends CommonApi implements MainframeInteraction.IMvs { public dataSet(filter: string, options?: zosfiles.IListOptions): Promise { - return zosfiles.List.dataSet(this.getSession(), filter, options); + return zosfiles.List.dataSet(this.getSession(), filter, { responseTimeout: this.profile?.profile?.responseTimeout, ...options }); } public allMembers(dataSetName: string, options?: zosfiles.IListOptions): Promise { - return zosfiles.List.allMembers(this.getSession(), dataSetName, options); + return zosfiles.List.allMembers(this.getSession(), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public getContents(dataSetName: string, options?: zosfiles.IDownloadSingleOptions): Promise { - return zosfiles.Download.dataSet(this.getSession(), dataSetName, options); + return zosfiles.Download.dataSet(this.getSession(), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public uploadFromBuffer(buffer: Buffer, dataSetName: string, options?: zosfiles.IUploadOptions): Promise { - return zosfiles.Upload.bufferToDataSet(this.getSession(), buffer, dataSetName, options); + return zosfiles.Upload.bufferToDataSet(this.getSession(), buffer, dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public putContents(inputFilePath: string, dataSetName: string, options?: zosfiles.IUploadOptions): Promise { - return zosfiles.Upload.pathToDataSet(this.getSession(), inputFilePath, dataSetName, options); + return zosfiles.Upload.pathToDataSet(this.getSession(), inputFilePath, dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public createDataSet( @@ -259,15 +283,23 @@ export namespace ZoweExplorerZosmf { dataSetName: string, options?: Partial ): Promise { - return zosfiles.Create.dataSet(this.getSession(), dataSetType, dataSetName, options); + return zosfiles.Create.dataSet(this.getSession(), dataSetType, dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public createDataSetMember(dataSetName: string, options?: zosfiles.IUploadOptions): Promise { - return zosfiles.Upload.bufferToDataSet(this.getSession(), Buffer.from(""), dataSetName, options); + return zosfiles.Upload.bufferToDataSet(this.getSession(), Buffer.from(""), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public allocateLikeDataSet(dataSetName: string, likeDataSetName: string): Promise { - return zosfiles.Create.dataSetLike(this.getSession(), dataSetName, likeDataSetName); + return zosfiles.Create.dataSetLike(this.getSession(), dataSetName, likeDataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + }); } public copyDataSetMember( @@ -282,7 +314,7 @@ export namespace ZoweExplorerZosmf { } else { newOptions = { ...options, - ...{ "from-dataset": { dsn: fromDataSetName, member: fromMemberName } }, + "from-dataset": { dsn: fromDataSetName, member: fromMemberName }, }; } } else { @@ -290,34 +322,59 @@ export namespace ZoweExplorerZosmf { // we will need to break the interface definition in the ZoweExplorerApi newOptions = { "from-dataset": { dsn: fromDataSetName, member: fromMemberName } }; } - return zosfiles.Copy.dataSet(this.getSession(), { dsn: toDataSetName, member: toMemberName }, newOptions); + return zosfiles.Copy.dataSet( + this.getSession(), + { dsn: toDataSetName, member: toMemberName }, + { + responseTimeout: this.profile?.profile?.responseTimeout, + ...newOptions, + } + ); } public renameDataSet(currentDataSetName: string, newDataSetName: string): Promise { - return zosfiles.Rename.dataSet(this.getSession(), currentDataSetName, newDataSetName); + return zosfiles.Rename.dataSet(this.getSession(), currentDataSetName, newDataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + }); } public renameDataSetMember(dataSetName: string, oldMemberName: string, newMemberName: string): Promise { - return zosfiles.Rename.dataSetMember(this.getSession(), dataSetName, oldMemberName, newMemberName); + return zosfiles.Rename.dataSetMember(this.getSession(), dataSetName, oldMemberName, newMemberName, { + responseTimeout: this.profile?.profile?.responseTimeout, + }); } public hMigrateDataSet(dataSetName: string): Promise { - return zosfiles.HMigrate.dataSet(this.getSession(), dataSetName); + return zosfiles.HMigrate.dataSet(this.getSession(), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + }); } public hRecallDataSet(dataSetName: string): Promise { - return zosfiles.HRecall.dataSet(this.getSession(), dataSetName); + return zosfiles.HRecall.dataSet(this.getSession(), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + }); } public deleteDataSet(dataSetName: string, options?: zosfiles.IDeleteDatasetOptions): Promise { - return zosfiles.Delete.dataSet(this.getSession(), dataSetName, options); + return zosfiles.Delete.dataSet(this.getSession(), dataSetName, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public dataSetsMatchingPattern(filter: string[], options?: zosfiles.IDsmListOptions): Promise { - return zosfiles.List.dataSetsMatchingPattern(this.getSession(), filter, options); + return zosfiles.List.dataSetsMatchingPattern(this.getSession(), filter, { + responseTimeout: this.profile?.profile?.responseTimeout, + ...options, + }); } public copyDataSet(fromDataSetName: string, toDataSetName: string, enq?: string, replace?: boolean): Promise { - return zosfiles.Copy.dataSet(this.getSession(), { dsn: toDataSetName }, { "from-dataset": { dsn: fromDataSetName }, enq, replace }); + return zosfiles.Copy.dataSet( + this.getSession(), + { dsn: toDataSetName }, + { "from-dataset": { dsn: fromDataSetName }, enq, replace, responseTimeout: this.profile?.profile?.responseTimeout } + ); } } diff --git a/packages/zowe-explorer/__tests__/__unit__/extending/ZoweExplorerZosmfApi.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/extending/ZoweExplorerZosmfApi.unit.test.ts index f206d9969..1f2b445f0 100644 --- a/packages/zowe-explorer/__tests__/__unit__/extending/ZoweExplorerZosmfApi.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/extending/ZoweExplorerZosmfApi.unit.test.ts @@ -30,7 +30,9 @@ describe("Zosmf API tests", () => { const api = new ZoweExplorerZosmf.MvsApi(); api.getSession = jest.fn(); - await api.copyDataSetMember({ dsn: "IBM.FROM", member: "IEFBR14" }, { dsn: "IBM.TO", member: "IEFBR15" }); + await api.copyDataSetMember({ dsn: "IBM.FROM", member: "IEFBR14" }, { dsn: "IBM.TO", member: "IEFBR15" }, { + responseTimeout: undefined, + } as any); }); it("should test that copy data set uses enq", async () => { @@ -46,7 +48,7 @@ describe("Zosmf API tests", () => { await api.copyDataSetMember( { dsn: "IBM.FROM", member: "IEFBR14" }, { dsn: "IBM.TO", member: "IEFBR15" }, - { enq: "SHR", "from-dataset": { dsn: "BROADCOM.FROM" } } + { enq: "SHR", "from-dataset": { dsn: "BROADCOM.FROM" }, responseTimeout: undefined } ); }); @@ -62,6 +64,7 @@ describe("Zosmf API tests", () => { api.getSession = jest.fn(); await api.copyDataSetMember({ dsn: "IBM.FROM", member: "IEFBR14" }, { dsn: "IBM.TO", member: "IEFBR15" }, { enq: "SHR", + responseTimeout: undefined, } as any); }); @@ -80,6 +83,7 @@ describe("Zosmf API tests", () => { await api.putContent("someLocalFile.txt", "/some/remote", { encoding: "285", + responseTimeout: undefined, }); }); diff --git a/packages/zowe-explorer/__tests__/__unit__/extending/__snapshots__/ZoweExplorerZosmfApi.unit.test.ts.snap b/packages/zowe-explorer/__tests__/__unit__/extending/__snapshots__/ZoweExplorerZosmfApi.unit.test.ts.snap index c3eed47e8..b8a072080 100644 --- a/packages/zowe-explorer/__tests__/__unit__/extending/__snapshots__/ZoweExplorerZosmfApi.unit.test.ts.snap +++ b/packages/zowe-explorer/__tests__/__unit__/extending/__snapshots__/ZoweExplorerZosmfApi.unit.test.ts.snap @@ -3,6 +3,7 @@ exports[`Zosmf API tests should test putContent method passes all options to Zowe api method 1`] = ` { "encoding": "285", + "responseTimeout": undefined, } `; @@ -12,6 +13,7 @@ exports[`Zosmf API tests should test that copy data set uses default options 1`] "dsn": "IBM.FROM", "member": "IEFBR14", }, + "responseTimeout": undefined, } `; @@ -21,6 +23,7 @@ exports[`Zosmf API tests should test that copy data set uses enq 1`] = ` "from-dataset": { "dsn": "BROADCOM.FROM", }, + "responseTimeout": undefined, } `; @@ -31,5 +34,6 @@ exports[`Zosmf API tests should test that copy data set uses enq only 1`] = ` "dsn": "IBM.FROM", "member": "IEFBR14", }, + "responseTimeout": undefined, } `; From ca2f5590ef511bd55c020bc1f0295a157be8214e Mon Sep 17 00:00:00 2001 From: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:55:19 -0500 Subject: [PATCH 08/14] run package Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- packages/zowe-explorer/l10n/bundle.l10n.json | 3 ++- packages/zowe-explorer/l10n/poeditor.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index 423bed79a..ef955fbf6 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -3,12 +3,13 @@ "Page Size:": "Page Size:", "Page": "Page", "No": "No", - "items": "items", "item": "item", + "s": "s", "selected": "selected", "Data Sets": "Data Sets", "Unix System Services (USS)": "Unix System Services (USS)", "Jobs": "Jobs", + "DataPanelContext has to be used within ": "DataPanelContext has to be used within ", "Refresh": "Refresh", "Search History": "Search History", "Favorites": "Favorites", diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index d76c582e7..c5ed995fc 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -414,12 +414,13 @@ "Page Size:": "", "Page": "", "No": "", - "items": "", "item": "", + "s": "", "selected": "", "Data Sets": "", "Unix System Services (USS)": "", "Jobs": "", + "DataPanelContext has to be used within ": "", "Refresh": "", "Search History": "", "Favorites": "", From 8ba33d6bd443a480436269bbd9647d54a77807a0 Mon Sep 17 00:00:00 2001 From: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:43:31 -0500 Subject: [PATCH 09/14] chore: remediation commit Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Third-Party DCO Remediation Commit for benjamin-t-santos <115251181+benjamin-t-santos@users.noreply.github.com> On behalf of benjamin-t-santos <115251181+benjamin-t-santos@users.noreply.github.com>, I, Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 8957a6429c4de5487a2d800616c92228b67a7493 Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Third-Party DCO Remediation Commit for SanthoshiBoyina1 <142206957+SanthoshiBoyina1@users.noreply.github.com> On behalf of SanthoshiBoyina1 <142206957+SanthoshiBoyina1@users.noreply.github.com>, I, Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 86a82f62d296a3b16270bb63885c8c76ffb70c41 Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> From 7c1a7ceddd732286f095d14c2f6ea7f48874eef1 Mon Sep 17 00:00:00 2001 From: benjamin-t-santos <115251181+benjamin-t-santos@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:57:29 -0400 Subject: [PATCH 10/14] DatasetFSProvider.readFile() makes at most one API call (#3279) * fix(ds): Call remoteLookupForResource when entry doesn't exist locally Signed-off-by: Trae Yelovich * readFile() uses one MVS API call Signed-off-by: Benjamin Santos * update unit tests, fix logic of calls Signed-off-by: Benjamin Santos * fix lint error Signed-off-by: Benjamin Santos * Update CHANGELOG.md Signed-off-by: Benjamin Santos --------- Signed-off-by: Trae Yelovich Signed-off-by: Benjamin Santos Signed-off-by: benjamin-t-santos <115251181+benjamin-t-santos@users.noreply.github.com> Co-authored-by: Trae Yelovich Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- .../dataset/DatasetFSProvider.unit.test.ts | 29 ++--- .../src/trees/dataset/DatasetFSProvider.ts | 113 +++++++++++------- 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts index 3bfacb4d3..54eb2acf0 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts @@ -298,7 +298,7 @@ describe("readFile", () => { throw FileSystemError.FileNotFound(uri as Uri); }); const lookupParentDir = jest.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory").mockReturnValueOnce(null); - const remoteLookupForResourceMock = jest.spyOn(DatasetFSProvider.instance, "remoteLookupForResource").mockResolvedValue(testEntries.pds); + const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValue(testEntries.pds); let err; try { @@ -311,7 +311,7 @@ describe("readFile", () => { expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.ps); _lookupAsFileMock.mockRestore(); lookupParentDir.mockRestore(); - remoteLookupForResourceMock.mockRestore(); + fetchDatasetAtUriMock.mockRestore(); }); it("throws an error if the entry does not exist and the error is not FileNotFound", async () => { @@ -339,7 +339,7 @@ describe("readFile", () => { profile: testProfile, path: "/USER.DATA.PS", }); - const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockImplementation(); + const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValueOnce(new DsEntry("USER.DATA.PS")); await DatasetFSProvider.instance.readFile(testUris.ps); expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.ps); @@ -348,28 +348,26 @@ describe("readFile", () => { _getInfoFromUriMock.mockRestore(); }); - it("calls remoteLookupForResource if entry does not exist locally", async () => { + it("calls fetchDatasetAtUri if entry does not exist locally", async () => { const _lookupAsFileMock = jest .spyOn(DatasetFSProvider.instance as any, "_lookupAsFile") .mockImplementationOnce(() => { throw FileSystemError.FileNotFound(testUris.pdsMember); }) .mockReturnValue(testEntries.pdsMember); - - const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockImplementation(); + const fetchDatasetAtUriMock = jest + .spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri") + .mockResolvedValueOnce(new DsEntry("USER.DATA.PDS(MEMBER)")); const _getInfoFromUriMock = jest.spyOn(DatasetFSProvider.instance as any, "_getInfoFromUri").mockReturnValueOnce({ profile: testProfile, path: "/USER.DATA.PS", }); - const remoteLookupForResourceMock = jest - .spyOn(DatasetFSProvider.instance, "remoteLookupForResource") - .mockResolvedValue(testEntries.pdsMember); await DatasetFSProvider.instance.readFile(testUris.pdsMember); expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember); - expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember); expect(fetchDatasetAtUriMock).toHaveBeenCalledWith(testUris.pdsMember, { isConflict: false }); _getInfoFromUriMock.mockRestore(); + fetchDatasetAtUriMock.mockRestore(); }); it("throws error if parent exists and file cannot be found", async () => { @@ -380,17 +378,14 @@ describe("readFile", () => { profile: testProfile, path: "/USER.DATA.PS", }); - const remoteLookupForResourceMock = jest - .spyOn(DatasetFSProvider.instance, "remoteLookupForResource") - .mockReset() - .mockResolvedValueOnce(null as any); - + const fetchDatasetAtUriMock = jest.spyOn(DatasetFSProvider.instance, "fetchDatasetAtUri").mockResolvedValueOnce(null); await expect(DatasetFSProvider.instance.readFile(testUris.pdsMember)).rejects.toThrow(); expect(_lookupAsFileMock).toHaveBeenCalledWith(testUris.pdsMember); - expect(remoteLookupForResourceMock).toHaveBeenCalledWith(testUris.pdsMember); + expect(fetchDatasetAtUriMock).toHaveBeenCalledWith(testUris.pdsMember, { isConflict: false }); + _getInfoFromUriMock.mockRestore(); _lookupAsFileMock.mockRestore(); - remoteLookupForResourceMock.mockRestore(); + fetchDatasetAtUriMock.mockRestore(); }); it("returns the data for an entry", async () => { diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index a9c3849bf..f42617f2a 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -339,40 +339,62 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem * @param uri The URI pointing to a valid file to fetch from the remote system * @param editor (optional) An editor instance to reload if the URI is already open */ - public async fetchDatasetAtUri(uri: vscode.Uri, options?: { editor?: vscode.TextEditor | null; isConflict?: boolean }): Promise { + public async fetchDatasetAtUri( + uri: vscode.Uri, + options?: { editor?: vscode.TextEditor | null; isConflict?: boolean } + ): Promise { ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri called with ${uri.toString()}`); - const file = this._lookupAsFile(uri) as DsEntry; - // we need to fetch the contents from the mainframe since the file hasn't been accessed yet + let dsEntry = this._lookupAsFile(uri, { silent: true }) as DsEntry | undefined; const bufBuilder = new BufferBuilder(); - const metadata = file.metadata ?? this._getInfoFromUri(uri); - const profileEncoding = file.encoding ? null : file.metadata.profile.profile?.encoding; - const resp = await ZoweExplorerApiRegister.getMvsApi(metadata.profile).getContents(metadata.dsName, { - binary: file.encoding?.kind === "binary", - encoding: file.encoding?.kind === "other" ? file.encoding.codepage : profileEncoding, - responseTimeout: metadata.profile.profile?.responseTimeout, - returnEtag: true, - stream: bufBuilder, - }); - const data: Uint8Array = bufBuilder.read() ?? new Uint8Array(); - - if (options?.isConflict) { - file.conflictData = { - contents: data, - etag: resp.apiResponse.etag, - size: data.byteLength, - }; - } else { - file.data = data; - file.etag = resp.apiResponse.etag; - file.size = file.data.byteLength; - file.mtime = Date.now(); - } + const metadata = dsEntry?.metadata ?? this._getInfoFromUri(uri); + const profileEncoding = dsEntry?.encoding ? null : dsEntry?.metadata.profile.profile?.encoding; + try { + const resp = await ZoweExplorerApiRegister.getMvsApi(metadata.profile).getContents(metadata.dsName, { + binary: dsEntry?.encoding?.kind === "binary", + encoding: dsEntry?.encoding?.kind === "other" ? dsEntry?.encoding.codepage : profileEncoding, + responseTimeout: metadata.profile.profile?.responseTimeout, + returnEtag: true, + stream: bufBuilder, + }); + const data: Uint8Array = bufBuilder.read() ?? new Uint8Array(); + //if an entry does not exist for the dataset, create it + if (!dsEntry) { + const uriInfo = FsAbstractUtils.getInfoForUri(uri, Profiles.getInstance()); + const uriPath = uri.path.substring(uriInfo.slashAfterProfilePos + 1).split("/"); + const pdsMember = uriPath.length === 2; + this.createDirectory(uri.with({ path: path.posix.join(uri.path, "..") })); + const parentDir = this._lookupParentDirectory(uri); + const dsname = uriPath[Number(pdsMember)]; + const ds = new DsEntry(dsname, pdsMember); + ds.metadata = new DsEntryMetadata({ path: path.posix.join(parentDir.metadata.path, dsname), profile: parentDir.metadata.profile }); + parentDir.entries.set(dsname, ds); + dsEntry = parentDir.entries.get(dsname) as DsEntry; + } + //update entry's contents, attributes + if (options?.isConflict) { + dsEntry.conflictData = { + contents: data, + etag: resp.apiResponse.etag, + size: data.byteLength, + }; + } else { + dsEntry.data = data; + dsEntry.etag = resp.apiResponse.etag; + dsEntry.size = dsEntry.data.byteLength; + dsEntry.mtime = Date.now(); + } - ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri fired a change event for ${uri.toString()}`); - this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); + ZoweLogger.trace(`[DatasetFSProvider] fetchDatasetAtUri fired a change event for ${uri.toString()}`); + this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); - if (options?.editor) { - await this._updateResourceInEditor(uri); + if (options?.editor) { + await this._updateResourceInEditor(uri); + } + return dsEntry; + } catch (error) { + //Response will error if the file is not found + //Callers of fetchDatasetAtUri() do not expect it to throw an error + return null; } } @@ -383,41 +405,40 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem */ public async readFile(uri: vscode.Uri): Promise { let ds: DsEntry | DirEntry; + const urlQuery = new URLSearchParams(uri.query); + const isConflict = urlQuery.has("conflict"); try { ds = this._lookupAsFile(uri) as DsEntry; } catch (err) { if (!(err instanceof vscode.FileSystemError) || err.code !== "FileNotFound") { throw err; } - - // do a remote lookup if the entry does not yet exist locally - ds = await this.remoteLookupForResource(uri); } - if (ds == null) { - throw vscode.FileSystemError.FileNotFound(uri); + // we need to fetch the contents from the mainframe if the file hasn't been accessed yet + if (!ds || (!ds.wasAccessed && !urlQuery.has("inDiff")) || isConflict) { + //try and fetch its contents from remote + ds = (await this.fetchDatasetAtUri(uri, { isConflict })) as DsEntry; + if (!isConflict && ds) { + ds.wasAccessed = true; + } } + if (FsAbstractUtils.isDirectoryEntry(ds)) { throw vscode.FileSystemError.FileIsADirectory(uri); } + //not found on remote, throw error + if (ds == null) { + throw vscode.FileSystemError.FileNotFound(uri); + } + const profInfo = this._getInfoFromUri(uri); if (profInfo.profile == null) { throw vscode.FileSystemError.FileNotFound(vscode.l10n.t("Profile does not exist for this file.")); } - const urlQuery = new URLSearchParams(uri.query); - const isConflict = urlQuery.has("conflict"); - - // we need to fetch the contents from the mainframe if the file hasn't been accessed yet - if ((!ds.wasAccessed && !urlQuery.has("inDiff")) || isConflict) { - await this.fetchDatasetAtUri(uri, { isConflict }); - if (!isConflict) { - ds.wasAccessed = true; - } - } - return isConflict ? ds.conflictData.contents : ds.data; } From 98a4d5130549cf9d775a17e677ab99b932aa990b Mon Sep 17 00:00:00 2001 From: Peter Haumer <4391934+phaumer@users.noreply.github.com> Date: Tue, 12 Nov 2024 08:07:11 -0800 Subject: [PATCH 11/14] Change default credentials manager check and not-found dialog (#3297) * updates to webpack and package.json for dev mode Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> * Remove endless startup loop when default CM cannot be loaded Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Initial set of test updates Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Merge branch 'main' into change-credentials-manager-check Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Convenience launch to only run currently open test Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Fixed ProfileUtils tests Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Clean up package files Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Updated changelog Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Reworded info message Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Update CHANGELOG.md Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> * Added detail to info message Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> * Updated resource files Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> --------- Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> Signed-off-by: Peter Haumer <4391934+phaumer@users.noreply.github.com> Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Co-authored-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> Co-authored-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- .vscode/launch.json | 9 ++ packages/zowe-explorer/CHANGELOG.md | 2 + .../__unit__/utils/ProfilesUtils.unit.test.ts | 49 +++++------ packages/zowe-explorer/l10n/bundle.l10n.json | 7 +- packages/zowe-explorer/l10n/poeditor.json | 7 +- .../zowe-explorer/src/utils/ProfilesUtils.ts | 88 ++++++++++++------- 6 files changed, 91 insertions(+), 71 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 098a5ca92..772a2e558 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,6 +37,15 @@ "console": "integratedTerminal", "sourceMaps": true }, + { + "type": "node", + "name": "Current Unit Tests (Jest)", + "request": "launch", + "runtimeArgs": ["--inspect-brk", "${workspaceFolder}/node_modules/jest/bin/jest", "-i", "${fileBasenameNoExtension}"], + "cwd": "${workspaceFolder}/packages/zowe-explorer", + "console": "integratedTerminal", + "sourceMaps": true + }, { "type": "node", "name": "API Unit Tests (Jest)", diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 2ef3a25a5..c3b9d827e 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -15,6 +15,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed an issue where calling `vscode.workspace.fs.readFile` with a PDS member URI would throw an error when the PDS already existed as a filesystem entry. [#3267](https://github.com/zowe/zowe-explorer-vscode/issues/3267) - Fixed issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280) - Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278) +- Fixed an issue to review inconsistent capitalization across translation strings. [#2935](https://github.com/zowe/zowe-explorer-vscode/issues/2935) +- Updated the test for the default credential manager for better compatibility with Cloud-based platforms such as Eclipse Che and Red Hat OpenShift Dev Spaces. [#3297](https://github.com/zowe/zowe-explorer-vscode/pull/3297) ## `3.0.2` 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 cf99a6b09..dfaa0e046 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -65,6 +65,7 @@ describe("ProfilesUtils unit tests", () => { Object.defineProperty(ZoweLogger, "info", { value: jest.fn(), configurable: true }); Object.defineProperty(SettingsConfig, "getDirectValue", { value: newMocks.mockGetDirectValue, configurable: true }); Object.defineProperty(ProfilesUtils, "PROFILE_SECURITY", { value: Constants.ZOWE_CLI_SCM, configurable: true }); + Object.defineProperty(ProfilesUtils, "checkDefaultCredentialManager", { value: jest.fn(), configurable: true }); return newMocks; } @@ -460,6 +461,7 @@ describe("ProfilesUtils unit tests", () => { describe("initializeZoweFolder", () => { it("should create directories and files that do not exist", async () => { const blockMocks = createBlockMocks(); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); blockMocks.mockGetDirectValue.mockReturnValue(true); blockMocks.mockExistsSync.mockReturnValue(false); jest.spyOn(fs, "readFileSync").mockReturnValue(Buffer.from(JSON.stringify({ overrides: { credentialManager: "@zowe/cli" } }), "utf-8")); @@ -472,6 +474,7 @@ describe("ProfilesUtils unit tests", () => { it("should skip creating directories and files that already exist", async () => { const blockMocks = createBlockMocks(); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); jest.spyOn(ProfilesUtils, "getCredentialManagerOverride").mockReturnValue("@zowe/cli"); blockMocks.mockGetDirectValue.mockReturnValue("@zowe/cli"); blockMocks.mockExistsSync.mockReturnValue(true); @@ -569,7 +572,7 @@ describe("ProfilesUtils unit tests", () => { it("should handle Imperative error thrown on read config from disk", async () => { const testError = new imperative.ImperativeError({ msg: "readConfigFromDisk failed" }); - const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockReturnValueOnce(); + const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockResolvedValueOnce(); const readConfigFromDiskSpy = jest.spyOn(ProfilesUtils, "readConfigFromDisk").mockRejectedValueOnce(testError); await ProfilesUtils.initializeZoweProfiles((msg) => ZoweExplorerExtender.showZoweConfigError(msg)); expect(initZoweFolderSpy).toHaveBeenCalledTimes(1); @@ -579,7 +582,7 @@ describe("ProfilesUtils unit tests", () => { it("should handle JSON parse error thrown on read config from disk", async () => { const testError = new Error("readConfigFromDisk failed"); - const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockReturnValueOnce(); + const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockResolvedValueOnce(); const readConfigFromDiskSpy = jest.spyOn(ProfilesUtils, "readConfigFromDisk").mockRejectedValueOnce(testError); const showZoweConfigErrorSpy = jest.spyOn(ZoweExplorerExtender, "showZoweConfigError").mockReturnValueOnce(); await ProfilesUtils.initializeZoweProfiles((msg) => ZoweExplorerExtender.showZoweConfigError(msg)); @@ -652,6 +655,7 @@ describe("ProfilesUtils unit tests", () => { it("should update the credential manager setting if secure value is true", () => { jest.spyOn(SettingsConfig, "isConfigSettingSetByUser").mockReturnValue(false); jest.spyOn(SettingsConfig, "getDirectValue").mockReturnValueOnce(true); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); const loggerInfoSpy = jest.spyOn(ZoweLogger, "info"); const recordCredMgrInConfigSpy = jest.spyOn(imperative.CredentialManagerOverride, "recordCredMgrInConfig"); ProfilesUtils.updateCredentialManagerSetting(); @@ -682,7 +686,8 @@ describe("ProfilesUtils unit tests", () => { let getCredentialManagerMapSpy: jest.SpyInstance; let setupCustomCredentialManagerSpy: jest.SpyInstance; let readProfilesFromDiskSpy: jest.SpyInstance; - let promptAndDisableCredentialManagementSpy: jest.SpyInstance; + let disableCredentialManagementSpy: jest.SpyInstance; + let checkDefaultCredentialManagerSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); @@ -695,7 +700,8 @@ describe("ProfilesUtils unit tests", () => { getCredentialManagerMapSpy = jest.spyOn(ProfilesUtils, "getCredentialManagerMap"); setupCustomCredentialManagerSpy = jest.spyOn(ProfilesUtils, "setupCustomCredentialManager"); readProfilesFromDiskSpy = jest.spyOn(imperative.ProfileInfo.prototype, "readProfilesFromDisk"); - promptAndDisableCredentialManagementSpy = jest.spyOn(ProfilesUtils, "promptAndDisableCredentialManagement"); + disableCredentialManagementSpy = jest.spyOn(ProfilesUtils, "disableCredentialManagement"); + checkDefaultCredentialManagerSpy = jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager"); }); it("should retrieve the custom credential manager", async () => { @@ -723,10 +729,11 @@ describe("ProfilesUtils unit tests", () => { await expect(ProfilesUtils.getProfileInfo()).resolves.toEqual({}); }); - it("should retrieve the default credential manager and prompt to disable credential management if environment not supported", async () => { + it("should throw exception of readProfilesFromDiskSpy fails", async () => { const expectedErrMsg = // eslint-disable-next-line max-len "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment."; + checkDefaultCredentialManagerSpy.mockReturnValue(false); getDirectValueSpy.mockReturnValueOnce(false); getCredentialManagerOverrideSpy.mockReturnValue("@zowe/cli"); isVSCodeCredentialPluginInstalledSpy.mockReturnValueOnce(false); @@ -744,11 +751,11 @@ describe("ProfilesUtils unit tests", () => { throw err; }); await expect(ProfilesUtils.getProfileInfo()).rejects.toThrow(expectedErrMsg); - expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(1); }); it("should ignore error if it is not an instance of ProfInfoErr", async () => { const expectedErrorMsg = "Another error unrelated to credential management"; + checkDefaultCredentialManagerSpy.mockReturnValue(true); getDirectValueSpy.mockReturnValueOnce(false); getCredentialManagerOverrideSpy.mockReturnValue("@zowe/cli"); isVSCodeCredentialPluginInstalledSpy.mockReturnValueOnce(false); @@ -759,7 +766,7 @@ describe("ProfilesUtils unit tests", () => { throw new Error(expectedErrorMsg); }); await expect(ProfilesUtils.getProfileInfo()).resolves.not.toThrow(); - expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(0); + expect(disableCredentialManagementSpy).toHaveBeenCalledTimes(0); }); }); @@ -963,10 +970,11 @@ describe("ProfilesUtils unit tests", () => { }); }); - describe("promptAndDisableCredentialManagement", () => { + describe("disableCredentialManagement", () => { let setDirectValueSpy: jest.SpyInstance; let warningMessageSpy: jest.SpyInstance; let executeCommandSpy: jest.SpyInstance; + let getDirectValueSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); @@ -975,33 +983,16 @@ describe("ProfilesUtils unit tests", () => { setDirectValueSpy = jest.spyOn(SettingsConfig, "setDirectValue"); warningMessageSpy = jest.spyOn(Gui, "warningMessage"); executeCommandSpy = jest.spyOn(vscode.commands, "executeCommand"); + getDirectValueSpy = jest.spyOn(SettingsConfig, "getDirectValue"); }); - it("should prompt whether to disable credential management, and disable globally if 'Yes, globally' selected", async () => { + it("should show warning that credential management was disabled", async () => { warningMessageSpy.mockResolvedValue("Yes, globally"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).resolves.not.toThrow(); + getDirectValueSpy.mockReturnValueOnce(true); + await expect(ProfilesUtils.disableCredentialManagement()).resolves.not.toThrow(); expect(setDirectValueSpy).toHaveBeenCalledWith(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); expect(executeCommandSpy).toHaveBeenCalledWith("workbench.action.reloadWindow"); }); - - it("should prompt whether to disable credential management, and disable on workspace if 'Only for this workspace' selected", async () => { - warningMessageSpy.mockResolvedValue("Only for this workspace"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).resolves.not.toThrow(); - expect(setDirectValueSpy).toHaveBeenCalledWith( - Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, - false, - vscode.ConfigurationTarget.Workspace - ); - expect(executeCommandSpy).toHaveBeenCalledWith("workbench.action.reloadWindow"); - }); - - it("should prompt whether to disable credential management, and throw error if 'No'", async () => { - warningMessageSpy.mockResolvedValue("No"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).rejects.toThrow( - // eslint-disable-next-line max-len - "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment." - ); - }); }); describe("v1ProfileOptions", () => { diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index ef955fbf6..c56c5984e 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -57,10 +57,9 @@ "Credential manager display name" ] }, - "Yes, globally": "Yes, globally", - "Only for this workspace": "Only for this workspace", - "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.": "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.", - "Do you wish to disable credential management? (VS Code window reload will be triggered)": "Do you wish to disable credential management? (VS Code window reload will be triggered)", + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.": "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.", + "Reload window": "Reload window", + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.": "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.", "No custom credential managers found, using the default instead.": "No custom credential managers found, using the default instead.", "Custom credential manager {0} found/Credential manager display name": { "message": "Custom credential manager {0} found", diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index c5ed995fc..654450a84 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -463,10 +463,9 @@ "Zowe explorer profiles are being set as secured.": "", "Custom credential manager failed to activate": "", "Custom credential manager {0} found, attempting to activate.": "", - "Yes, globally": "", - "Only for this workspace": "", - "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.": "", - "Do you wish to disable credential management? (VS Code window reload will be triggered)": "", + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.": "", + "Reload window": "", + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.": "", "No custom credential managers found, using the default instead.": "", "Custom credential manager {0} found": "", "Do you wish to use this credential manager instead?": "", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index a4569a3c0..6dcef1d18 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -28,7 +28,7 @@ export enum ProfilesConvertStatus { export class ProfilesUtils { public static PROFILE_SECURITY: string | boolean = Constants.ZOWE_CLI_SCM; - private static noConfigDialogShown: boolean = false; + private static noConfigDialogShown = false; /** * Check if the credential manager's vsix is installed for use @@ -82,18 +82,22 @@ export class ProfilesUtils { */ public static updateCredentialManagerSetting(credentialManager?: string | false): void { ZoweLogger.trace("ProfilesUtils.updateCredentialManagerSetting called."); + const currentProfileSecurity = this.PROFILE_SECURITY; const settingEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, true); + const defaultCredentialManagerFound = this.checkDefaultCredentialManager(); if (settingEnabled && credentialManager) { this.PROFILE_SECURITY = credentialManager; return; - } else if (!settingEnabled) { + } else if (!settingEnabled || !defaultCredentialManagerFound) { this.PROFILE_SECURITY = false; ZoweLogger.info(vscode.l10n.t(`Zowe explorer profiles are being set as unsecured.`)); } else { this.PROFILE_SECURITY = Constants.ZOWE_CLI_SCM; ZoweLogger.info(vscode.l10n.t(`Zowe explorer profiles are being set as secured.`)); } - imperative.CredentialManagerOverride.recordCredMgrInConfig(this.PROFILE_SECURITY); + if (currentProfileSecurity !== this.PROFILE_SECURITY) { + imperative.CredentialManagerOverride.recordCredMgrInConfig(this.PROFILE_SECURITY); + } } /** @@ -149,35 +153,38 @@ export class ProfilesUtils { * Prompt whether to disable credential management setting * * This will disable credential management on all settings - * scopes since order presedence can be hard to predict based on the user's setup + * scopes since order precedence can be hard to predict based on the user's setup */ - public static async promptAndDisableCredentialManagement(): Promise { - ZoweLogger.trace("ProfilesUtils.promptAndDisableCredentialManagement called."); - const noButton = vscode.l10n.t("No"); - const yesGloballyButton = vscode.l10n.t("Yes, globally"); - const yesWorkspaceButton = vscode.l10n.t("Only for this workspace"); - const response = await Gui.warningMessage( - vscode.l10n.t("Zowe Explorer failed to activate since the default credential manager is not supported in your environment."), - { - items: [noButton, yesGloballyButton, yesWorkspaceButton], - vsCodeOpts: { - modal: true, - detail: vscode.l10n.t("Do you wish to disable credential management? (VS Code window reload will be triggered)"), - }, - } - ); - if (response === yesGloballyButton || response === yesWorkspaceButton) { - const scope = response === yesGloballyButton ? vscode.ConfigurationTarget.Global : vscode.ConfigurationTarget.Workspace; - await SettingsConfig.setDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, scope); - await vscode.commands.executeCommand("workbench.action.reloadWindow"); - } else { - throw new imperative.ImperativeError({ - msg: vscode.l10n.t( - // eslint-disable-next-line max-len - "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment." + public static async disableCredentialManagement(): Promise { + ZoweLogger.trace("ProfilesUtils.disableCredentialManagement called."); + const settingEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, true); + if (settingEnabled) { + this.PROFILE_SECURITY = false; + await SettingsConfig.setDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); + await Gui.infoMessage( + vscode.l10n.t( + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager." ), - }); + { + items: [vscode.l10n.t("Reload window")], + } + ); + await vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + } + + public static checkDefaultCredentialManager(): boolean { + try { + ProfilesCache.requireKeyring(); + } catch (_error) { + ZoweLogger.info( + vscode.l10n.t( + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions." + ) + ); + return false; } + return true; } /** @@ -204,7 +211,7 @@ export class ProfilesUtils { return profileInfo; } catch (err) { if (err instanceof imperative.ProfInfoErr && err.errorCode === imperative.ProfInfoErr.LOAD_CRED_MGR_FAILED) { - await ProfilesUtils.promptAndDisableCredentialManagement(); + await ProfilesUtils.disableCredentialManagement(); } if (err instanceof Error) { ZoweLogger.error(err.message); @@ -306,8 +313,8 @@ export class ProfilesUtils { */ public static async getProfileInfo(): Promise { ZoweLogger.trace("ProfilesUtils.getProfileInfo called."); - const hasSecureCredentialManagerEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED); + const hasSecureCredentialManagerEnabled: boolean = this.checkDefaultCredentialManager(); if (hasSecureCredentialManagerEnabled) { const shouldCheckForCustomCredentialManagers = SettingsConfig.getDirectValue( Constants.SETTINGS_CHECK_FOR_CUSTOM_CREDENTIAL_MANAGERS @@ -327,9 +334,17 @@ export class ProfilesUtils { if (credentialManagerMap && isVSCodeCredentialPluginInstalled) { return this.setupCustomCredentialManager(credentialManagerMap); } + return this.setupDefaultCredentialManager(); } - return this.setupDefaultCredentialManager(); + const profileInfo = new imperative.ProfileInfo("zowe", {}); + const workspacePath = ZoweVsCodeExtension.workspaceRoot?.uri.fsPath; + // Trigger initialize() function of credential manager to throw an error early if failed to load + await profileInfo.readProfilesFromDisk({ + homeDir: FileManagement.getZoweDir(), + projectDir: workspacePath ? FileManagement.getFullPath(workspacePath) : undefined, + }); + return profileInfo; } public static async readConfigFromDisk(warnForMissingSchema?: boolean): Promise { @@ -466,7 +481,7 @@ export class ProfilesUtils { } } - public static initializeZoweFolder(): void { + public static async initializeZoweFolder(): Promise { ZoweLogger.trace("ProfilesUtils.initializeZoweFolder called."); // Ensure that ~/.zowe folder exists const zoweDir = FileManagement.getZoweDir(); @@ -477,6 +492,11 @@ export class ProfilesUtils { if (!fs.existsSync(settingsPath)) { fs.mkdirSync(settingsPath); } + + if (!this.checkDefaultCredentialManager()) { + await this.disableCredentialManagement(); + } + ProfilesUtils.writeOverridesFile(); // set global variable of security value to existing override // this will later get reverted to default in getProfilesInfo.ts if user chooses to @@ -553,7 +573,7 @@ export class ProfilesUtils { public static async initializeZoweProfiles(errorCallback: (msg: string) => unknown): Promise { ZoweLogger.trace("ProfilesUtils.initializeZoweProfiles called."); try { - ProfilesUtils.initializeZoweFolder(); + await ProfilesUtils.initializeZoweFolder(); } catch (err) { ZoweLogger.error(err); Gui.errorMessage( From bb88cfdf5cc692c08334fd30a6dd1373e7cb9daf Mon Sep 17 00:00:00 2001 From: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:03:17 -0500 Subject: [PATCH 12/14] prepare lerna and changelogs Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- lerna.json | 2 +- packages/eslint-plugin-zowe-explorer/CHANGELOG.md | 4 ---- packages/zowe-explorer-api/CHANGELOG.md | 2 -- packages/zowe-explorer-ftp-extension/CHANGELOG.md | 8 -------- packages/zowe-explorer/CHANGELOG.md | 2 -- 5 files changed, 1 insertion(+), 17 deletions(-) diff --git a/lerna.json b/lerna.json index afdeb9c13..95da4fafc 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "3.0.3-SNAPSHOT", + "version": "3.0.3", "command": { "version": { "forcePublish": true, diff --git a/packages/eslint-plugin-zowe-explorer/CHANGELOG.md b/packages/eslint-plugin-zowe-explorer/CHANGELOG.md index fd9c838c6..493274fee 100644 --- a/packages/eslint-plugin-zowe-explorer/CHANGELOG.md +++ b/packages/eslint-plugin-zowe-explorer/CHANGELOG.md @@ -2,10 +2,6 @@ All notable changes to the "eslint-plugin-zowe-explorer" package will be documen ## TBD Release -### New features and enhancements - -### Bug fixes - ## `3.0.2` ## `3.0.1` diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index 61d5cb54a..d5907a0bd 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -4,8 +4,6 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ## TBD Release -### New features and enhancements - ### Bug fixes - Fixed an issue where the `responseTimeout` profile property was ignored for z/OSMF MVS and USS API calls. [#3225](https://github.com/zowe/zowe-explorer-vscode/issues/3225) diff --git a/packages/zowe-explorer-ftp-extension/CHANGELOG.md b/packages/zowe-explorer-ftp-extension/CHANGELOG.md index 43628f808..de62e151d 100644 --- a/packages/zowe-explorer-ftp-extension/CHANGELOG.md +++ b/packages/zowe-explorer-ftp-extension/CHANGELOG.md @@ -2,16 +2,8 @@ All notable changes to the "zowe-explorer-ftp-extension" extension will be docum ## TBD Release -### New features and enhancements - -### Bug fixes - ## `3.0.2` -### New features and enhancements - -### Bug fixes - ## `3.0.1` ### Bug fixes diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index c3b9d827e..f59a9a79a 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -4,8 +4,6 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ## TBD Release -### New features and enhancements - ### Bug fixes - `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) From 246ca93316feecb67dd76268baa9d2ae8fdf5f72 Mon Sep 17 00:00:00 2001 From: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:55:51 -0500 Subject: [PATCH 13/14] review: address PR feedback Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> --- packages/zowe-explorer/CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 29d60b6bf..5aec7fb6c 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -6,15 +6,15 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### Bug fixes -- `DatasetFSProvider.stat()` will now throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) +- Updated the `DatasetFSProvider.stat()` function to throw a `FileNotFound` error for extenders trying to fetch an MVS resource that does not exist. [#3252](https://github.com/zowe/zowe-explorer-vscode/issues/3252) - Fixed an issue where renaming or deleting a USS file or data set did not update the opened editor. [#3260](https://github.com/zowe/zowe-explorer-vscode/issues/3260) - Fixed an issue during initialization where a broken team configuration file caused the "Show Config" action in the error dialog to stop working. [#3273](https://github.com/zowe/zowe-explorer-vscode/issues/3273) -- Fixed issue where switching the authentication methods would cause `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) +- Fixed an issue where switching authentication methods would cause a `Cannot read properties of undefined` error. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142) - Fixed an issue where calling `vscode.workspace.fs.readFile` with a PDS member URI would throw an error when the PDS already existed as a filesystem entry. [#3267](https://github.com/zowe/zowe-explorer-vscode/issues/3267) -- Fixed issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280) -- Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278) -- Fixed an issue to review inconsistent capitalization across translation strings. [#2935](https://github.com/zowe/zowe-explorer-vscode/issues/2935) -- Updated the test for the default credential manager for better compatibility with Cloud-based platforms such as Eclipse Che and Red Hat OpenShift Dev Spaces. [#3297](https://github.com/zowe/zowe-explorer-vscode/pull/3297) +- Fixed an issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280) +- Reduced the number of MVS API calls performed by the function `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278) +- Fixed inconsistent capitalization across translation strings. [#2935](https://github.com/zowe/zowe-explorer-vscode/issues/2935) +- Updated the check for default credential managers for better compatibility with Cloud-based platforms such as Eclipse Che and Red Hat OpenShift Dev Spaces. [#3297](https://github.com/zowe/zowe-explorer-vscode/pull/3297) - Fixed issue where users were not prompted to enter credentials if a 401 error was encountered when opening files, data sets or spools in the editor. [#3197](https://github.com/zowe/zowe-explorer-vscode/issues/3197) - Fixed issue where profile credential updates or token changes were not reflected within the filesystem. [#3289](https://github.com/zowe/zowe-explorer-vscode/issues/3289) - Fixed issue to update the success message when changing authentication from token to basic through the 'Change Authentication' option. From 80bbfc7e858fcebd56201b3cbc912b52eafe2792 Mon Sep 17 00:00:00 2001 From: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:21:36 -0500 Subject: [PATCH 14/14] audit: fix audit issue (thanks @awharn) Signed-off-by: Fernando Rijo Cedeno <37381190+zFernand0@users.noreply.github.com> --- pnpm-lock.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e99147adb..8b03b94af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3588,7 +3588,7 @@ packages: chalk: 4.1.2 cli-table3: 0.6.5 comment-json: 4.2.5 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 dataobject-parser: 1.2.25 deepmerge: 4.3.1 diff: 5.2.0 @@ -4932,8 +4932,8 @@ packages: - encoding dev: true - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} dependencies: path-key: 3.1.1 @@ -5832,7 +5832,7 @@ packages: '@humanwhocodes/config-array': 0.5.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 enquirer: 2.4.1 @@ -5886,7 +5886,7 @@ packages: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -5988,7 +5988,7 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -6003,7 +6003,7 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 8.0.1 human-signals: 5.0.0 is-stream: 3.0.0 @@ -6381,7 +6381,7 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 dev: true @@ -6390,7 +6390,7 @@ packages: engines: {node: '>=14'} requiresBuild: true dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 /fork-ts-checker-webpack-plugin@9.0.2(typescript@5.4.5)(webpack@5.94.0): @@ -12189,7 +12189,7 @@ packages: '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.94.0) colorette: 2.0.20 commander: 10.0.1 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 envinfo: 7.13.0 fastest-levenshtein: 1.0.16 import-local: 3.1.0