From 720786db6cca342504b63640c4241d3a80f1a92e Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Fri, 16 Feb 2024 09:34:52 -0800 Subject: [PATCH 1/6] Disable fields and buttons on proxy user's user profile page --- .../DisplaySettings/DisplaySettings.tsx | 4 +- .../manager/src/features/Profile/constants.ts | 2 + .../src/features/Users/UserProfile.tsx | 45 ++++++++++++------- 3 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 packages/manager/src/features/Profile/constants.ts diff --git a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx index cd45bec69f9..7ef2b218899 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx @@ -17,6 +17,7 @@ import { useNotificationsQuery } from 'src/queries/accountNotifications'; import { useMutateProfile, useProfile } from 'src/queries/profile'; import { ApplicationState } from 'src/store'; +import { restrictedProxyUserTooltip } from '../constants'; import { TimezoneForm } from './TimezoneForm'; export const DisplaySettings = () => { @@ -65,9 +66,6 @@ export const DisplaySettings = () => { ); - const restrictedProxyUserTooltip = - 'This account type cannot update this field.'; - return ( { } = props; const { data: profile } = useProfile(); + const { data: currentUser } = useAccountUser(username); const [ deleteConfirmDialogOpen, setDeleteConfirmDialogOpen, ] = React.useState(false); + const isProxyUserProfile = currentUser?.user_type === 'proxy'; + const renderProfileSection = () => { const hasAccountErrorFor = getAPIErrorFor( { username: 'Username' }, @@ -97,7 +101,11 @@ export const UserProfile = (props: UserProfileProps) => { /> )} { /> )} { Delete User - {profile?.username === originalUsername && ( - - )} Date: Fri, 16 Feb 2024 10:48:22 -0800 Subject: [PATCH 2/6] Separate Display and User Profile test specs; add coverage --- ...rname.spec.ts => display-settings.spec.ts} | 54 +------ .../e2e/core/account/user-profile.spec.ts | 151 ++++++++++++++++++ 2 files changed, 153 insertions(+), 52 deletions(-) rename packages/manager/cypress/e2e/core/account/{change-username.spec.ts => display-settings.spec.ts} (73%) create mode 100644 packages/manager/cypress/e2e/core/account/user-profile.spec.ts diff --git a/packages/manager/cypress/e2e/core/account/change-username.spec.ts b/packages/manager/cypress/e2e/core/account/display-settings.spec.ts similarity index 73% rename from packages/manager/cypress/e2e/core/account/change-username.spec.ts rename to packages/manager/cypress/e2e/core/account/display-settings.spec.ts index a0374fc698f..a08f8e04b94 100644 --- a/packages/manager/cypress/e2e/core/account/change-username.spec.ts +++ b/packages/manager/cypress/e2e/core/account/display-settings.spec.ts @@ -8,10 +8,7 @@ import { makeFeatureFlagData } from 'support/util/feature-flags'; import { mockGetProfile } from 'support/intercepts/profile'; import { getProfile } from 'support/api/account'; import { interceptGetProfile } from 'support/intercepts/profile'; -import { - interceptGetUser, - mockUpdateUsername, -} from 'support/intercepts/account'; +import { mockUpdateUsername } from 'support/intercepts/account'; import { ui } from 'support/ui'; import { randomString } from 'support/util/random'; @@ -59,54 +56,7 @@ const verifyUsernameAndEmail = ( } }; -describe('username', () => { - /* - * - Validates username update flow via the user profile page using mocked data. - */ - it('can change username via user profile page', () => { - const newUsername = randomString(12); - - getProfile().then((profile) => { - const username = profile.body.username; - - interceptGetUser(username).as('getUser'); - mockUpdateUsername(username, newUsername).as('updateUsername'); - - cy.visitWithLogin(`account/users/${username}`); - cy.wait('@getUser'); - - cy.findByText('Username').should('be.visible'); - cy.findByText('Email').should('be.visible'); - cy.findByText('Delete User').should('be.visible'); - - cy.get('[id="username"]') - .should('be.visible') - .should('have.value', username) - .clear() - .type(newUsername); - - cy.get('[data-qa-textfield-label="Username"]') - .parent() - .parent() - .parent() - .within(() => { - ui.button - .findByTitle('Save') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - cy.wait('@updateUsername'); - - // No confirmation gets shown on this page when changes are saved. - // Confirm that the text field has the correct value instead. - cy.get('[id="username"]') - .should('be.visible') - .should('have.value', newUsername); - }); - }); - +describe('Display Settings', () => { /* * - Validates username update flow via the profile display page using mocked data. */ diff --git a/packages/manager/cypress/e2e/core/account/user-profile.spec.ts b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts new file mode 100644 index 00000000000..a4da51b88c9 --- /dev/null +++ b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts @@ -0,0 +1,151 @@ +import { accountUserFactory } from 'src/factories/accountUsers'; +import { getProfile } from 'support/api/account'; +import { + interceptGetUser, + mockGetUser, + mockGetUsers, + mockUpdateUsername, +} from 'support/intercepts/account'; +import { randomString } from 'support/util/random'; +import { ui } from 'support/ui'; + +describe('User Profile', () => { + /* + * - Validates username update flow via the user profile page using mocked data. + */ + it('can change username', () => { + const newUsername = randomString(12); + + getProfile().then((profile) => { + const username = profile.body.username; + + interceptGetUser(username).as('getUser'); + mockUpdateUsername(username, newUsername).as('updateUsername'); + + cy.visitWithLogin(`account/users/${username}`); + cy.wait('@getUser'); + + cy.findByText('Username').should('be.visible'); + cy.findByText('Email').should('be.visible'); + cy.findByText('Delete User').should('be.visible'); + + cy.get('[id="username"]') + .should('be.visible') + .should('have.value', username) + .clear() + .type(newUsername); + + cy.get('[data-qa-textfield-label="Username"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@updateUsername'); + + // No confirmation gets shown on this page when changes are saved. + // Confirm that the text field has the correct value instead. + cy.get('[id="username"]') + .should('be.visible') + .should('have.value', newUsername); + }); + }); + + /* + * - Validates disabled username and email flow for a proxy user profile using mocked data. + */ + it('cannot change username or email for a proxy user or delete the proxy user', () => { + getProfile().then((profile) => { + const proxyUsername = 'proxy_user'; + const mockAccountUsers = accountUserFactory.buildList(1, { + username: proxyUsername, + user_type: 'proxy', + }); + + mockGetUsers(mockAccountUsers).as('getUsers'); + mockGetUser(mockAccountUsers[0]).as('getUser'); + + cy.visitWithLogin(`account/users/${proxyUsername}`); + + cy.wait('@getUser'); + + cy.findByText('Username').should('be.visible'); + cy.findByText('Email').should('be.visible'); + cy.findByText('Delete User').should('be.visible'); + + cy.get('[id="username"]') + .should('be.visible') + .should('be.disabled') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByAttribute('data-qa-help-button', 'true') + .should('be.visible') + .trigger('mouseover'); + // Click the button first, then confirm the tooltip is shown. + ui.tooltip + .findByText('This account type cannot update this field.') + .should('be.visible'); + }); + + cy.get('[data-qa-textfield-label="Username"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.disabled'); + }); + + cy.get('[id="email"]') + .should('be.visible') + .should('be.disabled') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByAttribute('data-qa-help-button', 'true') + .should('be.visible') + .trigger('mouseover'); + // Click the button first, then confirm the tooltip is shown. + ui.tooltip + .findByText('This account type cannot update this field.') + .should('be.visible'); + }); + + cy.get('[data-qa-textfield-label="Email"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.disabled') + .click(); + }); + + // Confirms the proxy user cannot be deleted. + ui.button + .findByTitle('Delete') + .should('be.visible') + .should('be.disabled') + .trigger('mouseover'); + // Click the button first, then confirm the tooltip is shown. + ui.tooltip + .findByText("You can't delete the proxy user.") + .should('be.visible'); + }); + }); +}); From bef35680c7573dfd455de9083440fd391c939635 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Fri, 16 Feb 2024 13:30:53 -0800 Subject: [PATCH 3/6] Finish test coverage --- .../e2e/core/account/user-profile.spec.ts | 150 ++++++++++++++++-- .../src/features/Users/UserProfile.tsx | 6 +- 2 files changed, 144 insertions(+), 12 deletions(-) diff --git a/packages/manager/cypress/e2e/core/account/user-profile.spec.ts b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts index a4da51b88c9..bda5a9189e7 100644 --- a/packages/manager/cypress/e2e/core/account/user-profile.spec.ts +++ b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts @@ -8,30 +8,161 @@ import { } from 'support/intercepts/account'; import { randomString } from 'support/util/random'; import { ui } from 'support/ui'; +import { mockUpdateProfile } from 'support/intercepts/profile'; describe('User Profile', () => { /* - * - Validates username update flow via the user profile page using mocked data. + * - Validates the flow of updating the username and email of the active account user via the User Profile page using mocked data. */ - it('can change username', () => { + it('can change email and username of the active account', () => { const newUsername = randomString(12); + const newEmail = `${newUsername}@example.com`; getProfile().then((profile) => { - const username = profile.body.username; + const activeUsername = profile.body.username; + const activeEmail = profile.body.email; - interceptGetUser(username).as('getUser'); - mockUpdateUsername(username, newUsername).as('updateUsername'); + interceptGetUser(activeUsername).as('getUser'); + mockUpdateUsername(activeUsername, newUsername).as('updateUsername'); + mockUpdateProfile({ + ...profile.body, + email: newEmail, + }).as('updateEmail'); - cy.visitWithLogin(`account/users/${username}`); + cy.visitWithLogin(`account/users/${activeUsername}`); cy.wait('@getUser'); cy.findByText('Username').should('be.visible'); cy.findByText('Email').should('be.visible'); cy.findByText('Delete User').should('be.visible'); + // Confirm the currently active user cannot be deleted. + ui.button + .findByTitle('Delete') + .should('be.visible') + .should('be.disabled') + .trigger('mouseover'); + // Click the button first, then confirm the tooltip is shown. + ui.tooltip + .findByText('You can\u{2019}t delete the currently active user.') + .should('be.visible'); + + // Confirm user can update their email before updating the username, since you cannot update a different user's (as determined by username) email. + cy.get('[id="email"]') + .should('be.visible') + .should('have.value', activeEmail) + .clear() + .type(newEmail); + + cy.get('[data-qa-textfield-label="Email"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@updateEmail'); + + // Confirm success notice displays. + cy.findByText('Email updated successfully').should('be.visible'); + + // Confirm user can update their username. + cy.get('[id="username"]') + .should('be.visible') + .should('have.value', activeUsername) + .clear() + .type(newUsername); + + cy.get('[data-qa-textfield-label="Username"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@updateUsername'); + + // No confirmation gets shown on this page when changes are saved. + // Confirm that the text field has the correct value instead. + cy.get('[id="username"]') + .should('be.visible') + .should('have.value', newUsername); + }); + }); + + /* + * - Validates the flow of updating the username and email of another user via the User Profile page using mocked data. + */ + it('can change the username but not email of another user account', () => { + const newUsername = randomString(12); + + getProfile().then((profile) => { + const additionalUsername = 'mock_user2'; + const mockAccountUsers = accountUserFactory.buildList(1, { + username: additionalUsername, + }); + const additionalUser = mockAccountUsers[0]; + + mockGetUsers(mockAccountUsers).as('getUsers'); + mockGetUser(additionalUser).as('getUser'); + mockUpdateUsername(additionalUsername, newUsername).as('updateUsername'); + + cy.visitWithLogin(`account/users/${additionalUsername}`); + + cy.wait('@getUser'); + + cy.findByText('Username').should('be.visible'); + cy.findByText('Email').should('be.visible'); + cy.findByText('Delete User').should('be.visible'); + ui.button.findByTitle('Delete').should('be.visible').should('be.enabled'); + + // Confirm email of another user cannot be updated. + cy.get('[id="email"]') + .should('be.visible') + .should('have.value', additionalUser.email) + .should('be.disabled') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByAttribute('data-qa-help-button', 'true') + .should('be.visible') + .trigger('mouseover'); + // Click the button first, then confirm the tooltip is shown. + ui.tooltip + .findByText( + 'You can\u{2019}t change another user\u{2019}s email address.' + ) + .should('be.visible'); + }); + + cy.get('[data-qa-textfield-label="Email"]') + .parent() + .parent() + .parent() + .within(() => { + ui.button + .findByTitle('Save') + .should('be.visible') + .should('be.disabled') + .click(); + }); + + // Confirm username of another user can be updated. cy.get('[id="username"]') .should('be.visible') - .should('have.value', username) + .should('have.value', additionalUsername) .clear() .type(newUsername); @@ -58,7 +189,7 @@ describe('User Profile', () => { }); /* - * - Validates disabled username and email flow for a proxy user profile using mocked data. + * - Validates disabled username and email flow for a proxy user via the User Profile page using mocked data. */ it('cannot change username or email for a proxy user or delete the proxy user', () => { getProfile().then((profile) => { @@ -81,6 +212,7 @@ describe('User Profile', () => { cy.get('[id="username"]') .should('be.visible') + .should('have.value', proxyUsername) .should('be.disabled') .parent() .parent() @@ -144,7 +276,7 @@ describe('User Profile', () => { .trigger('mouseover'); // Click the button first, then confirm the tooltip is shown. ui.tooltip - .findByText("You can't delete the proxy user.") + .findByText('You can\u{2019}t delete the proxy user.') .should('be.visible'); }); }); diff --git a/packages/manager/src/features/Users/UserProfile.tsx b/packages/manager/src/features/Users/UserProfile.tsx index f5d7f7638f4..01d271d689b 100644 --- a/packages/manager/src/features/Users/UserProfile.tsx +++ b/packages/manager/src/features/Users/UserProfile.tsx @@ -145,7 +145,7 @@ export const UserProfile = (props: UserProfileProps) => { isProxyUserProfile ? restrictedProxyUserTooltip : profile?.username !== originalUsername - ? "You can't change another user\u{2019}s email address." + ? 'You can\u{2019}t change another user\u{2019}s email address.' : undefined } data-qa-email @@ -197,9 +197,9 @@ export const UserProfile = (props: UserProfileProps) => { }} tooltipText={ profile?.username === originalUsername - ? "You can't delete the currently active user." + ? 'You can\u{2019}t delete the currently active user.' : isProxyUserProfile - ? "You can't delete the proxy user." + ? 'You can\u{2019}t delete the proxy user.' : undefined } buttonType="outlined" From 2d507a072113c2af2eee9ce7445ce40096bd5ecb Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Fri, 16 Feb 2024 13:35:57 -0800 Subject: [PATCH 4/6] Clean up --- packages/manager/src/features/Account/constants.ts | 3 +++ .../features/Profile/DisplaySettings/DisplaySettings.tsx | 6 +++--- packages/manager/src/features/Profile/constants.ts | 2 -- packages/manager/src/features/Users/UserProfile.tsx | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 packages/manager/src/features/Profile/constants.ts diff --git a/packages/manager/src/features/Account/constants.ts b/packages/manager/src/features/Account/constants.ts index 0bb075f38d4..83ac50faa9c 100644 --- a/packages/manager/src/features/Account/constants.ts +++ b/packages/manager/src/features/Account/constants.ts @@ -22,3 +22,6 @@ export const CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT = // TODO: Parent/Child: Requires updated copy... export const PARENT_SESSION_EXPIRED = 'Session expired. Please log in again to your business partner account.'; + +export const RESTRICTED_FIELD_TOOLTIP = + 'This account type cannot update this field.'; diff --git a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx index 7ef2b218899..d86f9f66760 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/DisplaySettings.tsx @@ -17,8 +17,8 @@ import { useNotificationsQuery } from 'src/queries/accountNotifications'; import { useMutateProfile, useProfile } from 'src/queries/profile'; import { ApplicationState } from 'src/store'; -import { restrictedProxyUserTooltip } from '../constants'; import { TimezoneForm } from './TimezoneForm'; +import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants'; export const DisplaySettings = () => { const theme = useTheme(); @@ -106,7 +106,7 @@ export const DisplaySettings = () => { profile?.restricted ? 'Restricted users cannot update their username. Please contact an account administrator.' : isProxyUser - ? restrictedProxyUserTooltip + ? RESTRICTED_FIELD_TOOLTIP : undefined } disabled={profile?.restricted || isProxyUser} @@ -136,7 +136,7 @@ export const DisplaySettings = () => { key={emailResetToken} label="Email" submitForm={updateEmail} - tooltipText={isProxyUser ? restrictedProxyUserTooltip : undefined} + tooltipText={isProxyUser ? RESTRICTED_FIELD_TOOLTIP : undefined} trimmed type="email" /> diff --git a/packages/manager/src/features/Profile/constants.ts b/packages/manager/src/features/Profile/constants.ts deleted file mode 100644 index 38bc6a81862..00000000000 --- a/packages/manager/src/features/Profile/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const restrictedProxyUserTooltip = - 'This account type cannot update this field.'; diff --git a/packages/manager/src/features/Users/UserProfile.tsx b/packages/manager/src/features/Users/UserProfile.tsx index 01d271d689b..2831d0a9a70 100644 --- a/packages/manager/src/features/Users/UserProfile.tsx +++ b/packages/manager/src/features/Users/UserProfile.tsx @@ -14,9 +14,9 @@ import { useAccountUser } from 'src/queries/accountUsers'; import { useProfile } from 'src/queries/profile'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; -import { restrictedProxyUserTooltip } from '../Profile/constants'; import { UserDeleteConfirmationDialog } from './UserDeleteConfirmationDialog'; import { StyledTitle, StyledWrapper } from './UserProfile.styles'; +import { RESTRICTED_FIELD_TOOLTIP } from '../Account/constants'; interface UserProfileProps { accountErrors?: APIError[]; @@ -102,7 +102,7 @@ export const UserProfile = (props: UserProfileProps) => { )} { } tooltipText={ isProxyUserProfile - ? restrictedProxyUserTooltip + ? RESTRICTED_FIELD_TOOLTIP : profile?.username !== originalUsername ? 'You can\u{2019}t change another user\u{2019}s email address.' : undefined From 6ca7a00cd8d4f8f4239e92793994bcb4e35cde84 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Fri, 16 Feb 2024 13:38:50 -0800 Subject: [PATCH 5/6] Meant to push the changesets too --- packages/manager/.changeset/pr-10202-tests-1708119481596.md | 5 +++++ .../.changeset/pr-10202-upcoming-features-1708119409383.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/manager/.changeset/pr-10202-tests-1708119481596.md create mode 100644 packages/manager/.changeset/pr-10202-upcoming-features-1708119409383.md diff --git a/packages/manager/.changeset/pr-10202-tests-1708119481596.md b/packages/manager/.changeset/pr-10202-tests-1708119481596.md new file mode 100644 index 00000000000..3017a0cdc53 --- /dev/null +++ b/packages/manager/.changeset/pr-10202-tests-1708119481596.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Improve User Profile integration test coverage and separate from Display Settings coverage ([#10202](https://github.com/linode/manager/pull/10202)) diff --git a/packages/manager/.changeset/pr-10202-upcoming-features-1708119409383.md b/packages/manager/.changeset/pr-10202-upcoming-features-1708119409383.md new file mode 100644 index 00000000000..1d906c12174 --- /dev/null +++ b/packages/manager/.changeset/pr-10202-upcoming-features-1708119409383.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Disable ability to edit or delete a proxy user via User Profile page ([#10202](https://github.com/linode/manager/pull/10202)) From cd225921419c256a79895c074ea20cc939a2422b Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Tue, 20 Feb 2024 09:52:46 -0800 Subject: [PATCH 6/6] Remove proxy user language --- packages/manager/cypress/e2e/core/account/user-profile.spec.ts | 2 +- packages/manager/src/features/Users/UserProfile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/manager/cypress/e2e/core/account/user-profile.spec.ts b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts index bda5a9189e7..a7ffc031bc6 100644 --- a/packages/manager/cypress/e2e/core/account/user-profile.spec.ts +++ b/packages/manager/cypress/e2e/core/account/user-profile.spec.ts @@ -276,7 +276,7 @@ describe('User Profile', () => { .trigger('mouseover'); // Click the button first, then confirm the tooltip is shown. ui.tooltip - .findByText('You can\u{2019}t delete the proxy user.') + .findByText('You can\u{2019}t delete a business partner user.') .should('be.visible'); }); }); diff --git a/packages/manager/src/features/Users/UserProfile.tsx b/packages/manager/src/features/Users/UserProfile.tsx index 2831d0a9a70..94dea27e373 100644 --- a/packages/manager/src/features/Users/UserProfile.tsx +++ b/packages/manager/src/features/Users/UserProfile.tsx @@ -199,7 +199,7 @@ export const UserProfile = (props: UserProfileProps) => { profile?.username === originalUsername ? 'You can\u{2019}t delete the currently active user.' : isProxyUserProfile - ? 'You can\u{2019}t delete the proxy user.' + ? 'You can\u{2019}t delete a business partner user.' : undefined } buttonType="outlined"