Files
edx-platform/cms/djangoapps/contentstore/views/tests/test_gating.py
Jesper Hodge a6c57811cd feat add xblock api endpoint (#32282)
* feat: add xblock endpoint for updating an xblock

fix: remove debugger

feat: make function call more generic

refactor: just use request.json for request data as before

refactor: extract method

fix: revert wrong method change

fix: refactor correct method

feat: use handle_xblock method so that we can do more than update xblocks

fix: usage_key_string defaults to None

add all CRUD operations

fix usage key parameter

refactor: create /views folder

refactor: move xblock view functions to xblock_services

fix: tests

fix: tests

refactor: move xblock API endpoint to contentstore

* docs: add explanatory comment to new xblock_service

* feat: add feature flag for enabling content editing api

* feat: raise 404 if studio content api is disabled

* tests: test xblock endpoint

* test: make all post tests work

* test: check that xblock_handler receives correct args

* refactor: create util mixin for course factories with staff

* refactor: extract course staff authorization tests

* refactor: extract tests to api view testcase class

* test: add get tests

* test: fix tests

* test: fix tests

* test: fix tests

* test: add all crud tests

* fix: refactor to fix tests

* fix: merge conflict

* fix: merge conflict

* fix: tests after merge

* fix: json request decorator

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: new test files

* fix: lint

* fix: lint and apply PR suggestions

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: lint

* fix: lint
2023-06-15 14:17:49 -04:00

161 lines
5.9 KiB
Python

"""
Unit tests for the gating feature in Studio
"""
import json
from unittest.mock import patch
import ddt
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE
from xmodule.modulestore.tests.factories import BlockFactory
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import reverse_usage_url
from openedx.core.lib.gating.api import GATING_NAMESPACE_QUALIFIER
from cms.djangoapps.contentstore.xblock_services.xblock_service import VisibilityState
@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().setUp()
# Enable subsection gating for the test course
self.course.enable_subsection_gating = True
self.save_course()
# create a chapter
self.chapter = BlockFactory.create(
parent_location=self.course.location,
category='chapter',
display_name='untitled chapter'
)
# create 2 sequentials
self.seq1 = BlockFactory.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 = BlockFactory.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('cms.djangoapps.contentstore.xblock_services.xblock_service.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('cms.djangoapps.contentstore.xblock_services.xblock_service.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('cms.djangoapps.contentstore.xblock_services.xblock_service.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': str(self.seq1.location), 'prereqMinScore': '100',
'prereqMinCompletion': '100'}
)
mock_set_required_content.assert_called_with(
self.course.id,
self.seq2.location,
str(self.seq1.location),
'100',
'100'
)
@patch('cms.djangoapps.contentstore.xblock_services.xblock_service.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('cms.djangoapps.contentstore.xblock_services.xblock_service.gating_api.get_prerequisites')
@patch('cms.djangoapps.contentstore.xblock_services.xblock_service.gating_api.get_required_content')
@patch('cms.djangoapps.contentstore.xblock_services.xblock_service.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 = str(self.seq1.location), min_score, min_completion
mock_get_prereqs.return_value = [
{'namespace': f'{str(self.seq1.location)}{GATING_NAMESPACE_QUALIFIER}'},
{'namespace': f'{str(self.seq2.location)}{GATING_NAMESPACE_QUALIFIER}'}
]
resp = json.loads(self.client.get_json(self.seq2_url).content.decode('utf-8'))
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'], str(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('cms.djangoapps.contentstore.signals.handlers.gating_api.set_required_content')
@patch('cms.djangoapps.contentstore.signals.handlers.gating_api.remove_prerequisite')
def test_delete_item_signal_handler_called(self, mock_remove_prereq, mock_set_required):
seq3 = BlockFactory.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)