test: add certificate feature unit tests and null-safety fix

Add comprehensive unit tests for the certificate upload, export,
and detail management feature:

- CertificateExportDialog: 21 tests covering format selection,
  blob download, error handling, and password-protected exports
- CertificateUploadDialog: 23 tests covering file validation,
  format detection, drag-and-drop, and upload flow
- CertificateDetailDialog: 19 tests covering detail display,
  loading state, missing fields, and branch coverage
- CertificateChainViewer: 8 tests covering chain visualization
- CertificateValidationPreview: 16 tests covering validation display
- FileDropZone: 18 tests covering drag-and-drop interactions
- useCertificates hooks: 10 tests covering all React Query hooks
- certificates API: 7 new tests for previously uncovered endpoints

Fix null-safety issue in ProxyHosts where cert.domains could be
undefined, causing a runtime error on split().

Frontend patch coverage: 90.6%, overall lines: 89.09%
This commit is contained in:
GitHub Actions
2026-04-13 01:55:40 +00:00
parent 9d8d97e556
commit e1bc648dfc
9 changed files with 1514 additions and 2 deletions

View File

@@ -0,0 +1,71 @@
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import type { ChainEntry } from '../../api/certificates'
import CertificateChainViewer from '../CertificateChainViewer'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
i18n: { language: 'en', changeLanguage: vi.fn() },
}),
}))
function makeChain(count: number): ChainEntry[] {
return Array.from({ length: count }, (_, i) => ({
subject: `Subject ${i}`,
issuer: `Issuer ${i}`,
expires_at: '2026-06-01T00:00:00Z',
}))
}
describe('CertificateChainViewer', () => {
it('renders empty state when chain is empty', () => {
render(<CertificateChainViewer chain={[]} />)
expect(screen.getByText('certificates.noChainData')).toBeTruthy()
})
it('renders single entry as leaf', () => {
render(<CertificateChainViewer chain={makeChain(1)} />)
expect(screen.getByText('certificates.chainLeaf')).toBeTruthy()
expect(screen.getByText('Subject 0')).toBeTruthy()
})
it('renders two entries as leaf + root', () => {
render(<CertificateChainViewer chain={makeChain(2)} />)
expect(screen.getByText('certificates.chainLeaf')).toBeTruthy()
expect(screen.getByText('certificates.chainRoot')).toBeTruthy()
})
it('renders three entries as leaf + intermediate + root', () => {
render(<CertificateChainViewer chain={makeChain(3)} />)
expect(screen.getByText('certificates.chainLeaf')).toBeTruthy()
expect(screen.getByText('certificates.chainIntermediate')).toBeTruthy()
expect(screen.getByText('certificates.chainRoot')).toBeTruthy()
})
it('displays issuer for each entry', () => {
render(<CertificateChainViewer chain={makeChain(2)} />)
expect(screen.getByText(/Issuer 0/)).toBeTruthy()
expect(screen.getByText(/Issuer 1/)).toBeTruthy()
})
it('displays formatted expiration dates', () => {
render(<CertificateChainViewer chain={makeChain(1)} />)
const dateStr = new Date('2026-06-01T00:00:00Z').toLocaleDateString()
expect(screen.getByText(new RegExp(dateStr))).toBeTruthy()
})
it('uses list role with list items', () => {
render(<CertificateChainViewer chain={makeChain(2)} />)
expect(screen.getByRole('list')).toBeTruthy()
expect(screen.getAllByRole('listitem')).toHaveLength(2)
})
it('has aria-label on list', () => {
render(<CertificateChainViewer chain={makeChain(1)} />)
expect(screen.getByRole('list').getAttribute('aria-label')).toBe(
'certificates.certificateChain',
)
})
})