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

test: [M3-7053] - Add Cypress integration test for VPC create flow #9730

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-9730-tests-1696881818677.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tests
---

Cypress tests for VPC create flows ([#9730](https://github.com/linode/manager/pull/9730))
346 changes: 346 additions & 0 deletions packages/manager/cypress/e2e/core/vpc/vpc-create.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
/**
* @file Integration tests for VPC create flow.
*/

import type { Subnet, VPC } from '@linode/api-v4';
import { vpcFactory, subnetFactory, linodeFactory } from '@src/factories';
import {
mockAppendFeatureFlags,
mockGetFeatureFlagClientstream,
} from 'support/intercepts/feature-flags';
import {
mockCreateVPCError,
mockCreateVPC,
mockGetSubnets,
} from 'support/intercepts/vpc';
import { makeFeatureFlagData } from 'support/util/feature-flags';
import {
randomLabel,
randomPhrase,
randomIp,
randomNumber,
randomString,
} from 'support/util/random';
import { chooseRegion } from 'support/util/regions';
import { ui } from 'support/ui';
import { buildArray } from 'support/util/arrays';
import { getUniqueLinodesFromSubnets } from 'src/features/VPCs/utils';

/**
* Gets the "Add another Subnet" section with the given index.
*
* @returns Cypress chainable.
*/
const getSubnetNodeSection = (index: number) => {
return cy.get(`[data-qa-subnet-node="${index}"]`);
};

describe('VPC create flow', () => {
/*
* - Confirms VPC creation flow using mock API data.
* - Confirms that users can create and delete subnets.
* - Confirms client side validation when entering invalid IP ranges.
* - Confirms that UI handles API errors gracefully.
* - Confirms that UI redirects to created VPC page after creating a VPC.
*/
it('can create a VPC', () => {
const vpcRegion = chooseRegion();

const mockSubnets: Subnet[] = buildArray(3, (index: number) => {
return subnetFactory.build({
label: randomLabel(),
id: randomNumber(10000, 99999),
ipv4: `${randomIp()}/${randomNumber(0, 32)}`,
linodes: linodeFactory.buildList(index + 1),
});
});

const mockSubnetToDelete: Subnet = subnetFactory.build();
const mockInvalidIpRange = `${randomIp()}/${randomNumber(33, 100)}`;

const mockVpc: VPC = vpcFactory.build({
id: randomNumber(10000, 99999),
label: randomLabel(),
region: vpcRegion.id,
description: randomPhrase(),
subnets: mockSubnets,
});

const ipValidationErrorMessage = 'The IPv4 range must be in CIDR format';
const vpcCreationErrorMessage = 'An unknown error has occurred.';
const totalSubnetUniqueLinodes = getUniqueLinodesFromSubnets(mockSubnets);

mockAppendFeatureFlags({
vpc: makeFeatureFlagData(true),
}).as('getFeatureFlags');
mockGetFeatureFlagClientstream().as('getClientstream');

cy.visitWithLogin('/vpcs/create');
cy.wait(['@getFeatureFlags', '@getClientstream']);

cy.findByText('Region')
.should('be.visible')
.click()
.type(`${vpcRegion.label}{enter}`);

cy.findByText('VPC Label').should('be.visible').click().type(mockVpc.label);

cy.findByText('Description')
.should('be.visible')
.click()
.type(mockVpc.description);

// Fill out the first Subnet.
// Insert an invalid empty IP range to confirm client side validation.
getSubnetNodeSection(0)
.should('be.visible')
.within(() => {
cy.findByText('Subnet Label')
.should('be.visible')
.click()
.type(mockSubnets[0].label);

cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`);
});

ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.findByText(ipValidationErrorMessage).should('be.visible');

// Enter a random non-IP address string to further test client side validation.
cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`)
.type(randomString(18));

ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.findByText(ipValidationErrorMessage).should('be.visible');

// Enter a valid IP address with an invalid network prefix to further test client side validation.
cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`)
.type(mockInvalidIpRange);

ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.findByText(ipValidationErrorMessage).should('be.visible');

// Replace invalid IP address range with valid range.
cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`)
.type(mockSubnets[0].ipv4!);

// Add another subnet that we will remove later.
ui.button
.findByTitle('Add another Subnet')
.should('be.visible')
.should('be.enabled')
.click();

// Fill out subnet section, but leave label blank, then attempt to create
// VPC with missing subnet label.
getSubnetNodeSection(1)
.should('be.visible')
.within(() => {
cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`)
.type(mockSubnetToDelete.ipv4!);
});

ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

// Confirm that label validation message is displayed, then remove the
// subnet and confirm that UI responds accordingly.
getSubnetNodeSection(1)
.should('be.visible')
.within(() => {
cy.findByText('Label is required').should('be.visible');

// Delete subnet.
cy.findByLabelText('Remove Subnet')
.should('be.visible')
.should('be.enabled')
.click();
});

getSubnetNodeSection(1).should('not.exist');
cy.findByText(mockSubnetToDelete.label).should('not.exist');

// Continue adding remaining subnets.
mockSubnets.slice(1).forEach((mockSubnet: Subnet, index: number) => {
ui.button
.findByTitle('Add another Subnet')
.should('be.visible')
.should('be.enabled')
.click();

getSubnetNodeSection(index + 1)
.should('be.visible')
.within(() => {
cy.findByText('Subnet Label')
.should('be.visible')
.click()
.type(mockSubnet.label);

cy.findByText('Subnet IP Address Range')
.should('be.visible')
.click()
.type(`{selectAll}{backspace}`)
.type(`${randomIp()}/${randomNumber(0, 32)}`);
});
});

// Click "Create VPC", mock an HTTP 500 error and confirm UI displays the message.
mockCreateVPCError(vpcCreationErrorMessage).as('createVPC');
ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.wait('@createVPC');
cy.findByText(vpcCreationErrorMessage).should('be.visible');

// Click "Create VPC", mock a successful response and confirm that Cloud
// redirects to the VPC details page for the new VPC.
mockCreateVPC(mockVpc).as('createVPC');
mockGetSubnets(mockVpc.id, mockVpc.subnets).as('getSubnets');
ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.wait('@createVPC');
cy.url().should('endWith', `/vpcs/${mockVpc.id}`);
cy.wait('@getSubnets');

// Confirm that new VPC information is displayed on details page as expected.
cy.findByText(mockVpc.label).should('be.visible');
cy.get('[data-qa-vpc-summary]')
.should('be.visible')
.within(() => {
cy.contains(`Subnets ${mockVpc.subnets.length}`).should('be.visible');
cy.contains(`Linodes ${totalSubnetUniqueLinodes}`).should('be.visible');
cy.contains(`VPC ID ${mockVpc.id}`).should('be.visible');
cy.contains(`Region ${vpcRegion.label}`).should('be.visible');
});

mockSubnets.forEach((mockSubnet: Subnet) => {
cy.findByText(mockSubnet.label)
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText(mockSubnet.id).should('be.visible');
cy.findByText(mockSubnet.ipv4!).should('be.visible');
cy.findByText(mockSubnet.linodes.length).should('be.visible');
});
});
});

/*
* - Confirms VPC creation flow without creating subnets using mock API data.
* - Confirms that users can delete the pre-existing subnet in the create form.
* - Confirms that "Add another Subnet" button label updates to reflect no subnets.
* - Confirms that Cloud Manager UI responds accordingly when creating a VPC without subnets.
*/
it('can create a VPC without any subnets', () => {
const vpcRegion = chooseRegion();
const mockVpc: VPC = vpcFactory.build({
id: randomNumber(10000, 99999),
label: randomLabel(),
region: vpcRegion.id,
description: randomPhrase(),
subnets: [],
});

const totalSubnetUniqueLinodes = getUniqueLinodesFromSubnets([]);

mockAppendFeatureFlags({
vpc: makeFeatureFlagData(true),
}).as('getFeatureFlags');
mockGetFeatureFlagClientstream().as('getClientstream');

cy.visitWithLogin('/vpcs/create');
cy.wait(['@getFeatureFlags', '@getClientstream']);

cy.findByText('Region')
.should('be.visible')
.click()
.type(`${vpcRegion.label}{enter}`);

cy.findByText('VPC Label').should('be.visible').click().type(mockVpc.label);

// Remove the subnet.
getSubnetNodeSection(0)
.should('be.visible')
.within(() => {
cy.findByLabelText('Remove Subnet')
.should('be.visible')
.should('be.enabled')
.click();
});

// Confirm that subnet button label is "Add a Subnet" when there are no
// subnets.
mockCreateVPC(mockVpc).as('createVpc');
mockGetSubnets(mockVpc.id, []).as('getSubnets');
ui.button
.findByTitle('Add a Subnet')
.should('be.visible')
.should('be.enabled');

cy.findByText('Add another Subnet').should('not.exist');

// Create the VPC and confirm the user is redirected to the details page.
ui.button
.findByTitle('Create VPC')
.should('be.visible')
.should('be.enabled')
.click();

cy.wait('@createVpc');
cy.url().should('endWith', `/vpcs/${mockVpc.id}`);
cy.wait('@getSubnets');

// Confirm that the expected VPC information is shown, and that no Subnets
// are listed in the table.
cy.get('[data-qa-vpc-summary]')
.should('be.visible')
.within(() => {
cy.contains(`Subnets ${mockVpc.subnets.length}`).should('be.visible');
cy.contains(`Linodes ${totalSubnetUniqueLinodes}`).should('be.visible');
cy.contains(`VPC ID ${mockVpc.id}`).should('be.visible');
cy.contains(`Region ${vpcRegion.label}`).should('be.visible');
});

cy.findByText('No Subnets are assigned.').should('be.visible');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ describe('VPC details page', () => {
.findByTitle(`Delete Subnet ${mockSubnet.label}`)
.should('be.visible')
.within(() => {
cy.findByLabelText('Subnet label')
cy.findByLabelText('Subnet Label')
.should('be.visible')
.click()
.type(mockSubnet.label);
Expand Down Expand Up @@ -238,7 +238,7 @@ describe('VPC details page', () => {
.findByTitle('Create Subnet')
.should('be.visible')
.within(() => {
cy.findByText('Subnet label')
cy.findByText('Subnet Label')
.should('be.visible')
.click()
.type(mockSubnet.label);
Expand Down
Loading