feat(09-04): SplitTab UI + splitCertsAction + render tests
- actions.ts: export SplitEntry + SplitResponse interfaces; add splitCertsAction(file) → POST /split - SplitTab.tsx: Aufteilen button (disabled without file); per-cert download list (bg-secondary rows) each row: subject.cn, validity.notAfter, Herunterladen button → downloadBase64 empty state / error state (text-destructive) matching InspectTab pattern - cert-manager.test.tsx: 2 new SplitTab tests (success: 2 download buttons; error: text-destructive) - All 11 cert-manager web tests green; production files type-clean
This commit is contained in:
@@ -43,6 +43,7 @@ vi.mock('next-intl', () => ({
|
||||
|
||||
import CertManagerPage from './page';
|
||||
import { InspectTab } from './components/InspectTab';
|
||||
import { SplitTab } from './components/SplitTab';
|
||||
import * as actions from './actions';
|
||||
|
||||
afterEach(() => {
|
||||
@@ -187,3 +188,80 @@ describe('InspectTab', () => {
|
||||
expect(errorEl.className).toContain('text-destructive');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SplitTab — unit tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const MOCK_SPLIT_RESPONSE: actions.SplitResponse = {
|
||||
count: 2,
|
||||
certs: [
|
||||
{
|
||||
index: 0,
|
||||
filename: 'cert-1.pem',
|
||||
content: btoa('-----BEGIN CERTIFICATE-----\nXXX\n-----END CERTIFICATE-----'),
|
||||
subject: { cn: 'cert1.example.com' },
|
||||
validity: { notAfter: '2027-01-01T00:00:00.000Z' },
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
filename: 'cert-2.pem',
|
||||
content: btoa('-----BEGIN CERTIFICATE-----\nYYY\n-----END CERTIFICATE-----'),
|
||||
subject: { cn: 'cert2.example.com' },
|
||||
validity: { notAfter: '2027-06-01T00:00:00.000Z' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('SplitTab', () => {
|
||||
it('renders two download buttons after clicking Aufteilen with mocked splitCertsAction', async () => {
|
||||
vi.spyOn(actions, 'splitCertsAction').mockResolvedValue(MOCK_SPLIT_RESPONSE);
|
||||
|
||||
const testFile = new File(['cert data'], 'fullchain.pem', {
|
||||
type: 'application/x-pem-file',
|
||||
});
|
||||
|
||||
render(<SplitTab file={testFile} pemText="" password="" />);
|
||||
|
||||
// Initial empty state
|
||||
expect(screen.getByText('Keine Datei geladen.')).toBeInTheDocument();
|
||||
|
||||
// Click the Aufteilen button (only one in this isolated render)
|
||||
fireEvent.click(screen.getByText('Aufteilen'));
|
||||
|
||||
// Wait for the async action to resolve and per-cert list to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('Herunterladen').length).toBe(2);
|
||||
});
|
||||
|
||||
// Empty state is gone
|
||||
expect(screen.queryByText('Keine Datei geladen.')).not.toBeInTheDocument();
|
||||
|
||||
// Both cert CN values are rendered
|
||||
expect(screen.getByText('cert1.example.com')).toBeInTheDocument();
|
||||
expect(screen.getByText('cert2.example.com')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows text-destructive error when splitCertsAction rejects', async () => {
|
||||
vi.spyOn(actions, 'splitCertsAction').mockRejectedValue(
|
||||
new Error('invalid format or corrupted file'),
|
||||
);
|
||||
|
||||
const testFile = new File(['garbage'], 'bad.pem', { type: 'text/plain' });
|
||||
|
||||
render(<SplitTab file={testFile} pemText="" password="" />);
|
||||
|
||||
fireEvent.click(screen.getByText('Aufteilen'));
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user