feat(export): add readable account archive
CI and Deploy / test (pull_request) Successful in 5m13s
CI and Deploy / deploy (pull_request) Has been skipped

This commit is contained in:
cesnimda
2026-08-15 18:36:40 +02:00
parent cdcc7163fa
commit 1ec9dd037e
15 changed files with 864 additions and 35 deletions
@@ -0,0 +1,54 @@
import React from 'react';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import BackupCard from './components/BackupCard';
import { I18nProvider } from './i18n/I18nProvider';
import { ToastProvider } from './toast';
import { api } from './api';
jest.mock('./api', () => ({
api: {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
},
getApiErrorMessage: (error: any, fallback?: string) => error?.response?.data?.detail || fallback || 'Request failed.',
}));
const mockedApi = api as jest.Mocked<typeof api>;
beforeEach(() => {
mockedApi.post.mockReset();
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: jest.fn(() => 'blob:account-export') });
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: jest.fn() });
jest.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined);
});
afterEach(() => jest.restoreAllMocks());
test('downloads the readable account ZIP from the protected endpoint', async () => {
mockedApi.post.mockResolvedValue({
data: new Blob(['zip']),
headers: { 'content-disposition': 'attachment; filename="jobjakt-account-export.zip"' },
} as any);
render(<ToastProvider><I18nProvider><BackupCard /></I18nProvider></ToastProvider>);
fireEvent.click(screen.getByRole('button', { name: /download readable account export/i }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/export/account', null, { responseType: 'blob' }));
expect(HTMLAnchorElement.prototype.click).toHaveBeenCalled();
expect(await screen.findByText(/readable account export downloaded/i)).toBeInTheDocument();
});
test('explains the recent-sign-in requirement without weakening it', async () => {
mockedApi.post.mockRejectedValue({ response: { status: 403, data: { detail: 'Sign in again before downloading a complete account export.' } } });
render(<ToastProvider><I18nProvider><BackupCard /></I18nProvider></ToastProvider>);
expect(screen.getByText(/requires a sign-in from the last 15 minutes/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /download readable account export/i }));
expect(await screen.findByText(/sign in again before downloading/i)).toBeInTheDocument();
});