Files
edx-platform/lms/djangoapps/courseware/tests/test_group_access.py
alangsto 4a1346b068 INCR-265 Run python-modernize on lms/djangoapps/courseware/management and lms/djangoapps/courseware/tests (#20716)
* updated files according to INCR-265

* fixed docstring and line-length problems from quality test

* Revert "fixed docstring and line-length problems from quality test"

This reverts commit d050f55a4ecfaa38f46b80ec4bb85ff399a79a8c.

* fixed errors reported in quality report

* had error, fixed it

* reversed change

* fixed over/under indentation, and added line to import.py that Ned had suggested

* tried disabling pylint for this line

* testing new email

* testing email in different window

* re-added symlink and docstring
2019-05-31 14:07:18 -04:00

409 lines
16 KiB
Python

"""
This module defines tests for courseware.access that are specific to group
access control rules.
"""
from __future__ import absolute_import
import ddt
from stevedore.extension import Extension, ExtensionManager
import courseware.access as access
from courseware.tests.factories import StaffFactory, UserFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.partitions.partitions import USER_PARTITION_SCHEME_NAMESPACE, Group, UserPartition
class MemoryUserPartitionScheme(object):
"""
In-memory partition scheme for testing.
"""
name = "memory"
def __init__(self):
self.current_group = {}
def set_group_for_user(self, user, user_partition, group):
"""
Link this user to this group in this partition, in memory.
"""
self.current_group.setdefault(user.id, {})[user_partition.id] = group
def get_group_for_user(self, course_id, user, user_partition): # pylint: disable=unused-argument
"""
Fetch the group to which this user is linked in this partition, or None.
"""
return self.current_group.get(user.id, {}).get(user_partition.id)
def resolve_attrs(test_method):
"""
Helper function used with ddt. It allows passing strings to test methods
via @ddt.data, which are the names of instance attributes on `self`, and
replaces them with the resolved values of those attributes in the method
call.
"""
def _wrapper(self, *args):
new_args = [getattr(self, arg) for arg in args]
return test_method(self, *new_args)
return _wrapper
@ddt.ddt
class GroupAccessTestCase(ModuleStoreTestCase):
"""
Tests to ensure that has_access() correctly enforces the visibility
restrictions specified in the `group_access` field of XBlocks.
"""
def set_user_group(self, user, partition, group):
"""
Internal DRY / shorthand.
"""
partition.scheme.set_group_for_user(user, partition, group)
def set_group_access(self, block_location, access_dict):
"""
Set group_access on block specified by location.
"""
block = modulestore().get_item(block_location)
block.group_access = access_dict
modulestore().update_item(block, 1)
def set_user_partitions(self, block_location, partitions):
"""
Sets the user_partitions on block specified by location.
"""
block = modulestore().get_item(block_location)
block.user_partitions = partitions
modulestore().update_item(block, 1)
def setUp(self):
super(GroupAccessTestCase, self).setUp()
UserPartition.scheme_extensions = ExtensionManager.make_test_instance(
[
Extension(
"memory",
USER_PARTITION_SCHEME_NAMESPACE,
MemoryUserPartitionScheme(),
None
),
Extension(
"random",
USER_PARTITION_SCHEME_NAMESPACE,
MemoryUserPartitionScheme(),
None
)
],
namespace=USER_PARTITION_SCHEME_NAMESPACE
)
self.cat_group = Group(10, 'cats')
self.dog_group = Group(20, 'dogs')
self.worm_group = Group(30, 'worms')
self.animal_partition = UserPartition(
0,
'Pet Partition',
'which animal are you?',
[self.cat_group, self.dog_group, self.worm_group],
scheme=UserPartition.get_scheme("memory"),
)
self.red_group = Group(1000, 'red')
self.blue_group = Group(2000, 'blue')
self.gray_group = Group(3000, 'gray')
self.color_partition = UserPartition(
100,
'Color Partition',
'what color are you?',
[self.red_group, self.blue_group, self.gray_group],
scheme=UserPartition.get_scheme("memory"),
)
self.course = CourseFactory.create(
user_partitions=[self.animal_partition, self.color_partition],
)
with self.store.bulk_operations(self.course.id, emit_signals=False):
chapter = ItemFactory.create(category='chapter', parent=self.course)
section = ItemFactory.create(category='sequential', parent=chapter)
vertical = ItemFactory.create(category='vertical', parent=section)
component = ItemFactory.create(category='problem', parent=vertical)
self.chapter_location = chapter.location
self.section_location = section.location
self.vertical_location = vertical.location
self.component_location = component.location
self.red_cat = UserFactory() # student in red and cat groups
self.set_user_group(self.red_cat, self.animal_partition, self.cat_group)
self.set_user_group(self.red_cat, self.color_partition, self.red_group)
self.blue_dog = UserFactory() # student in blue and dog groups
self.set_user_group(self.blue_dog, self.animal_partition, self.dog_group)
self.set_user_group(self.blue_dog, self.color_partition, self.blue_group)
self.white_mouse = UserFactory() # student in no group
self.gray_worm = UserFactory() # student in deleted group
self.set_user_group(self.gray_worm, self.animal_partition, self.worm_group)
self.set_user_group(self.gray_worm, self.color_partition, self.gray_group)
# delete the gray/worm groups from the partitions now so we can test scenarios
# for user whose group is missing.
self.animal_partition.groups.pop()
self.color_partition.groups.pop()
# add a staff user, whose access will be unconditional in spite of group access.
self.staff = StaffFactory.create(course_key=self.course.id)
# avoid repeatedly declaring the same sequence for ddt in all the test cases.
PARENT_CHILD_PAIRS = (
('chapter_location', 'chapter_location'),
('chapter_location', 'section_location'),
('chapter_location', 'vertical_location'),
('chapter_location', 'component_location'),
('section_location', 'section_location'),
('section_location', 'vertical_location'),
('section_location', 'component_location'),
('vertical_location', 'vertical_location'),
('vertical_location', 'component_location'),
)
def tearDown(self):
"""
Clear out the stevedore extension points on UserPartition to avoid
side-effects in other tests.
"""
UserPartition.scheme_extensions = None
super(GroupAccessTestCase, self).tearDown()
def check_access(self, user, block_location, is_accessible):
"""
DRY helper.
"""
self.assertIs(
bool(access.has_access(user, 'load', modulestore().get_item(block_location), self.course.id)),
is_accessible
)
def ensure_staff_access(self, block_location):
"""
Another DRY helper.
"""
block = modulestore().get_item(block_location)
self.assertTrue(access.has_access(self.staff, 'load', block, self.course.id))
# NOTE: in all the tests that follow, `block_specified` and
# `block_accessed` designate the place where group_access rules are
# specified, and where access is being checked in the test, respectively.
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_partition_single_group(self, block_specified, block_accessed):
"""
Access checks are correctly enforced on the block when a single group
is specified for a single partition.
"""
self.set_group_access(
block_specified,
{self.animal_partition.id: [self.cat_group.id]},
)
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_partition_two_groups(self, block_specified, block_accessed):
"""
Access checks are correctly enforced on the block when multiple groups
are specified for a single partition.
"""
self.set_group_access(
block_specified,
{self.animal_partition.id: [self.cat_group.id, self.dog_group.id]},
)
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_partition_disjoint_groups(self, block_specified, block_accessed):
"""
When the parent's and child's group specifications do not intersect,
access is denied to the child regardless of the user's groups.
"""
if block_specified == block_accessed:
# this test isn't valid unless block_accessed is a descendant of
# block_specified.
return
self.set_group_access(
block_specified,
{self.animal_partition.id: [self.dog_group.id]},
)
self.set_group_access(
block_accessed,
{self.animal_partition.id: [self.cat_group.id]},
)
self.check_access(self.red_cat, block_accessed, False)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_empty_partition(self, block_specified, block_accessed):
"""
No group access checks are enforced on the block when group_access
declares a partition but does not specify any groups.
"""
self.set_group_access(block_specified, {self.animal_partition.id: []})
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, True)
self.check_access(self.gray_worm, block_accessed, True)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_empty_dict(self, block_specified, block_accessed):
"""
No group access checks are enforced on the block when group_access is an
empty dictionary.
"""
self.set_group_access(block_specified, {})
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, True)
self.check_access(self.gray_worm, block_accessed, True)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_none(self, block_specified, block_accessed):
"""
No group access checks are enforced on the block when group_access is None.
"""
self.set_group_access(block_specified, None)
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, True)
self.check_access(self.gray_worm, block_accessed, True)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_partition_group_none(self, block_specified, block_accessed):
"""
No group access checks are enforced on the block when group_access
specifies a partition but its value is None.
"""
self.set_group_access(block_specified, {self.animal_partition.id: None})
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, True)
self.check_access(self.gray_worm, block_accessed, True)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_single_partition_group_empty_list(self, block_specified, block_accessed):
"""
No group access checks are enforced on the block when group_access
specifies a partition but its value is an empty list.
"""
self.set_group_access(block_specified, {self.animal_partition.id: []})
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, True)
self.check_access(self.white_mouse, block_accessed, True)
self.check_access(self.gray_worm, block_accessed, True)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_nonexistent_nonempty_partition(self, block_specified, block_accessed):
"""
Access will be denied to the block when group_access specifies a
nonempty partition that does not exist in course.user_partitions.
"""
self.set_group_access(block_specified, {9: [99]})
self.check_access(self.red_cat, block_accessed, False)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_has_access_nonexistent_group(self, block_specified, block_accessed):
"""
Access will be denied to the block when group_access contains a group
id that does not exist in its referenced partition.
"""
self.set_group_access(block_specified, {self.animal_partition.id: [99]})
self.check_access(self.red_cat, block_accessed, False)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_multiple_partitions(self, block_specified, block_accessed):
"""
Group access restrictions are correctly enforced when multiple partition
/ group rules are defined.
"""
self.set_group_access(
block_specified,
{
self.animal_partition.id: [self.cat_group.id],
self.color_partition.id: [self.red_group.id],
},
)
self.check_access(self.red_cat, block_accessed, True)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.white_mouse, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)
@ddt.data(*PARENT_CHILD_PAIRS)
@ddt.unpack
@resolve_attrs
def test_multiple_partitions_deny_access(self, block_specified, block_accessed):
"""
Group access restrictions correctly deny access even when some (but not
all) group_access rules are satisfied.
"""
self.set_group_access(
block_specified,
{
self.animal_partition.id: [self.cat_group.id],
self.color_partition.id: [self.blue_group.id],
},
)
self.check_access(self.red_cat, block_accessed, False)
self.check_access(self.blue_dog, block_accessed, False)
self.check_access(self.gray_worm, block_accessed, False)
self.ensure_staff_access(block_accessed)