""" Test script for source-based SLA functionality. Tests: 1. SLA config retrieval for different sources 2. Reminder timing calculation (hours after creation) 3. Escalation timing 4. SLA due date calculation 5. Fallback mechanism """ import os import sys import django # Setup Django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'PX360.settings') sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) django.setup() from django.utils import timezone from datetime import timedelta from apps.organizations.models import Hospital from apps.px_sources.models import PXSource from apps.complaints.models import Complaint, ComplaintSLAConfig from apps.accounts.models import User def print_section(title): """Print a formatted section header""" print("\n" + "="*70) print(f" {title}") print("="*70) def print_test(test_name, result, details=""): """Print test result""" status = "✓ PASS" if result else "✗ FAIL" color = "\033[92m" if result else "\033[91m" reset = "\033[0m" print(f"{color}{status}{reset} - {test_name}") if details: print(f" {details}") def test_source_configs(): """Test that source-based configs exist""" print_section("Test 1: Source-Based SLA Configs") hospital = Hospital.objects.first() if not hospital: print("✗ FAIL: No hospitals found") return False print(f"Hospital: {hospital.name}") sources_to_test = [ ('Ministry of Health', 24, 12, 30, 24), ('Council of Cooperative Health Insurance', 48, 24, 60, 48), ('Patient', 72, 24, 72, 72), ('Family Member', 72, 24, 72, 72), ] all_passed = True for source_name, expected_sla, expected_first_rem, expected_second_rem, expected_escalation in sources_to_test: source = PXSource.objects.filter(name_en=source_name).first() if not source: print_test(f"Source '{source_name}' exists", False, "Source not found") all_passed = False continue config = ComplaintSLAConfig.objects.filter( hospital=hospital, source=source, is_active=True ).first() if not config: print_test(f"SLA config for '{source_name}' exists", False, "Config not found") all_passed = False continue sla_ok = config.sla_hours == expected_sla first_rem_ok = config.first_reminder_hours_after == expected_first_rem second_rem_ok = config.second_reminder_hours_after == expected_second_rem escalation_ok = config.escalation_hours_after == expected_escalation details = f"SLA: {config.sla_hours}h, 1st Rem: {config.first_reminder_hours_after}h, 2nd Rem: {config.second_reminder_hours_after}h, Escalation: {config.escalation_hours_after}h" if sla_ok and first_rem_ok and second_rem_ok and escalation_ok: print_test(f"SLA config for '{source_name}' has correct values", True, details) else: print_test(f"SLA config for '{source_name}' has correct values", False, details) all_passed = False return all_passed def test_sla_config_methods(): """Test helper methods on ComplaintSLAConfig""" print_section("Test 2: SLA Config Helper Methods") hospital = Hospital.objects.first() moh_source = PXSource.objects.filter(name_en='Ministry of Health').first() if not hospital or not moh_source: print("✗ FAIL: Missing required data") return False config = ComplaintSLAConfig.objects.filter( hospital=hospital, source=moh_source ).first() if not config: print_test("Source-based config found", False, "MOH config not found") return False # Test get_first_reminder_hours_after() expected_first = 12 actual_first = config.get_first_reminder_hours_after() print_test( f"get_first_reminder_hours_after() returns {expected_first}h", actual_first == expected_first, f"Expected: {expected_first}, Got: {actual_first}" ) # Test get_second_reminder_hours_after() expected_second = 30 actual_second = config.get_second_reminder_hours_after() print_test( f"get_second_reminder_hours_after() returns {expected_second}h", actual_second == expected_second, f"Expected: {expected_second}, Got: {actual_second}" ) # Test get_escalation_hours_after() expected_escalation = 24 actual_escalation = config.get_escalation_hours_after() print_test( f"get_escalation_hours_after() returns {expected_escalation}h", actual_escalation == expected_escalation, f"Expected: {expected_escalation}, Got: {actual_escalation}" ) # Test legacy config (no source) legacy_config = ComplaintSLAConfig.objects.filter( hospital=hospital, source__isnull=True ).first() if legacy_config and legacy_config.reminder_hours_before: legacy_first = legacy_config.get_first_reminder_hours_after() print_test( "Legacy config (no source) returns None for source-based timing", legacy_first is None, f"Expected: None, Got: {legacy_first}" ) return True def test_complaint_get_sla_config(): """Test Complaint.get_sla_config() method""" print_section("Test 3: Complaint get_sla_config() Method") hospital = Hospital.objects.first() moh_source = PXSource.objects.filter(name_en='Ministry of Health').first() patient_source = PXSource.objects.filter(name_en='Patient').first() if not hospital or not moh_source or not patient_source: print("✗ FAIL: Missing required data") return False # Create test complaints (without saving to database) moh_complaint = Complaint( hospital=hospital, source=moh_source, severity='high', priority='high', status='open' ) patient_complaint = Complaint( hospital=hospital, source=patient_source, severity='medium', priority='medium', status='open' ) # Test MOH complaint moh_config = moh_complaint.get_sla_config() print_test( "MOH complaint returns source-based config", moh_config is not None and moh_config.source == moh_source, f"Config source: {moh_config.source if moh_config else 'None'}" ) if moh_config: print_test( "MOH config has correct SLA hours (24h)", moh_config.sla_hours == 24, f"SLA hours: {moh_config.sla_hours}" ) # Test Patient complaint patient_config = patient_complaint.get_sla_config() print_test( "Patient complaint returns source-based config", patient_config is not None and patient_config.source == patient_source, f"Config source: {patient_config.source if patient_config else 'None'}" ) if patient_config: print_test( "Patient config has correct SLA hours (72h)", patient_config.sla_hours == 72, f"SLA hours: {patient_config.sla_hours}" ) return True def test_sla_due_date_calculation(): """Test SLA due date calculation for different sources""" print_section("Test 4: SLA Due Date Calculation") hospital = Hospital.objects.first() moh_source = PXSource.objects.filter(name_en='Ministry of Health').first() cchi_source = PXSource.objects.filter(name_en='Council of Cooperative Health Insurance').first() patient_source = PXSource.objects.filter(name_en='Patient').first() if not hospital: print("✗ FAIL: No hospital found") return False base_time = timezone.now() # MOH: 24 hours if moh_source: moh_complaint = Complaint( hospital=hospital, source=moh_source, created_at=base_time ) moh_complaint.calculate_sla_due_date() moh_due_at = moh_complaint.due_at if moh_due_at: moh_sla_hours = (moh_due_at - base_time).total_seconds() / 3600 print_test( "MOH complaint SLA is 24 hours", 23 < moh_sla_hours <= 24, f"Calculated SLA: {moh_sla_hours:.1f} hours" ) # CCHI: 48 hours if cchi_source: cchi_complaint = Complaint( hospital=hospital, source=cchi_source, created_at=base_time ) cchi_complaint.calculate_sla_due_date() cchi_due_at = cchi_complaint.due_at if cchi_due_at: cchi_sla_hours = (cchi_due_at - base_time).total_seconds() / 3600 print_test( "CCHI complaint SLA is 48 hours", 47 < cchi_sla_hours <= 48, f"Calculated SLA: {cchi_sla_hours:.1f} hours" ) # Patient: 72 hours if patient_source: patient_complaint = Complaint( hospital=hospital, source=patient_source, created_at=base_time ) patient_complaint.calculate_sla_due_date() patient_due_at = patient_complaint.due_at if patient_due_at: patient_sla_hours = (patient_due_at - base_time).total_seconds() / 3600 print_test( "Patient complaint SLA is 72 hours", 71 < patient_sla_hours <= 72, f"Calculated SLA: {patient_sla_hours:.1f} hours" ) return True def test_reminder_timing(): """Test reminder timing (hours after creation)""" print_section("Test 5: Reminder Timing (Hours After Creation)") hospital = Hospital.objects.first() moh_source = PXSource.objects.filter(name_en='Ministry of Health').first() if not hospital or not moh_source: print("✗ FAIL: Missing required data") return False config = ComplaintSLAConfig.objects.filter( hospital=hospital, source=moh_source ).first() if not config: print_test("MOH config exists", False, "Config not found") return False # MOH: 1st reminder at 12h, 2nd at 30h (12 + 18) base_time = timezone.now() # Test 1st reminder timing first_reminder_hours = config.get_first_reminder_hours_after() first_reminder_at = base_time + timedelta(hours=first_reminder_hours) if first_reminder_hours else None print_test( "MOH 1st reminder is scheduled at 12 hours after creation", first_reminder_hours == 12, f"Hours after creation: {first_reminder_hours}" ) if first_reminder_at: print_test( "1st reminder datetime is correctly calculated", True, f"Created: {base_time.strftime('%H:%M')}, Reminder: {first_reminder_at.strftime('%H:%M')}" ) # Test 2nd reminder timing second_reminder_hours = config.get_second_reminder_hours_after() second_reminder_at = base_time + timedelta(hours=second_reminder_hours) if second_reminder_hours else None print_test( "MOH 2nd reminder is scheduled at 30 hours after creation", second_reminder_hours == 30, f"Hours after creation: {second_reminder_hours}" ) if second_reminder_at: print_test( "2nd reminder datetime is correctly calculated", True, f"Created: {base_time.strftime('%H:%M')}, Reminder: {second_reminder_at.strftime('%H:%M')}" ) # Test escalation timing escalation_hours = config.get_escalation_hours_after() escalation_at = base_time + timedelta(hours=escalation_hours) if escalation_hours else None print_test( "MOH escalation is scheduled at 24 hours after creation", escalation_hours == 24, f"Hours after creation: {escalation_hours}" ) return True def test_fallback_mechanism(): """Test fallback mechanism (source → severity/priority → settings)""" print_section("Test 6: Fallback Mechanism") hospital = Hospital.objects.first() if not hospital: print("✗ FAIL: No hospital found") return False # Create a complaint with a source that has no config call_center_source = PXSource.objects.filter(name_en='Call Center').first() if call_center_source: complaint = Complaint( hospital=hospital, source=call_center_source, severity='high', priority='high', status='open' ) config = complaint.get_sla_config() # Should fall back to severity/priority-based config if config: has_config = True is_source_based = config.source == call_center_source is_severity_based = config.severity == 'high' and config.priority == 'high' print_test( "Fallback: Returns a config when source config doesn't exist", has_config, f"Config found: {has_config}" ) print_test( "Fallback: Returns severity/priority-based config", not is_source_based, f"Source-based: {is_source_based}, Severity-based: {is_severity_based}" ) else: print_test( "Fallback: Returns config even without source config", False, "No config found" ) return True def main(): """Run all tests""" print("\n" + "="*70) print(" SOURCE-BASED SLA FUNCTIONALITY TESTS") print("="*70) results = [] # Run all tests results.append(("Source-Based SLA Configs", test_source_configs())) results.append(("SLA Config Helper Methods", test_sla_config_methods())) results.append(("Complaint get_sla_config()", test_complaint_get_sla_config())) results.append(("SLA Due Date Calculation", test_sla_due_date_calculation())) results.append(("Reminder Timing", test_reminder_timing())) results.append(("Fallback Mechanism", test_fallback_mechanism())) # Print summary print_section("TEST SUMMARY") total_tests = len(results) passed_tests = sum(1 for _, result in results if result) failed_tests = total_tests - passed_tests for test_name, result in results: status = "✓ PASS" if result else "✗ FAIL" print(f"{status} - {test_name}") print("\n" + "="*70) print(f" Total: {total_tests} | Passed: {passed_tests} | Failed: {failed_tests}") print("="*70) if failed_tests == 0: print("\n✓ All tests passed!") else: print(f"\n✗ {failed_tests} test(s) failed") return failed_tests == 0 if __name__ == '__main__': success = main() sys.exit(0 if success else 1)