HH/test_complaint_sms_notifications.py

399 lines
15 KiB
Python

"""
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)