Skip to content

Commit

Permalink
fix: [M3-7706, 9005] - Set VPC interface as primary when creating a L…
Browse files Browse the repository at this point in the history
…inode (#11450)

* update getInterfaces test

* Added changeset: VPC interface not being set as the primary interface when creating a Linode

* address feedback pt1

* feedback pt2 - add test coverage for unrecommended config notice

* Added changeset: Add cypress tests confirming Lionde Config Unrecommended status displays as expected in VPC Subnet table
  • Loading branch information
coliu-akamai authored Jan 2, 2025
1 parent 260903f commit 3a93d2a
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 3 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11450-fixed-1734717495561.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

VPC interface not being set as the primary interface when creating a Linode ([#11450](https://github.com/linode/manager/pull/11450))
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11450-tests-1735836787604.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tests
---

Add cypress tests confirming Lionde Config Unrecommended status displays as expected in VPC Subnet table ([#11450](https://github.com/linode/manager/pull/11450))
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
regionFactory,
subnetFactory,
vpcFactory,
linodeConfigFactory,
LinodeConfigInterfaceFactoryWithVPC,
} from 'src/factories';
import { mockGetLinodeConfigs } from 'support/intercepts/configs';
import {
mockCreateLinode,
mockGetLinodeDetails,
Expand All @@ -12,6 +15,7 @@ import { mockGetRegions } from 'support/intercepts/regions';
import {
mockCreateVPC,
mockCreateVPCError,
mockGetSubnets,
mockGetVPC,
mockGetVPCs,
} from 'support/intercepts/vpc';
Expand All @@ -25,12 +29,14 @@ import {
randomString,
} from 'support/util/random';
import { chooseRegion } from 'support/util/regions';
import { WARNING_ICON_UNRECOMMENDED_CONFIG } from 'src/features/VPCs/constants';

describe('Create Linode with VPCs', () => {
/*
* - Confirms UI flow to create a Linode with an existing VPC assigned using mock API data.
* - Confirms that VPC assignment is reflected in create summary section.
* - Confirms that outgoing API request contains expected VPC interface data.
* - Confirms newly assigned Linode does not have an unrecommended config notice inside VPC
*/
it('can assign existing VPCs during Linode Create flow', () => {
const linodeRegion = chooseRegion({ capabilities: ['VPCs'] });
Expand All @@ -55,6 +61,27 @@ describe('Create Linode with VPCs', () => {
region: linodeRegion.id,
});

const mockInterface = LinodeConfigInterfaceFactoryWithVPC.build({
vpc_id: mockVPC.id,
subnet_id: mockSubnet.id,
primary: true,
active: true,
});

const mockLinodeConfig = linodeConfigFactory.build({
interfaces: [mockInterface],
});

const mockUpdatedSubnet = {
...mockSubnet,
linodes: [
{
id: mockLinode.id,
interfaces: [{ id: mockInterface.id, active: true }],
},
],
};

mockGetVPCs([mockVPC]).as('getVPCs');
mockGetVPC(mockVPC).as('getVPC');
mockCreateLinode(mockLinode).as('createLinode');
Expand Down Expand Up @@ -105,19 +132,35 @@ describe('Create Linode with VPCs', () => {
expect(expectedVpcInterface['ipv4']).to.be.an('object').that.is.empty;
expect(expectedVpcInterface['subnet_id']).to.equal(mockSubnet.id);
expect(expectedVpcInterface['purpose']).to.equal('vpc');
// Confirm that VPC interfaces are always marked as the primary interface
expect(expectedVpcInterface['primary']).to.equal(true);
});

// Confirm redirect to new Linode.
cy.url().should('endWith', `/linodes/${mockLinode.id}`);
// Confirm toast notification should appear on Linode create.
ui.toast.assertMessage(`Your Linode ${mockLinode.label} is being created.`);

// Confirm newly created Linode does not have unrecommended configuration notice
mockGetVPC(mockVPC).as('getVPC');
mockGetSubnets(mockVPC.id, [mockUpdatedSubnet]).as('getSubnets');
mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode');
mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as(
'getLinodeConfigs'
);

cy.visit(`/vpcs/${mockVPC.id}`);
cy.findByLabelText(`expand ${mockSubnet.label} row`).click();
cy.wait('@getLinodeConfigs');
cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist');
});

/*
* - Confirms UI flow to create a Linode with a new VPC assigned using mock API data.
* - Creates a VPC and a subnet from within the Linode Create flow.
* - Confirms that Cloud responds gracefully when VPC create API request fails.
* - Confirms that outgoing API request contains correct VPC interface data.
* - Confirms newly assigned Linode does not have an unrecommended config notice inside VPC
*/
it('can assign new VPCs during Linode Create flow', () => {
const linodeRegion = chooseRegion({ capabilities: ['VPCs'] });
Expand Down Expand Up @@ -145,6 +188,27 @@ describe('Create Linode with VPCs', () => {
region: linodeRegion.id,
});

const mockInterface = LinodeConfigInterfaceFactoryWithVPC.build({
vpc_id: mockVPC.id,
subnet_id: mockSubnet.id,
primary: true,
active: true,
});

const mockLinodeConfig = linodeConfigFactory.build({
interfaces: [mockInterface],
});

const mockUpdatedSubnet = {
...mockSubnet,
linodes: [
{
id: mockLinode.id,
interfaces: [{ id: mockInterface.id, active: true }],
},
],
};

mockGetVPCs([]);
mockCreateLinode(mockLinode).as('createLinode');
cy.visitWithLogin('/linodes/create');
Expand Down Expand Up @@ -232,11 +296,26 @@ describe('Create Linode with VPCs', () => {
expect(expectedVpcInterface['ipv4']).to.deep.equal({ nat_1_1: 'any' });
expect(expectedVpcInterface['subnet_id']).to.equal(mockSubnet.id);
expect(expectedVpcInterface['purpose']).to.equal('vpc');
// Confirm that VPC interfaces are always marked as the primary interface
expect(expectedVpcInterface['primary']).to.equal(true);
});

cy.url().should('endWith', `/linodes/${mockLinode.id}`);
// Confirm toast notification should appear on Linode create.
ui.toast.assertMessage(`Your Linode ${mockLinode.label} is being created.`);

// Confirm newly created Linode does not have unrecommended configuration notice
mockGetVPC(mockVPC).as('getVPC');
mockGetSubnets(mockVPC.id, [mockUpdatedSubnet]).as('getSubnets');
mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode');
mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as(
'getLinodeConfigs'
);

cy.visit(`/vpcs/${mockVPC.id}`);
cy.findByLabelText(`expand ${mockSubnet.label} row`).click();
cy.wait('@getLinodeConfigs');
cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist');
});

/*
Expand Down
125 changes: 124 additions & 1 deletion packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@ import {
mockEditSubnet,
mockGetSubnets,
} from 'support/intercepts/vpc';
import { subnetFactory, vpcFactory } from '@src/factories';
import { mockGetLinodeConfigs } from 'support/intercepts/configs';
import { mockGetLinodeDetails } from 'support/intercepts/linodes';
import {
linodeFactory,
linodeConfigFactory,
LinodeConfigInterfaceFactoryWithVPC,
subnetFactory,
vpcFactory,
} from '@src/factories';
import { randomLabel, randomNumber, randomPhrase } from 'support/util/random';
import { chooseRegion } from 'support/util/regions';
import type { VPC } from '@linode/api-v4';
import { getRegionById } from 'support/util/regions';
import { ui } from 'support/ui';
import { WARNING_ICON_UNRECOMMENDED_CONFIG } from 'src/features/VPCs/constants';

describe('VPC details page', () => {
/**
Expand Down Expand Up @@ -266,4 +276,117 @@ describe('VPC details page', () => {
cy.findByText('No Subnets are assigned.');
cy.findByText(mockEditedSubnet.label).should('not.exist');
});

/**
* - Confirms UI for Linode with a recommended config (no notice displayed)
*/
it('does not display an unrecommended config notice for a Linode', () => {
const linodeRegion = chooseRegion({ capabilities: ['VPCs'] });

const mockInterfaceId = randomNumber();
const mockLinode = linodeFactory.build({
id: randomNumber(),
label: randomLabel(),
region: linodeRegion.id,
});

const mockSubnet = subnetFactory.build({
id: randomNumber(),
label: randomLabel(),
linodes: [
{
id: mockLinode.id,
interfaces: [{ id: mockInterfaceId, active: true }],
},
],
ipv4: '10.0.0.0/24',
});

const mockVPC = vpcFactory.build({
id: randomNumber(),
label: randomLabel(),
region: linodeRegion.id,
subnets: [mockSubnet],
});

const mockInterface = LinodeConfigInterfaceFactoryWithVPC.build({
vpc_id: mockVPC.id,
subnet_id: mockSubnet.id,
primary: true,
active: true,
});

const mockLinodeConfig = linodeConfigFactory.build({
interfaces: [mockInterface],
});

mockGetVPC(mockVPC).as('getVPC');
mockGetSubnets(mockVPC.id, [mockSubnet]).as('getSubnets');
mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode');
mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as(
'getLinodeConfigs'
);

cy.visitWithLogin(`/vpcs/${mockVPC.id}`);
cy.findByLabelText(`expand ${mockSubnet.label} row`).click();
cy.wait('@getLinodeConfigs');
cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist');
});

/**
* - Confirms UI for Linode with an unrecommended config (notice displayed)
*/
it('displays an unrecommended config notice for a Linode', () => {
const linodeRegion = chooseRegion({ capabilities: ['VPCs'] });

const mockInterfaceId = randomNumber();
const mockLinode = linodeFactory.build({
id: randomNumber(),
label: randomLabel(),
region: linodeRegion.id,
});

const mockSubnet = subnetFactory.build({
id: randomNumber(),
label: randomLabel(),
linodes: [
{
id: mockLinode.id,
interfaces: [{ id: mockInterfaceId, active: true }],
},
],
ipv4: '10.0.0.0/24',
});

const mockVPC = vpcFactory.build({
id: randomNumber(),
label: randomLabel(),
region: linodeRegion.id,
subnets: [mockSubnet],
});

const mockInterface = LinodeConfigInterfaceFactoryWithVPC.build({
id: mockInterfaceId,
vpc_id: mockVPC.id,
subnet_id: mockSubnet.id,
primary: false,
active: true,
});

const mockLinodeConfig = linodeConfigFactory.build({
interfaces: [mockInterface],
});

mockGetVPC(mockVPC).as('getVPC');
mockGetSubnets(mockVPC.id, [mockSubnet]).as('getSubnets');
mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode');
mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as(
'getLinodeConfigs'
);

cy.visitWithLogin(`/vpcs/${mockVPC.id}`);
cy.findByLabelText(`expand ${mockSubnet.label} row`).click();
cy.wait('@getLinodeConfigs');
cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('exist');
});
});
5 changes: 3 additions & 2 deletions packages/manager/src/factories/subnets.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {
import Factory from 'src/factories/factoryProxy';

import type {
Subnet,
SubnetAssignedLinodeData,
} from '@linode/api-v4/lib/vpcs/types';
import Factory from 'src/factories/factoryProxy';

// NOTE: Changing to fixed array length for the interfaces and linodes fields of the
// subnetAssignedLinodeDataFactory and subnetFactory respectively -- see [M3-7227] for more details
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
// Confirms VPC interface passed in is returned as expected - VPC interfaces should be marked as primary if they exist
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -177,6 +179,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -190,6 +193,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -210,6 +214,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -228,6 +233,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -248,6 +254,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -266,6 +273,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand All @@ -286,6 +294,7 @@ describe('getInterfacesPayload', () => {
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
vpc_id: 5,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ const defaultInterfaces: InterfacePayload[] = [
{
ipam_address: '',
label: '',
primary: true,
purpose: 'vpc',
},
{
Expand Down

0 comments on commit 3a93d2a

Please sign in to comment.