Initial changes to gate section based on completion percentage code refactoring and added evaluation of completion milestone Fixed broken unit tests and added new tests Fixed broken tests and quality violations Fixed Pep8 violation Fixed eslint quality violations Test changes as suggested by reviewer changes after feedbacy from reviewer Update the docstring with suggested changes excluding chapter from the blocks Disallow empty values for min score and min completion Changes afte feedback from UX/Accessibility removed blank line
157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
"""
|
|
Unit tests for the gating feature in Studio
|
|
"""
|
|
import ddt
|
|
import json
|
|
|
|
from mock import patch
|
|
|
|
from contentstore.tests.utils import CourseTestCase
|
|
from contentstore.utils import reverse_usage_url
|
|
from contentstore.views.item import VisibilityState
|
|
from openedx.core.lib.gating.api import GATING_NAMESPACE_QUALIFIER
|
|
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE
|
|
from xmodule.modulestore.tests.factories import ItemFactory
|
|
|
|
|
|
@ddt.ddt
|
|
class TestSubsectionGating(CourseTestCase):
|
|
"""
|
|
Tests for the subsection gating feature
|
|
"""
|
|
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
|
ENABLED_SIGNALS = ['item_deleted']
|
|
|
|
def setUp(self):
|
|
"""
|
|
Initial data setup
|
|
"""
|
|
super(TestSubsectionGating, self).setUp()
|
|
|
|
# Enable subsection gating for the test course
|
|
self.course.enable_subsection_gating = True
|
|
self.save_course()
|
|
|
|
# create a chapter
|
|
self.chapter = ItemFactory.create(
|
|
parent_location=self.course.location,
|
|
category='chapter',
|
|
display_name='untitled chapter'
|
|
)
|
|
|
|
# create 2 sequentials
|
|
self.seq1 = ItemFactory.create(
|
|
parent_location=self.chapter.location,
|
|
category='sequential',
|
|
display_name='untitled sequential 1'
|
|
)
|
|
self.seq1_url = reverse_usage_url('xblock_handler', self.seq1.location)
|
|
|
|
self.seq2 = ItemFactory.create(
|
|
parent_location=self.chapter.location,
|
|
category='sequential',
|
|
display_name='untitled sequential 2'
|
|
)
|
|
self.seq2_url = reverse_usage_url('xblock_handler', self.seq2.location)
|
|
|
|
@patch('contentstore.views.item.gating_api.add_prerequisite')
|
|
def test_add_prerequisite(self, mock_add_prereq):
|
|
"""
|
|
Test adding a subsection as a prerequisite
|
|
"""
|
|
|
|
self.client.ajax_post(
|
|
self.seq1_url,
|
|
data={'isPrereq': True}
|
|
)
|
|
mock_add_prereq.assert_called_with(self.course.id, self.seq1.location)
|
|
|
|
@patch('contentstore.views.item.gating_api.remove_prerequisite')
|
|
def test_remove_prerequisite(self, mock_remove_prereq):
|
|
"""
|
|
Test removing a subsection as a prerequisite
|
|
"""
|
|
|
|
self.client.ajax_post(
|
|
self.seq1_url,
|
|
data={'isPrereq': False}
|
|
)
|
|
mock_remove_prereq.assert_called_with(self.seq1.location)
|
|
|
|
@patch('contentstore.views.item.gating_api.set_required_content')
|
|
def test_add_gate(self, mock_set_required_content):
|
|
"""
|
|
Test adding a gated subsection
|
|
"""
|
|
|
|
self.client.ajax_post(
|
|
self.seq2_url,
|
|
data={'prereqUsageKey': unicode(self.seq1.location), 'prereqMinScore': '100', 'prereqMinCompletion': '100'}
|
|
)
|
|
mock_set_required_content.assert_called_with(
|
|
self.course.id,
|
|
self.seq2.location,
|
|
unicode(self.seq1.location),
|
|
'100',
|
|
'100'
|
|
)
|
|
|
|
@patch('contentstore.views.item.gating_api.set_required_content')
|
|
def test_remove_gate(self, mock_set_required_content):
|
|
"""
|
|
Test removing a gated subsection
|
|
"""
|
|
|
|
self.client.ajax_post(
|
|
self.seq2_url,
|
|
data={'prereqUsageKey': '', 'prereqMinScore': '', 'prereqMinCompletion': ''}
|
|
)
|
|
mock_set_required_content.assert_called_with(
|
|
self.course.id,
|
|
self.seq2.location,
|
|
'',
|
|
'',
|
|
''
|
|
)
|
|
|
|
@patch('contentstore.views.item.gating_api.get_prerequisites')
|
|
@patch('contentstore.views.item.gating_api.get_required_content')
|
|
@patch('contentstore.views.item.gating_api.is_prerequisite')
|
|
@ddt.data(
|
|
(90, None),
|
|
(None, 90),
|
|
(100, 100),
|
|
)
|
|
@ddt.unpack
|
|
def test_get_prerequisite(
|
|
self, min_score, min_completion,
|
|
mock_is_prereq, mock_get_required_content, mock_get_prereqs
|
|
):
|
|
mock_is_prereq.return_value = True
|
|
mock_get_required_content.return_value = unicode(self.seq1.location), min_score, min_completion
|
|
mock_get_prereqs.return_value = [
|
|
{'namespace': '{}{}'.format(unicode(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
|
|
{'namespace': '{}{}'.format(unicode(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
|
|
]
|
|
resp = json.loads(self.client.get_json(self.seq2_url).content)
|
|
mock_is_prereq.assert_called_with(self.course.id, self.seq2.location)
|
|
mock_get_required_content.assert_called_with(self.course.id, self.seq2.location)
|
|
mock_get_prereqs.assert_called_with(self.course.id)
|
|
self.assertTrue(resp['is_prereq'])
|
|
self.assertEqual(resp['prereq'], unicode(self.seq1.location))
|
|
self.assertEqual(resp['prereq_min_score'], min_score)
|
|
self.assertEqual(resp['prereq_min_completion'], min_completion)
|
|
self.assertEqual(resp['visibility_state'], VisibilityState.gated)
|
|
|
|
@patch('contentstore.signals.handlers.gating_api.set_required_content')
|
|
@patch('contentstore.signals.handlers.gating_api.remove_prerequisite')
|
|
def test_delete_item_signal_handler_called(self, mock_remove_prereq, mock_set_required):
|
|
seq3 = ItemFactory.create(
|
|
parent_location=self.chapter.location,
|
|
category='sequential',
|
|
display_name='untitled sequential 3'
|
|
)
|
|
self.client.delete(reverse_usage_url('xblock_handler', seq3.location))
|
|
mock_remove_prereq.assert_called_with(seq3.location)
|
|
mock_set_required.assert_called_with(seq3.location.course_key, seq3.location, None, None, None)
|