From 8755aad40e25c2e770cd52dac7ee1433fd1918f9 Mon Sep 17 00:00:00 2001 From: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Date: Wed, 5 Jul 2023 10:41:55 -0400 Subject: [PATCH] chore: [M3-6777] - Rename Cypress functions and variables related to secrets (#9350) * Rename functions and variables related to secrets, improve object storage mock utils * Fix failing access key revoke test * Fix mismatched error variable name in prebuild script --- .../e2e/core/account/add-oauth-app.spec.ts | 20 +++-- .../account/personal-access-tokens.spec.ts | 33 ++++---- .../e2e/core/account/reset-oauth-app.spec.ts | 18 ++--- .../account/third-party-access-tokens.spec.ts | 26 +++---- .../e2e/core/account/two-factor-auth.spec.ts | 32 +++----- .../objectStorage/access-keys.smoke.spec.ts | 75 +++++++------------ .../support/intercepts/object-storage.ts | 28 +++---- .../manager/cypress/support/util/random.ts | 8 +- packages/manager/scripts/prebuild.mjs | 2 +- 9 files changed, 101 insertions(+), 141 deletions(-) diff --git a/packages/manager/cypress/e2e/core/account/add-oauth-app.spec.ts b/packages/manager/cypress/e2e/core/account/add-oauth-app.spec.ts index 4287cff44e2..8be953fee98 100644 --- a/packages/manager/cypress/e2e/core/account/add-oauth-app.spec.ts +++ b/packages/manager/cypress/e2e/core/account/add-oauth-app.spec.ts @@ -7,7 +7,7 @@ import { mockGetOAuthApps, } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { randomLabel, randomSecret } from 'support/util/random'; +import { randomLabel, randomHex } from 'support/util/random'; import { OAuthClient } from '@linode/api-v4/types'; /** @@ -128,14 +128,20 @@ describe('Add OAuth Apps', () => { * - Confirms that the oauth app is listed correctly on OAuth Apps landing page. */ it('Adds an OAuth App', () => { - const oauthApps = oauthClientFactory.buildList(2); + const oauthApps = [ + oauthClientFactory.build({ + label: randomLabel(), + secret: randomHex(64), + }), + oauthClientFactory.build({ + label: randomLabel(), + secret: randomHex(64), + public: true, + }), + ]; + const privateOauthApp = oauthApps[0]; - privateOauthApp.label = randomLabel(5); - privateOauthApp.secret = randomSecret(64); const publicOauthApp = oauthApps[1]; - publicOauthApp.label = randomLabel(5); - publicOauthApp.public = true; - publicOauthApp.secret = randomSecret(64); interceptGetProfile().as('getProfile'); cy.visitWithLogin('/profile/clients'); diff --git a/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts b/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts index 37837a7f7b4..e8921ef1e18 100644 --- a/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts +++ b/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts @@ -23,11 +23,9 @@ describe('Personal access tokens', () => { * - Confirms that user can open and close "View Scopes" drawer */ it('can create personal access tokens', () => { - const tokenLabel = randomLabel(); - const tokenSecret = randomString(64); const token = appTokenFactory.build({ - label: tokenLabel, - token: tokenSecret, + label: randomLabel(), + token: randomString(64), }); mockGetPersonalAccessTokens([]).as('getTokens'); @@ -80,7 +78,7 @@ describe('Personal access tokens', () => { .should('be.visible') .should('be.enabled') .click() - .type(tokenLabel); + .type(token.label); ui.buttonGroup .findButtonByTitle('Create Token') @@ -108,7 +106,7 @@ describe('Personal access tokens', () => { // Confirm that PAT is shown. cy.get('[data-testid="textfield-input"]') .should('be.visible') - .should('have.attr', 'value', tokenSecret); + .should('have.attr', 'value', token.token); ui.button .findByTitle('I Have Saved My Personal Access Token') @@ -119,7 +117,7 @@ describe('Personal access tokens', () => { // Confirm that new PAT is shown in list and "View Scopes" drawer works. cy.wait('@getTokens'); - cy.findByText(tokenLabel) + cy.findByText(token.label) .should('be.visible') .closest('tr') .within(() => { @@ -131,7 +129,7 @@ describe('Personal access tokens', () => { }); ui.drawer - .findByTitle(tokenLabel) + .findByTitle(token.label) .should('be.visible') .within(() => { ui.drawerCloseButton.find().click(); @@ -144,17 +142,14 @@ describe('Personal access tokens', () => { * - Confirms that token is removed from list after revoking it */ it('can rename and revoke personal access tokens', () => { - const tokenOldLabel = randomLabel(); - const tokenNewLabel = randomLabel(); - const oldToken: Token = appTokenFactory.build({ - label: tokenOldLabel, + label: randomLabel(), token: randomString(64), }); const newToken: Token = { ...oldToken, - label: tokenNewLabel, + label: randomLabel(), }; mockGetPersonalAccessTokens([oldToken]).as('getTokens'); @@ -166,7 +161,7 @@ describe('Personal access tokens', () => { cy.wait(['@getTokens', '@getAppTokens']); // Find token in list, click "Rename", and fill out and submit form. - cy.findByText(tokenOldLabel) + cy.findByText(oldToken.label) .should('be.visible') .closest('tr') .within(() => { @@ -185,7 +180,7 @@ describe('Personal access tokens', () => { .should('be.visible') .click() .clear() - .type(tokenNewLabel); + .type(newToken.label); ui.buttonGroup .findButtonByTitle('Save') @@ -196,7 +191,7 @@ describe('Personal access tokens', () => { // Confirm that token has been renamed, initiate revocation. cy.wait('@updateToken'); - cy.findByText(tokenNewLabel) + cy.findByText(newToken.label) .should('be.visible') .closest('tr') .within(() => { @@ -209,7 +204,7 @@ describe('Personal access tokens', () => { mockGetPersonalAccessTokens([]).as('getTokens'); ui.dialog - .findByTitle(`Revoke ${tokenNewLabel}?`) + .findByTitle(`Revoke ${newToken.label}?`) .should('be.visible') .within(() => { ui.buttonGroup @@ -221,11 +216,11 @@ describe('Personal access tokens', () => { // Confirm that token is removed from list after revoking. cy.wait(['@revokeToken', '@getTokens']); - ui.toast.assertMessage(`Successfully revoked ${tokenNewLabel}`); + ui.toast.assertMessage(`Successfully revoked ${newToken.label}`); cy.findByLabelText('List of Personal Access Tokens') .should('be.visible') .within(() => { - cy.findByText(tokenNewLabel).should('not.exist'); + cy.findByText(newToken.label).should('not.exist'); cy.findByText('No items to display.').should('be.visible'); }); }); diff --git a/packages/manager/cypress/e2e/core/account/reset-oauth-app.spec.ts b/packages/manager/cypress/e2e/core/account/reset-oauth-app.spec.ts index ecf6f582fe1..55f95ff2b89 100644 --- a/packages/manager/cypress/e2e/core/account/reset-oauth-app.spec.ts +++ b/packages/manager/cypress/e2e/core/account/reset-oauth-app.spec.ts @@ -1,4 +1,4 @@ -import { fbtVisible, fbtClick, fbltClick } from 'support/helpers'; +import { fbtVisible, fbtClick } from 'support/helpers'; import { oauthClientFactory } from '@src/factories'; import 'cypress-file-upload'; import { @@ -7,7 +7,7 @@ import { mockResetOAuthApps, } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { randomLabel, randomSecret } from 'support/util/random'; +import { randomLabel, randomHex } from 'support/util/random'; describe('Reset OAuth Apps', () => { /* @@ -16,15 +16,13 @@ describe('Reset OAuth Apps', () => { * - Confirms that the oauth app is reset correctly on OAuth Apps landing page. */ it('Resets an OAuth App', () => { - const oauthApps = oauthClientFactory.buildList(2); - const privateOauthApp = oauthApps[0]; - privateOauthApp.label = randomLabel(5); - const publicOauApp = oauthApps[1]; - publicOauApp.label = randomLabel(5); - publicOauApp.public = true; + const privateOauthApp = oauthClientFactory.build({ + label: randomLabel(5), + secret: randomHex(64), + }); interceptGetProfile().as('getProfile'); - mockGetOAuthApps(oauthApps).as('getOAuthApps'); + mockGetOAuthApps([privateOauthApp]).as('getOAuthApps'); cy.visitWithLogin('/profile/clients'); cy.wait('@getProfile'); cy.wait('@getOAuthApps'); @@ -57,7 +55,7 @@ describe('Reset OAuth Apps', () => { fbtVisible('Reset'); fbtClick('Reset'); }); - privateOauthApp['secret'] = randomSecret(64); + mockResetOAuthApps(privateOauthApp.id, privateOauthApp).as('resetOAuthApp'); ui.dialog .findByTitle(`Reset secret for ${privateOauthApp.label}?`) diff --git a/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts b/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts index 2680d5a18f2..0662d0730c5 100644 --- a/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts +++ b/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts @@ -14,16 +14,12 @@ import { authenticate } from 'support/api/authentication'; authenticate(); describe('Third party access tokens', () => { - let tokenLabel: string; - let tokenSecret: string; let token: Token; beforeEach(() => { - tokenLabel = randomLabel(); - tokenSecret = randomString(64); token = appTokenFactory.build({ - label: tokenLabel, - token: tokenSecret, + label: randomLabel(), + token: randomString(64), }); mockGetPersonalAccessTokens([]).as('getTokens'); @@ -38,10 +34,10 @@ describe('Third party access tokens', () => { * - Confirms that third party apps are listed with expected information. */ it('Third party access tokens are listed with expected information', () => { - cy.findByText(tokenLabel) + cy.findByText(token.label) .closest('tr') .within(() => { - cy.findByText(tokenLabel).should('be.visible'); + cy.findByText(token.label).should('be.visible'); cy.defer(getProfile()).then((profile: Profile) => { const dateFormatOptions = { timezone: profile.timezone }; cy.findByText(formatDate(token.created, dateFormatOptions)).should( @@ -61,13 +57,13 @@ describe('Third party access tokens', () => { Linodes: 2, }); - cy.findByText(tokenLabel) + cy.findByText(token.label) .closest('tr') .within(() => { ui.button.findByTitle('View Scopes').should('be.visible').click(); }); ui.drawer - .findByTitle(tokenLabel) + .findByTitle(token.label) .should('be.visible') .within(() => { Object.keys(access).forEach((key) => { @@ -88,13 +84,13 @@ describe('Third party access tokens', () => { */ it('Revokes a third party access token', () => { // Cancelling will keep the list unchanged. - cy.findByText(tokenLabel) + cy.findByText(token.label) .closest('tr') .within(() => { ui.button.findByTitle('Revoke').should('be.visible').click(); }); ui.dialog - .findByTitle(`Revoke ${tokenLabel}?`) + .findByTitle(`Revoke ${token.label}?`) .should('be.visible') .within(() => { ui.buttonGroup @@ -106,13 +102,13 @@ describe('Third party access tokens', () => { // Confirms revoke will remove the third party app. mockRevokeAppToken(token.id).as('deleteAppToken'); - cy.findByText(tokenLabel) + cy.findByText(token.label) .closest('tr') .within(() => { ui.button.findByTitle('Revoke').should('be.visible').click(); }); ui.dialog - .findByTitle(`Revoke ${tokenLabel}?`) + .findByTitle(`Revoke ${token.label}?`) .should('be.visible') .within(() => { ui.buttonGroup @@ -127,6 +123,6 @@ describe('Third party access tokens', () => { mockGetAppTokens([]).as('getAppTokens'); cy.visitWithLogin('/profile/tokens'); cy.wait(['@getTokens', '@getAppTokens']); - cy.findByText(tokenLabel).should('not.exist'); + cy.findByText(token.label).should('not.exist'); }); }); diff --git a/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts b/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts index 03fba9bdfad..8843be2f2b0 100644 --- a/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts +++ b/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts @@ -15,7 +15,12 @@ import { mockGetSecurityQuestions, } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { randomNumber, randomLabel, randomString } from 'support/util/random'; +import { + randomNumber, + randomLabel, + randomString, + randomHex, +} from 'support/util/random'; /** * Returns a Cypress chainable for the "Two-Factor Authentication". @@ -26,23 +31,6 @@ const getTwoFactorSection = (): Cypress.Chainable => { return cy.contains('h3', 'Two-Factor Authentication (2FA)').parent(); }; -/** - * Generates a random 2FA secret key for mocking. - * - * @returns 2FA secret key. - */ -const randomSecret = (): string => { - const randomSecretOptions = { - lowercase: false, - uppercase: true, - numbers: true, - symbols: false, - spaces: false, - }; - - return randomString(16, randomSecretOptions); -}; - /** * Generates a random 2FA scratch code for mocking. * @@ -155,7 +143,7 @@ describe('Two-factor authentication', () => { it('can enable two factor auth', () => { const invalidToken = randomToken(); const validToken = randomToken(); - const mockedSecret = randomSecret(); + const mockedKey = randomHex(16); const mockedScratchCode = randomScratchCode(); // Mock profile data to ensure that 2FA is disabled. @@ -168,7 +156,7 @@ describe('Two-factor authentication', () => { cy.wait('@getSecurityQuestions'); getTwoFactorSection().within(() => { - mockEnableTwoFactorAuth(mockedSecret).as('enableTwoFactorAuth'); + mockEnableTwoFactorAuth(mockedKey).as('enableTwoFactorAuth'); ui.toggle .find() @@ -181,7 +169,7 @@ describe('Two-factor authentication', () => { cy.findByLabelText('Secret Key') .should('be.visible') - .should('have.value', mockedSecret); + .should('have.value', mockedKey); // Type an invalid token first, confirm that error message appears as expected. cy.findByLabelText('Token').should('be.visible').type(invalidToken); @@ -312,7 +300,7 @@ describe('Two-factor authentication', () => { cy.wait('@getSecurityQuestions'); getTwoFactorSection().within(() => { - mockEnableTwoFactorAuth(randomSecret()).as('resetTwoFactorAuth'); + mockEnableTwoFactorAuth(randomHex(16)).as('resetTwoFactorAuth'); // Confirm that reset link is present, click on it. cy.findByText('Reset two-factor authentication') diff --git a/packages/manager/cypress/e2e/core/objectStorage/access-keys.smoke.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/access-keys.smoke.spec.ts index eb14432ee28..7b9f44abe2f 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/access-keys.smoke.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/access-keys.smoke.spec.ts @@ -8,7 +8,6 @@ import { mockDeleteAccessKey, mockGetAccessKeys, } from 'support/intercepts/object-storage'; -import { paginateResponse } from 'support/util/paginate'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { ui } from 'support/ui'; @@ -20,20 +19,14 @@ describe('object storage access keys smoke tests', () => { * - Confirms access key is listed in table. */ it('can create access key - smoke', () => { - const keyLabel = randomLabel(); - // Mocked key values - const accessKey = randomString(20); - const secretKey = randomString(39); - - mockGetAccessKeys(paginateResponse([])).as('getKeys'); + const mockAccessKey = objectStorageKeyFactory.build({ + label: randomLabel(), + access_key: randomString(20), + secret_key: randomString(39), + }); - mockCreateAccessKey({ - body: objectStorageKeyFactory.build({ - label: keyLabel, - access_key: accessKey, - secret_key: secretKey, - }), - }).as('createKey'); + mockGetAccessKeys([]).as('getKeys'); + mockCreateAccessKey(mockAccessKey).as('createKey'); cy.visitWithLogin('object-storage/access-keys'); cy.wait('@getKeys'); @@ -44,21 +37,12 @@ describe('object storage access keys smoke tests', () => { cy.findByText('Create Access Key').should('be.visible').click(); }); - mockGetAccessKeys( - paginateResponse( - objectStorageKeyFactory.build({ - label: keyLabel, - access_key: accessKey, - secret_key: secretKey, - }) - ) - ).as('getKeys'); - + mockGetAccessKeys([mockAccessKey]).as('getKeys'); ui.drawer .findByTitle('Create Access Key') .should('be.visible') .within(() => { - cy.findByLabelText('Label').click().type(keyLabel); + cy.findByLabelText('Label').click().type(mockAccessKey.label); ui.buttonGroup .findButtonByTitle('Create Access Key') .should('be.visible') @@ -74,10 +58,10 @@ describe('object storage access keys smoke tests', () => { .within(() => { cy.get('input[id="access-key"]') .should('be.visible') - .should('have.value', accessKey); + .should('have.value', mockAccessKey.access_key); cy.get('input[id="secret-key"]') .should('be.visible') - .should('have.value', secretKey); + .should('have.value', mockAccessKey.secret_key); ui.buttonGroup .findButtonByTitle('I Have Saved My Secret Key') @@ -87,8 +71,8 @@ describe('object storage access keys smoke tests', () => { }); cy.findByLabelText('List of Object Storage Access Keys').within(() => { - cy.findByText(keyLabel).should('be.visible'); - cy.findByText(accessKey).should('be.visible'); + cy.findByText(mockAccessKey.label).should('be.visible'); + cy.findByText(mockAccessKey.access_key).should('be.visible'); }); }); @@ -99,39 +83,30 @@ describe('object storage access keys smoke tests', () => { * - Confirms access key is no longer listed in table. */ it('can revoke access key - smoke', () => { - const keyId = randomNumber(1, 99999); - const keyLabel = randomLabel(); - // Mocked key values - const accessKey = randomString(20); - const secretKey = randomString(39); + const accessKey = objectStorageKeyFactory.build({ + label: randomLabel(), + id: randomNumber(1, 99999), + access_key: randomString(20), + secret_key: randomString(39), + }); // Mock initial GET request to include an access key. - mockGetAccessKeys( - paginateResponse( - objectStorageKeyFactory.build({ - id: keyId, - label: keyLabel, - access_key: accessKey, - secret_key: secretKey, - }) - ) - ).as('getKeys'); - - mockDeleteAccessKey(keyId).as('deleteKey'); + mockGetAccessKeys([accessKey]).as('getKeys'); + mockDeleteAccessKey(accessKey.id).as('deleteKey'); cy.visitWithLogin('/object-storage/access-keys'); cy.wait('@getKeys'); cy.findByLabelText('List of Object Storage Access Keys').within(() => { - cy.findByText(keyLabel).should('be.visible'); - cy.findByText(accessKey).should('be.visible'); + cy.findByText(accessKey.label).should('be.visible'); + cy.findByText(accessKey.access_key).should('be.visible'); cy.findByText('Revoke').should('be.visible').click(); }); // Mock next GET request to respond with no data to reflect key revocation. - mockGetAccessKeys(paginateResponse([])).as('getKeys'); + mockGetAccessKeys([]).as('getKeys'); - ui.dialog.findByTitle(`Revoking ${keyLabel}`).within(() => { + ui.dialog.findByTitle(`Revoking ${accessKey.label}`).within(() => { ui.buttonGroup .findButtonByTitle('Revoke') .should('be.visible') diff --git a/packages/manager/cypress/support/intercepts/object-storage.ts b/packages/manager/cypress/support/intercepts/object-storage.ts index 1619ef5de53..9ab02e22b75 100644 --- a/packages/manager/cypress/support/intercepts/object-storage.ts +++ b/packages/manager/cypress/support/intercepts/object-storage.ts @@ -2,9 +2,11 @@ * @file Cypress intercepts and mocks for Cloud Manager Object Storage operations. */ +import type { ObjectStorageKey } from '@linode/api-v4'; import { Response } from 'support/util/response'; import { sequentialStub } from 'support/stubs/sequential-stub'; import { apiMatcher } from 'support/util/intercepts'; +import { makeResponse } from 'support/util/response'; import { paginateResponse } from 'support/util/paginate'; import { objectStorageBucketFactory } from 'src/factories/objectStorage'; @@ -308,13 +310,13 @@ export const interceptGetAccessKeys = (): Cypress.Chainable => { * @returns Cypress chainable. */ export const mockGetAccessKeys = ( - response: Partial + accessKeys: ObjectStorageKey[] ): Cypress.Chainable => { - return cy.intercept('GET', apiMatcher('object-storage/keys*'), { - statusCode: 200, - body: {}, - ...response, - }); + return cy.intercept( + 'GET', + apiMatcher('object-storage/keys*'), + paginateResponse(accessKeys) + ); }; /** @@ -329,18 +331,18 @@ export const interceptCreateAccessKey = (): Cypress.Chainable => { /** * Intercepts object storage access key POST request and mocks response. * - * @param response - Mocked response. + * @param accessKey - Access key with which to mock response. * * @returns Cypress chainable. */ export const mockCreateAccessKey = ( - response: Partial + accessKey: ObjectStorageKey ): Cypress.Chainable => { - return cy.intercept('POST', apiMatcher('object-storage/keys'), { - statusCode: 200, - body: {}, - ...response, - }); + return cy.intercept( + 'POST', + apiMatcher('object-storage/keys'), + makeResponse(accessKey) + ); }; /** diff --git a/packages/manager/cypress/support/util/random.ts b/packages/manager/cypress/support/util/random.ts index a6a8d5f0383..2a3d9a30851 100644 --- a/packages/manager/cypress/support/util/random.ts +++ b/packages/manager/cypress/support/util/random.ts @@ -228,13 +228,13 @@ export const randomUuid = (): string => { }; /** - * Returns a random secret of fixed length. + * Returns a random hexadecimal string of a given length. * - * @param length - Length of the secret strings. + * @param length - Length of the hexadecimal string. * - * @returns Random secret. + * @returns Random hexadecimal string. */ -export const randomSecret = (length: number = 64): string => { +export const randomHex = (length: number = 64): string => { const hexNumber = '0123456789abcdef'; const characterSelection = hexNumber.split(''); diff --git a/packages/manager/scripts/prebuild.mjs b/packages/manager/scripts/prebuild.mjs index 599bcb28b08..7bcf187078c 100644 --- a/packages/manager/scripts/prebuild.mjs +++ b/packages/manager/scripts/prebuild.mjs @@ -58,7 +58,7 @@ async function prebuild() { await Promise.all(requests); console.log('Caching successful'); } catch (error) { - console.error('Caching failed', e); + console.error('Caching failed', error); } }