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

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)