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)) 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..5ee32465d36 --- /dev/null +++ b/packages/manager/cypress/e2e/core/placementGroups/update-placement-group-label.spec.ts @@ -0,0 +1,163 @@ +/** + * @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, + mockUpdatePlacementGroup, + mockUpdatePlacementGroupError, +} from 'support/intercepts/placement-groups'; +import { accountFactory, placementGroupFactory } from 'src/factories'; +import { mockGetAccount } from 'support/intercepts/account'; +import type { Flags } from 'src/featureFlags'; +import { chooseRegion } from 'support/util/regions'; +import { ui } from 'support/ui'; + +const mockAccount = accountFactory.build(); + +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); + }); + + /** + * - 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. + * - 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. + */ + 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 mockPlacementGroupUpdated = { + ...mockPlacementGroup, + label: randomLabel(), + }; + + mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); + + mockUpdatePlacementGroup( + mockPlacementGroup.id, + mockPlacementGroupUpdated.label + ).as('updatePlacementGroupLabel'); + + cy.visitWithLogin('/placement-groups'); + cy.wait(['@getPlacementGroups']); + + // Confirm that 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". + 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}${mockPlacementGroupUpdated.label}`); + + cy.findByText('Edit').should('be.visible').click(); + + cy.wait('@updatePlacementGroupLabel').then((intercept) => { + expect(intercept.request.body['label']).to.equal( + mockPlacementGroupUpdated.label + ); + }); + }); + + ui.toast.assertMessage( + `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 with error message", () => { + 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 mockPlacementGroupUpdated = { + ...mockPlacementGroup, + label: randomLabel(), + }; + + mockGetPlacementGroups([mockPlacementGroup]).as('getPlacementGroups'); + + mockUpdatePlacementGroupError( + mockPlacementGroup.id, + 'An unexpected error occurred.', + 400 + ).as('updatePlacementGroupLabelError'); + + cy.visitWithLogin('/placement-groups'); + cy.wait(['@getPlacementGroups']); + + // Confirm that 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}${mockPlacementGroupUpdated.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..be6777dc4b0 100644 --- a/packages/manager/cypress/support/intercepts/placement-groups.ts +++ b/packages/manager/cypress/support/intercepts/placement-groups.ts @@ -1,8 +1,9 @@ +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 } from '@linode/api-v4'; /** * Intercepts GET request to fetch Placement Groups and mocks response. @@ -175,3 +176,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 mockUpdatePlacementGroup = ( + placementGroupId: number, + placementGroupData: string +): 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 mockUpdatePlacementGroupError = ( + placementGroupId: number, + errorMessage: string = 'An error has occurred', + errorCode: number = 500 +): Cypress.Chainable => { + return cy.intercept( + 'PUT', + apiMatcher(`placement/groups/${placementGroupId}`), + makeErrorResponse(errorMessage, errorCode) + ); +};