# OmniDiag — Testing Checklist > **الهدف**: تغطية شاملة لكل طبقات النظام — من الدوال الصغيرة حتى سلوك الـ API الكامل. > **الحالة الراهنة**: الاختبارات الموجودة في `tests/` تغطي Auth + RBAC + Clinical + Patients + Admin + Cache على مستوى HTTP. > **ما ينقص**: Unit tests، اختبارات الأمان، اختبارات النماذج، اختبارات Active Learning، والـ Audit log. --- ## الملفات الموجودة (لا تُعاد كتابتها — فقط تُكمَّل) | الملف | ما يغطيه الآن | |-------|--------------| | `tests/test_auth.py` | Register, Login, GetMe, Refresh, Logout | | `tests/test_rbac.py` | Unauthenticated / Viewer / Doctor / Admin | | `tests/test_clinical.py` | Predict, Explain, Counterfactuals, Schema, Diseases list | | `tests/test_patients.py` | Create, List, Search patients | | `tests/test_admin.py` | Admin endpoints | | `tests/test_cache.py` | Cache hit/miss | --- ## 1. Unit Tests — ما يجب إنشاؤه ### `tests/test_unit_auth.py` **المكونات**: `backend/auth/hashing.py` · `backend/auth/jwt.py` | # | الحالة | الوصف | |---|--------|-------| | U-1 | `hash_password` يُنتج hash مختلف عن النص الأصلي | | | U-2 | `verify_password(plain, hash)` يُرجع `True` للكلمة الصحيحة | | | U-3 | `verify_password(wrong, hash)` يُرجع `False` | | | U-4 | `create_access_token` يحتوي على `type=access` في الـ payload | | | U-5 | `create_refresh_token` يحتوي على `type=refresh` في الـ payload | | | U-6 | `decode_token` يُرجع الـ payload الصحيح | | | U-7 | `decode_token` يرفع 401 على token منتهي الصلاحية | | | U-8 | `decode_token` يرفع 401 على token مزيف | | | U-9 | استخدام refresh token كـ access token يرفع 401 (token type confusion) | | | U-10 | `extract_token_from_request` يقرأ من Cookie أولاً ثم Header | | --- ### `tests/test_unit_active_learning.py` **المكون**: `backend/active_learning/sampler.py` | # | الحالة | الوصف | |---|--------|-------| | U-11 | `prediction_entropy(0.5)` يُرجع `1.0` (أعلى قيمة) | | | U-12 | `prediction_entropy(0.0)` و `prediction_entropy(1.0)` يُرجعان `0.0` | | | U-13 | `should_queue_for_review(0.5)` يُرجع `True` | | | U-14 | `should_queue_for_review(0.99)` يُرجع `False` | | | U-15 | `uncertainty_band(0.5)` يُرجع `"UNCERTAIN"` | | | U-16 | `uncertainty_band(0.95)` يُرجع `"CERTAIN"` | | | U-17 | `uncertainty_band(0.72)` يُرجع `"CONFIDENT"` | | | U-18 | `uncertainty_band(0.62)` يُرجع `"BORDERLINE"` | | --- ### `tests/test_unit_counterfactuals.py` **المكون**: `backend/counterfactual_generator.py` | # | الحالة | الوصف | |---|--------|-------| | U-19 | الـ features الـ IMMUTABLE لا تتغير في أي counterfactual ناتج | | | U-20 | `BMI` في الناتج دائماً بين `15.0` و`50.0` | | | U-21 | الـ binary features لا تأخذ قيماً كسرية (فقط 0 أو 1) | | | U-22 | `MentHlth` و`PhysHlth` في النطاق `[0, 30]` | | | U-23 | الناتج يحتوي على `status=success` و`counterfactuals` كـ list | | --- ### `tests/test_unit_cache.py` **المكون**: `backend/cache.py` | # | الحالة | الوصف | |---|--------|-------| | U-24 | `predict_cache_key` ينتج مفتاحاً مختلفاً لمدخلات مختلفة | | | U-25 | `predict_cache_key` ينتج نفس المفتاح لنفس المدخلات | | | U-26 | `schema_cache_key` يتضمن اسم المرض | | --- ## 2. Integration Tests — ما يجب إكماله ### إضافات على `tests/test_clinical.py` | # | الحالة | الوصف | |---|--------|-------| | I-1 | `/api/v4/diabetes/predict` يعمل بمدخلات صحيحة | | | I-2 | `/api/v4/diabetes/explain` يُرجع `chart_data` | | | I-3 | `/api/v4/heart_disease/predict` بمدخلات ناقصة يُرجع 422 | | | I-4 | `confidence` في الاستجابة دائماً بين `0.0` و`1.0` | | | I-5 | `prediction` في الاستجابة دائماً `0` أو `1` | | | I-6 | `diagnosis` في الاستجابة يطابق قيمة `prediction` (Positive/Negative) | | --- ### `tests/test_audit_log.py` — جديد بالكامل | # | الحالة | الوصف | |---|--------|-------| | I-7 | كل طلب مصادق عليه يُضيف صفاً في `audit_logs` | | | I-8 | الـ `endpoint` المُسجَّل يطابق المسار الفعلي | | | I-9 | الـ `status_code` المُسجَّل يطابق كود الاستجابة | | | I-10 | طلب غير مصادق لا يُضيف صفاً في `audit_logs` (أو يُضيف بـ user_id=null) | | | I-11 | `duration_ms` مسجَّل وقيمته أكبر من صفر | | --- ### `tests/test_active_learning_api.py` — جديد | # | الحالة | الوصف | |---|--------|-------| | I-12 | predict بثقة عالية (`0.99`) لا يُضيف في `review_queue` | | | I-13 | predict بثقة منخفضة (`~0.5`) يُضيف في `review_queue` | | | I-14 | GET `/active-learning/queue` يُرجع القائمة للـ doctor | | | I-15 | PATCH على review item يُغير الـ status | | --- ### إضافات على `tests/test_patients.py` | # | الحالة | الوصف | |---|--------|-------| | I-16 | Soft delete: DELETE مريض لا يحذفه فعلياً — `deleted_at` يُضبط | | | I-17 | مريض محذوف لا يظهر في list إلا مع `?include_deleted=true` | | | I-18 | تاريخ تنبؤات المريض `GET /patients/{id}/predictions` | | | I-19 | Viewer لا يستطيع إنشاء مريض (403) | | --- ## 3. Security Tests — `tests/test_security.py` — جديد | # | الحالة | الوصف | |---|--------|-------| | S-1 | token منتهي الصلاحية يُرجع 401 | | | S-2 | استخدام refresh token في endpoint يتطلب access token → 401 | | | S-3 | تعديل payload الـ JWT يُبطل التوقيع → 401 | | | S-4 | Rate limiting: 6 طلبات login في دقيقة → 429 | | | S-5 | Security headers موجودة: `X-Content-Type-Options`, `X-Frame-Options` | | | S-6 | `Content-Security-Policy` header موجود في الاستجابة | | | S-7 | حقل `search` في `GET /patients/` يعمل بأمان مع محارف خاصة (`' OR 1=1`) | | | S-8 | Token للـ viewer لا يُقبل في admin endpoints حتى لو عُدِّل يدوياً | | --- ## 4. Database Tests — `tests/test_database.py` — جديد | # | الحالة | الوصف | |---|--------|-------| | D-1 | إنشاء مريضين بنفس الـ MRN يُطلق `IntegrityError` (unique constraint) | | | D-2 | Soft delete يضبط `deleted_at` دون حذف فعلي | | | D-3 | حذف user له predictions لا يكسر `predictions` (FK behavior) | | | D-4 | `Prediction` يحفظ `input_features` كـ JSON قابل للاسترجاع | | | D-5 | `audit_logs.user_id` يقبل NULL (طلبات بدون auth) | | --- ## 5. ML Model Tests — `tests/test_ml_models.py` — جديد > **ملاحظة**: هذه الاختبارات تحتاج تحميل النماذج الفعلية — استخدم `pytest.mark.slow` عليها. | # | الحالة | الوصف | |---|--------|-------| | M-1 | `model_loader.py` يُحمِّل النموذج بدون استثناء | | | M-2 | `predict(valid_input)` يُرجع dict يحتوي `prediction` و`confidence` | | | M-3 | `confidence` دائماً في `[0.0, 1.0]` | | | M-4 | `prediction` دائماً `0` أو `1` | | | M-5 | Ensemble: نتيجة `voting_ensemble` متسقة مع base models على حالة واضحة | | | M-6 | SHAP: `shap_values` تُحسَب بدون استثناء | | | M-7 | SHAP: مجموع القيم + `base_value` قريب من `confidence` (±0.1) | | | M-8 | Snapshot regression: مدخلات معروفة تُنتج نفس النتيجة المحفوظة | | --- ## 6. LLM Report Tests — `tests/test_llm_report.py` — جديد > تعتمد على mock للـ Anthropic API. | # | الحالة | الوصف | |---|--------|-------| | L-1 | بدون `ANTHROPIC_API_KEY` يُرجع fallback template بدون استثناء | | | L-2 | الـ fallback يحتوي على `disease_display` و`probability` في النص | | | L-3 | مع mock للـ API يُرجع النص الذي أعاده Claude | | | L-4 | `shap_summary` مبني بشكل صحيح من قائمة الـ SHAP values | | --- ## 7. Drift Monitoring Tests — `tests/test_drift.py` — جديد | # | الحالة | الوصف | |---|--------|-------| | V-1 | `DriftMonitor` يُنشأ بدون استثناء | | | V-2 | `run(current_df)` يُرجع dict يحتوي `drift_detected` و`dataset_drift_score` | | | V-3 | بدون `evidently` يُعيد رسالة واضحة بدل استثناء غامض | | | V-4 | `GET /admin/drift/status` يُرجع 200 للـ admin | | | V-5 | `GET /admin/drift/status` يُرجع 403 للـ viewer | | --- ## إعداد البيئة ```bash # تشغيل كل الاختبارات cd /path/to/project pytest tests/ -v # بدون الاختبارات البطيئة (ML models) pytest tests/ -v -m "not slow" # مع تقرير التغطية pytest tests/ --cov=backend --cov-report=html # ملف واحد pytest tests/test_unit_auth.py -v ``` --- ## أهداف التغطية المستهدفة | الطبقة | التغطية الحالية (تقريبي) | الهدف | |--------|--------------------------|-------| | `backend/auth/` | ~60% | **90%** | | `backend/active_learning/` | 0% | **85%** | | `backend/counterfactual_generator.py` | 0% | **80%** | | `backend/monitoring/` | 0% | **70%** | | `backend/llm/` | 0% | **70%** | | `backend/patients/` | ~50% | **85%** | | `backend/middleware/` | ~30% | **75%** | | **المجموع** | ~40% | **80%** |