feat(public-share): Oeffentliche Share-Zugriffsseite fuer externe Besucher + SPA-Links im DMS-ShareDialog — Modul 13/16 des UI-Backlogs
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* PublicShare page tests — external share link access UI (UI-Backlog M13).
|
||||
*
|
||||
* Covers: loading, unlocked state with download, password flow (required,
|
||||
* invalid, valid), error states (404 not found, 410 expired, other).
|
||||
*/
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { PublicSharePage } from '@/pages/PublicShare';
|
||||
import type { PublicShareInfo } from '@/api/publicShare';
|
||||
|
||||
const { fetchInfo, verifyPw } = vi.hoisted(() => ({
|
||||
fetchInfo: vi.fn(),
|
||||
verifyPw: vi.fn(),
|
||||
}));
|
||||
|
||||
const makeInfo = (overrides: Partial<PublicShareInfo> = {}): PublicShareInfo => ({
|
||||
file_name: 'Angebot.pdf',
|
||||
file_size: 102400,
|
||||
mime_type: 'application/pdf',
|
||||
access_level: 'download',
|
||||
requires_password: false,
|
||||
expires_at: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
vi.mock('@/api/publicShare', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/api/publicShare')>();
|
||||
return {
|
||||
...actual,
|
||||
fetchPublicShareInfo: fetchInfo,
|
||||
verifyPublicSharePassword: verifyPw,
|
||||
};
|
||||
});
|
||||
|
||||
function renderSharePage(token = 'test-token-123') {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={[`/share/${token}`]}>
|
||||
<Routes>
|
||||
<Route path="/share/:token" element={<PublicSharePage />} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
fetchInfo.mockReset();
|
||||
verifyPw.mockReset();
|
||||
});
|
||||
|
||||
describe('PublicSharePage', () => {
|
||||
it('shows loading state initially', () => {
|
||||
fetchInfo.mockReturnValue(new Promise(() => {}));
|
||||
renderSharePage();
|
||||
expect(screen.getByTestId('public-share-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows unlocked file card with download link for unprotected shares', async () => {
|
||||
fetchInfo.mockResolvedValue(makeInfo());
|
||||
renderSharePage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-unlocked')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('public-share-file-name')).toHaveTextContent('Angebot.pdf');
|
||||
const dl = screen.getByTestId('public-share-download-btn');
|
||||
expect(dl).toHaveAttribute('href', '/api/v1/public/share/test-token-123/download');
|
||||
});
|
||||
|
||||
it('shows expiry date when the link expires', async () => {
|
||||
fetchInfo.mockResolvedValue(makeInfo({ expires_at: '2026-12-01T12:00:00Z' }));
|
||||
renderSharePage();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-unlocked')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('public-share-expiry')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('requires a password for protected shares and unlocks on valid password', async () => {
|
||||
fetchInfo.mockResolvedValue(makeInfo({ requires_password: true }));
|
||||
verifyPw.mockResolvedValue({ valid: true });
|
||||
renderSharePage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-password-input')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId('public-share-download-btn')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByTestId('public-share-password-input'), {
|
||||
target: { value: 'geheim123' },
|
||||
});
|
||||
fireEvent.click(screen.getByTestId('public-share-unlock-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-unlocked')).toBeInTheDocument();
|
||||
});
|
||||
expect(verifyPw).toHaveBeenCalledWith('test-token-123', 'geheim123');
|
||||
expect(screen.getByTestId('public-share-download-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows an error message on invalid password', async () => {
|
||||
fetchInfo.mockResolvedValue(makeInfo({ requires_password: true }));
|
||||
verifyPw.mockRejectedValue(new Error('403'));
|
||||
renderSharePage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-password-input')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.change(screen.getByTestId('public-share-password-input'), {
|
||||
target: { value: 'falsch' },
|
||||
});
|
||||
fireEvent.click(screen.getByTestId('public-share-unlock-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-password-error')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId('public-share-unlocked')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows not-found error for 404 responses', async () => {
|
||||
fetchInfo.mockRejectedValue({ response: { status: 404 } });
|
||||
renderSharePage();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-error')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Link nicht gefunden')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows expired error for 410 responses', async () => {
|
||||
fetchInfo.mockRejectedValue({ response: { status: 410 } });
|
||||
renderSharePage();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-error')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Link abgelaufen')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows generic error for other failures', async () => {
|
||||
fetchInfo.mockRejectedValue(new Error('network'));
|
||||
renderSharePage();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-error')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Fehler')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('unlock button stays disabled without a password', async () => {
|
||||
fetchInfo.mockResolvedValue(makeInfo({ requires_password: true }));
|
||||
renderSharePage();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('public-share-unlock-btn')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('public-share-unlock-btn')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user