diff --git a/lms/djangoapps/course_blocks/tests/test_api.py b/lms/djangoapps/course_blocks/tests/test_api.py new file mode 100644 index 000000000000..25b4f0374707 --- /dev/null +++ b/lms/djangoapps/course_blocks/tests/test_api.py @@ -0,0 +1,148 @@ +# pylint: disable=attribute-defined-outside-init +""" +Tests for course_blocks API +""" + +from unittest.mock import Mock, patch + +import ddt +from django.http.request import HttpRequest + +from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase +from lms.djangoapps.course_blocks.transformers.tests.test_user_partitions import UserPartitionTestMixin +from lms.djangoapps.courseware.block_render import make_track_function, prepare_runtime_for_user +from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers +from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort +from xmodule.modulestore.django import modulestore + + +def get_block_side_effect(block_locator, user_known): + """ + Side effect for `CachingDescriptorSystem.get_block` + """ + store = modulestore() + course = store.get_course(block_locator.course_key) + block = store.get_item(block_locator) + runtime = block.runtime + user = UserFactory.create() + user.known = user_known + + prepare_runtime_for_user( + user=user, + student_data=Mock(), + runtime=runtime, + course_id=block_locator.course_key, + track_function=make_track_function(HttpRequest()), + request_token=Mock(), + course=course, + ) + return block.runtime.get_block_for_descriptor(block) + + +def get_block_side_effect_for_known_user(self, *args, **kwargs): + """ + Side effect for known user test. + """ + return get_block_side_effect(self, True) + + +def get_block_side_effect_for_unknown_user(self, *args, **kwargs): + """ + Side effect for unknown user test. + """ + return get_block_side_effect(self, False) + + + +@ddt.ddt +class TestGetCourseBlocks(UserPartitionTestMixin, CourseStructureTestCase): + """ + Tests `get_course_blocks` API + """ + + def setup_partitions_and_course(self): + """ + Setup course structure. + """ + # Set up user partitions and groups. + self.setup_groups_partitions(active=True, num_groups=1) + self.user_partition = self.user_partitions[0] + + # Build course. + self.course_hierarchy = self.get_course_hierarchy() + self.blocks = self.build_course(self.course_hierarchy) + self.course = self.blocks['course'] + + # Set up cohorts. + self.setup_cohorts(self.course) + + def get_course_hierarchy(self): + """ + Returns a course hierarchy to test with. + """ + # course + # / \ + # / \ + # A[0, 1, 2, 3] B + # | + # | + # O + + return [ + { + 'org': 'UserPartitionTransformer', + 'course': 'UP101F', + 'run': 'test_run', + 'user_partitions': [self.user_partition], + '#type': 'course', + '#ref': 'course', + '#children': [ + { + '#type': 'vertical', + '#ref': 'A', + 'metadata': {'group_access': {self.user_partition.id: [0]}}, + }, + {'#type': 'vertical', '#ref': 'B'}, + ], + }, + { + '#type': 'vertical', + '#ref': 'O', + '#parents': ['B'], + }, + ] + + @ddt.data( + (1, ('course', 'B', 'O'), True), + (1, ('course', 'A', 'B', 'O'), False), + ) + @ddt.unpack + def test_get_course_blocks(self, group_id, expected_blocks, user_known): + """ + Tests that `get_course_blocks` returns blocks without access checks for unknown users. + + Access checks are done through the transformers and through Runtime get_block_for_descriptor. Due + to the runtime limitations during the tests, the Runtime access checks are not performed as + get_block_for_descriptor is never called and Block is returned by CachingDescriptorSystem.get_block. + In this test, we mock the CachingDescriptorSystem.get_block and check block access for known and unknown users. + For known users, it performs the Runtime access checks through get_block_for_descriptor. For unknown, it + skips the access checks. + """ + self.setup_partitions_and_course() + if group_id: + cohort = self.partition_cohorts[self.user_partition.id - 1][group_id - 1] + add_user_to_cohort(cohort, self.user.username) + + side_effect = get_block_side_effect_for_known_user if user_known else get_block_side_effect_for_unknown_user + with patch('xmodule.modulestore.split_mongo.split.CachingDescriptorSystem.get_block', side_effect=side_effect): + block_structure = get_course_blocks( + self.user, + self.course.location, + BlockStructureTransformers([]), + ) + self.assertSetEqual( + set(block_structure.get_block_keys()), + self.get_block_key_set(self.blocks, *expected_blocks) + ) diff --git a/openedx/core/djangoapps/content/block_structure/tests/test_manager.py b/openedx/core/djangoapps/content/block_structure/tests/test_manager.py index aa248f093c82..b76e4d30d21d 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/test_manager.py +++ b/openedx/core/djangoapps/content/block_structure/tests/test_manager.py @@ -5,15 +5,10 @@ import ddt import pytest +from django.core.cache import cache from django.test import TestCase from edx_toggles.toggles.testutils import override_waffle_switch -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory -from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase -from lms.djangoapps.course_blocks.transformers.tests.test_user_partitions import UserPartitionTestMixin -from openedx.core.djangoapps.content.block_structure.api import get_block_structure_manager -from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort - from ..block_structure import BlockStructureBlockData from ..config import STORAGE_BACKING_FOR_CACHE from ..exceptions import UsageKeyNotInBlockStructure @@ -227,150 +222,3 @@ def test_clear(self): self.bs_manager.clear() self.collect_and_verify(expect_modulestore_called=True, expect_cache_updated=True) assert TestTransformer1.collect_call_count == 2 - - -@ddt.ddt -class TestBlockStructureManagerGetCollected(UserPartitionTestMixin, CourseStructureTestCase): - """ - Tests `BlockStructureManager.get_collected` - """ - - def setup_partitions_and_course(self): - """ - Setup course structure and create user. - """ - # Set up user partitions and groups. - self.setup_groups_partitions(active=True) - self.user_partition = self.user_partitions[0] - - # Build course. - self.course_hierarchy = self.get_course_hierarchy() - self.blocks = self.build_course(self.course_hierarchy) - self.course = self.blocks['course'] - - # Enroll user in course. - CourseEnrollmentFactory.create( - user=self.user, course_id=self.course.id, is_active=True - ) - - # Set up cohorts. - self.setup_cohorts(self.course) - - def get_course_hierarchy(self): - """ - Returns a course hierarchy to test with. - """ - # course - # / \ - # / \ - # A[1, 2, 3] B - # / | \ | - # / | \ | - # / | \ | - # C[1, 2] D[2, 3] E / - # / | \ | / \ / - # / | \ | / \ / - # / | \ | / \ / - # F G[1] H[2] I J K[4] / - # / \ / / \ / - # / \ / / \ / - # / \ / / \/ - # L[1, 2] M[1, 2, 3] N O - # - return [ - { - 'org': 'UserPartitionTransformer', - 'course': 'UP101F', - 'run': 'test_run', - 'user_partitions': [self.user_partition], - '#type': 'course', - '#ref': 'course', - '#children': [ - { - '#type': 'vertical', - '#ref': 'A', - 'metadata': {'group_access': {self.user_partition.id: [0, 1, 2, 3]}}, - }, - {'#type': 'vertical', '#ref': 'B'}, - ], - }, - { - '#type': 'vertical', - '#ref': 'C', - '#parents': ['A'], - 'metadata': {'group_access': {self.user_partition.id: [1, 2]}}, - '#children': [ - {'#type': 'vertical', '#ref': 'F'}, - { - '#type': 'vertical', - '#ref': 'G', - 'metadata': {'group_access': {self.user_partition.id: [1]}}, - }, - { - '#type': 'vertical', - '#ref': 'H', - 'metadata': {'group_access': {self.user_partition.id: [2]}}, - }, - ], - }, - { - '#type': 'vertical', - '#ref': 'D', - '#parents': ['A'], - 'metadata': {'group_access': {self.user_partition.id: [2, 3]}}, - '#children': [{'#type': 'vertical', '#ref': 'I'}], - }, - { - '#type': 'vertical', - '#ref': 'E', - '#parents': ['A'], - '#children': [{'#type': 'vertical', '#ref': 'J'}], - }, - { - '#type': 'vertical', - '#ref': 'K', - '#parents': ['E'], - 'metadata': {'group_access': {self.user_partition.id: [4, 51]}}, - '#children': [{'#type': 'vertical', '#ref': 'N'}], - }, - { - '#type': 'vertical', - '#ref': 'L', - '#parents': ['G'], - 'metadata': {'group_access': {self.user_partition.id: [1, 2]}}, - }, - { - '#type': 'vertical', - '#ref': 'M', - '#parents': ['G', 'H'], - 'metadata': {'group_access': {self.user_partition.id: [1, 2, 3]}}, - }, - { - '#type': 'vertical', - '#ref': 'O', - '#parents': ['K', 'B'], - }, - ] - - @ddt.data( - (None, ('course', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O')), - (1, ('course', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O')), - (2, ('course', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O')), - (3, ('course', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O')), - (4, ('course', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O')), - ) - @ddt.unpack - def test_get_collected(self, group_id, expected_blocks): - """ - Test that `BlockStructureManager.get_collected` returns all course blocks regardless of the user group. - """ - self.setup_partitions_and_course() - if group_id: - cohort = self.partition_cohorts[self.user_partition.id - 1][group_id - 1] - add_user_to_cohort(cohort, self.user.username) - - trans_block_structure = get_block_structure_manager(self.course.location.course_key).get_collected(self.user) - self.assertSetEqual( - set(trans_block_structure.get_block_keys()), - self.get_block_key_set(self.blocks, *expected_blocks) - )