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.
154 lines
6.6 KiB
154 lines
6.6 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, 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_ru, _ = Language.objects.update_or_create(
|
|
code='ru',
|
|
defaults={'name': 'Russian', '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_ru,
|
|
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": "Курс 1", "language_code": "ru"}
|
|
],
|
|
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))
|
|
|
|
# Reset mock_send because LessonCompletion creation triggered a "Lesson Completed" notification
|
|
mock_send.reset_mock()
|
|
|
|
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("Курс 1", notif.message)
|
|
self.assertTrue(mock_send.called)
|