Skip to content

Commit

Permalink
feat: [M3-7029] - Add AGLB Certificate Create Drawer (#9616)
Browse files Browse the repository at this point in the history
* add component and test

* do goofy radio styling

* improve validation

* Added changeset: Add AGLB Certificate Create Drawer

* fix test

* add `trimmed` prop to SSH Key TextFields

* Add AGLB cert upload integration tests and related utils

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
Co-authored-by: Joe D'Amore <jdamore@linode.com>
  • Loading branch information
3 people authored Sep 8, 2023
1 parent e05da85 commit 69a4d5b
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 4 deletions.
3 changes: 2 additions & 1 deletion packages/api-v4/src/aglb/certificates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Request, {
import { BETA_API_ROOT } from 'src/constants';
import { Filter, Params, ResourcePage } from '../types';
import { Certificate, CreateCertificatePayload } from './types';
import { CreateCertificateSchema } from '@linode/validation';

/**
* getLoadbalancerCertificates
Expand Down Expand Up @@ -60,7 +61,7 @@ export const createLoadbalancerCertificate = (
`${BETA_API_ROOT}/aglb/${encodeURIComponent(loadbalancerId)}/certificates`
),
setMethod('POST'),
setData(data)
setData(data, CreateCertificateSchema)
);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add AGLB Certificate Create Drawer ([#9616](https://github.com/linode/manager/pull/9616))
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* @file Integration tests for Akamai Global Load Balancer details page.
*/

import {
mockAppendFeatureFlags,
mockGetFeatureFlagClientstream,
} from 'support/intercepts/feature-flags';
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 {
mockGetLoadBalancer,
mockGetLoadBalancerCertificates,
mockUploadLoadBalancerCertificate,
} from 'support/intercepts/load-balancers';

/**
* Uploads a Load Balancer certificate using the "Upload Certificate" drawer.
*
* This function assumes the "Upload Certificate" drawer is already open.
*
* @param type - Certificate type; either 'tls' or 'service-target'.
* @param label - Certificate label.
*/
const uploadCertificate = (type: 'tls' | 'service-target', label: string) => {
const radioSelector =
type === 'tls' ? '[data-qa-cert-tls]' : '[data-qa-cert-service-target]';

ui.drawer
.findByTitle('Upload Certificate')
.should('be.visible')
.within(() => {
cy.get(radioSelector).should('be.visible').click();

cy.findByLabelText('Label').should('be.visible').type(label);

cy.findByLabelText('TLS Certificate')
.should('be.visible')
.type(randomString(32));

cy.findByLabelText('Private Key')
.should('be.visible')
.type(randomString(32));

ui.buttonGroup
.findButtonByTitle('Upload Certificate')
.scrollIntoView()
.should('be.visible')
.should('be.enabled')
.click();
});
};

describe('Akamai Global Load Balancer details page', () => {
/*
* - 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 Load Balancer Certificate', () => {
const mockLoadBalancer = loadbalancerFactory.build();
const mockLoadBalancerCertTls = certificateFactory.build({
label: randomLabel(),
type: 'downstream',
});
const mockLoadBalancerCertServiceTarget = certificateFactory.build({
label: randomLabel(),
type: 'ca',
});

mockAppendFeatureFlags({
aglb: makeFeatureFlagData(true),
}).as('getFeatureFlags');
mockGetFeatureFlagClientstream().as('getClientStream');
mockGetLoadBalancer(mockLoadBalancer).as('getLoadBalancer');
mockGetLoadBalancerCertificates(mockLoadBalancer.id, []).as(
'getCertificates'
);

cy.visitWithLogin(`/loadbalancers/${mockLoadBalancer.id}/certificates`);
cy.wait([
'@getFeatureFlags',
'@getClientStream',
'@getLoadBalancer',
'@getCertificates',
]);

// Confirm that no certificates are listed.
cy.findByText('No items to display.').should('be.visible');

// Upload a TLS certificate.
ui.button
.findByTitle('Upload Certificate')
.should('be.visible')
.should('be.enabled')
.click();

mockUploadLoadBalancerCertificate(
mockLoadBalancer.id,
mockLoadBalancerCertTls
).as('uploadCertificate');
mockGetLoadBalancerCertificates(mockLoadBalancer.id, [
mockLoadBalancerCertTls,
]).as('getCertificates');
uploadCertificate('tls', mockLoadBalancerCertTls.label);

// Confirm that new certificate is listed in the table with expected info.
cy.wait(['@uploadCertificate', '@getCertificates']);
cy.findByText(mockLoadBalancerCertTls.label)
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText('TLS Certificate').should('be.visible');
});

ui.button
.findByTitle('Upload Certificate')
.should('be.visible')
.should('be.enabled')
.click();

// Upload a service target certificate.
mockUploadLoadBalancerCertificate(
mockLoadBalancer.id,
mockLoadBalancerCertServiceTarget
).as('uploadCertificate');
mockGetLoadBalancerCertificates(mockLoadBalancer.id, [
mockLoadBalancerCertTls,
mockLoadBalancerCertServiceTarget,
]).as('getCertificates');
uploadCertificate(
'service-target',
mockLoadBalancerCertServiceTarget.label
);

// Confirm that both new certificates are listed in the table with expected info.
cy.wait(['@uploadCertificate', '@getCertificates']);
cy.findByText(mockLoadBalancerCertTls.label).should('be.visible');
cy.findByText(mockLoadBalancerCertServiceTarget.label)
.should('be.visible')
.closest('tr')
.within(() => {
cy.findByText('Service Target Certificate').should('be.visible');
});
});
});
54 changes: 54 additions & 0 deletions packages/manager/cypress/support/intercepts/load-balancers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ import type {
ServiceTarget,
Loadbalancer,
Configuration,
Certificate,
} from '@linode/api-v4';
import { makeResponse } from 'support/util/response';

/**
* Intercepts GET request to fetch an AGLB load balancer and mocks response.
*
* @param loadBalancer - Load balancer with which to mock response.
*
* @returns Cypress chainable.
*/
export const mockGetLoadBalancer = (loadBalancer: Loadbalancer) => {
return cy.intercept(
'GET',
apiMatcher(`/aglb/${loadBalancer.id}`),
makeResponse(loadBalancer)
);
};

/**
* Intercepts GET request to retrieve AGLB load balancers and mocks response.
Expand Down Expand Up @@ -41,6 +58,43 @@ export const mockGetLoadBalancerConfigurations = (
);
};

/**
* Intercepts GET requests to retrieve AGLB load balancer certificates and mocks response.
*
* @param loadBalancerId - ID of load balancer for which to mock certificates.
* @param certificates - Load balancer certificates with which to mock response.
*
* @returns Cypress chainable.
*/
export const mockGetLoadBalancerCertificates = (
loadBalancerId: number,
certificates: Certificate[]
) => {
return cy.intercept(
'GET',
apiMatcher(`/aglb/${loadBalancerId}/certificates*`),
paginateResponse(certificates)
);
};

/**
* Intercepts POST request to upload an AGLB load balancer certificate and mocks a success response.
*
* @param loadBalancerId - ID of load balancer for which to mock certificates.
*
* @returns Cypress chainable.
*/
export const mockUploadLoadBalancerCertificate = (
loadBalancerId: number,
certificate: Certificate
) => {
return cy.intercept(
'POST',
apiMatcher(`/aglb/${loadBalancerId}/certificates`),
makeResponse(certificate)
);
};

/**
* Intercepts GET request to retrieve AGLB service targets and mocks response.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { act, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { CreateCertificateDrawer } from './CreateCertificateDrawer';

describe('CreateCertificateDrawer', () => {
it('should be submittable when form is filled out correctly', async () => {
const onClose = jest.fn();

const { getByLabelText, getByTestId } = renderWithTheme(
<CreateCertificateDrawer loadbalancerId={0} onClose={onClose} open />
);

const labelInput = getByLabelText('Label');
const certInput = getByLabelText('TLS Certificate');
const keyInput = getByLabelText('Private Key');

act(() => {
userEvent.type(labelInput, 'my-cert-0');
userEvent.type(certInput, 'massive cert');
userEvent.type(keyInput, 'massive key');

userEvent.click(getByTestId('submit'));
});

await waitFor(() => expect(onClose).toBeCalled());
});
});
Loading

0 comments on commit 69a4d5b

Please sign in to comment.