Files
2024-03-26 11:11:13 +01:00

243 lines
8.9 KiB
Python

""" Tests calling the grades api directly """
from unittest.mock import patch
import ddt
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.grades import api
from lms.djangoapps.grades.models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
PersistentCourseGrade
)
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory # lint-amnesty, pylint: disable=wrong-import-order
@ddt.ddt
class OverrideSubsectionGradeTests(ModuleStoreTestCase):
"""
Tests for the override subsection grades api call
"""
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.user = UserFactory()
cls.overriding_user = UserFactory()
cls.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send')
cls.signal_patcher.start()
cls.id_patcher = patch('lms.djangoapps.grades.api.create_new_event_transaction_id')
cls.mock_create_id = cls.id_patcher.start()
cls.mock_create_id.return_value = 1
cls.type_patcher = patch('lms.djangoapps.grades.api.set_event_transaction_type')
cls.type_patcher.start()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.signal_patcher.stop()
cls.id_patcher.stop()
cls.type_patcher.stop()
def setUp(self):
super().setUp()
self.course = CourseFactory.create()
self.subsection = BlockFactory.create(parent=self.course, category="sequential", display_name="Subsection")
self.grade = PersistentSubsectionGrade.update_or_create_grade(
user_id=self.user.id,
course_id=self.course.id,
usage_key=self.subsection.location,
first_attempted=None,
visible_blocks=[],
earned_all=6.0,
possible_all=6.0,
earned_graded=5.0,
possible_graded=5.0
)
def tearDown(self):
super().tearDown()
PersistentSubsectionGradeOverride.objects.all().delete()
@ddt.data(0.0, None, 3.0)
def test_override_subsection_grade(self, earned_graded):
api.override_subsection_grade(
self.user.id,
self.course.id,
self.subsection.location,
overrider=self.overriding_user,
earned_graded=earned_graded,
comment='Test Override Comment',
)
override_obj = api.get_subsection_grade_override(
self.user.id,
self.course.id,
self.subsection.location
)
assert override_obj is not None
assert override_obj.earned_graded_override == earned_graded
assert override_obj.override_reason == 'Test Override Comment'
for i in range(3):
override_obj.override_reason = 'this field purposefully left blank'
override_obj.earned_graded_override = i
override_obj.save()
api.override_subsection_grade(
self.user.id,
self.course.id,
self.subsection.location,
overrider=self.overriding_user,
earned_graded=earned_graded,
comment='Test Override Comment 2',
)
override_obj = api.get_subsection_grade_override(
self.user.id,
self.course.id,
self.subsection.location
)
assert override_obj is not None
assert override_obj.earned_graded_override == earned_graded
assert override_obj.override_reason == 'Test Override Comment 2'
assert 5 == len(override_obj.history.all())
for history_entry in override_obj.history.all():
if history_entry.override_reason.startswith('Test Override Comment'):
assert self.overriding_user == history_entry.history_user
assert self.overriding_user.id == history_entry.history_user_id
else:
assert history_entry.history_user is None
assert history_entry.history_user_id is None
class ClearGradeTests(ModuleStoreTestCase):
"""
Tests for the clearing grades api call
"""
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.user = UserFactory()
cls.overriding_user = UserFactory()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
def setUp(self):
super().setUp()
self.course = CourseFactory.create()
self.subsection = BlockFactory.create(parent=self.course)
self.grade = PersistentSubsectionGrade.update_or_create_grade(
user_id=self.user.id,
course_id=self.course.id,
usage_key=self.subsection.location,
first_attempted=None,
visible_blocks=[],
earned_all=6.0,
possible_all=6.0,
earned_graded=5.0,
possible_graded=5.0
)
self.params = {
"user_id": self.user.id,
"course_id": self.course.id,
"course_version": "JoeMcEwing",
"percent_grade": 77.7,
"letter_grade": "Great job",
"passed": True,
}
PersistentCourseGrade.update_or_create(**self.params)
def tearDown(self):
super().tearDown()
PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides
def test_clear_user_course_grades(self):
api.override_subsection_grade(
self.user.id,
self.course.id,
self.subsection.location,
overrider=self.overriding_user,
earned_graded=0.0,
comment='Test Override Comment',
)
override_obj = api.get_subsection_grade_override(
self.user.id,
self.course.id,
self.subsection.location
)
course_grade = PersistentCourseGrade.read(self.user.id, self.course.id)
self.assertIsNotNone(course_grade)
self.assertIsNotNone(override_obj)
api.clear_user_course_grades(self.user.id, self.course.id)
with self.assertRaises(PersistentCourseGrade.DoesNotExist):
PersistentCourseGrade.read(self.user.id, self.course.id)
with self.assertRaises(PersistentSubsectionGrade.DoesNotExist):
api.get_subsection_grade_override(
self.user.id,
self.course.id,
self.subsection.location
)
def _create_and_get_user_grades(self, user_id):
""" Creates grades for a user and override object """
api.override_subsection_grade(
user_id,
self.course.id,
self.subsection.location,
overrider=self.overriding_user,
earned_graded=0.0,
comment='Test Override Comment',
)
return api.get_subsection_grade_override(
user_id,
self.course.id,
self.subsection.location
)
def test_clear_other_user_course_grades(self):
"""
Make sure it deletes grades for other_user and not self.user
"""
# Create grades for 2 users
other_user = UserFactory()
user_override_obj = self._create_and_get_user_grades(self.user.id)
other_user_override_obj = self._create_and_get_user_grades(other_user.id)
# fetch and assert grades are available for both users
user_course_grade = PersistentCourseGrade.read(self.user.id, self.course.id)
other_user_course_grade = PersistentCourseGrade.read(self.user.id, self.course.id)
self.assertIsNotNone(user_course_grade)
self.assertIsNotNone(user_override_obj)
self.assertIsNotNone(other_user_override_obj)
self.assertIsNotNone(other_user_course_grade)
api.clear_user_course_grades(other_user.id, self.course.id)
# assert grades after deletion for other_user
after_clear_override_obj = api.get_subsection_grade_override(
self.user.id,
self.course.id,
self.subsection.location
)
after_clear_user_course_grade = PersistentCourseGrade.read(self.user.id, self.course.id)
with self.assertRaises(PersistentCourseGrade.DoesNotExist):
PersistentCourseGrade.read(other_user.id, self.course.id)
self.assertIsNotNone(after_clear_override_obj)
self.assertIsNotNone(after_clear_user_course_grade)
@patch('lms.djangoapps.grades.models_api._PersistentSubsectionGrade')
@patch('lms.djangoapps.grades.models_api._PersistentCourseGrade')
def test_assert_clear_grade_methods_called(self, mock_course_grade, mock_subsection_grade):
api.clear_user_course_grades(self.user.id, self.course.id)
mock_course_grade.delete_course_grade_for_learner.assert_called_with(self.course.id, self.user.id)
mock_subsection_grade.delete_subsection_grades_for_learner.assert_called_with(self.user.id, self.course.id)