Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: Lock profiles during async operations (phase 1: filesystem) #3370

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
acf538c
move DeferredPromise to API
traeok Dec 18, 2024
aa5251c
wip: AuthHandler
traeok Dec 18, 2024
43eba14
refactor: 'promptForAuthOnError' -> 'lockProfileOnAuthError'
traeok Dec 18, 2024
cea4f85
wip: reload workspaces once reauthenticated
traeok Dec 19, 2024
1216b03
wip: lock in other FS providers; fix update credentials logic
traeok Dec 19, 2024
b9ea559
wip(tests): resolve multiple failing tests
traeok Dec 19, 2024
aefbbb4
tests: resolve failing ProfilesUtils cases
traeok Dec 19, 2024
0dce568
wip: rename AuthUtils method, update FS providers
traeok Dec 20, 2024
167977a
refactor: Mutex class & use in AuthHandler; TSDoc
traeok Dec 20, 2024
18d485a
add reloadActiveEditorForProfile and related logic
traeok Dec 20, 2024
665c4a4
expose Mutex, fix failing tests
traeok Dec 20, 2024
8765304
chore: fix unused warnings in test code
traeok Dec 23, 2024
c597755
chore: fix lint and formatting errors
traeok Dec 23, 2024
929853b
tests: DeferredPromise, Mutex
traeok Dec 23, 2024
3c8bf4a
chore: rename test file in API to match source file
traeok Dec 23, 2024
49aee1d
remove unused import
traeok Dec 23, 2024
0507c3b
refactor: use async-mutex instead of homemade mutex
traeok Dec 23, 2024
d83c9ac
refactor: move fs helper fns, isLocked -> isProfileLocked
traeok Dec 23, 2024
5ca4607
wip: AuthHandler tests
traeok Dec 23, 2024
410755c
wip: patch coverage for AuthHandler, DeferredPromise
traeok Dec 23, 2024
00fe312
refactor: lockedProfiles -> profileLocks, patch cov in ProfilesUtils
traeok Dec 23, 2024
6dc0e01
tests: FileManagement functions
traeok Dec 23, 2024
ebc1f79
remove unused var 'credsEntered'
traeok Dec 23, 2024
5f35e85
refactor: save decorator for phase 2 (API changes)
traeok Dec 23, 2024
b1a6984
tests: AuthHandler SSO login during auth prompt
traeok Dec 23, 2024
f443bc6
add test for basic creds, move logic for updating trees
traeok Dec 23, 2024
d986e47
fix lint errors
traeok Dec 23, 2024
9264fde
tests: AuthHandler.updateTreeProvidersWithProfile
traeok Dec 24, 2024
4dcad76
chore: add changelogs
traeok Dec 24, 2024
3dc39b1
fix: refresh resources if updated in 'Manage Profile' option
traeok Dec 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ rules:
- warn
- "ignore": [-2, -1, 0, 1, 2, 4]
no-multiple-empty-lines: warn
no-return-await: warn
no-return-await: off
no-sequences: warn
no-shadow: off
no-sparse-arrays: warn
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ node_modules
pnpm-lock.yaml
**/bundle.l10n.json
**/.wdio-vscode-service
packages/zowe-explorer/__tests__/**/zowe.config.json
zedc/target
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* 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 { Mutex } from "async-mutex";
import { AuthHandler } from "../../../src";
import { FileManagement } from "../../../src/utils/FileManagement";
import { ImperativeError } from "@zowe/imperative";

const TEST_PROFILE_NAME = "lpar.zosmf";

describe("AuthHandler.isProfileLocked", () => {
it("returns true if the profile is locked", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
expect(AuthHandler.isProfileLocked(TEST_PROFILE_NAME)).toBe(true);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
});

it("returns false if the profile is not locked", async () => {
expect(AuthHandler.isProfileLocked(TEST_PROFILE_NAME)).toBe(false);
});

it("returns false if no mutex is present for the given profile", async () => {
expect(AuthHandler.isProfileLocked("unused_lpar.zosmf")).toBe(false);
});
});

describe("AuthHandler.lockProfile", () => {
it("assigns and acquires a Mutex to the profile in the profile map", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
expect((AuthHandler as any).lockedProfiles.has(TEST_PROFILE_NAME)).toBe(true);
expect((AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME)).toBeInstanceOf(Mutex);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
});

it("handle promptForAuthentication call if error and options are given", async () => {
const promptForAuthenticationMock = jest.spyOn(AuthHandler, "promptForAuthentication").mockResolvedValueOnce(true);
const impError = new ImperativeError({ msg: "Example auth error" });
const promptOpts = {
promptCredentials: jest.fn(),
ssoLogin: jest.fn(),
};
const releaseSpy = jest.spyOn(Mutex.prototype, "release");
const result = await AuthHandler.lockProfile(TEST_PROFILE_NAME, impError, promptOpts);
expect(result).toBe(true);
expect(promptForAuthenticationMock).toHaveBeenCalledTimes(1);
expect(promptForAuthenticationMock).toHaveBeenCalledWith(impError, TEST_PROFILE_NAME, promptOpts);
expect(releaseSpy).toHaveBeenCalledTimes(1);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
});

it("reuses the same Mutex for the profile if it already exists", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
expect((AuthHandler as any).lockedProfiles.has(TEST_PROFILE_NAME)).toBe(true);
// cache initial mutex for comparison
const mutex = (AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME);
expect(mutex).toBeInstanceOf(Mutex);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);

// same mutex is still present in map since lock/unlock sequence was used
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
expect(mutex).toBe((AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME));
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
});
});

describe("AuthHandler.unlockProfile", () => {
it("releases the Mutex for the profile in the profile map", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
expect((AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME)!.isLocked()).toBe(false);
});

it("does nothing if there is no mutex in the profile map", async () => {
const releaseSpy = jest.spyOn(Mutex.prototype, "release").mockClear();
AuthHandler.unlockProfile("unused_lpar.zosmf");
expect(releaseSpy).not.toHaveBeenCalled();
});

it("does nothing if the mutex in the map is not locked", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);

const releaseSpy = jest.spyOn(Mutex.prototype, "release").mockClear();
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
expect(releaseSpy).not.toHaveBeenCalled();
});

it("reuses the same Mutex for the profile if it already exists", async () => {
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
expect((AuthHandler as any).lockedProfiles.has(TEST_PROFILE_NAME)).toBe(true);
// cache initial mutex for comparison
const mutex = (AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME);

// same mutex is still present in map since lock/unlock sequence was used
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
AuthHandler.unlockProfile(TEST_PROFILE_NAME);
expect(mutex).toBe((AuthHandler as any).lockedProfiles.get(TEST_PROFILE_NAME));
});

it("refreshes resources if refreshResources parameter is true", async () => {
const reloadActiveEditorMock = jest.spyOn(FileManagement, "reloadActiveEditorForProfile").mockResolvedValueOnce(undefined);
const reloadWorkspaceMock = jest.spyOn(FileManagement, "reloadWorkspacesForProfile").mockResolvedValueOnce(undefined);
await AuthHandler.lockProfile(TEST_PROFILE_NAME);
AuthHandler.unlockProfile(TEST_PROFILE_NAME, true);
expect(reloadActiveEditorMock).toHaveBeenCalledWith(TEST_PROFILE_NAME);
expect(reloadWorkspaceMock).toHaveBeenCalledWith(TEST_PROFILE_NAME);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 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 { DeferredPromise } from "../../../src";

describe("DeferredPromise constructor", () => {
it("sets resolve and reject functions", () => {
const deferred = new DeferredPromise();
expect(deferred.promise).toBeInstanceOf(Promise);
expect(deferred.reject).toBeInstanceOf(Function);
expect(deferred.resolve).toBeInstanceOf(Function);
});
});

describe("DeferredPromise.status", () => {
it("returns pending when not yet resolved", () => {
const deferred = new DeferredPromise();
expect(deferred.status).toBe("pending");
});

it("returns fulfilled when resolved", () => {
const deferred = new DeferredPromise();
deferred.resolve(null);
expect(deferred.status).toBe("fulfilled");
});

it("returns rejected when rejected", async () => {
const deferred = new DeferredPromise();
let errorCaught = false;
setImmediate(() => deferred.reject());
try {
await deferred.promise;
} catch (err) {
errorCaught = true;
}
expect(deferred.status).toBe("rejected");
expect(errorCaught).toBe(true);
});
});
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@zowe/zos-tso-for-zowe-sdk": "^8.8.3",
"@zowe/zos-uss-for-zowe-sdk": "^8.8.3",
"@zowe/zosmf-for-zowe-sdk": "^8.8.3",
"async-mutex": "^0.5.0",
"deep-object-diff": "^1.1.9",
"mustache": "^4.2.0",
"semver": "^7.6.0"
Expand Down
Loading
Loading