diff --git a/apps/account/tests/notifications/test_communication_notifications.py b/apps/account/tests/notifications/test_communication_notifications.py new file mode 100644 index 0000000..8e65b57 --- /dev/null +++ b/apps/account/tests/notifications/test_communication_notifications.py @@ -0,0 +1,184 @@ +import datetime +from django.utils import timezone +from django.test import TestCase +from unittest.mock import patch +from django.core.files.uploadedfile import SimpleUploadedFile + +from dj_language.models import Language +from apps.account.models import StudentUser, User, Notification +from apps.course.models.course import Course, CourseCategory +from apps.course.models.participant import Participant +from apps.chat.models import RoomMessage, ChatMessage + +class CommunicationNotificationsFlowTests(TestCase): + def setUp(self): + # Create languages + self.lang_fa, _ = Language.objects.update_or_create( + code='fa', + defaults={'name': 'Persian', 'status': True, 'countries': []} + ) + self.lang_en, _ = Language.objects.update_or_create( + code='en', + defaults={'name': 'English', 'status': True, 'countries': []} + ) + + # Create category + self.category = CourseCategory.objects.create( + name=[{"title": "Category", "language_code": "en"}], + slug=[{"title": "category", "language_code": "en"}] + ) + + # Create student user + self.student = StudentUser.objects.create( + email='student_comm@example.com', + username='student_comm', + fullname='Comm Student', + language=self.lang_fa, + fcm='token_comm_student' + ) + + # Create teacher user + self.teacher = User.objects.create( + email='teacher_comm@example.com', + username='teacher_comm', + fullname='Teacher Comm', + user_type='professor', + is_active=True + ) + + # Create support user (admin) + self.support = User.objects.create( + email='support_comm@example.com', + username='support_comm', + fullname='Support Comm', + user_type='admin', + is_active=True + ) + + # Create Course + thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg') + self.course = Course.objects.create( + title=[ + {"title": "Course 1", "language_code": "en"}, + {"title": "دوره ۱", "language_code": "fa"} + ], + slug=[{"title": "course-1", "language_code": "en"}], + category=self.category, + thumbnail=thumbnail, + video_type=Course.VedioTypeChoices.YOUTUBE_LINK, + video_link='https://example.com/video', + is_online=False, + level=Course.LevelChoices.BEGINNER, + duration=10, + lessons_count=0, + description='Description', + short_description='Short description', + status=[{"title": "ongoing", "language_code": "en"}], + is_free=True, + ) + + # Enroll student in the course + self.participant = Participant.objects.create( + student=self.student, + course=self.course, + is_active=True + ) + + @patch('apps.account.notification_service.send_notification') + def test_teacher_replied_private_chat(self, mock_send): + # Create a private chat room between student and teacher + room = RoomMessage.objects.create( + name="Private chat with teacher", + initiator=self.student, + recipient=self.teacher, + room_type=RoomMessage.RoomTypeChoices.PRIVATE + ) + + # Teacher sends a message + ChatMessage.objects.create( + room=room, + sender=self.teacher, + content="Hello from teacher", + content_type=ChatMessage.ChatTypeChoices.TEXT + ) + + # Verify notification sent to the student + notif = Notification.objects.filter(user=self.student, title="پیام جدید از طرف استاد").first() + self.assertIsNotNone(notif) + self.assertIn("Hello from teacher", notif.message) + self.assertTrue(mock_send.called) + + @patch('apps.account.notification_service.send_notification') + def test_support_replied_private_chat(self, mock_send): + # Create a private chat room between student and support + room = RoomMessage.objects.create( + name="Support chat", + initiator=self.student, + recipient=self.support, + room_type=RoomMessage.RoomTypeChoices.PRIVATE + ) + + # Support agent sends a message + ChatMessage.objects.create( + room=room, + sender=self.support, + content="How can I help you?", + content_type=ChatMessage.ChatTypeChoices.TEXT + ) + + # Verify notification sent to the student + notif = Notification.objects.filter(user=self.student, title="پاسخ پشتیبانی").first() + self.assertIsNotNone(notif) + self.assertIn("How can I help you?", notif.message) + self.assertTrue(mock_send.called) + + @patch('apps.account.notification_service.send_notification') + def test_teacher_posted_in_course_chat(self, mock_send): + # Create a course chat room + room = RoomMessage.objects.create( + name="Course group chat", + course=self.course, + initiator=self.teacher, + room_type=RoomMessage.RoomTypeChoices.GROUP + ) + + # Teacher sends a message to course chat + ChatMessage.objects.create( + room=room, + sender=self.teacher, + content="Hello class, remember the homework", + content_type=ChatMessage.ChatTypeChoices.TEXT + ) + + # Verify notification sent to the student enrolled + notif = Notification.objects.filter(user=self.student, title="پیام استاد در گفتگوی دوره").first() + self.assertIsNotNone(notif) + self.assertIn("homework", notif.message) + self.assertIn("دوره ۱", notif.message) + self.assertTrue(mock_send.called) + + @patch('apps.account.notification_service.send_notification') + def test_student_message_no_notification(self, mock_send): + # Create a private chat room + room = RoomMessage.objects.create( + name="Private chat", + initiator=self.student, + recipient=self.teacher, + room_type=RoomMessage.RoomTypeChoices.PRIVATE + ) + + # Clear notifications + Notification.objects.filter(user=self.student).delete() + mock_send.reset_mock() + + # Student sends a message + ChatMessage.objects.create( + room=room, + sender=self.student, + content="Hello professor", + content_type=ChatMessage.ChatTypeChoices.TEXT + ) + + # Student should not receive a notification for their own message + self.assertFalse(Notification.objects.filter(user=self.student).exists()) + self.assertFalse(mock_send.called) diff --git a/apps/chat/apps.py b/apps/chat/apps.py index 2d27770..08a568e 100644 --- a/apps/chat/apps.py +++ b/apps/chat/apps.py @@ -4,3 +4,6 @@ from django.apps import AppConfig class ChatConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'apps.chat' + + def ready(self): + import apps.chat.signals diff --git a/apps/chat/signals.py b/apps/chat/signals.py new file mode 100644 index 0000000..8dd489a --- /dev/null +++ b/apps/chat/signals.py @@ -0,0 +1,100 @@ +import logging +from django.db.models.signals import post_save +from django.dispatch import receiver +from apps.chat.models import ChatMessage, RoomMessage +from apps.account.models import User +from apps.account.notification_service import create_and_send_notification +from apps.course.models.course import get_localized_field +from apps.course.models.participant import Participant + +logger = logging.getLogger(__name__) + +def get_message_preview(instance, lang='fa'): + if instance.content_type == 'text': + preview = instance.content or '' + if len(preview) > 50: + return preview[:50] + '...' + return preview + elif instance.content_type == 'image': + return 'تصویر' if lang == 'fa' else 'Image' + elif instance.content_type == 'audio': + return 'صدا' if lang == 'fa' else 'Audio' + else: + return 'فایل ضمیمه' if lang == 'fa' else 'Attachment' + +@receiver(post_save, sender=ChatMessage) +def notify_on_new_chat_message(sender, instance, created, **kwargs): + if not created: + return + + sender_user = instance.sender + room = instance.room + + # 1. Check if the message is in a PRIVATE room + if room.room_type == RoomMessage.RoomTypeChoices.PRIVATE: + # Determine the recipient user (the other person in the private room) + recipient_user = None + if room.initiator == sender_user: + recipient_user = room.recipient + else: + recipient_user = room.initiator + + if not recipient_user: + return + + # Case A: Teacher Replied in a Private Chat + if sender_user.user_type == User.UserType.PROFESSOR: + if recipient_user.user_type in [User.UserType.STUDENT, User.UserType.CLIENT]: + preview_en = get_message_preview(instance, 'en') + preview_fa = get_message_preview(instance, 'fa') + + create_and_send_notification( + user=recipient_user, + title_en="New Message from Teacher", + body_en=f"Teacher {sender_user.fullname or sender_user.username} replied: {preview_en}", + title_fa="پیام جدید از طرف استاد", + body_fa=f"استاد {sender_user.fullname or sender_user.username} پاسخ داد: {preview_fa}", + service='imam-javad', + data={'type': 'teacher_reply_private', 'message_id': instance.id, 'room_id': room.id} + ) + + # Case B: Support Replied + elif sender_user.user_type in [User.UserType.ADMIN, User.UserType.SUPER_ADMIN, User.UserType.CONSULTANT]: + if recipient_user.user_type in [User.UserType.STUDENT, User.UserType.CLIENT]: + preview_en = get_message_preview(instance, 'en') + preview_fa = get_message_preview(instance, 'fa') + + create_and_send_notification( + user=recipient_user, + title_en="New Message from Support", + body_en=f"Support agent {sender_user.fullname or sender_user.username} replied to your message: {preview_en}", + title_fa="پاسخ پشتیبانی", + body_fa=f"پشتیبان {sender_user.fullname or sender_user.username} به پیام شما پاسخ داد: {preview_fa}", + service='imam-javad', + data={'type': 'support_reply', 'message_id': instance.id, 'room_id': room.id} + ) + + # 2. Check if the message is in a GROUP/COURSE room + elif room.room_type == RoomMessage.RoomTypeChoices.GROUP and room.course: + # Case A: Teacher Replied in a Course Chat + if sender_user.user_type == User.UserType.PROFESSOR: + course = room.course + course_title_en = get_localized_field('en', course.title) + course_title_fa = get_localized_field('fa', course.title) + + preview_en = get_message_preview(instance, 'en') + preview_fa = get_message_preview(instance, 'fa') + + # Notify all active enrolled students of the course + participants = Participant.objects.filter(course=course, is_active=True).select_related('student') + for p in participants: + if p.student != sender_user: # Exclude sender + create_and_send_notification( + user=p.student, + title_en="Teacher Post in Course Chat", + body_en=f"Teacher {sender_user.fullname or sender_user.username} posted in '{course_title_en}': {preview_en}", + title_fa="پیام استاد در گفتگوی دوره", + body_fa=f"استاد {sender_user.fullname or sender_user.username} در گفتگوی دوره «{course_title_fa}» پیام جدیدی فرستاد: {preview_fa}", + service='imam-javad', + data={'type': 'teacher_reply_course', 'message_id': instance.id, 'room_id': room.id} + )