feat(09-05): implement ConvertTab + convertCertAction + render tests
Tessera CI/CD / Tests (push) Has been cancelled
Tessera CI/CD / Build & Publish Images (push) Has been cancelled
Tessera CI/CD / Lint & Type Check (push) Has been cancelled

- Add FileResponse interface to actions.ts
- Add convertCertAction(input, targetFormat): builds FormData with
  file/pemText/password + targetFormat, calls postForm convert endpoint
- Implement ConvertTab: native select for pem/der/p7b targetFormat,
  Konvertieren button with loading swap, error classification, empty state
- On success: calls downloadBase64(filename, content, mimeType)
- 3 new ConvertTab tests: format selector options, downloadBase64 invoked
  on success, text-destructive error on format rejection
- All 14 web cert-manager tests green
This commit is contained in:
2026-07-02 07:39:41 +02:00
parent 59694642dd
commit f89d6566b2
3 changed files with 178 additions and 4 deletions
@@ -44,6 +44,7 @@ vi.mock('next-intl', () => ({
import CertManagerPage from './page';
import { InspectTab } from './components/InspectTab';
import { SplitTab } from './components/SplitTab';
import { ConvertTab } from './components/ConvertTab';
import * as actions from './actions';
afterEach(() => {
@@ -265,3 +266,74 @@ describe('SplitTab', () => {
expect(errorEl.className).toContain('text-destructive');
});
});
// ---------------------------------------------------------------------------
// ConvertTab — unit tests
// ---------------------------------------------------------------------------
const MOCK_FILE_RESPONSE: actions.FileResponse = {
filename: 'converted.der',
content: btoa('fake der bytes'),
mimeType: 'application/x-x509-ca-cert',
};
describe('ConvertTab', () => {
it('renders format selector with pem, der, p7b options', () => {
render(<ConvertTab file={null} pemText="" password="" />);
const select = screen.getByRole('combobox');
const optionValues = Array.from(select.querySelectorAll('option')).map(
(o) => (o as HTMLOptionElement).value,
);
expect(optionValues).toContain('pem');
expect(optionValues).toContain('der');
expect(optionValues).toContain('p7b');
});
it('calls downloadBase64 after clicking Konvertieren when convertCertAction resolves', async () => {
vi.spyOn(actions, 'convertCertAction').mockResolvedValue(MOCK_FILE_RESPONSE);
const downloadSpy = vi.spyOn(actions, 'downloadBase64').mockImplementation(() => {});
const testFile = new File(['cert data'], 'cert.pem', {
type: 'application/x-pem-file',
});
render(<ConvertTab file={testFile} pemText="" password="" />);
// Click the Konvertieren button
fireEvent.click(screen.getByText('Konvertieren'));
// Wait for async action to resolve and downloadBase64 to be called
await waitFor(() => {
expect(downloadSpy).toHaveBeenCalledWith(
MOCK_FILE_RESPONSE.filename,
MOCK_FILE_RESPONSE.content,
MOCK_FILE_RESPONSE.mimeType,
);
});
});
it('shows text-destructive error when convertCertAction rejects with format error', async () => {
vi.spyOn(actions, 'convertCertAction').mockRejectedValue(
new Error('unknown format or invalid certificate'),
);
const testFile = new File(['garbage'], 'bad.bin', { type: 'application/octet-stream' });
render(<ConvertTab file={testFile} pemText="" password="" />);
fireEvent.click(screen.getByText('Konvertieren'));
await waitFor(() => {
expect(
screen.getByText(
'Unbekanntes Format. Die Datei konnte nicht als Zertifikat erkannt werden.',
),
).toBeInTheDocument();
});
const errorEl = screen.getByText(
'Unbekanntes Format. Die Datei konnte nicht als Zertifikat erkannt werden.',
);
expect(errorEl.className).toContain('text-destructive');
});
});