6 changed files with 263 additions and 1 deletions
-
57apps/account/tasks.py
-
151apps/account/tests/notifications/test_retention_notifications.py
-
47apps/course/signals.py
-
1apps/quiz/models/quiz.py
-
4config/settings/base.py
-
4config/settings/production.py
@ -0,0 +1,151 @@ |
|||
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, 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, LiveSessionUser |
|||
from apps.account.tasks import check_inactive_students_task |
|||
|
|||
class RetentionNotificationsFlowTests(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_ret@example.com', |
|||
username='student_ret', |
|||
fullname='Ret Student', |
|||
language=self.lang_fa, |
|||
fcm='token_ret_student' |
|||
) |
|||
|
|||
# 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_inactive_student_inactivity_notification(self, mock_send): |
|||
# Event 1: Inactivity (Self-paced) |
|||
# Setup student to be registered 8 days ago with last_login 8 days ago |
|||
self.student.date_joined = timezone.now() - datetime.timedelta(days=8) |
|||
self.student.last_login = timezone.now() - datetime.timedelta(days=8) |
|||
self.student.save() |
|||
|
|||
# Run task |
|||
check_inactive_students_task() |
|||
|
|||
# Check DB notification |
|||
notif = Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").first() |
|||
self.assertIsNotNone(notif) |
|||
self.assertTrue(mock_send.called) |
|||
|
|||
@patch('apps.account.notification_service.send_notification') |
|||
def test_active_student_no_inactivity_notification(self, mock_send): |
|||
# Date joined 8 days ago, but login 3 days ago |
|||
self.student.date_joined = timezone.now() - datetime.timedelta(days=8) |
|||
self.student.last_login = timezone.now() - datetime.timedelta(days=3) |
|||
self.student.save() |
|||
|
|||
check_inactive_students_task() |
|||
|
|||
self.assertFalse(Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").exists()) |
|||
self.assertFalse(mock_send.called) |
|||
|
|||
@patch('apps.account.notification_service.send_notification') |
|||
def test_student_with_recent_lesson_completion_no_inactivity_notification(self, mock_send): |
|||
# Date joined 8 days ago, last login 8 days ago, but completed a lesson 2 days ago |
|||
self.student.date_joined = timezone.now() - datetime.timedelta(days=8) |
|||
self.student.last_login = timezone.now() - datetime.timedelta(days=8) |
|||
self.student.save() |
|||
|
|||
chapter = CourseChapter.objects.create(course=self.course, title='Chapter 1', priority=1, is_active=True) |
|||
l1 = Lesson.objects.create(title='Lesson 1', content_type='video_file', duration=5) |
|||
cl1 = CourseLesson.objects.create(course=self.course, chapter=chapter, lesson=l1, priority=1, is_active=True) |
|||
|
|||
# Complete lesson 2 days ago |
|||
completion = LessonCompletion.objects.create(student=self.student, course_lesson=cl1) |
|||
# Manually force completed_at to be 2 days ago (since auto_now_add makes it now) |
|||
LessonCompletion.objects.filter(id=completion.id).update(completed_at=timezone.now() - datetime.timedelta(days=2)) |
|||
|
|||
check_inactive_students_task() |
|||
|
|||
self.assertFalse(Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").exists()) |
|||
self.assertFalse(mock_send.called) |
|||
|
|||
@patch('apps.account.notification_service.send_notification') |
|||
def test_missed_live_sessions_notification(self, mock_send): |
|||
# Event 2: Missed LIVE Sessions |
|||
# Create session 1 (ended) |
|||
s1 = CourseLiveSession.objects.create( |
|||
course=self.course, |
|||
subject='Live Class One', |
|||
started_at=timezone.now() - datetime.timedelta(days=3), |
|||
ended_at=timezone.now() - datetime.timedelta(days=3, hours=2), |
|||
is_cancelled=False, |
|||
reminder_sent=True |
|||
) |
|||
|
|||
# Create session 2 (ended just now) |
|||
s2 = CourseLiveSession.objects.create( |
|||
course=self.course, |
|||
subject='Live Class Two', |
|||
started_at=timezone.now() - datetime.timedelta(hours=2), |
|||
is_cancelled=False, |
|||
reminder_sent=True |
|||
) |
|||
|
|||
# The student has not joined either s1 or s2 (no LiveSessionUser entries). |
|||
# Set ended_at to trigger the signal |
|||
s2.ended_at = timezone.now() |
|||
s2.save() |
|||
|
|||
# Check DB notification |
|||
notif = Notification.objects.filter(user=self.student, title="جای شما در کلاس زنده خالی بود!").first() |
|||
self.assertIsNotNone(notif) |
|||
self.assertIn("Course 1", notif.message) |
|||
self.assertTrue(mock_send.called) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue