From ca18d98036724fb8f5877003731310c1787b0316 Mon Sep 17 00:00:00 2001 From: Cassie Liu Date: Thu, 26 Oct 2023 17:45:21 -0400 Subject: [PATCH 1/2] M3-7133 Add integration tests for AGLB certificate delete flow --- .../load-balancer-certificates.spec.ts | 198 +++++++++++++++++- .../support/intercepts/load-balancers.ts | 42 ++++ 2 files changed, 237 insertions(+), 3 deletions(-) diff --git a/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-certificates.spec.ts b/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-certificates.spec.ts index f943313d483..ed7cbe40634 100644 --- a/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-certificates.spec.ts +++ b/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-certificates.spec.ts @@ -9,21 +9,99 @@ import { import { makeFeatureFlagData } from 'support/util/feature-flags'; import { loadbalancerFactory, certificateFactory } from '@src/factories'; import { ui } from 'support/ui'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomItem, randomLabel, randomString } from 'support/util/random'; import { + mockDeleteLoadBalancerCertificate, + mockDeleteLoadBalancerCertificateError, mockGetLoadBalancer, mockGetLoadBalancerCertificates, mockUploadLoadBalancerCertificate, } from 'support/intercepts/load-balancers'; +import { Loadbalancer, Certificate } from '@linode/api-v4/types'; + +/** + * Deletes the TLS / Service Target certificate in the AGLB landing page. + * + * @param loadBalancer - The load balancer that contains the certificate to be deleted. + * @param certificatesDeleteBefore - The array of certificates to be displayed before deleting. + * @param certificatesDeleteAfter - The array of certificates to be displayed after deleting. + * + * Asserts that the landing page has updated to reflect the changes. + */ +const deleteCertificate = ( + loadBalancer: Loadbalancer, + certificatesDeleteBefore: Certificate[], + certificatesDeleteAfter: Certificate[] +) => { + mockAppendFeatureFlags({ + aglb: makeFeatureFlagData(true), + }).as('getFeatureFlags'); + mockGetFeatureFlagClientstream().as('getClientStream'); + mockGetLoadBalancer(loadBalancer).as('getLoadBalancer'); + mockGetLoadBalancerCertificates(loadBalancer.id, certificatesDeleteBefore).as( + 'getCertificates' + ); + + cy.visitWithLogin(`/loadbalancers/${loadBalancer.id}/certificates`); + cy.wait([ + '@getFeatureFlags', + '@getClientStream', + '@getLoadBalancer', + '@getCertificates', + ]); + + // Delete a TLS/Service Target certificate. + const certificateToDeleteLabel = certificatesDeleteBefore[0].label; + ui.actionMenu + .findByTitle(`Action Menu for certificate ${certificateToDeleteLabel}`) + .should('be.visible') + .click(); + ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); + + mockDeleteLoadBalancerCertificate( + loadBalancer.id, + certificatesDeleteBefore[0].id + ).as('deleteCertificate'); + + mockGetLoadBalancerCertificates(loadBalancer.id, certificatesDeleteAfter).as( + 'getCertificates' + ); + + ui.dialog + .findByTitle(`Delete Certificate ${certificateToDeleteLabel}?`) + .should('be.visible') + .within(() => { + ui.button + .findByTitle('Delete') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait(['@deleteCertificate', '@getCertificates']); + + // Confirm that the deleted certificate is removed from the table with expected info. + cy.findByText(certificateToDeleteLabel).should('not.exist'); + + if (certificatesDeleteAfter.length === 0) { + // Confirm that Cloud Manager allows users to delete the last certificate, and display empty state gracefully. + cy.findByText('No items to display.').should('be.visible'); + } +}; describe('Akamai Global Load Balancer certificates page', () => { + let mockLoadBalancer: Loadbalancer; + + before(() => { + mockLoadBalancer = loadbalancerFactory.build(); + }); + /* * - Confirms Load Balancer certificate upload UI flow using mocked API requests. * - Confirms that TLS and Service Target certificates can be uploaded. * - Confirms that certificates table update to reflects uploaded certificates. */ it('can upload a TLS certificate', () => { - const mockLoadBalancer = loadbalancerFactory.build(); const mockLoadBalancerCertTls = certificateFactory.build({ label: randomLabel(), type: 'downstream', @@ -94,8 +172,8 @@ describe('Akamai Global Load Balancer certificates page', () => { // Confirm that new certificate is listed in the table with expected info. cy.findByText(mockLoadBalancerCertTls.label).should('be.visible'); }); + it('can upload a service target certificate', () => { - const mockLoadBalancer = loadbalancerFactory.build(); const mockLoadBalancerCertServiceTarget = certificateFactory.build({ label: randomLabel(), type: 'ca', @@ -162,4 +240,118 @@ describe('Akamai Global Load Balancer certificates page', () => { // Confirm that both new certificates are listed in the table with expected info. cy.findByText(mockLoadBalancerCertServiceTarget.label).should('be.visible'); }); + + /* + * - Confirms Load Balancer certificate delete UI flow using mocked API requests. + * - Confirms that TLS and Service Target certificates can be deleted. + * - Confirms that certificates table update to reflects deleted certificates. + * - Confirms that the last certificate can be deleted. + */ + it('can delete a TLS certificate', () => { + const mockLoadBalancerCertsTls = certificateFactory.buildList(5, { + type: 'downstream', + }); + const mockLoadBalancerAfterDeleteCertsTls = mockLoadBalancerCertsTls.slice( + 1 + ); + + deleteCertificate( + mockLoadBalancer, + mockLoadBalancerCertsTls, + mockLoadBalancerAfterDeleteCertsTls + ); + }); + + it('can delete a service target certificate', () => { + const mockLoadBalancerCertsTls = certificateFactory.buildList(5, { + type: 'ca', + }); + const mockLoadBalancerAfterDeleteCertsTls = mockLoadBalancerCertsTls.slice( + 1 + ); + + deleteCertificate( + mockLoadBalancer, + mockLoadBalancerCertsTls, + mockLoadBalancerAfterDeleteCertsTls + ); + }); + + it('can delete the last certificate', () => { + const mockLoadBalancerCertsTls = certificateFactory.buildList(1, { + type: randomItem(['ca', 'downstream']), + }); + const mockLoadBalancerAfterDeleteCertsTls = mockLoadBalancerCertsTls.slice( + 1 + ); + + deleteCertificate( + mockLoadBalancer, + mockLoadBalancerCertsTls, + mockLoadBalancerAfterDeleteCertsTls + ); + }); + + it('can handle server errors gracefully when failing to delete the certificate', () => { + const mockLoadBalancerCertsTls = certificateFactory.buildList(1, { + type: randomItem(['ca', 'downstream']), + }); + const mockLoadBalancerAfterDeleteCertsTls = mockLoadBalancerCertsTls.slice( + 1 + ); + + mockAppendFeatureFlags({ + aglb: makeFeatureFlagData(true), + }).as('getFeatureFlags'); + mockGetFeatureFlagClientstream().as('getClientStream'); + mockGetLoadBalancer(mockLoadBalancer).as('getLoadBalancer'); + mockGetLoadBalancerCertificates( + mockLoadBalancer.id, + mockLoadBalancerCertsTls + ).as('getCertificates'); + + cy.visitWithLogin(`/loadbalancers/${mockLoadBalancer.id}/certificates`); + cy.wait([ + '@getFeatureFlags', + '@getClientStream', + '@getLoadBalancer', + '@getCertificates', + ]); + + // Delete a TLS/Service Target certificate. + const certificateToDeleteLabel = mockLoadBalancerCertsTls[0].label; + ui.actionMenu + .findByTitle(`Action Menu for certificate ${certificateToDeleteLabel}`) + .should('be.visible') + .click(); + ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); + + mockDeleteLoadBalancerCertificateError( + mockLoadBalancer.id, + mockLoadBalancerCertsTls[0].id + ).as('deleteCertificateError'); + + ui.dialog + .findByTitle(`Delete Certificate ${certificateToDeleteLabel}?`) + .should('be.visible') + .within(() => { + ui.button + .findByTitle('Delete') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@deleteCertificateError'); + + ui.dialog + .findByTitle(`Delete Certificate ${certificateToDeleteLabel}?`) + .should('be.visible') + .within(() => { + // Confirm that an error message shows up in the dialog + cy.findByText( + 'An error occurred while deleting Load Balancer certificate.' + ).should('be.visible'); + }); + }); }); diff --git a/packages/manager/cypress/support/intercepts/load-balancers.ts b/packages/manager/cypress/support/intercepts/load-balancers.ts index ce028c70a86..19cf0a5b884 100644 --- a/packages/manager/cypress/support/intercepts/load-balancers.ts +++ b/packages/manager/cypress/support/intercepts/load-balancers.ts @@ -97,6 +97,48 @@ export const mockUploadLoadBalancerCertificate = ( ); }; +/** + * Intercepts DELETE request to delete an AGLB load balancer certificate and mocks a success response. + * + * @param loadBalancerId - ID of load balancer for which to delete certificates. + * @param certificateId - ID of certificate for which to remove. + * + * @returns Cypress chainable. + */ +export const mockDeleteLoadBalancerCertificate = ( + loadBalancerId: number, + certificateId: number +) => { + return cy.intercept( + 'DELETE', + apiMatcher(`/aglb/${loadBalancerId}/certificates/${certificateId}`), + makeResponse() + ); +}; + +/** + * Intercepts GET request to retrieve AGLB service targets and mocks HTTP 500 error response. + * + * @param loadBalancerId - ID of load balancer for which to delete certificates. + * @param certificateId - ID of certificate for which to remove. + * @param message - Optional error message with which to respond. + * + * @returns Cypress chainable. + */ +export const mockDeleteLoadBalancerCertificateError = ( + loadBalancerId: number, + certificateId: number, + message?: string +) => { + const defaultMessage = + 'An error occurred while deleting Load Balancer certificate.'; + return cy.intercept( + 'DELETE', + apiMatcher(`/aglb/${loadBalancerId}/certificates/${certificateId}`), + makeErrorResponse(message ?? defaultMessage, 500) + ); +}; + /** * Intercepts GET request to retrieve AGLB service targets and mocks response. * From d939e8f6781dee05d06f31bb8c490bfb72e87845 Mon Sep 17 00:00:00 2001 From: Cassie Liu Date: Fri, 3 Nov 2023 10:58:39 -0400 Subject: [PATCH 2/2] Add changeset --- packages/manager/.changeset/pr-9846-added-1699023355495.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-9846-added-1699023355495.md diff --git a/packages/manager/.changeset/pr-9846-added-1699023355495.md b/packages/manager/.changeset/pr-9846-added-1699023355495.md new file mode 100644 index 00000000000..ff4d9565c33 --- /dev/null +++ b/packages/manager/.changeset/pr-9846-added-1699023355495.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Add integration tests for AGLB certificate delete flow ([#9846](https://github.com/linode/manager/pull/9846))