461 lines
15 KiB
Python
461 lines
15 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
Test script for Complaint to Appreciation Conversion Feature
|
||
|
||
This script tests the conversion of appreciation-type complaints to Appreciation records.
|
||
"""
|
||
|
||
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.contrib.auth import get_user_model
|
||
from apps.complaints.models import Complaint, ComplaintUpdate
|
||
from apps.organizations.models import Hospital, Department, Staff
|
||
from app.models import Patient
|
||
from apps.appreciation.models import Appreciation, AppreciationCategory
|
||
from apps.accounts.models import User
|
||
from django.utils import timezone
|
||
from django.urls import reverse
|
||
from django.test import TestCase, Client
|
||
from rest_framework.test import APIClient
|
||
import json
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
def print_section(title):
|
||
"""Print a formatted section header"""
|
||
print(f"\n{'='*60}")
|
||
print(f" {title}")
|
||
print(f"{'='*60}\n")
|
||
|
||
|
||
def print_success(message):
|
||
"""Print success message"""
|
||
print(f"✓ {message}")
|
||
|
||
|
||
def print_error(message):
|
||
"""Print error message"""
|
||
print(f"✗ {message}")
|
||
|
||
|
||
def print_info(message):
|
||
"""Print info message"""
|
||
print(f"ℹ {message}")
|
||
|
||
|
||
def test_default_category():
|
||
"""Test that default category exists"""
|
||
print_section("Test 1: Default Category Check")
|
||
|
||
try:
|
||
category = AppreciationCategory.objects.get(code='patient_feedback')
|
||
print_success(f"Default category exists: {category.name_en}")
|
||
print_info(f"Category ID: {category.id}")
|
||
print_info(f"Name (AR): {category.name_ar}")
|
||
return True
|
||
except AppreciationCategory.DoesNotExist:
|
||
print_error("Default category 'patient_feedback' not found!")
|
||
print_info("Run: python manage.py create_patient_feedback_category")
|
||
return False
|
||
|
||
|
||
def create_test_data():
|
||
"""Create test data for conversion tests"""
|
||
print_section("Test 2: Create Test Data")
|
||
|
||
# Create PX Admin user
|
||
try:
|
||
px_admin = User.objects.get(email='test_px_admin@example.com')
|
||
except User.DoesNotExist:
|
||
px_admin = User.objects.create_user(
|
||
email='test_px_admin@example.com',
|
||
password='testpass123',
|
||
first_name='Test',
|
||
last_name='PX Admin',
|
||
is_px_admin=True
|
||
)
|
||
print_success("Created PX Admin user")
|
||
|
||
# Create Hospital
|
||
try:
|
||
hospital = Hospital.objects.get(code='TEST_HOSPITAL')
|
||
except Hospital.DoesNotExist:
|
||
hospital = Hospital.objects.create(
|
||
code='TEST_HOSPITAL',
|
||
name_en='Test Hospital',
|
||
name_ar='مستشفى تجريبي',
|
||
city='Riyadh'
|
||
)
|
||
print_success("Created Hospital")
|
||
|
||
# Create Department
|
||
try:
|
||
department = Department.objects.get(code='TEST_DEPT')
|
||
except Department.DoesNotExist:
|
||
department = Department.objects.create(
|
||
code='TEST_DEPT',
|
||
hospital=hospital,
|
||
name_en='Test Department',
|
||
name_ar='قسم تجريبي'
|
||
)
|
||
print_success("Created Department")
|
||
|
||
# Create Staff
|
||
try:
|
||
staff = Staff.objects.get(email='test_staff@example.com')
|
||
except Staff.DoesNotExist:
|
||
staff = Staff.objects.create(
|
||
email='test_staff@example.com',
|
||
hospital=hospital,
|
||
department=department,
|
||
first_name='Test',
|
||
last_name='Staff',
|
||
first_name_ar='تجريبي',
|
||
last_name_ar='موظف',
|
||
job_title='Test Employee',
|
||
job_title_ar='موظف تجريبي'
|
||
)
|
||
# Create user account for staff
|
||
staff.user = User.objects.create_user(
|
||
email='test_staff@example.com',
|
||
password='staffpass123',
|
||
first_name='Test',
|
||
last_name='Staff'
|
||
)
|
||
staff.save()
|
||
print_success("Created Staff with user account")
|
||
|
||
# Create Patient
|
||
try:
|
||
patient = Patient.objects.get(mrn='TEST_PATIENT_001')
|
||
except Patient.DoesNotExist:
|
||
patient = Patient.objects.create(
|
||
mrn='TEST_PATIENT_001',
|
||
hospital=hospital,
|
||
first_name='Test',
|
||
last_name='Patient',
|
||
first_name_ar='تجريبي',
|
||
last_name_ar='مريض'
|
||
)
|
||
print_success("Created Patient")
|
||
|
||
return {
|
||
'px_admin': px_admin,
|
||
'hospital': hospital,
|
||
'department': department,
|
||
'staff': staff,
|
||
'patient': patient
|
||
}
|
||
|
||
|
||
def test_appreciation_type_complaint(test_data):
|
||
"""Test creating an appreciation-type complaint"""
|
||
print_section("Test 3: Create Appreciation-Type Complaint")
|
||
|
||
complaint = Complaint.objects.create(
|
||
patient=test_data['patient'],
|
||
hospital=test_data['hospital'],
|
||
department=test_data['department'],
|
||
staff=test_data['staff'],
|
||
complaint_type='appreciation',
|
||
category='quality_of_care',
|
||
subcategory='Positive Feedback',
|
||
severity='low',
|
||
priority='medium',
|
||
source='web',
|
||
title='Excellent Service by Test Staff',
|
||
description='I want to appreciate the excellent service provided by Test Staff. They were very helpful and professional.',
|
||
short_description='Positive feedback about excellent service',
|
||
short_description_ar='تغذية راجعة إيجابية عن خدمة ممتازة',
|
||
status='open',
|
||
created_by=test_data['px_admin']
|
||
)
|
||
|
||
print_success(f"Created appreciation-type complaint: {complaint.id}")
|
||
print_info(f"Complaint type: {complaint.complaint_type}")
|
||
print_info(f"Title: {complaint.title}")
|
||
|
||
return complaint
|
||
|
||
|
||
def test_api_endpoint(test_data, complaint):
|
||
"""Test the conversion API endpoint"""
|
||
print_section("Test 4: Test Conversion API Endpoint")
|
||
|
||
# Get default category
|
||
category = AppreciationCategory.objects.get(code='patient_feedback')
|
||
|
||
# Prepare request data
|
||
data = {
|
||
'recipient_type': 'user',
|
||
'recipient_id': str(test_data['staff'].user.id),
|
||
'category_id': str(category.id),
|
||
'message_en': complaint.description,
|
||
'message_ar': complaint.short_description_ar,
|
||
'visibility': 'department',
|
||
'is_anonymous': True,
|
||
'close_complaint': True
|
||
}
|
||
|
||
# Create API client
|
||
client = APIClient()
|
||
client.force_authenticate(user=test_data['px_admin'])
|
||
|
||
# Make API request
|
||
url = reverse('complaints:convert_to_appreciation', kwargs={'pk': complaint.id})
|
||
print_info(f"API URL: {url}")
|
||
print_info(f"Request data: {json.dumps(data, indent=2)}")
|
||
|
||
response = client.post(url, data, format='json')
|
||
|
||
print_info(f"Response status: {response.status_code}")
|
||
|
||
if response.status_code == 201:
|
||
print_success("Conversion API call successful!")
|
||
result = response.json()
|
||
print_info(f"Response: {json.dumps(result, indent=2)}")
|
||
|
||
# Verify appreciation was created
|
||
appreciation_id = result.get('appreciation_id')
|
||
if appreciation_id:
|
||
try:
|
||
appreciation = Appreciation.objects.get(id=appreciation_id)
|
||
print_success(f"Appreciation record created: {appreciation.id}")
|
||
print_info(f"Appreciation message: {appreciation.message_en[:50]}...")
|
||
|
||
# Verify complaint metadata
|
||
complaint.refresh_from_db()
|
||
if complaint.metadata.get('appreciation_id'):
|
||
print_success("Complaint metadata updated with appreciation link")
|
||
else:
|
||
print_error("Complaint metadata not updated!")
|
||
|
||
# Verify complaint status changed
|
||
if complaint.status == 'closed':
|
||
print_success("Complaint status changed to 'closed'")
|
||
else:
|
||
print_error(f"Complaint status is '{complaint.status}', expected 'closed'")
|
||
|
||
# Verify timeline entry
|
||
timeline_entries = ComplaintUpdate.objects.filter(
|
||
complaint=complaint,
|
||
update_type='note'
|
||
).count()
|
||
if timeline_entries > 0:
|
||
print_success(f"Timeline entry created ({timeline_entries} entries)")
|
||
else:
|
||
print_error("No timeline entry created!")
|
||
|
||
return appreciation
|
||
except Appreciation.DoesNotExist:
|
||
print_error("Appreciation record not found in database!")
|
||
else:
|
||
print_error("No appreciation_id in response!")
|
||
else:
|
||
print_error(f"API call failed with status {response.status_code}")
|
||
print_info(f"Response: {response.json()}")
|
||
|
||
return None
|
||
|
||
|
||
def test_prevention_of_duplicate_conversion(test_data, complaint):
|
||
"""Test that duplicate conversions are prevented"""
|
||
print_section("Test 5: Prevent Duplicate Conversion")
|
||
|
||
# Create API client
|
||
client = APIClient()
|
||
client.force_authenticate(user=test_data['px_admin'])
|
||
|
||
# Get default category
|
||
category = AppreciationCategory.objects.get(code='patient_feedback')
|
||
|
||
# Prepare request data
|
||
data = {
|
||
'recipient_type': 'user',
|
||
'recipient_id': str(test_data['staff'].user.id),
|
||
'category_id': str(category.id),
|
||
'message_en': 'Duplicate test',
|
||
'message_ar': 'اختبار مكرر',
|
||
'visibility': 'private',
|
||
'is_anonymous': True,
|
||
'close_complaint': False
|
||
}
|
||
|
||
# Make API request (should fail)
|
||
url = reverse('complaints:convert_to_appreciation', kwargs={'pk': complaint.id})
|
||
response = client.post(url, data, format='json')
|
||
|
||
print_info(f"Response status: {response.status_code}")
|
||
|
||
if response.status_code == 400:
|
||
print_success("Duplicate conversion prevented as expected!")
|
||
result = response.json()
|
||
print_info(f"Error message: {result.get('error')}")
|
||
return True
|
||
else:
|
||
print_error("Duplicate conversion was not prevented!")
|
||
print_info(f"Response: {response.json()}")
|
||
return False
|
||
|
||
|
||
def test_non_appreciation_type(test_data):
|
||
"""Test that non-appreciation complaints cannot be converted"""
|
||
print_section("Test 6: Non-Appreciation Type Prevention")
|
||
|
||
# Create regular complaint type
|
||
complaint = Complaint.objects.create(
|
||
patient=test_data['patient'],
|
||
hospital=test_data['hospital'],
|
||
department=test_data['department'],
|
||
staff=test_data['staff'],
|
||
complaint_type='complaint', # Not appreciation
|
||
category='quality_of_care',
|
||
subcategory='Wait Time',
|
||
severity='medium',
|
||
priority='high',
|
||
source='web',
|
||
title='Long Wait Time',
|
||
description='I waited too long for my appointment.',
|
||
status='open',
|
||
created_by=test_data['px_admin']
|
||
)
|
||
|
||
print_success("Created regular complaint-type complaint")
|
||
|
||
# Try to convert
|
||
client = APIClient()
|
||
client.force_authenticate(user=test_data['px_admin'])
|
||
|
||
category = AppreciationCategory.objects.get(code='patient_feedback')
|
||
|
||
data = {
|
||
'recipient_type': 'user',
|
||
'recipient_id': str(test_data['staff'].user.id),
|
||
'category_id': str(category.id),
|
||
'message_en': 'Test',
|
||
'message_ar': 'اختبار',
|
||
'visibility': 'private',
|
||
'is_anonymous': True,
|
||
'close_complaint': False
|
||
}
|
||
|
||
url = reverse('complaints:convert_to_appreciation', kwargs={'pk': complaint.id})
|
||
response = client.post(url, data, format='json')
|
||
|
||
print_info(f"Response status: {response.status_code}")
|
||
|
||
if response.status_code == 400:
|
||
print_success("Non-appreciation conversion prevented as expected!")
|
||
result = response.json()
|
||
print_info(f"Error message: {result.get('error')}")
|
||
return True
|
||
else:
|
||
print_error("Non-appreciation conversion was not prevented!")
|
||
return False
|
||
|
||
|
||
def cleanup_test_data():
|
||
"""Clean up test data"""
|
||
print_section("Cleanup: Remove Test Data")
|
||
|
||
# Delete test appreciations
|
||
Appreciation.objects.filter(
|
||
recipient=test_data['staff']
|
||
).delete()
|
||
print_success("Deleted test appreciations")
|
||
|
||
# Delete test complaints
|
||
Complaint.objects.filter(
|
||
patient=test_data['patient']
|
||
).delete()
|
||
print_success("Deleted test complaints")
|
||
|
||
# Delete test patient
|
||
test_data['patient'].delete()
|
||
print_success("Deleted test patient")
|
||
|
||
# Delete test staff user
|
||
if test_data['staff'].user:
|
||
test_data['staff'].user.delete()
|
||
test_data['staff'].delete()
|
||
print_success("Deleted test staff")
|
||
|
||
# Delete test department
|
||
test_data['department'].delete()
|
||
print_success("Deleted test department")
|
||
|
||
# Delete test hospital
|
||
test_data['hospital'].delete()
|
||
print_success("Deleted test hospital")
|
||
|
||
# Delete test px admin
|
||
test_data['px_admin'].delete()
|
||
print_success("Deleted test PX admin")
|
||
|
||
|
||
def main():
|
||
"""Main test runner"""
|
||
print_section("Complaint to Appreciation Conversion - Test Suite")
|
||
print_info("This test suite validates the conversion feature functionality\n")
|
||
|
||
all_passed = True
|
||
|
||
# Test 1: Check default category
|
||
if not test_default_category():
|
||
print_error("Cannot proceed without default category!")
|
||
return False
|
||
|
||
# Test 2: Create test data
|
||
global test_data
|
||
test_data = create_test_data()
|
||
|
||
# Test 3: Create appreciation-type complaint
|
||
complaint = test_appreciation_type_complaint(test_data)
|
||
|
||
# Test 4: Test API endpoint
|
||
appreciation = test_api_endpoint(test_data, complaint)
|
||
if not appreciation:
|
||
all_passed = False
|
||
|
||
# Test 5: Prevent duplicate conversion
|
||
if complaint.metadata.get('appreciation_id'):
|
||
if not test_prevention_of_duplicate_conversion(test_data, complaint):
|
||
all_passed = False
|
||
|
||
# Test 6: Prevent non-appreciation conversion
|
||
if not test_non_appreciation_type(test_data):
|
||
all_passed = False
|
||
|
||
# Cleanup
|
||
cleanup_test_data()
|
||
|
||
# Summary
|
||
print_section("Test Summary")
|
||
if all_passed:
|
||
print_success("All tests passed! ✓")
|
||
print_info("\nThe Complaint to Appreciation Conversion feature is working correctly.")
|
||
else:
|
||
print_error("Some tests failed! ✗")
|
||
print_info("\nPlease review the error messages above and fix any issues.")
|
||
|
||
return all_passed
|
||
|
||
|
||
if __name__ == '__main__':
|
||
try:
|
||
success = main()
|
||
sys.exit(0 if success else 1)
|
||
except Exception as e:
|
||
print_error(f"Unexpected error: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
sys.exit(1)
|