Files
edx-platform/lms/djangoapps/gating/tests/test_integration.py
Michael Terry cb1bb7fa64 test: switch default test store to the split store
It's long past time that the default test modulestore was Split,
instead of Old Mongo. This commit switches the default store and
fixes some tests that now fail:
- Tests that didn't expect MFE to be enabled (because we don't
  enable MFE for Old Mongo) - opt out of MFE for those
- Tests that hardcoded old key string formats
- Lots of other random little differences

In many places, I didn't spend much time trying to figure out how to
properly fix the test, and instead just set the modulestore to Old
Mongo.

For those tests that I didn't spend time investigating, I've set
the modulestore to TEST_DATA_MONGO_AMNESTY_MODULESTORE - search for
that string to find further work.
2022-02-04 14:32:50 -05:00

210 lines
8.6 KiB
Python

"""
Integration tests for gated content.
"""
import ddt
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
from crum import set_current_request
from edx_django_utils.cache import RequestCache
from edx_toggles.toggles.testutils import override_waffle_switch
from milestones import api as milestones_api
from milestones.tests.utils import MilestonesTestCaseMixin
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_AMNESTY_MODULESTORE, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.grades.tests.utils import answer_problem
from openedx.core.djangolib.testing.utils import get_mock_request
from openedx.core.lib.gating import api as gating_api
@ddt.ddt
class TestGatedContent(MilestonesTestCaseMixin, SharedModuleStoreTestCase):
"""
Base TestCase class for setting up a basic course structure
and testing the gating feature
"""
MODULESTORE = TEST_DATA_MONGO_AMNESTY_MODULESTORE
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.set_up_course()
def setUp(self):
super().setUp()
self.setup_gating_milestone(50, 100)
self.non_staff_user = UserFactory()
self.staff_user = UserFactory(is_staff=True, is_superuser=True)
self.addCleanup(set_current_request, None)
self.request = get_mock_request(self.non_staff_user)
@classmethod
def set_up_course(cls):
"""
Set up a course for testing gated content.
"""
cls.course = CourseFactory.create(
org='edX',
number='EDX101',
run='EDX101_RUN1',
display_name='edX 101'
)
with modulestore().bulk_operations(cls.course.id):
cls.course.enable_subsection_gating = True
grading_policy = {
"GRADER": [{
"type": "Homework",
"min_count": 3,
"drop_count": 0,
"short_label": "HW",
"weight": 1.0
}]
}
cls.course.grading_policy = grading_policy
cls.course.save()
cls.store.update_item(cls.course, 0)
# create chapter
cls.chapter1 = ItemFactory.create(
parent_location=cls.course.location,
category='chapter',
display_name='chapter 1'
)
# create sequentials
cls.seq1 = ItemFactory.create(
parent_location=cls.chapter1.location,
category='sequential',
display_name='gating sequential 1',
graded=True,
format='Homework',
)
cls.seq2 = ItemFactory.create(
parent_location=cls.chapter1.location,
category='sequential',
display_name='gated sequential 2',
graded=True,
format='Homework',
)
cls.seq3 = ItemFactory.create(
parent_location=cls.chapter1.location,
category='sequential',
display_name='sequential 3',
graded=True,
format='Homework',
)
# create problem
cls.gating_prob1 = ItemFactory.create(
parent_location=cls.seq1.location,
category='problem',
display_name='gating problem 1',
)
# add a discussion block to the prerequisite subsection
# this should give us ability to test gating with blocks
# which needs to be excluded from completion tracking
ItemFactory.create(
parent_location=cls.seq1.location,
category="discussion",
discussion_id="discussion 1",
discussion_category="discussion category",
discussion_target="discussion target",
)
cls.gated_prob2 = ItemFactory.create(
parent_location=cls.seq2.location,
category='problem',
display_name='gated problem 2',
)
cls.prob3 = ItemFactory.create(
parent_location=cls.seq3.location,
category='problem',
display_name='problem 3',
)
def setup_gating_milestone(self, min_score, min_completion):
"""
Setup a gating milestone for testing.
Gating content: seq1 (must be fulfilled before access to seq2)
Gated content: seq2 (requires completion of seq1 before access)
"""
gating_api.add_prerequisite(self.course.id, str(self.seq1.location))
gating_api.set_required_content(
self.course.id, str(self.seq2.location), str(self.seq1.location), min_score, min_completion
)
self.prereq_milestone = gating_api.get_gating_milestone(self.course.id, self.seq1.location, 'fulfills')
def assert_access_to_gated_content(self, user):
"""
Verifies access to gated content for the given user is as expected.
"""
# clear the request cache to flush any cached access results
RequestCache.clear_all_namespaces()
# access to gating content (seq1) remains constant
assert bool(has_access(user, 'load', self.seq1, self.course.id))
# access to gated content (seq2) remains constant, access is prevented in SeqModule loading
assert bool(has_access(user, 'load', self.seq2, self.course.id))
def assert_user_has_prereq_milestone(self, user, expected_has_milestone):
"""
Verifies whether or not the user has the prereq milestone
"""
assert milestones_api.user_has_milestone({'id': user.id}, self.prereq_milestone) == expected_has_milestone
def assert_course_grade(self, user, expected_percent):
"""
Verifies the given user's course grade is the expected percentage.
Also verifies the user's grade information contains values for
all problems in the course, whether or not they are currently
gated.
"""
course_grade = CourseGradeFactory().read(user, self.course)
for prob in [self.gating_prob1, self.gated_prob2, self.prob3]:
assert prob.location in course_grade.problem_scores
assert course_grade.percent == expected_percent
def test_gated_for_nonstaff(self):
self.assert_user_has_prereq_milestone(self.non_staff_user, expected_has_milestone=False)
self.assert_access_to_gated_content(self.non_staff_user)
def test_not_gated_for_staff(self):
self.assert_user_has_prereq_milestone(self.staff_user, expected_has_milestone=False)
self.assert_access_to_gated_content(self.staff_user)
def test_gated_content_always_in_grades(self):
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, True):
# start with a grade from a non-gated subsection
answer_problem(self.course, self.request, self.prob3, 10, 10)
# verify gated status and overall course grade percentage
self.assert_user_has_prereq_milestone(self.non_staff_user, expected_has_milestone=False)
self.assert_access_to_gated_content(self.non_staff_user)
self.assert_course_grade(self.non_staff_user, .33)
# fulfill the gated requirements
answer_problem(self.course, self.request, self.gating_prob1, 10, 10)
# verify gated status and overall course grade percentage
self.assert_user_has_prereq_milestone(self.non_staff_user, expected_has_milestone=True)
self.assert_access_to_gated_content(self.non_staff_user)
self.assert_course_grade(self.non_staff_user, .67)
@ddt.data((1, 1, True), (1, 2, True), (1, 3, False), (0, 1, False))
@ddt.unpack
def test_ungating_when_fulfilled(self, earned, max_possible, result):
self.assert_user_has_prereq_milestone(self.non_staff_user, expected_has_milestone=False)
self.assert_access_to_gated_content(self.non_staff_user)
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, True):
answer_problem(self.course, self.request, self.gating_prob1, earned, max_possible)
self.assert_user_has_prereq_milestone(self.non_staff_user, expected_has_milestone=result)
self.assert_access_to_gated_content(self.non_staff_user)