Files
edx-platform/lms/djangoapps/support/tests/test_tasks.py
Rodrigo Martin e768d6d9e5 feat: send email after course reset completion (#34460)
* feat: send email after course reset completion

* fix: lint test

* fix: clean code

* fix: correct expected email parts

* fix: logs

* fix: email assertion
2024-04-05 12:27:21 -03:00

251 lines
10 KiB
Python

"""
Unit tests for reset_student_course task
"""
from unittest.mock import patch, Mock, call
from django.conf import settings
from django.core import mail
from xmodule.modulestore.tests.factories import BlockFactory
from lms.djangoapps.courseware.tests.test_submitting_problems import TestSubmittingProblems
from lms.djangoapps.courseware.models import StudentModule
from lms.djangoapps.support.tasks import reset_student_course
from lms.djangoapps.support.tests.factories import CourseResetAuditFactory, CourseResetCourseOptInFactory
from lms.djangoapps.support.models import CourseResetAudit
from common.djangoapps.student.models.course_enrollment import CourseEnrollment
from common.djangoapps.student.roles import SupportStaffRole
from common.djangoapps.student.tests.factories import UserFactory
from xmodule.video_block import VideoBlock
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
class ResetStudentCourse(TestSubmittingProblems):
""" Test reset_student_course task """
USERNAME = "support"
EMAIL = "support@example.com"
PASSWORD = "support"
def setUp(self):
"""
Set permissions, create a course and learner, enroll learner and opt into course reset
"""
super().setUp()
self.user = UserFactory(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
SupportStaffRole().add_users(self.user)
self.course_id = str(self.course.id)
self.enrollment = CourseEnrollment.objects.filter(user=self.student_user, course_id=self.course.id).first()
self.opt_in = CourseResetCourseOptInFactory.create(course_id=self.course.id)
self.audit = CourseResetAuditFactory.create(
course=self.opt_in,
course_enrollment=self.enrollment,
reset_by=self.user,
status=CourseResetAudit.CourseResetStatus.ENQUEUED
)
self.p1 = ''
self.p2 = ''
self.p3 = ''
self.video = ''
# Patch BlockCompletion for the whole test
completion_patcher = patch('lms.djangoapps.support.tasks.BlockCompletion')
self.mock_block_completion = completion_patcher.start()
self.addCleanup(completion_patcher.stop)
# Patch clear_user_course_grades for the whole test
grades_patcher = patch('lms.djangoapps.support.tasks.clear_user_course_grades')
self.mock_clear_user_course_grades = grades_patcher.start()
self.addCleanup(grades_patcher.stop)
@property
def mock_clear_block_completion(self):
""" Helper property to access the two-mock-layers-deep clear_learning_context_completion """
return self.mock_block_completion.objects.clear_learning_context_completion
def basic_setup(self):
"""
Set up a simple course for testing basic grading functionality.
"""
grading_policy = {
"GRADER": [{
"type": "Homework",
"min_count": 1,
"drop_count": 0,
"short_label": "HW",
"weight": 1.0
}],
"GRADE_CUTOFFS": {
'A': .9,
'B': .33
}
}
self.add_grading_policy(grading_policy)
# set up a simple course with four problems
homework = self.add_graded_section_to_course('homework')
vertical = BlockFactory.create(
parent_location=homework.location,
category='vertical',
display_name='Unit 1',
)
self.p1 = self.add_dropdown_to_section(vertical.location, 'p1', 1)
self.p2 = self.add_dropdown_to_section(vertical.location, 'p2', 1)
self.p3 = self.add_dropdown_to_section(vertical.location, 'p3', 1)
video_sample_xml = """
<video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false"
from="1.0"
to="60.0">
<source src="http://www.example.com/file.mp4"/>
<track src="http://www.example.com/track"/>
</video>
"""
video_data = VideoBlock.parse_video_xml(video_sample_xml)
video_data.pop('source')
self.video = BlockFactory.create(
category='video',
parent_location=vertical.location,
**video_data
)
self.refresh_course()
def assert_email_sent_successfully(self, expected):
"""
Verify that the course reset email has been sent to the user.
"""
from_email = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
sent_message = mail.outbox[-1]
body = sent_message.body
assert expected['subject'] in sent_message.subject
assert expected['body'] in body
assert sent_message.from_email == from_email
assert len(sent_message.to) == 1
assert self.student_user.email in sent_message.to
def test_reset_student_course(self):
""" Test that it resets student attempts """
with patch(
'lms.djangoapps.support.tasks.reset_student_attempts',
) as mock_reset_student_attempts:
self.basic_setup()
reset_student_course(self.course_id, self.student_user.email, self.user.email)
mock_reset_student_attempts.assert_has_calls([
call(
self.course.id,
self.student_user,
self.p1.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
),
call(
self.course.id,
self.student_user,
self.p2.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
),
call(
self.course.id,
self.student_user,
self.p3.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
),
call(
self.course.id,
self.student_user,
self.video.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
)
])
self.mock_clear_block_completion.assert_called_once_with(self.student_user, self.course.id)
self.mock_clear_user_course_grades.assert_called_once_with(self.student_user.id, self.course.id)
course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment)
self.assertIsNotNone(course_reset_audit.completed_at)
self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE)
self.assert_email_sent_successfully({
'subject': f'The course { self.course.display_name } has been reset !',
'body': f'Your progress in course { self.course.display_name } has been reset on your behalf.'
})
def test_reset_student_course_student_module_not_found(self):
with patch(
'lms.djangoapps.support.tasks.reset_student_attempts',
Mock(side_effect=StudentModule.DoesNotExist())
) as mock_reset_student_attempts:
self.basic_setup()
reset_student_course(self.course_id, self.student_user.email, self.user.email)
mock_reset_student_attempts.assert_has_calls([
call(
self.course.id,
self.student_user,
self.p1.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
),
call(
self.course.id,
self.student_user,
self.p2.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
),
call(
self.course.id,
self.student_user,
self.p3.location,
self.user,
delete_module=True,
emit_signals_and_events=False,
)
])
self.mock_clear_block_completion.assert_called_once_with(self.student_user, self.course.id)
self.mock_clear_user_course_grades.assert_called_once_with(self.student_user.id, self.course.id)
course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment)
self.assertRaises(StudentModule.DoesNotExist, mock_reset_student_attempts)
self.assertIsNotNone(course_reset_audit.completed_at)
self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE)
@patch('lms.djangoapps.support.tasks.reset_student_attempts')
def test_reset_student_course_fail(self, mock_reset_student_attempts):
with patch(
'lms.djangoapps.support.tasks.get_blocks',
Mock(side_effect=Exception())
):
reset_student_course(self.course_id, self.student_user.email, self.user.email)
mock_reset_student_attempts.assert_not_called()
self.mock_clear_block_completion.assert_not_called()
self.mock_clear_user_course_grades.assert_not_called()
course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment)
self.assertIsNone(course_reset_audit.completed_at)
self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.FAILED)
def test_reset_student_attempts_raise_exception(self):
with patch(
'lms.djangoapps.support.tasks.reset_student_attempts',
Mock(side_effect=Exception())
) as mock_reset_student_attempts:
self.basic_setup()
reset_student_course(self.course_id, self.student_user.email, self.user.email)
mock_reset_student_attempts.assert_called_once()
self.mock_clear_block_completion.assert_not_called()
self.mock_clear_user_course_grades.assert_not_called()
course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment)
self.assertIsNone(course_reset_audit.completed_at)
self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.FAILED)