From 14dcd373ebe97bbcf9671c18b2ba181e3650b968 Mon Sep 17 00:00:00 2001 From: cliu-akamai <126020611+cliu-akamai@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:41:54 -0400 Subject: [PATCH] test: [M3-7955] - Cypress integration test to add SSH key via Linode Create (#10448) * M3-7955 Cypress integration test to add SSH key via Linode Create * Added changeset: Cypress integration test to add SSH key via Linode Create * Fixed merge issue --- .../pr-10448-tests-1718906550137.md | 5 + .../e2e/core/linodes/create-linode.spec.ts | 211 +++++++++++++++++- .../manager/cypress/support/api/profile.ts | 18 ++ .../manager/cypress/support/util/cleanup.ts | 3 + 4 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 packages/manager/.changeset/pr-10448-tests-1718906550137.md diff --git a/packages/manager/.changeset/pr-10448-tests-1718906550137.md b/packages/manager/.changeset/pr-10448-tests-1718906550137.md new file mode 100644 index 00000000000..7e1b6df51b5 --- /dev/null +++ b/packages/manager/.changeset/pr-10448-tests-1718906550137.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Cypress integration test to add SSH key via Linode Create ([#10448](https://github.com/linode/manager/pull/10448)) diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts index be6fad33848..4f883cefc1d 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts @@ -4,7 +4,7 @@ import { ui } from 'support/ui'; import { chooseRegion } from 'support/util/regions'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomString, randomNumber } from 'support/util/random'; import { LINODE_CREATE_TIMEOUT } from 'support/constants/linodes'; import { cleanUp } from 'support/util/cleanup'; import { linodeCreatePage } from 'support/ui/pages'; @@ -16,13 +16,47 @@ import { import { interceptCreateLinode } from 'support/intercepts/linodes'; import { makeFeatureFlagData } from 'support/util/feature-flags'; import { interceptGetProfile } from 'support/intercepts/profile'; - +import { Region, VLAN, Config, Disk } from '@linode/api-v4'; +import { getRegionById } from 'support/util/regions'; +import { + linodeFactory, + linodeConfigFactory, + VLANFactory, + vpcFactory, + subnetFactory, + regionFactory, + LinodeConfigInterfaceFactory, + LinodeConfigInterfaceFactoryWithVPC, +} from 'src/factories'; +import { dcPricingMockLinodeTypes } from 'support/constants/dc-specific-pricing'; +import { + mockGetLinodeType, + mockGetLinodeTypes, +} from 'support/intercepts/linodes'; +import { mockGetRegions } from 'support/intercepts/regions'; +import { mockGetVLANs } from 'support/intercepts/vlans'; +import { mockGetVPC, mockGetVPCs } from 'support/intercepts/vpc'; +import { + mockCreateLinode, + mockGetLinodeDisks, + mockGetLinodeVolumes, +} from 'support/intercepts/linodes'; +import { mockGetLinodeConfigs } from 'support/intercepts/configs'; +import { + fbtClick, + fbtVisible, + getClick, + getVisible, + containsVisible, +} from 'support/helpers'; +import {} from 'support/helpers'; let username: string; authenticate(); describe('Create Linode', () => { before(() => { cleanUp('linodes'); + cleanUp('ssh-keys'); }); // Enable the `linodeCreateRefactor` feature flag. @@ -143,4 +177,177 @@ describe('Create Linode', () => { }); }); }); + + it('adds an SSH key to the linode during create flow', () => { + const rootpass = randomString(32); + const sshPublicKeyLabel = randomLabel(); + const randomKey = randomString(400, { + uppercase: true, + lowercase: true, + numbers: true, + spaces: false, + symbols: false, + }); + const sshPublicKey = `ssh-rsa e2etestkey${randomKey} e2etest@linode`; + const linodeLabel = randomLabel(); + const region: Region = getRegionById('us-southeast'); + const diskLabel: string = 'Debian 10 Disk'; + const mockLinode = linodeFactory.build({ + label: linodeLabel, + region: region.id, + type: dcPricingMockLinodeTypes[0].id, + }); + const mockVLANs: VLAN[] = VLANFactory.buildList(2); + const mockSubnet = subnetFactory.build({ + id: randomNumber(2), + label: randomLabel(), + }); + const mockVPC = vpcFactory.build({ + id: randomNumber(), + region: 'us-southeast', + subnets: [mockSubnet], + }); + const mockVPCRegion = regionFactory.build({ + id: region.id, + label: region.label, + capabilities: ['Linodes', 'VPCs', 'Vlans'], + }); + const mockPublicConfigInterface = LinodeConfigInterfaceFactory.build({ + ipam_address: null, + purpose: 'public', + }); + const mockVlanConfigInterface = LinodeConfigInterfaceFactory.build(); + const mockVpcConfigInterface = LinodeConfigInterfaceFactoryWithVPC.build({ + vpc_id: mockVPC.id, + purpose: 'vpc', + active: true, + }); + const mockConfig: Config = linodeConfigFactory.build({ + id: randomNumber(), + interfaces: [ + // The order of this array is significant. Index 0 (eth0) should be public. + mockPublicConfigInterface, + mockVlanConfigInterface, + mockVpcConfigInterface, + ], + }); + const mockDisks: Disk[] = [ + { + id: 44311273, + status: 'ready', + label: diskLabel, + created: '2020-08-21T17:26:14', + updated: '2020-08-21T17:26:30', + filesystem: 'ext4', + size: 81408, + }, + { + id: 44311274, + status: 'ready', + label: '512 MB Swap Image', + created: '2020-08-21T17:26:14', + updated: '2020-08-21T17:26:31', + filesystem: 'swap', + size: 512, + }, + ]; + + // Mock requests to get individual types. + mockGetLinodeType(dcPricingMockLinodeTypes[0]); + mockGetLinodeType(dcPricingMockLinodeTypes[1]); + mockGetLinodeTypes(dcPricingMockLinodeTypes).as('getLinodeTypes'); + + mockAppendFeatureFlags({ + vpc: makeFeatureFlagData(true), + }).as('getFeatureFlags'); + mockGetFeatureFlagClientstream().as('getClientStream'); + + mockGetRegions([mockVPCRegion]).as('getRegions'); + + mockGetVLANs(mockVLANs); + mockGetVPC(mockVPC).as('getVPC'); + mockGetVPCs([mockVPC]).as('getVPCs'); + mockCreateLinode(mockLinode).as('linodeCreated'); + mockGetLinodeConfigs(mockLinode.id, [mockConfig]).as('getLinodeConfigs'); + mockGetLinodeDisks(mockLinode.id, mockDisks).as('getDisks'); + mockGetLinodeVolumes(mockLinode.id, []).as('getVolumes'); + + // intercept request + cy.visitWithLogin('/linodes/create'); + cy.wait([ + '@getLinodeTypes', + '@getClientStream', + '@getFeatureFlags', + '@getVPCs', + ]); + + cy.get('[data-qa-header="Create"]').should('have.text', 'Create'); + + // Check the 'Backups' add on + cy.get('[data-testid="backups"]').should('be.visible').click(); + ui.regionSelect.find().click().type(`${region.label} {enter}`); + fbtClick('Shared CPU'); + getClick(`[id="${dcPricingMockLinodeTypes[0].id}"]`); + + // the "VPC" section is present, and the VPC in the same region of + // the linode can be selected. + getVisible('[data-testid="vpc-panel"]').within(() => { + containsVisible('Assign this Linode to an existing VPC.'); + // select VPC + cy.get('[data-qa-enhanced-select="None"]') + .should('be.visible') + .click() + .type(`${mockVPC.label}{enter}`); + // select subnet + cy.findByText('Select Subnet') + .should('be.visible') + .click() + .type(`${mockSubnet.label}{enter}`); + }); + + // The drawer opens when clicking "Add an SSH Key" button + ui.button + .findByTitle('Add an SSH Key') + .should('be.visible') + .should('be.enabled') + .click(); + ui.drawer + .findByTitle('Add SSH Key') + .should('be.visible') + .within(() => { + cy.get('[id="label"]').clear().type(sshPublicKeyLabel); + + // An alert displays when the format of SSH key is incorrect + cy.get('[id="ssh-public-key"]').clear().type('WrongFormatSshKey'); + ui.button + .findByTitle('Add Key') + .should('be.visible') + .should('be.enabled') + .click(); + cy.findAllByText( + 'SSH Key key-type must be ssh-dss, ssh-rsa, ecdsa-sha2-nistp, ssh-ed25519, or sk-ecdsa-sha2-nistp256.' + ).should('be.visible'); + + // Create a new ssh key + cy.get('[id="ssh-public-key"]').clear().type(sshPublicKey); + ui.button + .findByTitle('Add Key') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // When a user creates an SSH key, a toast notification appears that says "Successfully created SSH key." + ui.toast.assertMessage('Successfully created SSH key.'); + + // When a user creates an SSH key, the list of SSH keys for each user updates to show the new key for the signed in user + cy.findAllByText(sshPublicKeyLabel).should('be.visible'); + + getClick('#linode-label').clear().type(linodeLabel); + cy.get('#root-password').type(rootpass); + getClick('[data-qa-deploy-linode]'); + cy.wait('@linodeCreated').its('response.statusCode').should('eq', 200); + fbtVisible(linodeLabel); + cy.contains('RUNNING', { timeout: 300000 }).should('be.visible'); + }); }); diff --git a/packages/manager/cypress/support/api/profile.ts b/packages/manager/cypress/support/api/profile.ts index 60639d8ae03..f30da11e89e 100644 --- a/packages/manager/cypress/support/api/profile.ts +++ b/packages/manager/cypress/support/api/profile.ts @@ -2,6 +2,9 @@ import { OAuthClient, deleteOAuthClient, getOAuthClients, + SSHKey, + deleteSSHKey, + getSSHKeys, } from '@linode/api-v4'; import { isTestLabel } from 'support/api/common'; import { pageSize } from 'support/constants/api'; @@ -26,3 +29,18 @@ export const deleteAllTestOAuthApps = async (): Promise => { await Promise.all(deletionPromises); }; + +export const deleteAllTestSSHKeys = async (): Promise => { + const sshKeys = await depaginate((page: number) => { + return getSSHKeys({ page, page_size: pageSize }); + }); + + const deletionPromises = sshKeys + .filter((sshKey: SSHKey) => isTestLabel(sshKey.label)) + .map(async (sshKey: SSHKey) => { + console.log(`deleting ${sshKey.label}`); + await deleteSSHKey(sshKey.id); + }); + + await Promise.all(deletionPromises); +}; diff --git a/packages/manager/cypress/support/util/cleanup.ts b/packages/manager/cypress/support/util/cleanup.ts index 0621107c4cf..f9696458e8d 100644 --- a/packages/manager/cypress/support/util/cleanup.ts +++ b/packages/manager/cypress/support/util/cleanup.ts @@ -13,6 +13,7 @@ import { import { deleteAllTestStackScripts } from 'support/api/stackscripts'; import { deleteAllTestTags } from 'support/api/tags'; import { deleteAllTestVolumes } from 'support/api/volumes'; +import { deleteAllTestSSHKeys } from 'support/api/profile'; /** Types of resources that can be cleaned up. */ export type CleanUpResource = @@ -27,6 +28,7 @@ export type CleanUpResource = | 'obj-buckets' | 'service-transfers' | 'stackscripts' + | 'ssh-keys' | 'tags' | 'volumes'; @@ -48,6 +50,7 @@ const cleanUpMap: CleanUpMap = { 'obj-buckets': () => deleteAllTestBuckets(), 'service-transfers': () => cancelAllTestEntityTransfers(), stackscripts: () => deleteAllTestStackScripts(), + 'ssh-keys': () => deleteAllTestSSHKeys(), tags: () => deleteAllTestTags(), volumes: () => deleteAllTestVolumes(), };