You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

316 lines
14 KiB

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.lesson import CourseChapter, CourseLesson, Lesson, LessonCompletion
from apps.course.models.participant import Participant
from apps.course.models.live_session import CourseLiveSession
from apps.certificate.models import Certificate
from apps.account.tasks import check_live_class_reminders_task, check_new_course_registration_task
class NotificationFlowTests(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_test@example.com',
username='student_test',
fullname='Test Student',
language=self.lang_fa, # Persian template check
fcm='token_student'
)
# Create English student user for template check
self.student_en = StudentUser.objects.create(
email='student_en@example.com',
username='student_en',
fullname='Test Student EN',
language=self.lang_en,
fcm='token_student_en'
)
def _create_course(self, slug, title_en, title_fa, status='ongoing'):
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
return Course.objects.create(
title=[
{"title": title_en, "language_code": "en"},
{"title": title_fa, "language_code": "fa"}
],
slug=[{"title": slug, "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": status, "language_code": "en"},
{"title": status, "language_code": "ru"}
],
is_free=True,
)
@patch('apps.account.notification_service.send_notification')
def test_course_access_granted_notification(self, mock_send):
# Event 1: Course Access Granted
course = self._create_course('course-access', 'Course 1', 'دوره ۱')
# Create participant (active=True by default)
Participant.objects.create(student=self.student, course=course)
# Verify notification created in DB (in Persian)
notif_fa = Notification.objects.filter(user=self.student).first()
self.assertIsNotNone(notif_fa)
self.assertEqual(notif_fa.title, "دسترسی به دوره فعال شد")
self.assertIn("دوره ۱", notif_fa.message)
self.assertEqual(notif_fa.notification_type, "course_access_granted")
self.assertEqual(notif_fa.action, "navigate")
self.assertEqual(notif_fa.navigate_to, "/course_info?slug=course-access")
# Create participant for English student
Participant.objects.create(student=self.student_en, course=course)
notif_en = Notification.objects.filter(user=self.student_en).first()
self.assertIsNotNone(notif_en)
self.assertEqual(notif_en.title, "Course Access Granted")
self.assertIn("Course 1", notif_en.message)
self.assertEqual(notif_en.notification_type, "course_access_granted")
self.assertEqual(notif_en.action, "navigate")
self.assertEqual(notif_en.navigate_to, "/course_info?slug=course-access")
# Verify send_notification was called
self.assertTrue(mock_send.called)
# Check that target routing keys were passed to send_notification call
called_args, called_kwargs = mock_send.call_args
sent_data = called_args[3] if len(called_args) > 3 else called_kwargs.get('data')
self.assertEqual(sent_data.get('type'), "course_access_granted")
self.assertEqual(sent_data.get('action'), "navigate")
self.assertEqual(sent_data.get('navigate_to'), "/course_info?slug=course-access")
@patch('apps.account.notification_service.send_notification')
def test_live_class_reminder_notification(self, mock_send):
# Event 2: LIVE Class Reminder
course = self._create_course('course-live', 'Course 2', 'دوره ۲')
Participant.objects.create(student=self.student, course=course)
# Create a live session starting in 2 hours
session = CourseLiveSession.objects.create(
course=course,
subject='Live Lesson 1',
started_at=timezone.now() + datetime.timedelta(hours=2),
is_cancelled=False,
reminder_sent=False
)
# Run periodic task
check_live_class_reminders_task()
# Verify reminder sent
session.refresh_from_db()
self.assertTrue(session.reminder_sent)
notif = Notification.objects.filter(user=self.student, title="یادآوری کلاس زنده").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۲", notif.message)
# Test rescheduling resets reminder_sent
session.started_at = timezone.now() + datetime.timedelta(hours=5)
session.save()
session.refresh_from_db()
self.assertFalse(session.reminder_sent)
@patch('apps.account.notification_service.send_notification')
def test_course_completed_notification(self, mock_send):
# Event 3: Course Completed
course = self._create_course('course-complete', 'Course 3', 'دوره ۳')
Participant.objects.create(student=self.student, course=course)
chapter = CourseChapter.objects.create(
course=course,
title='Chapter 1',
priority=1,
is_active=True
)
# Add 2 lessons
l1 = Lesson.objects.create(title='Lesson 1', content_type='video_file', duration=5)
cl1 = CourseLesson.objects.create(course=course, chapter=chapter, lesson=l1, priority=1, is_active=True)
l2 = Lesson.objects.create(title='Lesson 2', content_type='video_file', duration=5)
cl2 = CourseLesson.objects.create(course=course, chapter=chapter, lesson=l2, priority=2, is_active=True)
# Complete first lesson
LessonCompletion.objects.create(student=self.student, course_lesson=cl1)
self.assertFalse(Notification.objects.filter(user=self.student, title="دوره به پایان رسید").exists())
# Complete second (last) lesson
LessonCompletion.objects.create(student=self.student, course_lesson=cl2)
# Verify notification triggered
self.assertTrue(Notification.objects.filter(user=self.student, title="دوره به پایان رسید").exists())
@patch('apps.account.notification_service.send_notification')
def test_certificate_issued_notification(self, mock_send):
# Event 4: Certificate Issued
course = self._create_course('course-cert', 'Course 4', 'دوره ۴')
cert = Certificate.objects.create(
student=self.student,
course=course,
status='pending'
)
# Verify no notification yet
self.assertFalse(Notification.objects.filter(user=self.student, title="گواهی صادر شد").exists())
# Approve certificate
cert.status = 'approved'
cert.save()
# Verify notification triggered
self.assertTrue(Notification.objects.filter(user=self.student, title="گواهی صادر شد").exists())
@patch('apps.account.notification_service.send_notification')
def test_new_course_registration_notification(self, mock_send):
# Event 5: New Course Registration
student_recipient = User.objects.create(
email='student_recipient@example.com',
username='student_recipient',
fullname='Recipient Student',
user_type='student',
is_active=True,
language=self.lang_fa,
fcm='token_recipient'
)
course = self._create_course('course-new', 'New Course A', 'دوره جدید الف', status='registering')
check_new_course_registration_task()
course.refresh_from_db()
self.assertTrue(course.notified_new_registration)
notif = Notification.objects.filter(user=student_recipient, title="ثبت‌نام دوره جدید").first()
self.assertIsNotNone(notif)
self.assertIn("دوره جدید الف", notif.message)
@patch('apps.account.notification_service.send_notification')
def test_notification_template_customization_and_deactivation(self, mock_send):
from apps.account.models import NotificationTemplate
# 1. Create a custom template in the database
template = NotificationTemplate.objects.create(
notification_type="custom_test_type",
name="Test Template",
title_fa="سلام {student_name} به دوره {course_title}",
title_en="Hello {student_name} to course {course_title}",
body_fa="توضیحات فارسی {student_name}",
body_en="English description {student_name}",
is_active=True
)
course = self._create_course('test-tpl-course', 'Tpl Course', 'دوره تی‌پی‌ال')
# 2. Trigger notification and verify custom text formatting
from apps.account.notification_service import create_and_send_notification
create_and_send_notification(
user=self.student,
title_en="Default Title EN",
body_en="Default Body EN",
title_fa="Default Title FA",
body_fa="Default Body FA",
service='imam-javad',
data={'type': 'custom_test_type', 'course_id': course.id}
)
notif = Notification.objects.filter(user=self.student).order_by('-id').first()
self.assertIsNotNone(notif)
# Verify it loaded from template and formatted using course name & student name
self.assertEqual(notif.title, f"سلام {self.student.fullname} به دوره دوره تی‌پی‌ال")
self.assertEqual(notif.message, f"توضیحات فارسی {self.student.fullname}")
# 3. Disable template and verify notification is discarded
template.is_active = False
template.save()
count_before = Notification.objects.filter(user=self.student).count()
result = create_and_send_notification(
user=self.student,
title_en="Default Title EN",
body_en="Default Body EN",
title_fa="Default Title FA",
body_fa="Default Body FA",
service='imam-javad',
data={'type': 'custom_test_type', 'course_id': course.id}
)
self.assertIsNone(result)
count_after = Notification.objects.filter(user=self.student).count()
self.assertEqual(count_before, count_after)
def test_scheduled_notification_syncs_periodic_task(self):
from apps.account.models import NotificationTemplate, ScheduledNotification
from django_celery_beat.models import PeriodicTask
template = NotificationTemplate.objects.create(
notification_type="sched_test",
name="Scheduled Test",
title_fa="تست زمان‌بندی",
title_en="Scheduled Test",
body_fa="تست",
body_en="Test"
)
# Create ScheduledNotification
sched_notif = ScheduledNotification.objects.create(
name="My Weekly Campaign",
template=template,
target_group=ScheduledNotification.TargetGroupChoices.ALL_STUDENTS,
schedule_type=ScheduledNotification.ScheduleTypeChoices.WEEKLY,
days_of_week="6,0",
time_of_day="14:30:00",
is_active=True
)
# Verify PeriodicTask and CrontabSchedule are created
self.assertIsNotNone(sched_notif.periodic_task)
pt = sched_notif.periodic_task
self.assertEqual(pt.name, "Scheduled Notification: My Weekly Campaign")
self.assertEqual(pt.crontab.minute, "30")
self.assertEqual(pt.crontab.hour, "14")
self.assertEqual(pt.crontab.day_of_week, "6,0")
self.assertTrue(pt.enabled)
# Deactivate and verify sync
sched_notif.is_active = False
sched_notif.save()
pt.refresh_from_db()
self.assertFalse(pt.enabled)
# Delete and verify dynamic cleanup
task_id = pt.id
sched_notif.delete()
self.assertFalse(PeriodicTask.objects.filter(id=task_id).exists())