6e0bb98844
CI / checks (pull_request) Successful in 27m28s
Request public certificates (optionally exportable) or import your own, see each domain's DNS validation records and what uses a certificate, download the certificate and chain, export an exportable one with its encrypted key, and revoke it. ListCertificates asks for every key type, since AWS otherwise leaves EC certificates out.
89 lines
4.7 KiB
TypeScript
89 lines
4.7 KiB
TypeScript
import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
|
|
import {render, screen, within} from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import {beforeEach, expect, test, vi} from 'vitest'
|
|
import {CertificatePanel} from './CertificatePanel'
|
|
import type {CloudResource} from '@/types/resource'
|
|
|
|
const api = vi.hoisted(() => ({
|
|
getCloudResource: vi.fn(),
|
|
getCertificatePem: vi.fn(),
|
|
exportCertificate: vi.fn(),
|
|
revokeCertificate: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/api/cloudProxyClient', () => api)
|
|
vi.mock('@/lib/accountStore', () => ({useAccountId: () => '000000000000'}))
|
|
|
|
const ID = '5ed9dc37-d9fb-456e-911e-e87f091480ba'
|
|
function certificate(metadata: Record<string, unknown> = {}, status = 'ISSUED'): CloudResource {
|
|
return {
|
|
id: ID, name: 'notes.thecloud.localhost', cloud: 'aws', service: 'certificates', type: 'certificate',
|
|
region: 'us-east-1', createdAt: null, status,
|
|
metadata: {
|
|
certificateType: 'Amazon issued', exportable: false, inUseBy: [],
|
|
domainValidations: [{domainName: 'notes.thecloud.localhost', status: 'SUCCESS', method: 'DNS', record: {name: '_a1.notes.thecloud.localhost.', type: 'CNAME', value: '_e8.acm-validations.aws.'}}],
|
|
...metadata,
|
|
},
|
|
}
|
|
}
|
|
|
|
function renderPanel(resource: CloudResource) {
|
|
api.getCloudResource.mockResolvedValue(resource)
|
|
const client = new QueryClient({defaultOptions: {queries: {retry: false}}})
|
|
return render(<QueryClientProvider client={client}><CertificatePanel resource={resource} runtimeReachable/></QueryClientProvider>)
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
api.getCertificatePem.mockResolvedValue({certificate: '-----BEGIN CERTIFICATE-----\nLEAF', certificateChain: '-----BEGIN CERTIFICATE-----\nCA'})
|
|
api.exportCertificate.mockResolvedValue({certificate: 'LEAF', certificateChain: null, privateKey: '-----BEGIN ENCRYPTED PRIVATE KEY-----'})
|
|
api.revokeCertificate.mockResolvedValue(certificate({exportable: true}, 'REVOKED'))
|
|
})
|
|
|
|
test('shows the validation records and that nothing uses the certificate', async () => {
|
|
renderPanel(certificate())
|
|
|
|
expect(await screen.findByText('_a1.notes.thecloud.localhost.')).toBeInTheDocument()
|
|
expect(screen.getByText('_e8.acm-validations.aws.')).toBeInTheDocument()
|
|
expect(screen.getByText(/Nothing uses this certificate/)).toBeInTheDocument()
|
|
// Export and revoke exist only for certificates requested as exportable, as on AWS.
|
|
expect(screen.queryByRole('form', {name: 'Export certificate'})).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('form', {name: 'Revoke certificate'})).not.toBeInTheDocument()
|
|
})
|
|
|
|
test('lists what uses the certificate, since ACM will not delete it then', async () => {
|
|
renderPanel(certificate({inUseBy: ['arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/web/1']}))
|
|
|
|
expect(await screen.findByText('arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/web/1')).toBeInTheDocument()
|
|
expect(screen.getByText(/will not delete the certificate/)).toBeInTheDocument()
|
|
})
|
|
|
|
test('gets the certificate and chain as downloadable PEM files', async () => {
|
|
const user = userEvent.setup()
|
|
renderPanel(certificate())
|
|
|
|
await user.click(await screen.findByRole('button', {name: 'Get certificate'}))
|
|
expect(await screen.findByRole('textbox', {name: 'Certificate'})).toHaveValue('-----BEGIN CERTIFICATE-----\nLEAF')
|
|
expect(screen.getByRole('textbox', {name: 'Certificate chain'})).toHaveValue('-----BEGIN CERTIFICATE-----\nCA')
|
|
expect(screen.getByRole('link', {name: /notes.thecloud.localhost.pem/})).toHaveAttribute('download', 'notes.thecloud.localhost.pem')
|
|
})
|
|
|
|
test('exports an exportable certificate with the passphrase, then revokes it', async () => {
|
|
const user = userEvent.setup()
|
|
vi.spyOn(window, 'confirm').mockReturnValue(true)
|
|
renderPanel(certificate({exportable: true}))
|
|
|
|
const exportForm = await screen.findByRole('form', {name: 'Export certificate'})
|
|
await user.type(within(exportForm).getByLabelText(/Passphrase/), 'open-sesame')
|
|
await user.click(within(exportForm).getByRole('button', {name: 'Export'}))
|
|
expect(api.exportCertificate).toHaveBeenCalledWith('aws', ID, 'open-sesame')
|
|
expect(await screen.findByRole('textbox', {name: 'Private key (encrypted)'})).toHaveValue('-----BEGIN ENCRYPTED PRIVATE KEY-----')
|
|
|
|
const revokeForm = screen.getByRole('form', {name: 'Revoke certificate'})
|
|
await user.selectOptions(within(revokeForm).getByLabelText('Reason'), 'SUPERSEDED')
|
|
await user.click(within(revokeForm).getByRole('button', {name: 'Revoke'}))
|
|
expect(api.revokeCertificate).toHaveBeenCalledWith('aws', ID, 'SUPERSEDED')
|
|
expect(await screen.findByText(/Certificate revoked/)).toBeInTheDocument()
|
|
})
|