-
Notifications
You must be signed in to change notification settings - Fork 364
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
test: [M3-7516] - Cypress tests for Parent β Child account switching flows #10110
Changes from 7 commits
a8a5046
7004b21
051133c
9bca651
463a15a
bd63002
9fd9b39
2c48a53
fe71427
c57737b
6a1eb9f
ab83c02
5c8f557
af70d4a
e37aec8
08d7b30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
import { | ||
accountFactory, | ||
appTokenFactory, | ||
profileFactory, | ||
} from '@src/factories'; | ||
import { accountUserFactory } from '@src/factories/accountUsers'; | ||
import { DateTime } from 'luxon'; | ||
import { | ||
mockCreateChildAccountToken, | ||
mockCreateChildAccountTokenError, | ||
mockGetAccount, | ||
mockGetChildAccounts, | ||
mockGetChildAccountsError, | ||
mockGetUser, | ||
} from 'support/intercepts/account'; | ||
import { | ||
mockAppendFeatureFlags, | ||
mockGetFeatureFlagClientstream, | ||
} from 'support/intercepts/feature-flags'; | ||
import { mockAllApiRequests } from 'support/intercepts/general'; | ||
import { mockGetProfile } from 'support/intercepts/profile'; | ||
import { ui } from 'support/ui'; | ||
import { makeFeatureFlagData } from 'support/util/feature-flags'; | ||
import { assertLocalStorageValue } from 'support/util/local-storage'; | ||
import { randomLabel, randomNumber, randomString } from 'support/util/random'; | ||
|
||
/** | ||
* Confirms expected username and company name are shown in user menu button and yields the button. | ||
* | ||
* @param username - Username to expect in user menu button. | ||
* @param companyName - Company name to expect in user menu button. | ||
* | ||
* @returns Cypress chainable that yields the user menu button. | ||
*/ | ||
const assertUserMenuButton = (username: string, companyName: string) => { | ||
return ui.userMenuButton | ||
.find() | ||
.should('be.visible') | ||
.within(() => { | ||
cy.findByText(username).should('be.visible'); | ||
cy.findByText(companyName).should('be.visible'); | ||
}); | ||
}; | ||
|
||
/** | ||
* Confirms that expected authentication values are set in Local Storage. | ||
* | ||
* @param token - Authentication token value to assert. | ||
* @param expiry - Authentication expiry value to assert. | ||
* @param scopes - Authentication scope value to assert. | ||
*/ | ||
const assertAuthLocalStorage = ( | ||
token: string, | ||
expiry: string, | ||
scopes: string | ||
) => { | ||
assertLocalStorageValue('authentication/token', token); | ||
assertLocalStorageValue('authentication/expire', expiry); | ||
assertLocalStorageValue('authentication/scopes', scopes); | ||
}; | ||
|
||
const mockParentAccount = accountFactory.build({ | ||
company: 'Parent Company', | ||
}); | ||
|
||
const mockParentProfile = profileFactory.build({ | ||
username: randomLabel(), | ||
user_type: 'parent', | ||
}); | ||
|
||
const mockParentUser = accountUserFactory.build({ | ||
username: mockParentProfile.username, | ||
user_type: 'parent', | ||
}); | ||
|
||
const mockChildAccount = accountFactory.build({ | ||
company: 'Child Company', | ||
}); | ||
|
||
const mockChildAccountToken = appTokenFactory.build({ | ||
id: randomNumber(), | ||
created: DateTime.now().toISO(), | ||
expiry: DateTime.now().plus({ hours: 1 }).toISO(), | ||
label: `${mockChildAccount.company}_proxy`, | ||
scopes: '*', | ||
token: randomString(32), | ||
website: undefined, | ||
thumbnail_url: undefined, | ||
}); | ||
|
||
const mockErrorMessage = 'An unknown error has occurred.'; | ||
|
||
describe('Parent/Child account switching', () => { | ||
/* | ||
* Tests to confirm that Parent account users can switch to Child accounts as expected. | ||
*/ | ||
describe('From Parent to Child', () => { | ||
jdamore-linode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
beforeEach(() => { | ||
// @TODO M3-7554, M3-7559: Remove feature flag mocks after feature launch and clean-up. | ||
mockAppendFeatureFlags({ | ||
parentChildAccountAccess: makeFeatureFlagData(true), | ||
}); | ||
mockGetFeatureFlagClientstream(); | ||
}); | ||
|
||
/* | ||
* - Confirms that Parent account user can switch to Child account from Account Billing page. | ||
* - Confirms that Child account information is displayed in user menu button after switch. | ||
* - Confirms that Cloud updates local storage auth values upon account switch. | ||
*/ | ||
it('can switch from Parent account to Child account from Billing page', () => { | ||
mockGetProfile(mockParentProfile); | ||
mockGetAccount(mockParentAccount); | ||
mockGetChildAccounts([mockChildAccount]); | ||
mockGetUser(mockParentUser); | ||
|
||
cy.visitWithLogin('/account/billing'); | ||
|
||
// Confirm that "Switch Account" button is present, then click it. | ||
ui.button | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.should('be.enabled') | ||
.click(); | ||
|
||
mockCreateChildAccountToken(mockChildAccount, mockChildAccountToken).as( | ||
'switchAccount' | ||
); | ||
|
||
ui.drawer | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.within(() => { | ||
cy.findByText(mockChildAccount.company).should('be.visible').click(); | ||
}); | ||
|
||
cy.wait('@switchAccount'); | ||
|
||
// Confirm that Cloud Manager updates local storage authentication values. | ||
assertAuthLocalStorage( | ||
mockChildAccountToken.token, | ||
mockChildAccountToken.expiry, | ||
mockChildAccountToken.scopes | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also want to ensure that parent tokens are being created here too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, the Switch Account drawer does that here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Going to hold off on this for now -- the Cypress tests deal with a real token, so asserting its value will cause it to be logged and recorded. |
||
|
||
// From this point forward, we will not have a valid test account token stored in local storage, | ||
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login. | ||
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the | ||
// individual requests as needed. | ||
mockAllApiRequests(); | ||
mockGetAccount(mockChildAccount); | ||
mockGetProfile(mockParentProfile); | ||
mockGetUser(mockParentUser); | ||
Comment on lines
+160
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also hoping for an extra set of eyes here -- do these mocks accurately reflect what the account, profile, and user API request will look like in this situation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that mockGetUser is now left over from when we previously had to call Otherwise, this seems correct. We'll have the child account once logged in as a proxy user and we'll also need to fetch the parent's username from |
||
|
||
// TODO Remove the call to `cy.reload()` once Cloud Manager automatically updates itself upon account switching. | ||
// TODO Add assertions for toast upon account switch. | ||
cy.reload(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @jaalah-akamai @mjac0bs -- I think this relates to this discussion in the account switching PR. Cloud definitely isn't responding as expected upon updating the Local Storage auth values, so a page reload is currently necessary on the Cypress (i.e. user) side to trigger the change. I'm guessing if we want to avoid an automatic page reload, it'll involve at least resetting Cloud's Axios interceptors, resetting React Query, and maybe doing something with our Redux state? Either way, this test can be used to develop/troubleshoot this functionality without having to wait for anything from the API -- just remove these calls to |
||
|
||
// Confirm expected username and company are shown in user menu button. | ||
assertUserMenuButton( | ||
mockParentProfile.username, | ||
mockChildAccount.company | ||
); | ||
}); | ||
|
||
/* | ||
* - Confirms that Parent account user can switch to Child account using the user menu. | ||
* - Confirms that Parent account information is initially displayed in user menu button. | ||
* - Confirms that Child account information is displayed in user menu button after switch. | ||
* - Confirms that Cloud updates local storage auth values upon account switch. | ||
*/ | ||
it('can switch from Parent account to Child account using user menu', () => { | ||
mockGetProfile(mockParentProfile); | ||
mockGetAccount(mockParentAccount); | ||
mockGetChildAccounts([mockChildAccount]); | ||
mockGetUser(mockParentUser); | ||
|
||
cy.visitWithLogin('/'); | ||
|
||
// Confirm that Parent account username and company name are shown in user | ||
// menu button, then click the button. | ||
assertUserMenuButton( | ||
mockParentProfile.username, | ||
mockParentAccount.company | ||
).click(); | ||
|
||
// Click "Switch Account" button in user menu. | ||
ui.userMenu | ||
.find() | ||
.should('be.visible') | ||
.within(() => { | ||
ui.button | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.should('be.enabled') | ||
.click(); | ||
}); | ||
|
||
// Click mock company name in "Switch Account" drawer. | ||
mockCreateChildAccountToken(mockChildAccount, mockChildAccountToken).as( | ||
'switchAccount' | ||
); | ||
|
||
ui.drawer | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.within(() => { | ||
cy.findByText(mockChildAccount.company).should('be.visible').click(); | ||
}); | ||
|
||
cy.wait('@switchAccount'); | ||
|
||
// Confirm that Cloud Manager updates local storage authentication values. | ||
assertAuthLocalStorage( | ||
mockChildAccountToken.token, | ||
mockChildAccountToken.expiry, | ||
mockChildAccountToken.scopes | ||
); | ||
|
||
// From this point forward, we will not have a valid test account token stored in local storage, | ||
// so all non-intercepted API requests will respond with a 401 status code and we will get booted to login. | ||
// We'll mitigate this by broadly mocking ALL API-v4 requests, then applying more specific mocks to the | ||
// individual requests as needed. | ||
mockAllApiRequests(); | ||
mockGetAccount(mockChildAccount); | ||
mockGetProfile(mockParentProfile); | ||
mockGetUser(mockParentUser); | ||
|
||
// TODO Remove the call to `cy.reload()` once Cloud Manager automatically updates itself upon account switching. | ||
// TODO Add assertions for toast upon account switch. | ||
cy.reload(); | ||
|
||
// Confirm expected username and company are shown in user menu button. | ||
assertUserMenuButton( | ||
mockParentProfile.username, | ||
mockChildAccount.company | ||
); | ||
}); | ||
}); | ||
|
||
/* | ||
* Tests to confirm that Cloud handles account switching errors gracefully. | ||
*/ | ||
describe('Error flows', () => { | ||
/* | ||
* - Confirms error handling upon failure to fetch child accounts. | ||
* - Confirms "Try Again" button can be used to re-fetch child accounts successfully. | ||
* - Confirms error handling upon failure to create child account token. | ||
*/ | ||
it('handles account switching API errors', () => { | ||
mockGetProfile(mockParentProfile); | ||
mockGetAccount(mockParentAccount); | ||
mockGetChildAccountsError('An unknown error has occurred', 500); | ||
mockGetUser(mockParentUser); | ||
|
||
cy.visitWithLogin('/account/billing'); | ||
ui.button | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.should('be.enabled') | ||
.click(); | ||
|
||
ui.drawer | ||
.findByTitle('Switch Account') | ||
.should('be.visible') | ||
.within(() => { | ||
// Confirm error message upon failure to fetch child accounts. | ||
cy.findByText('Unable to load data.').should('be.visible'); | ||
cy.findByText( | ||
'Try again or contact support if the issue persists.' | ||
).should('be.visible'); | ||
|
||
// Click "Try Again" button and mock a successful response. | ||
mockGetChildAccounts([mockChildAccount]); | ||
ui.button | ||
.findByTitle('Try again') | ||
.should('be.visible') | ||
.should('be.enabled') | ||
.click(); | ||
|
||
// Click child company and mock an error. | ||
// Confirm that Cloud Manager displays the error message in the drawer. | ||
mockCreateChildAccountTokenError(mockChildAccount, mockErrorMessage); | ||
cy.findByText(mockChildAccount.company).click(); | ||
cy.findByText(mockErrorMessage).should('be.visible'); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question for @jaalah-akamai or @mjac0bs: does the token value returned by the API already include the
Bearer
prefix, or do we have logic somewhere that prepends it before saving it in Local Storage?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe it includes the
Bearer
prefix. It looks like the token first gets set when a session begins andBearer
comes fromtoken_type
, which is how it ends up withBearer
in local storage.And from the API spec:
On a related bearer-in-token-related note, I'm not sure that we're supposed to have
Bearer
when callinggetChildAccountPersonalAccessToken
here in the switch account drawer. Maybe it was a oversight or I'm missing something and @jaalah-akamai can clarify.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true - left over probably from one of my iterations β
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jaalah-akamai and @mjac0bs -- sorry for the slow follow up here.
If I'm understanding your explanation @mjac0bs, it sounds like we're supposed to have the
Bearer
prefix in local storage but the token that gets stored during the account switching flow does not contain the prefix.Is there a ticket for this? I can update this test to expect the
Bearer
prefix but it will fail in the meantime.