diff --git a/packages/manager/.changeset/pr-11444-tests-1734632019649.md b/packages/manager/.changeset/pr-11444-tests-1734632019649.md new file mode 100644 index 00000000000..ffc53ad1798 --- /dev/null +++ b/packages/manager/.changeset/pr-11444-tests-1734632019649.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Add test for LKE cluster rename flow ([#11444](https://github.com/linode/manager/pull/11444)) diff --git a/packages/manager/cypress/e2e/core/kubernetes/lke-update.spec.ts b/packages/manager/cypress/e2e/core/kubernetes/lke-update.spec.ts index e83f966beb6..788f19e2d31 100644 --- a/packages/manager/cypress/e2e/core/kubernetes/lke-update.spec.ts +++ b/packages/manager/cypress/e2e/core/kubernetes/lke-update.spec.ts @@ -30,6 +30,7 @@ import { mockUpdateControlPlaneACLError, mockGetControlPlaneACLError, mockGetTieredKubernetesVersions, + mockUpdateClusterError, } from 'support/intercepts/lke'; import { mockGetLinodeType, @@ -929,6 +930,74 @@ describe('LKE cluster updates', () => { .should('be.visible') .should('be.disabled'); }); + + /* + * - Confirms LKE summary page updates to reflect new cluster name. + */ + it('can rename cluster', () => { + const mockCluster = kubernetesClusterFactory.build({ + k8s_version: latestKubernetesVersion, + }); + const mockNewCluster = kubernetesClusterFactory.build({ + label: 'newClusterName', + }); + + mockGetCluster(mockCluster).as('getCluster'); + mockGetKubernetesVersions().as('getVersions'); + mockGetClusterPools(mockCluster.id, mockNodePools).as('getNodePools'); + mockUpdateCluster(mockCluster.id, mockNewCluster).as('updateCluster'); + + cy.visitWithLogin(`/kubernetes/clusters/${mockCluster.id}/summary`); + cy.wait(['@getCluster', '@getNodePools', '@getVersions']); + + // LKE clusters can be renamed by clicking on the cluster's name in the breadcrumbs towards the top of the page. + cy.get('[data-testid="editable-text"] > [data-testid="button"]').click(); + cy.findByTestId('textfield-input') + .should('be.visible') + .should('have.value', mockCluster.label) + .clear() + .type(`${mockNewCluster.label}{enter}`); + + cy.wait('@updateCluster'); + + cy.findAllByText(mockNewCluster.label).should('be.visible'); + cy.findAllByText(mockCluster.label).should('not.exist'); + }); + + /* + * - Confirms error message shows when the API request fails. + */ + it('can handle API errors when renaming cluster', () => { + const mockCluster = kubernetesClusterFactory.build({ + k8s_version: latestKubernetesVersion, + }); + const mockErrorCluster = kubernetesClusterFactory.build({ + label: 'errorClusterName', + }); + const mockErrorMessage = 'API request fails'; + + mockGetCluster(mockCluster).as('getCluster'); + mockGetKubernetesVersions().as('getVersions'); + mockGetClusterPools(mockCluster.id, mockNodePools).as('getNodePools'); + mockUpdateClusterError(mockCluster.id, mockErrorMessage).as( + 'updateClusterError' + ); + + cy.visitWithLogin(`/kubernetes/clusters/${mockCluster.id}/summary`); + cy.wait(['@getCluster', '@getNodePools', '@getVersions']); + + // LKE cluster can be renamed by clicking on the cluster's name in the breadcrumbs towards the top of the page. + cy.get('[data-testid="editable-text"] > [data-testid="button"]').click(); + cy.findByTestId('textfield-input') + .should('be.visible') + .should('have.value', mockCluster.label) + .clear() + .type(`${mockErrorCluster.label}{enter}`); + + // Error message shows when API request fails. + cy.wait('@updateClusterError'); + cy.findAllByText(mockErrorMessage).should('be.visible'); + }); }); it('can add and delete node pool tags', () => { diff --git a/packages/manager/cypress/support/intercepts/lke.ts b/packages/manager/cypress/support/intercepts/lke.ts index 6f30bdeda1e..88905b33b38 100644 --- a/packages/manager/cypress/support/intercepts/lke.ts +++ b/packages/manager/cypress/support/intercepts/lke.ts @@ -508,3 +508,24 @@ export const mockGetLKEClusterTypes = ( ): Cypress.Chainable => { return cy.intercept('GET', apiMatcher('lke/types*'), paginateResponse(types)); }; + +/** + * Intercepts PUT request to update an LKE cluster and mocks an error response. + * + * @param clusterId - ID of cluster for which to intercept PUT request. + * @param errorMessage - Optional error message with which to mock response. + * @param statusCode - HTTP status code with which to mock response. + * + * @returns Cypress chainable. + */ +export const mockUpdateClusterError = ( + clusterId: number, + errorMessage: string = 'An unknown error occurred.', + statusCode: number = 500 +): Cypress.Chainable => { + return cy.intercept( + 'PUT', + apiMatcher(`lke/clusters/${clusterId}`), + makeErrorResponse(errorMessage, statusCode) + ); +}; diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts index ae540c93146..6057a3dc770 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts @@ -267,7 +267,10 @@ export const generateGraphData = (props: GraphDataOptionsProps): GraphData => { * @param unit base unit of the values * @returns maximum possible rolled up unit based on the unit */ -export const generateMaxUnit = (legendRowsData: MetricsDisplayRow[], unit: string) => { +export const generateMaxUnit = ( + legendRowsData: MetricsDisplayRow[], + unit: string +) => { const maxValue = Math.max( 0, ...legendRowsData?.map((row) => row?.data.max ?? 0) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index 961f28c4ee1..f3460a97b88 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -139,7 +139,7 @@ export const getResourcesProperties = ( placeholder, resourceType: dashboard.service_type, savePreferences: !isServiceAnalyticsIntegration, - xFilter: buildXFilter(config, dependentFilters ?? {}) + xFilter: buildXFilter(config, dependentFilters ?? {}), }; }; diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx index 716883ed945..e5d3a1fae74 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.test.tsx @@ -188,7 +188,7 @@ describe('Cloud pulse widgets', () => { expect(queryMocks.useCloudPulseMetricsQuery).toHaveBeenCalledWith( 'linode', expect.objectContaining({ - time_granularity: { unit: 'min', value: 5 } + time_granularity: { unit: 'min', value: 5 }, }), expect.any(Object) ); diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseResourcesSelect.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseResourcesSelect.tsx index fbefa9356a1..b6dee07b2cb 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseResourcesSelect.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseResourcesSelect.tsx @@ -50,7 +50,11 @@ export const CloudPulseResourcesSelect = React.memo( const flags = useFlags(); const resourceFilterMap: Record = { - dbaas: { '+order': 'asc', '+order_by': 'label', platform: 'rdbms-default' }, + dbaas: { + '+order': 'asc', + '+order_by': 'label', + platform: 'rdbms-default', + }, }; const { data: resources, isError, isLoading } = useResourcesQuery( diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx index 34ee1545f99..1359c2324ab 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx @@ -15,7 +15,10 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Link } from 'src/components/Link'; import { useFlags } from 'src/hooks/useFlags'; -import { ALGORITHM_HELPER_TEXT, SESSION_STICKINESS_DEFAULTS } from './constants'; +import { + ALGORITHM_HELPER_TEXT, + SESSION_STICKINESS_DEFAULTS, +} from './constants'; import { ActiveCheck } from './NodeBalancerActiveCheck'; import { NodeBalancerConfigNode } from './NodeBalancerConfigNode'; import { PassiveCheck } from './NodeBalancerPassiveCheck';