""" Test complaint SMS notifications This test verifies that SMS notifications are sent automatically when: 1. A complaint is created (confirmation SMS with tracking URL) 2. A complaint status changes to resolved or closed Tests both the signal handlers and the integration with NotificationService. """ import os import django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'PX360.settings') django.setup() from django.test import TestCase, override_settings from django.utils import timezone from unittest.mock import patch, MagicMock from datetime import timedelta from apps.complaints.models import Complaint, ComplaintStatus, ComplaintUpdate from apps.organizations.models import Hospital, Location, MainSection, SubSection from apps.accounts.models import User from apps.notifications.models import NotificationLog class ComplaintSMSNotificationTest(TestCase): """Test complaint SMS notification signals""" def setUp(self): """Set up test data""" # Create hospital self.hospital = Hospital.objects.create( name="Test Hospital", code="TEST001", is_active=True ) # Create location self.location = Location.objects.create( name="Riyadh", code="RYD", hospital=self.hospital ) # Create main section self.main_section = MainSection.objects.create( name="Emergency", code="EMG", location=self.location ) # Create subsection self.subsection = SubSection.objects.create( name="ER Triage", code="ER_TRI", main_section=self.main_section ) # Create user self.user = User.objects.create_user( email="test@example.com", password="testpass123", first_name="Test", last_name="User" ) @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_creation_sms_sent(self, mock_send_sms): """Test that SMS is sent when complaint is created""" # Mock the send_sms to return a notification log mock_notification_log = MagicMock(spec=NotificationLog) mock_notification_log.id = 'test-log-id' mock_send_sms.return_value = mock_notification_log # Create a complaint with phone number complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", contact_email="john@example.com", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Verify SMS was sent mock_send_sms.assert_called_once() # Check the call arguments call_args = mock_send_sms.call_args self.assertEqual(call_args[1]['phone'], '+966501234567') self.assertIn('received', call_args[1]['message']) self.assertIn(complaint.reference_number, call_args[1]['message']) # Verify metadata contains tracking information metadata = call_args[1]['metadata'] self.assertEqual(metadata['notification_type'], 'complaint_created') self.assertEqual(metadata['reference_number'], complaint.reference_number) self.assertIn('tracking_url', metadata) # Verify ComplaintUpdate was created to track SMS updates = ComplaintUpdate.objects.filter(complaint=complaint, update_type='communication') self.assertEqual(updates.count(), 1) self.assertIn('SMS notification sent', updates.first().message) print(f"✓ Test passed: Creation SMS sent for complaint #{complaint.id}") @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_creation_no_phone_no_sms(self, mock_send_sms): """Test that no SMS is sent when complaint has no phone number""" # Create a complaint without phone number complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_email="john@example.com", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Verify SMS was NOT sent mock_send_sms.assert_not_called() print(f"✓ Test passed: No SMS sent for complaint #{complaint.id} without phone number") @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_status_change_to_resolved_sends_sms(self, mock_send_sms): """Test that SMS is sent when complaint status changes to resolved""" # Mock the send_sms to return a notification log mock_notification_log = MagicMock(spec=NotificationLog) mock_notification_log.id = 'test-log-id' mock_send_sms.return_value = mock_notification_log # Create a complaint complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Change status to resolved complaint.status = ComplaintStatus.RESOLVED complaint.resolution = "Issue resolved" complaint.resolved_at = timezone.now() complaint.resolved_by = self.user complaint.save() # Verify SMS was sent mock_send_sms.assert_called_once() # Check the call arguments call_args = mock_send_sms.call_args self.assertEqual(call_args[1]['phone'], '+966501234567') self.assertIn('resolved', call_args[1]['message']) self.assertIn(complaint.reference_number, call_args[1]['message']) # Verify metadata metadata = call_args[1]['metadata'] self.assertEqual(metadata['notification_type'], 'complaint_status_change') self.assertEqual(metadata['old_status'], ComplaintStatus.OPEN) self.assertEqual(metadata['new_status'], ComplaintStatus.RESOLVED) # Verify ComplaintUpdate was created updates = ComplaintUpdate.objects.filter( complaint=complaint, update_type='communication' ) self.assertEqual(updates.count(), 1) print(f"✓ Test passed: Status change SMS sent when complaint resolved") @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_status_change_to_closed_sends_sms(self, mock_send_sms): """Test that SMS is sent when complaint status changes to closed""" # Mock the send_sms to return a notification log mock_notification_log = MagicMock(spec=NotificationLog) mock_notification_log.id = 'test-log-id' mock_send_sms.return_value = mock_notification_log # Create a resolved complaint complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.RESOLVED, due_at=timezone.now() + timedelta(hours=72), resolution="Issue resolved", resolved_at=timezone.now(), resolved_by=self.user, created_by=self.user ) # Change status to closed complaint.status = ComplaintStatus.CLOSED complaint.closed_at = timezone.now() complaint.closed_by = self.user complaint.save() # Verify SMS was sent mock_send_sms.assert_called_once() # Check the call arguments call_args = mock_send_sms.call_args self.assertIn('closed', call_args[1]['message']) # Verify metadata metadata = call_args[1]['metadata'] self.assertEqual(metadata['old_status'], ComplaintStatus.RESOLVED) self.assertEqual(metadata['new_status'], ComplaintStatus.CLOSED) print(f"✓ Test passed: Status change SMS sent when complaint closed") @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_status_change_to_in_progress_no_sms(self, mock_send_sms): """Test that no SMS is sent when status changes to in_progress""" # Create a complaint complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Change status to in_progress complaint.status = ComplaintStatus.IN_PROGRESS complaint.save() # Verify SMS was NOT sent mock_send_sms.assert_not_called() print(f"✓ Test passed: No SMS sent for status change to in_progress") @patch('apps.notifications.services.NotificationService.send_sms') def test_complaint_status_change_same_status_no_sms(self, mock_send_sms): """Test that no SMS is sent when status doesn't actually change""" # Create a complaint complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Save without changing status complaint.description = "Updated description" complaint.save(update_fields=['description']) # Verify SMS was NOT sent mock_send_sms.assert_not_called() print(f"✓ Test passed: No SMS sent when status doesn't change") @patch('apps.notifications.services.NotificationService.send_sms') def test_tracking_url_in_creation_sms(self, mock_send_sms): """Test that tracking URL is included in creation SMS""" # Mock the send_sms mock_notification_log = MagicMock(spec=NotificationLog) mock_notification_log.id = 'test-log-id' mock_send_sms.return_value = mock_notification_log # Create a complaint complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Get the tracking URL tracking_url = complaint.get_tracking_url() # Verify tracking URL is in SMS message call_args = mock_send_sms.call_args message = call_args[1]['message'] self.assertIn('Track:', message) self.assertIn(tracking_url, message) print(f"✓ Test passed: Tracking URL included in creation SMS") print(f" Tracking URL: {tracking_url}") @patch('apps.notifications.services.NotificationService.send_sms') def test_sms_send_error_doesnt_fail_complaint(self, mock_send_sms): """Test that SMS send errors don't cause complaint save to fail""" # Mock send_sms to raise an exception mock_send_sms.side_effect = Exception("SMS service unavailable") # Create a complaint - should succeed even though SMS fails complaint = Complaint.objects.create( hospital=self.hospital, location=self.location, main_section=self.main_section, subsection=self.subsection, title="Test Complaint", description="This is a test complaint", contact_name="John Doe", contact_phone="+966501234567", status=ComplaintStatus.OPEN, due_at=timezone.now() + timedelta(hours=72), created_by=self.user ) # Verify complaint was created successfully self.assertIsNotNone(complaint.id) self.assertEqual(complaint.status, ComplaintStatus.OPEN) # Verify SMS was attempted mock_send_sms.assert_called_once() # Verify no ComplaintUpdate was created (because SMS failed) updates = ComplaintUpdate.objects.filter(complaint=complaint, update_type='communication') self.assertEqual(updates.count(), 0) print(f"✓ Test passed: Complaint created successfully even when SMS fails") def run_tests(): """Run all tests""" print("=" * 70) print("Testing Complaint SMS Notifications") print("=" * 70) print() test = ComplaintSMSNotificationTest() test.setUp() try: test.test_complaint_creation_sms_sent() test.test_complaint_creation_no_phone_no_sms() test.test_complaint_status_change_to_resolved_sends_sms() test.test_complaint_status_change_to_closed_sends_sms() test.test_complaint_status_change_to_in_progress_no_sms() test.test_complaint_status_change_same_status_no_sms() test.test_tracking_url_in_creation_sms() test.test_sms_send_error_doesnt_fail_complaint() print() print("=" * 70) print("✓ All tests passed!") print("=" * 70) return True except Exception as e: print() print("=" * 70) print(f"✗ Test failed: {str(e)}") print("=" * 70) import traceback traceback.print_exc() return False if __name__ == '__main__': success = run_tests() exit(0 if success else 1)