- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks. - Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content. - Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
124 lines
4.6 KiB
TypeScript
124 lines
4.6 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { renderWithProviders } from '../test/utils';
|
|
|
|
vi.mock('../lib/api', () => ({
|
|
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
|
ApiError: class extends Error {},
|
|
}));
|
|
|
|
// react-leaflet به DOM واقعی نقشه نیاز دارد؛ در jsdom با یک placeholder جایگزین میشود.
|
|
vi.mock('react-leaflet', () => ({
|
|
MapContainer: ({ children }: any) => <div data-testid="map">{children}</div>,
|
|
TileLayer: () => null,
|
|
Marker: () => null,
|
|
useMapEvents: () => null,
|
|
useMap: () => ({ flyTo: vi.fn() }),
|
|
}));
|
|
vi.mock('leaflet', () => ({
|
|
default: { Icon: { Default: { prototype: {}, mergeOptions: vi.fn() } } },
|
|
}));
|
|
vi.mock('leaflet/dist/leaflet.css', () => ({}));
|
|
|
|
vi.mock('../components/ClinicDoctorsManager', () => ({
|
|
default: () => <div data-testid="doctors-manager" />,
|
|
}));
|
|
|
|
import { Routes, Route } from 'react-router';
|
|
import { api } from '../lib/api';
|
|
import { useAuthStore } from '../stores/authStore';
|
|
import ClinicDetailPage from './ClinicDetailPage';
|
|
|
|
/** صفحه از useParams میخواند، پس باید زیر یک Route واقعی رندر شود. */
|
|
function renderPage() {
|
|
return renderWithProviders(
|
|
<Routes>
|
|
<Route path="/admin/clinics/:uuid" element={<ClinicDetailPage />} />
|
|
</Routes>,
|
|
{ route: '/admin/clinics/c1' },
|
|
);
|
|
}
|
|
|
|
const get = api.get as ReturnType<typeof vi.fn>;
|
|
|
|
const clinic = {
|
|
uuid: 'c1',
|
|
name: 'کلینیک نمونه',
|
|
is_active: true,
|
|
phone: '02112345678',
|
|
specialties: [{ id: 1, name: 'قلب' }],
|
|
list_bime: [],
|
|
services: [],
|
|
images_clinic: [],
|
|
};
|
|
|
|
const address = {
|
|
id: '1', uuid: 'a1', name: 'شعبه مرکزی', address: 'خیابان اول',
|
|
telephone: '02100000000',
|
|
map: { latitude: '35.7', longitude: '51.4' },
|
|
city: { id: '1', name: 'تهران' },
|
|
province: { id: '1', name: 'تهران' },
|
|
};
|
|
|
|
function mockApi({ addresses = [address] as any[] } = {}) {
|
|
get.mockImplementation((url: string) => {
|
|
if (url.includes('/addresses')) return Promise.resolve({ success: true, data: { data: addresses } });
|
|
if (url.includes('/api/v1/clinic/')) return Promise.resolve({ success: true, data: { data: clinic } });
|
|
return Promise.resolve({ success: true, data: { data: [] } });
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
useAuthStore.setState({ primaryRole: 'admin', dbUuid: 'someone', token: 't' } as any);
|
|
});
|
|
|
|
describe('ClinicDetailPage', () => {
|
|
it('نام کلینیک را یکبار بهعنوان عنوان صفحه نشان میدهد، نه تکراری در کارت', async () => {
|
|
mockApi();
|
|
renderPage();
|
|
|
|
// عنوان صفحه + آخرین بردکرامب = دو نمونه؛ کارت هویت دیگر نام را تکرار نمیکند
|
|
await waitFor(() => expect(screen.getAllByText('کلینیک نمونه')).toHaveLength(2));
|
|
});
|
|
|
|
it('بردکرامب به فهرست کلینیکها لینک میدهد', async () => {
|
|
mockApi();
|
|
renderPage();
|
|
|
|
const crumb = await screen.findByRole('link', { name: 'کلینیکها' });
|
|
expect(crumb).toHaveAttribute('href', '/admin/clinics');
|
|
});
|
|
|
|
it('شهر و استان را در کارت هویت نشان میدهد', async () => {
|
|
mockApi();
|
|
renderPage();
|
|
await waitFor(() => expect(screen.getAllByText('تهران، تهران').length).toBeGreaterThan(0));
|
|
});
|
|
|
|
it('فرم آدرس با کامپوننت Modal مشترک باز میشود', async () => {
|
|
mockApi({ addresses: [] });
|
|
renderPage();
|
|
|
|
const addBtn = await screen.findByRole('button', { name: /افزودن آدرس/ });
|
|
fireEvent.click(addBtn);
|
|
|
|
expect(await screen.findByText('افزودن آدرس جدید')).toBeInTheDocument();
|
|
// لیبلهای فرم بهجای input دستساز، از field-block طرح استفاده میکنند
|
|
expect(screen.getByText('نام شعبه / عنوان')).toBeInTheDocument();
|
|
expect(screen.getByText('استان')).toBeInTheDocument();
|
|
});
|
|
|
|
it('دکمهٔ حذف آدرس با تم danger رندر میشود (نه رنگ ناموجود --error)', async () => {
|
|
mockApi();
|
|
const { container } = renderPage();
|
|
|
|
await screen.findByText('شعبه مرکزی');
|
|
const del = container.querySelector('button[title="حذف آدرس"]') as HTMLElement;
|
|
expect(del).toBeTruthy();
|
|
expect(del.className).toContain('mini-btn');
|
|
expect(del.className).toContain('danger');
|
|
expect(del.getAttribute('style') ?? '').not.toContain('--error');
|
|
});
|
|
});
|