From 918c260b1500c5320b9ec3ee12b8e30ba1cf6fd9 Mon Sep 17 00:00:00 2001 From: ecarrill Date: Thu, 30 May 2024 05:01:23 -0700 Subject: [PATCH 1/4] test: [M3-7784] - Cypress integration tests for Placement Group update label flow --- .../update-placement-group-label.spec.ts | 153 ++++++++++++++++++ .../support/intercepts/placement-groups.ts | 50 +++++- 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts diff --git a/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts new file mode 100644 index 00000000000..36514088004 --- /dev/null +++ b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts @@ -0,0 +1,153 @@ +/** + * @file Integration tests for Placement Group update label flows. + */ + +import { + mockAppendFeatureFlags, + mockGetFeatureFlagClientstream, +} from 'support/intercepts/feature-flags'; +import { makeFeatureFlagData } from 'support/util/feature-flags'; +import { randomLabel, randomNumber } from 'support/util/random'; +import { + mockGetPlacementGroups, + mockUpdatePlacementGroupLabel, + mockUpdatePlacementGroupLabelError, +} from 'support/intercepts/placement-groups'; +import { accountFactory, placementGroupFactory } from 'src/factories'; +import { mockGetAccount } from 'support/intercepts/account'; +import { authenticate } from 'support/api/authentication'; +import type { Flags } from 'src/featureFlags'; +import { chooseRegion } from 'support/util/regions'; +import { ui } from 'support/ui'; +import type { UpdatePlacementGroupPayload } from '@linode/api-v4'; + +const mockAccount = accountFactory.build(); +authenticate(); + +describe('Placement Group update label flow', () => { + // Mock the VM Placement Groups feature flag to be enabled for each test in this block. + beforeEach(() => { + mockAppendFeatureFlags({ + placementGroups: makeFeatureFlagData({ + beta: true, + enabled: true, + }), + }); + mockGetFeatureFlagClientstream(); + mockGetAccount(mockAccount).as('getAccount'); + }); + + /** + * - Confirms that a Placement Group's label can be updated from the landing page. + * - Confirms that clicking "Edit" opens PG edit drawer. + * - Only the label field is shown in the edit drawer. + * - New label can be entered into the label field. + * - Confirms that Placement Groups landing page updates to reflect successful label update. + * - Confirms a toast notification is shown upon successful label update. + */ + it("update to a Placement Group's label is successful", () => { + const mockPlacementGroupCompliantRegion = chooseRegion(); + + const mockPlacementGroup = placementGroupFactory.build({ + id: randomNumber(), + label: randomLabel(), + region: mockPlacementGroupCompliantRegion.id, + affinity_type: 'anti_affinity:local', + is_compliant: true, + is_strict: false, + members: [], + }); + + const newLabel: UpdatePlacementGroupPayload = { + label: randomLabel(), + }; + + mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); + + mockUpdatePlacementGroupLabel(mockPlacementGroup.id, newLabel).as( + 'updatePlacementGroupLabel' + ); + + cy.visitWithLogin('/placement-groups'); + cy.wait(['@getPlacementGroups']); + + // Confirm that compliant Placement Group is listed on landing page, click "Edit" to open drawer. + cy.findByText(mockPlacementGroup.label) + .should('be.visible') + .closest('tr') + .within(() => { + cy.findByText('Edit').click(); + }); + + // Enter new label, click "Edit". + cy.get('[data-qa-drawer="true"]').within(() => { + cy.findByText('Edit').should('be.visible'); + cy.findByDisplayValue(mockPlacementGroup.label) + .should('be.visible') + .click() + .type(`{selectall}{backspace}${newLabel.label}`); + + cy.findByText('Edit').should('be.visible').click(); + + cy.wait('@updatePlacementGroupLabel').then((intercept) => { + expect(intercept.request.body['label']).to.equal(newLabel.label); + }); + }); + + ui.toast.assertMessage( + `Placement Group ${newLabel.label} successfully updated.` + ); + }); + + it("update to a Placement Group's label fails", () => { + const mockPlacementGroupCompliantRegion = chooseRegion(); + + const mockPlacementGroup = placementGroupFactory.build({ + id: randomNumber(), + label: randomLabel(), + region: mockPlacementGroupCompliantRegion.id, + affinity_type: 'anti_affinity:local', + is_compliant: true, + is_strict: false, + members: [], + }); + + const newLabel: UpdatePlacementGroupPayload = { + label: randomLabel(), + }; + + mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); + + mockUpdatePlacementGroupLabelError( + mockPlacementGroup.id, + 'An unexpected error occurred.', + 400 + ).as('updatePlacementGroupLabelError'); + + cy.visitWithLogin('/placement-groups'); + cy.wait(['@getPlacementGroups']); + + // Confirm that compliant Placement Group is listed on landing page, click "Edit" to open drawer. + cy.findByText(mockPlacementGroup.label) + .should('be.visible') + .closest('tr') + .within(() => { + cy.findByText('Edit').click(); + }); + + // Enter new label, click "Edit". + cy.get('[data-qa-drawer="true"]').within(() => { + cy.findByText('Edit').should('be.visible'); + cy.findByDisplayValue(mockPlacementGroup.label) + .should('be.visible') + .click() + .type(`{selectall}{backspace}${newLabel.label}`); + + cy.findByText('Edit').should('be.visible').click(); + + // Confirm error message is displayed in the drawer. + cy.wait('@updatePlacementGroupLabelError'); + cy.findByText('An unexpected error occurred.').should('be.visible'); + }); + }); +}); diff --git a/packages/manager/cypress/support/intercepts/placement-groups.ts b/packages/manager/cypress/support/intercepts/placement-groups.ts index 4511a188885..575a1b73577 100644 --- a/packages/manager/cypress/support/intercepts/placement-groups.ts +++ b/packages/manager/cypress/support/intercepts/placement-groups.ts @@ -1,8 +1,12 @@ +import { makeErrorResponse } from 'support/util/errors'; import { apiMatcher } from 'support/util/intercepts'; import { paginateResponse } from 'support/util/paginate'; -import type { PlacementGroup } from '@linode/api-v4'; import { makeResponse } from 'support/util/response'; -import { makeErrorResponse } from 'support/util/errors'; + +import type { + PlacementGroup, + UpdatePlacementGroupPayload, +} from '@linode/api-v4'; /** * Intercepts GET request to fetch Placement Groups and mocks response. @@ -175,3 +179,45 @@ export const mockDeletePlacementGroupError = ( makeErrorResponse(errorMessage, errorCode) ); }; + +/** + * Intercepts PUT request to update Placement Group label and mocks response. + * + * @param placementGroupId - ID of Placement Group for which to intercept update label request. + * @param placementGroupData - Placement Group object with which to mock response. + * + * @returns Cypress chainable. + */ +export const mockUpdatePlacementGroupLabel = ( + placementGroupId: number, + placementGroupData: UpdatePlacementGroupPayload +): Cypress.Chainable => { + return cy.intercept( + 'PUT', + apiMatcher(`placement/groups/${placementGroupId}`), + makeResponse(placementGroupData) + ); +}; + +/** + * Intercepts PUT request to update Placement Group label and mocks HTTP error response. + * + * By default, a 500 response is mocked. + * + * @param placementGroupId - ID of Placement Group for which to intercept update label request. + * @param errorMessage - Optional error message with which to mock response. + * @param errorCode - Optional error code with which to mock response. Default is `500`. + * + * @returns Cypress chainable. + */ +export const mockUpdatePlacementGroupLabelError = ( + placementGroupId: number, + errorMessage: string = 'An error has occurred', + errorCode: number = 500 +): Cypress.Chainable => { + return cy.intercept( + 'PUT', + apiMatcher(`placement/groups/${placementGroupId}`), + makeErrorResponse(errorMessage, errorCode) + ); +}; From 5159bd68a4b2e7536c8c5e3fba90fa42c674a5dc Mon Sep 17 00:00:00 2001 From: ecarrill Date: Thu, 30 May 2024 05:29:16 -0700 Subject: [PATCH 2/4] Add changeset --- packages/manager/.changeset/pr-10529-tests-1717072137790.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-10529-tests-1717072137790.md diff --git a/packages/manager/.changeset/pr-10529-tests-1717072137790.md b/packages/manager/.changeset/pr-10529-tests-1717072137790.md new file mode 100644 index 00000000000..3a3ba23bc07 --- /dev/null +++ b/packages/manager/.changeset/pr-10529-tests-1717072137790.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Cypress integration tests for PG update label flow ([#10529](https://github.com/linode/manager/pull/10529)) From 83d36e964cc7dee93c42ec55c098885d7f198450 Mon Sep 17 00:00:00 2001 From: ecarrill Date: Mon, 3 Jun 2024 01:31:42 -0700 Subject: [PATCH 3/4] Update tests based on PR feedback --- .../update-placement-group-label.spec.ts | 44 ++++++++++++------- .../support/intercepts/placement-groups.ts | 11 ++--- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts index 36514088004..31f8cf78aeb 100644 --- a/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts +++ b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts @@ -10,8 +10,8 @@ import { makeFeatureFlagData } from 'support/util/feature-flags'; import { randomLabel, randomNumber } from 'support/util/random'; import { mockGetPlacementGroups, - mockUpdatePlacementGroupLabel, - mockUpdatePlacementGroupLabelError, + mockUpdatePlacementGroup, + mockUpdatePlacementGroupError, } from 'support/intercepts/placement-groups'; import { accountFactory, placementGroupFactory } from 'src/factories'; import { mockGetAccount } from 'support/intercepts/account'; @@ -19,7 +19,6 @@ import { authenticate } from 'support/api/authentication'; import type { Flags } from 'src/featureFlags'; import { chooseRegion } from 'support/util/regions'; import { ui } from 'support/ui'; -import type { UpdatePlacementGroupPayload } from '@linode/api-v4'; const mockAccount = accountFactory.build(); authenticate(); @@ -41,7 +40,7 @@ describe('Placement Group update label flow', () => { * - Confirms that a Placement Group's label can be updated from the landing page. * - Confirms that clicking "Edit" opens PG edit drawer. * - Only the label field is shown in the edit drawer. - * - New label can be entered into the label field. + * - A new value can be entered into the label field. * - Confirms that Placement Groups landing page updates to reflect successful label update. * - Confirms a toast notification is shown upon successful label update. */ @@ -58,20 +57,22 @@ describe('Placement Group update label flow', () => { members: [], }); - const newLabel: UpdatePlacementGroupPayload = { + const mockPlacementGroupUpdated = { + ...mockPlacementGroup, label: randomLabel(), }; mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); - mockUpdatePlacementGroupLabel(mockPlacementGroup.id, newLabel).as( - 'updatePlacementGroupLabel' - ); + mockUpdatePlacementGroup( + mockPlacementGroup.id, + mockPlacementGroupUpdated.label + ).as('updatePlacementGroupLabel'); cy.visitWithLogin('/placement-groups'); cy.wait(['@getPlacementGroups']); - // Confirm that compliant Placement Group is listed on landing page, click "Edit" to open drawer. + // Confirm that Placement Group is listed on landing page, click "Edit" to open drawer. cy.findByText(mockPlacementGroup.label) .should('be.visible') .closest('tr') @@ -80,25 +81,35 @@ describe('Placement Group update label flow', () => { }); // Enter new label, click "Edit". + mockGetPlacementGroups([mockPlacementGroupUpdated]).as( + 'getPlacementGroups' + ); cy.get('[data-qa-drawer="true"]').within(() => { cy.findByText('Edit').should('be.visible'); cy.findByDisplayValue(mockPlacementGroup.label) .should('be.visible') .click() - .type(`{selectall}{backspace}${newLabel.label}`); + .type(`{selectall}{backspace}${mockPlacementGroupUpdated.label}`); cy.findByText('Edit').should('be.visible').click(); cy.wait('@updatePlacementGroupLabel').then((intercept) => { - expect(intercept.request.body['label']).to.equal(newLabel.label); + expect(intercept.request.body['label']).to.equal( + mockPlacementGroupUpdated.label + ); }); }); ui.toast.assertMessage( - `Placement Group ${newLabel.label} successfully updated.` + `Placement Group ${mockPlacementGroupUpdated.label} successfully updated.` ); }); + /** + * - Confirms that an http error is handled gracefully for Placement Group label update. + * - A new value can be entered into the label field. + * - Confirms an error notice is shown upon failure to label update. + */ it("update to a Placement Group's label fails", () => { const mockPlacementGroupCompliantRegion = chooseRegion(); @@ -112,13 +123,14 @@ describe('Placement Group update label flow', () => { members: [], }); - const newLabel: UpdatePlacementGroupPayload = { + const mockPlacementGroupUpdated = { + ...mockPlacementGroup, label: randomLabel(), }; mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); - mockUpdatePlacementGroupLabelError( + mockUpdatePlacementGroupError( mockPlacementGroup.id, 'An unexpected error occurred.', 400 @@ -127,7 +139,7 @@ describe('Placement Group update label flow', () => { cy.visitWithLogin('/placement-groups'); cy.wait(['@getPlacementGroups']); - // Confirm that compliant Placement Group is listed on landing page, click "Edit" to open drawer. + // Confirm that Placement Group is listed on landing page, click "Edit" to open drawer. cy.findByText(mockPlacementGroup.label) .should('be.visible') .closest('tr') @@ -141,7 +153,7 @@ describe('Placement Group update label flow', () => { cy.findByDisplayValue(mockPlacementGroup.label) .should('be.visible') .click() - .type(`{selectall}{backspace}${newLabel.label}`); + .type(`{selectall}{backspace}${mockPlacementGroupUpdated.label}`); cy.findByText('Edit').should('be.visible').click(); diff --git a/packages/manager/cypress/support/intercepts/placement-groups.ts b/packages/manager/cypress/support/intercepts/placement-groups.ts index 575a1b73577..be6777dc4b0 100644 --- a/packages/manager/cypress/support/intercepts/placement-groups.ts +++ b/packages/manager/cypress/support/intercepts/placement-groups.ts @@ -3,10 +3,7 @@ import { apiMatcher } from 'support/util/intercepts'; import { paginateResponse } from 'support/util/paginate'; import { makeResponse } from 'support/util/response'; -import type { - PlacementGroup, - UpdatePlacementGroupPayload, -} from '@linode/api-v4'; +import type { PlacementGroup } from '@linode/api-v4'; /** * Intercepts GET request to fetch Placement Groups and mocks response. @@ -188,9 +185,9 @@ export const mockDeletePlacementGroupError = ( * * @returns Cypress chainable. */ -export const mockUpdatePlacementGroupLabel = ( +export const mockUpdatePlacementGroup = ( placementGroupId: number, - placementGroupData: UpdatePlacementGroupPayload + placementGroupData: string ): Cypress.Chainable => { return cy.intercept( 'PUT', @@ -210,7 +207,7 @@ export const mockUpdatePlacementGroupLabel = ( * * @returns Cypress chainable. */ -export const mockUpdatePlacementGroupLabelError = ( +export const mockUpdatePlacementGroupError = ( placementGroupId: number, errorMessage: string = 'An error has occurred', errorCode: number = 500 From 21861dc70d8f7dab066d7db9847fc5eeef4185e5 Mon Sep 17 00:00:00 2001 From: ecarrill Date: Mon, 3 Jun 2024 08:17:44 -0700 Subject: [PATCH 4/4] Minor fixes to address PR feedback --- .../placementGroups/update-placement-group-label.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts index 31f8cf78aeb..5ee32465d36 100644 --- a/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts +++ b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts @@ -15,13 +15,11 @@ import { } from 'support/intercepts/placement-groups'; import { accountFactory, placementGroupFactory } from 'src/factories'; import { mockGetAccount } from 'support/intercepts/account'; -import { authenticate } from 'support/api/authentication'; import type { Flags } from 'src/featureFlags'; import { chooseRegion } from 'support/util/regions'; import { ui } from 'support/ui'; const mockAccount = accountFactory.build(); -authenticate(); describe('Placement Group update label flow', () => { // Mock the VM Placement Groups feature flag to be enabled for each test in this block. @@ -33,7 +31,7 @@ describe('Placement Group update label flow', () => { }), }); mockGetFeatureFlagClientstream(); - mockGetAccount(mockAccount).as('getAccount'); + mockGetAccount(mockAccount); }); /** @@ -110,7 +108,7 @@ describe('Placement Group update label flow', () => { * - A new value can be entered into the label field. * - Confirms an error notice is shown upon failure to label update. */ - it("update to a Placement Group's label fails", () => { + it("update to a Placement Group's label fails with error message", () => { const mockPlacementGroupCompliantRegion = chooseRegion(); const mockPlacementGroup = placementGroupFactory.build({