399 lines
15 KiB
Python
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) |