feat: implement OTP login flow and enhance role-based access control
- Added OTP login functionality in driver.mjs to handle user authentication with a fixed code in dev environment. - Enhanced RoleRoute component in App.tsx to support clinic-scoped doctor roles and permissions. - Updated ClinicDoctorsManager component to include pagination and search functionality for better user experience. - Refactored tests for ClinicDoctorsManager to cover new features and ensure proper API mocking. - Adjusted permissions in settingsMenu.ts and PracticeDomainSettingsPage.tsx to align with updated backend requirements. - Created RoleRoute.test.tsx to validate role-based access logic for different user scenarios.
This commit is contained in:
@@ -1,48 +1,129 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { renderWithProviders } from '../test/utils';
|
||||
|
||||
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
|
||||
vi.mock('../lib/api', () => ({
|
||||
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
||||
ApiError: class extends Error {},
|
||||
}));
|
||||
|
||||
import { api } from '../lib/api';
|
||||
import ClinicDoctorsManager from './ClinicDoctorsManager';
|
||||
|
||||
const get = api.get as ReturnType<typeof vi.fn>;
|
||||
vi.mock('../lib/api', () => ({ api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() } }));
|
||||
|
||||
beforeEach(() => {
|
||||
get.mockReset();
|
||||
get.mockImplementation((url: string) => {
|
||||
if (url.includes('/clinic/doctor-list/')) return Promise.resolve({ success: true, data: [
|
||||
{ id: '1', uuid: 'doc-uuid-1', name: 'دکتر رضایی', gender: null, degree: null,
|
||||
img: [], specialties: [{ id: '2', name: 'قلب' }], active: true },
|
||||
] });
|
||||
if (url.includes('/invitations')) return Promise.resolve({ success: true, data: [
|
||||
{ uuid: 'inv-1', mobile: '09120000000', invited_name: 'دکتر مهمان', invited_specialty: null,
|
||||
status: 'pending', token_used: false, invited_at: 1, expires_at: 9_999_999_999,
|
||||
responded_at: null, doctor: null },
|
||||
], meta: { totalRecords: 1, totalPages: 1, currentPage: 1 } });
|
||||
return Promise.resolve({ success: true, data: [] });
|
||||
const DOCTOR = {
|
||||
id: '1',
|
||||
uuid: 'doc-1',
|
||||
name: 'پزشک دعوتشده',
|
||||
gender: null,
|
||||
degree: null,
|
||||
img: [],
|
||||
specialties: [],
|
||||
active: false, // نوبتدهی آنلاینِ خودِ پزشک خاموش است
|
||||
};
|
||||
|
||||
/** پاسخها بر اساس مسیر تفکیک میشوند، چون سه کوئری موازی میروند. */
|
||||
function mockApi({ doctors = [DOCTOR], total = 1, permissions = [] as any[], failDoctors = false } = {}) {
|
||||
(api.get as any).mockImplementation((url: string) => {
|
||||
if (url.includes('/doctor-permissions')) return Promise.resolve({ data: permissions });
|
||||
if (url.includes('/invitations')) return Promise.resolve({ data: [] });
|
||||
if (url.includes('/doctor-list/')) {
|
||||
return failDoctors
|
||||
? Promise.reject(new Error('boom'))
|
||||
: Promise.resolve({ data: doctors, meta: { totalRecords: total, totalPages: 1, currentPage: 1 } });
|
||||
}
|
||||
return Promise.resolve({ data: [] });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('ClinicDoctorsManager', () => {
|
||||
it('lists clinic doctors and shows management controls by default', async () => {
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="clinic-1" />, { route: '/admin/settings/clinic-doctors' });
|
||||
expect(await screen.findByText('دکتر رضایی')).toBeInTheDocument();
|
||||
expect(screen.getByText('قلب')).toBeInTheDocument();
|
||||
// manager controls
|
||||
expect(screen.getByText('دعوت پزشک')).toBeInTheDocument();
|
||||
expect(screen.getByTitle('جداسازی از کلینیک')).toBeInTheDocument();
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
it('صفحه و سقف را به سرور میفرستد — وگرنه backend سر ۱۰ پزشک بیصدا میبرد', async () => {
|
||||
mockApi();
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
await waitFor(() => expect(api.get).toHaveBeenCalled());
|
||||
const listCall = (api.get as any).mock.calls.find((c: any[]) => c[0].includes('/doctor-list/'));
|
||||
expect(listCall[0]).toContain('page=1');
|
||||
expect(listCall[0]).toContain('limit=10');
|
||||
});
|
||||
|
||||
it('hides every mutating control when readOnly', async () => {
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="clinic-1" readOnly />, { route: '/admin/settings/clinic-doctors' });
|
||||
expect(await screen.findByText('دکتر رضایی')).toBeInTheDocument();
|
||||
expect(screen.queryByText('دعوت پزشک')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTitle('جداسازی از کلینیک')).not.toBeInTheDocument();
|
||||
it('شمارندهٔ تب از meta میآید، نه از طول صفحهٔ جاری', async () => {
|
||||
mockApi({ doctors: [DOCTOR], total: 25 });
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText(/پزشکان \(۲۵\)/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('«نوبتدهی آنلاین» و «دسترسی در کلینیک» دو ستون جدا هستند', async () => {
|
||||
mockApi();
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('دسترسی در کلینیک')).toBeInTheDocument();
|
||||
expect(screen.getByText('نوبتدهی آنلاین')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('شمار مجوزها زیر همان بجِ دسترسی میآید، نه در ستون جدا', async () => {
|
||||
mockApi({
|
||||
permissions: [{
|
||||
doctor_uuid: 'doc-1',
|
||||
active: true,
|
||||
permissions: { resources: { appointments: { view: true, create: false } } },
|
||||
}],
|
||||
});
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('۱ از ۲ مجوز')).toBeInTheDocument();
|
||||
expect(screen.queryByText('مجوزها')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('پزشکِ بدون ردیف مجوز، «پیشفرض» و دسترسی فعال میگیرد', async () => {
|
||||
mockApi({ permissions: [] });
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('پیشفرض')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('ردیف مجوزِ خاموش، «غیرفعال» نشان میدهد و شمار مجوزها را میآورد', async () => {
|
||||
mockApi({
|
||||
permissions: [{
|
||||
doctor_uuid: 'doc-1',
|
||||
active: false,
|
||||
permissions: { resources: { appointments: { view: true, create: false } } },
|
||||
}],
|
||||
});
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('۱ از ۲ مجوز')).toBeInTheDocument();
|
||||
// دو «غیرفعال»: یکی دسترسیِ کلینیک، یکی نوبتدهی آنلاین. پیش از این یک بج جای
|
||||
// هر دو مینشست و همین ابهام، پزشکِ سالم را «غیرفعال» نشان میداد.
|
||||
expect(screen.getAllByText('غیرفعال')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('دسترسیِ فعال با نوبتدهیِ خاموش قاطی نمیشود', async () => {
|
||||
mockApi({
|
||||
permissions: [{
|
||||
doctor_uuid: 'doc-1',
|
||||
active: true,
|
||||
permissions: { resources: { appointments: { view: true } } },
|
||||
}],
|
||||
});
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
// دسترسی فعال است…
|
||||
expect(await screen.findByText('فعال')).toBeInTheDocument();
|
||||
// …ولی نوبتدهی آنلاینِ خودِ پزشک همچنان خاموش.
|
||||
expect(screen.getByText('غیرفعال')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('خطای فهرست، پیام خطا میدهد نه حالت خالی', async () => {
|
||||
mockApi({ failDoctors: true });
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('فهرست پزشکان بارگذاری نشد')).toBeInTheDocument();
|
||||
expect(screen.queryByText('هیچ پزشکی به این کلینیک متصل نیست')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('فهرست خالی، پیام خالی میدهد', async () => {
|
||||
mockApi({ doctors: [], total: 0 });
|
||||
renderWithProviders(<ClinicDoctorsManager clinicUuid="cl-1" />);
|
||||
|
||||
expect(await screen.findByText('هیچ پزشکی به این کلینیک متصل نیست')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user