def test_exclude_partitions_with_no_groups(self): # The cohort partition has no groups defined self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[], ), UserPartition( id=1, name="Verification user partition", scheme=UserPartition.get_scheme("verification"), description="Verification user partition", groups=[ Group(id=0, name="Group C"), ], ), ]) # Expect that the partition with no groups is excluded from the results partitions = self._get_partition_info() self.assertEqual(len(partitions), 1) self.assertEqual(partitions[0]["scheme"], "verification")
def setUp(self): """Create a dummy course. """ super(GetUserPartitionInfoTest, self).setUp() self.course = CourseFactory() self.block = ItemFactory.create(category="problem", parent_location=self.course.location) # pylint: disable=no-member # Set up some default partitions self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Random user partition", scheme=UserPartition.get_scheme("random"), description="Random user partition", groups=[ Group(id=0, name="Group C"), ], ), ])
def test_preserves_existing_user_partitions(self): # Add other, non-verified partition to the course self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name='Random user partition', scheme=UserPartition.get_scheme('random'), description='Random user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), ] self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Update the verification partitions. # The existing partitions should still be available self._update_partitions() partition_ids = [p.id for p in self.course.user_partitions] self.assertEqual(len(partition_ids), 3) self.assertIn(0, partition_ids) self.assertIn(1, partition_ids)
def test_preserves_existing_user_partitions(self): # Add other, non-verified partition to the course self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name='Random user partition', scheme=UserPartition.get_scheme('random'), description='Random user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), ] self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Update the verification partitions. # The existing partitions should still be available self._update_partitions() partition_ids = [p.id for p in self.course.user_partitions] self.assertEqual(len(partition_ids), 3) self.assertIn(0, partition_ids) self.assertIn(1, partition_ids)
def setUp(self): """Create a dummy course. """ super(GetUserPartitionInfoTest, self).setUp() self.course = CourseFactory() self.block = ItemFactory.create(category="problem", parent_location=self.course.location) # pylint: disable=no-member # Set up some default partitions self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Random user partition", scheme=UserPartition.get_scheme("random"), description="Random user partition", groups=[ Group(id=0, name="Group C"), ], ), ])
class PartitionTestCase(TestCase): """Base class for test cases that require partitions""" TEST_ID = 0 TEST_NAME = "Mock Partition" TEST_DESCRIPTION = "for testing purposes" TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')] TEST_SCHEME_NAME = "mock" def setUp(self): super(PartitionTestCase, self).setUp() # Set up two user partition schemes: mock and random self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME) self.random_scheme = MockUserPartitionScheme("random") extensions = [ Extension(self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None), Extension(self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None), ] UserPartition.scheme_extensions = ExtensionManager.make_test_instance( extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE) # Create a test partition self.user_partition = UserPartition(self.TEST_ID, self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS, extensions[0].plugin) # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests). self.user_partition.get_scheme(self.non_random_scheme.name) self.user_partition.get_scheme(self.random_scheme.name)
def test_exclude_inactive_partitions(self): # Include an inactive verification scheme self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Verification user partition", scheme=UserPartition.get_scheme("verification"), description="Verification user partition", groups=[ Group(id=0, name="Group C"), ], active=False, ), ]) # Expect that the inactive scheme is excluded from the results partitions = self._get_partition_info() self.assertEqual(len(partitions), 1) self.assertEqual(partitions[0]["scheme"], "cohort")
def test_exclude_partitions_with_no_groups(self): # The cohort partition has no groups defined self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[], ), UserPartition( id=1, name="Verification user partition", scheme=UserPartition.get_scheme("verification"), description="Verification user partition", groups=[ Group(id=0, name="Group C"), ], ), ]) # Expect that the partition with no groups is excluded from the results partitions = self._get_partition_info() self.assertEqual(len(partitions), 1) self.assertEqual(partitions[0]["scheme"], "verification")
def test_exclude_inactive_partitions(self): # Include an inactive verification scheme self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Verification user partition", scheme=UserPartition.get_scheme("verification"), description="Verification user partition", groups=[ Group(id=0, name="Group C"), ], active=False, ), ]) # Expect that the inactive scheme is excluded from the results partitions = self._get_partition_info() self.assertEqual(len(partitions), 1) self.assertEqual(partitions[0]["scheme"], "cohort")
def setUp(self): super(GroupVisibilityTest, self).setUp() chapter = ItemFactory.create(category='chapter', parent_location=self.course.location) sequential = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical = ItemFactory.create(category='vertical', parent_location=sequential.location) html = ItemFactory.create(category='html', parent_location=vertical.location) problem = ItemFactory.create(category='problem', parent_location=vertical.location, data="<problem></problem>") self.sequential = self.store.get_item(sequential.location) self.vertical = self.store.get_item(vertical.location) self.html = self.store.get_item(html.location) self.problem = self.store.get_item(problem.location) # Add partitions to the course self.course.user_partitions = [ UserPartition( id=0, name="Partition 0", description="Partition 0", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Partition 1", description="Partition 1", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group C"), Group(id=1, name="Group D"), ], ), UserPartition( id=2, name="Partition 2", description="Partition 2", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group E"), Group(id=1, name="Group F"), Group(id=2, name="Group G"), Group(id=3, name="Group H"), ], ), ] self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test)
class PartitionTestCase(TestCase): """Base class for test cases that require partitions""" shard = 2 TEST_ID = 0 TEST_NAME = "Mock Partition" TEST_DESCRIPTION = "for testing purposes" TEST_PARAMETERS = {"location": "block-v1:edX+DemoX+Demo+type@block@uuid"} TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')] TEST_SCHEME_NAME = "mock" ENROLLMENT_TRACK_SCHEME_NAME = "enrollment_track" def setUp(self): super(PartitionTestCase, self).setUp() # Set up two user partition schemes: mock and random self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME) self.random_scheme = MockUserPartitionScheme("random") self.enrollment_track_scheme = MockEnrollmentTrackUserPartitionScheme(self.ENROLLMENT_TRACK_SCHEME_NAME) extensions = [ Extension( self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None ), Extension( self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None ), Extension( self.enrollment_track_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.enrollment_track_scheme, None ), ] UserPartition.scheme_extensions = ExtensionManager.make_test_instance( extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE ) # Be sure to clean up the global scheme_extensions after the test. self.addCleanup(self.cleanup_scheme_extensions) # Create a test partition self.user_partition = UserPartition( self.TEST_ID, self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS, extensions[0].plugin, self.TEST_PARAMETERS, ) # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests). self.user_partition.get_scheme(self.non_random_scheme.name) self.user_partition.get_scheme(self.random_scheme.name) def cleanup_scheme_extensions(self): """ Unset the UserPartition.scheme_extensions cache. """ UserPartition.scheme_extensions = None
class PartitionTestCase(TestCase): """Base class for test cases that require partitions""" shard = 2 TEST_ID = 0 TEST_NAME = "Mock Partition" TEST_DESCRIPTION = "for testing purposes" TEST_PARAMETERS = {"location": "block-v1:edX+DemoX+Demo+type@block@uuid"} TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')] TEST_SCHEME_NAME = "mock" ENROLLMENT_TRACK_SCHEME_NAME = "enrollment_track" def setUp(self): super(PartitionTestCase, self).setUp() # Set up two user partition schemes: mock and random self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME) self.random_scheme = MockUserPartitionScheme("random") self.enrollment_track_scheme = MockEnrollmentTrackUserPartitionScheme(self.ENROLLMENT_TRACK_SCHEME_NAME) extensions = [ Extension( self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None ), Extension( self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None ), Extension( self.enrollment_track_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.enrollment_track_scheme, None ), ] UserPartition.scheme_extensions = ExtensionManager.make_test_instance( extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE ) # Be sure to clean up the global scheme_extensions after the test. self.addCleanup(self.cleanup_scheme_extensions) # Create a test partition self.user_partition = UserPartition( self.TEST_ID, self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS, extensions[0].plugin, self.TEST_PARAMETERS, ) # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests). self.user_partition.get_scheme(self.non_random_scheme.name) self.user_partition.get_scheme(self.random_scheme.name) def cleanup_scheme_extensions(self): """ Unset the UserPartition.scheme_extensions cache. """ UserPartition.scheme_extensions = None
def setUp(self): super(GroupVisibilityTest, self).setUp() chapter = ItemFactory.create(category='chapter', parent_location=self.course.location) sequential = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical = ItemFactory.create(category='vertical', parent_location=sequential.location) html = ItemFactory.create(category='html', parent_location=vertical.location) problem = ItemFactory.create( category='problem', parent_location=vertical.location, data="<problem></problem>" ) self.sequential = self.store.get_item(sequential.location) self.vertical = self.store.get_item(vertical.location) self.html = self.store.get_item(html.location) self.problem = self.store.get_item(problem.location) # Add partitions to the course self.course.user_partitions = [ UserPartition( id=0, name="Partition 0", description="Partition 0", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name="Partition 1", description="Partition 1", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group C"), Group(id=1, name="Group D"), ], ), UserPartition( id=2, name="Partition 2", description="Partition 2", scheme=UserPartition.get_scheme("random"), groups=[ Group(id=0, name="Group E"), Group(id=1, name="Group F"), Group(id=2, name="Group G"), Group(id=3, name="Group H"), ], ), ] self.course = self.store.update_item(self.course, ModuleStoreEnum.UserID.test)
def test_group_access_short_circuits(self): """ Test that the group_access check short-circuits if there are no user_partitions defined except user_partitions in use by the split_test module. """ # Initially, "red_cat" user can't view the vertical. self.set_group_access(self.chapter, {self.animal_partition.id: [self.dog_group.id]}) self.check_access(self.red_cat, self.vertical, False) # Change the vertical's user_partitions value to the empty list. Now red_cat can view the vertical. self.vertical.user_partitions = [] self.check_access(self.red_cat, self.vertical, True) # Change the vertical's user_partitions value to include only "split_test" partitions. split_test_partition = UserPartition( 199, 'split_test partition', 'nothing to look at here', [Group(2, 'random group')], scheme=UserPartition.get_scheme("random"), ) self.vertical.user_partitions = [split_test_partition] self.check_access(self.red_cat, self.vertical, True) # Finally, add back in a cohort user_partition self.vertical.user_partitions = [ split_test_partition, self.animal_partition ] self.check_access(self.red_cat, self.vertical, False)
def test_group_access_short_circuits(self): """ Test that the group_access check short-circuits if there are no user_partitions defined except user_partitions in use by the split_test module. """ # Initially, "red_cat" user can't view the vertical. self.set_group_access(self.chapter_location, {self.animal_partition.id: [self.dog_group.id]}) self.check_access(self.red_cat, self.vertical_location, False) # Change the vertical's user_partitions value to the empty list. Now red_cat can view the vertical. self.set_user_partitions(self.vertical_location, []) self.check_access(self.red_cat, self.vertical_location, True) # Change the vertical's user_partitions value to include only "split_test" partitions. split_test_partition = UserPartition( 199, 'split_test partition', 'nothing to look at here', [Group(2, 'random group')], scheme=UserPartition.get_scheme("random"), ) self.set_user_partitions(self.vertical_location, [split_test_partition]) self.check_access(self.red_cat, self.vertical_location, True) # Finally, add back in a cohort user_partition self.set_user_partitions(self.vertical_location, [split_test_partition, self.animal_partition]) self.check_access(self.red_cat, self.vertical_location, False)
def setUp(self): super(ReverificationPartitionTest, self).setUp() # creating course, checkpoint location and user partition mock object. self.course = CourseFactory.create() self.checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/first_uuid'.format( org=self.course.id.org, course=self.course.id.course ) scheme = UserPartition.get_scheme("verification") self.user_partition = UserPartition( id=0, name=u"Verification Checkpoint", description=u"Verification Checkpoint", scheme=scheme, parameters={"location": self.checkpoint_location}, groups=[ Group(scheme.ALLOW, "Allow access to content"), Group(scheme.DENY, "Deny access to content"), ] ) self.first_checkpoint = VerificationCheckpoint.objects.create( course_id=self.course.id, checkpoint_location=self.checkpoint_location )
def _create_enrollment_track_partition(course): """ Create and return the dynamic enrollment track user partition. If it cannot be created, None is returned. """ if not FEATURES.get('ENABLE_ENROLLMENT_TRACK_USER_PARTITION'): return None try: enrollment_track_scheme = UserPartition.get_scheme("enrollment_track") except UserPartitionError: log.warning("No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created.") return None used_ids = set(p.id for p in course.user_partitions) if ENROLLMENT_TRACK_PARTITION_ID in used_ids: log.warning( "Can't add 'enrollment_track' partition, as ID {id} is assigned to {partition} in course {course}.".format( id=ENROLLMENT_TRACK_PARTITION_ID, partition=_get_partition_from_id(course.user_partitions, ENROLLMENT_TRACK_PARTITION_ID).name, course=unicode(course.id) ) ) return None partition = enrollment_track_scheme.create_user_partition( id=ENROLLMENT_TRACK_PARTITION_ID, name=_(u"Enrollment Track Groups"), description=_(u"Partition for segmenting users by enrollment track"), parameters={"course_id": unicode(course.id)} ) return partition
def _create_enrollment_track_partition(course): """ Create and return the dynamic enrollment track user partition. If it cannot be created, None is returned. """ if not FEATURES.get('ENABLE_ENROLLMENT_TRACK_USER_PARTITION'): return None try: enrollment_track_scheme = UserPartition.get_scheme("enrollment_track") except UserPartitionError: log.warning( "No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created." ) return None used_ids = set(p.id for p in course.user_partitions) if ENROLLMENT_TRACK_PARTITION_ID in used_ids: log.warning( "Can't add 'enrollment_track' partition, as ID {id} is assigned to {partition} in course {course}." .format(id=ENROLLMENT_TRACK_PARTITION_ID, partition=_get_partition_from_id( course.user_partitions, ENROLLMENT_TRACK_PARTITION_ID).name, course=unicode(course.id))) return None partition = enrollment_track_scheme.create_user_partition( id=ENROLLMENT_TRACK_PARTITION_ID, name=_(u"Enrollment Track Groups"), description=_(u"Partition for segmenting users by enrollment track"), parameters={"course_id": unicode(course.id)}) return partition
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ if not (CONTENT_TYPE_GATING_FLAG.is_enabled() or CONTENT_TYPE_GATING_STUDIO_UI_FLAG.is_enabled()): return None try: content_gate_scheme = UserPartition.get_scheme("content_type_gate") except UserPartitionError: LOG.warning("No 'content_type_gate' scheme registered, ContentTypeGatingPartitionScheme will not be created.") return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( "Can't add 'content_type_gate' partition, as ID {id} is assigned to {partition} in course {course}.".format( id=CONTENT_GATING_PARTITION_ID, partition=_get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, course=unicode(course.id) ) ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, name=_(u"Feature-based Enrollments"), description=_(u"Partition for segmenting users by access to gated content types"), parameters={"course_id": unicode(course.id)} ) return partition
def test_can_get_correct_usage_info_for_split_test(self): """ When a split test is created and content group access is set for a problem within a group, the usage info should return a url to the split test, not to the group. """ # Create user partition for groups in the split test, # and another partition to set group access for the problem within the split test. self._add_user_partitions(count=1) self.course.user_partitions += [ UserPartition( id=1, name='Cohort User Partition', scheme=UserPartition.get_scheme('cohort'), description='Cohort User Partition', groups=[Group(id=3, name="Problem Group")], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) __, split_test, problem = self._create_content_experiment( cid=0, name_suffix='0', group_id=3, cid_for_problem=1) expected = { 'id': 1, 'name': 'Cohort User Partition', 'scheme': 'cohort', 'description': 'Cohort User Partition', 'version': UserPartition.VERSION, 'groups': [ { 'id': 3, 'name': 'Problem Group', 'version': 1, 'usage': [{ 'url': '/container/{}'.format(split_test.location), 'label': 'Condition 1 vertical / Test Problem' }] }, ], u'parameters': {}, u'active': True, } actual = self._get_user_partition('cohort') self.assertEqual(actual, expected)
def test_create_user_partition(self): user_partition = UserPartition.get_scheme('enrollment_track').create_user_partition( 301, "partition", "test partition", parameters={"course_id": unicode(self.course.id)} ) self.assertEqual(type(user_partition), EnrollmentTrackUserPartition) self.assertEqual(user_partition.name, "partition") groups = user_partition.groups self.assertEqual(1, len(groups)) self.assertEqual("Audit", groups[0].name)
def test_create_user_partition(self): user_partition = UserPartition.get_scheme('enrollment_track').create_user_partition( 301, "partition", "test partition", parameters={"course_id": str(self.course.id)} ) assert isinstance(user_partition, EnrollmentTrackUserPartition) assert user_partition.name == 'partition' groups = user_partition.groups assert 1 == len(groups) assert 'Audit' == groups[0].name
def test_can_handle_multiple_partitions(self): # Create the user partitions self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name='Random user partition', scheme=UserPartition.get_scheme('random'), description='Random user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Assign group access rules for multiple partitions, one of which is a cohorted partition __, problem = self._create_problem_with_content_group(0, 1) problem.group_access = { 0: [0], 1: [1], } self.store.update_item(problem, ModuleStoreEnum.UserID.test) # This used to cause an exception since the code assumed that # only one partition would be available. actual = GroupConfiguration.get_content_groups_usage_info( self.store, self.course) self.assertEqual(actual.keys(), [0]) actual = GroupConfiguration.get_content_groups_items_usage_info( self.store, self.course) self.assertEqual(actual.keys(), [0])
def create_enrollment_track_partition(course): """ Create an EnrollmentTrackUserPartition instance for the given course. """ enrollment_track_scheme = UserPartition.get_scheme("enrollment_track") partition = enrollment_track_scheme.create_user_partition( id=1, name="Test Enrollment Track Partition", description="Test partition for segmenting users by enrollment track", parameters={"course_id": unicode(course.id)}) return partition
def test_can_handle_duplicate_group_ids(self): # Create the user partitions self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition 1', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=2, name="Group 1A"), Group(id=3, name="Group 1B"), ], ), UserPartition( id=1, name='Cohort user partition 2', scheme=UserPartition.get_scheme('cohort'), description='Random user partition', groups=[ Group(id=2, name="Group 2A"), Group(id=3, name="Group 2B"), ], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Assign group access rules for multiple partitions, one of which is a cohorted partition self._create_problem_with_content_group(0, 2, name_suffix='0') self._create_problem_with_content_group(1, 3, name_suffix='1') # This used to cause an exception since the code assumed that # only one partition would be available. actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course) self.assertEqual(list(actual.keys()), [0, 1]) self.assertEqual(list(actual[0].keys()), [2]) self.assertEqual(list(actual[1].keys()), [3]) actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course) self.assertEqual(list(actual.keys()), [0, 1]) self.assertEqual(list(actual[0].keys()), [2]) self.assertEqual(list(actual[1].keys()), [3])
def test_can_handle_duplicate_group_ids(self): # Create the user partitions self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition 1', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=2, name="Group 1A"), Group(id=3, name="Group 1B"), ], ), UserPartition( id=1, name='Cohort user partition 2', scheme=UserPartition.get_scheme('cohort'), description='Random user partition', groups=[ Group(id=2, name="Group 2A"), Group(id=3, name="Group 2B"), ], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Assign group access rules for multiple partitions, one of which is a cohorted partition self._create_problem_with_content_group(0, 2, name_suffix='0') self._create_problem_with_content_group(1, 3, name_suffix='1') # This used to cause an exception since the code assumed that # only one partition would be available. actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course) self.assertEqual(actual.keys(), [0, 1]) self.assertEqual(actual[0].keys(), [2]) self.assertEqual(actual[1].keys(), [3]) actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course) self.assertEqual(actual.keys(), [0, 1]) self.assertEqual(actual[0].keys(), [2]) self.assertEqual(actual[1].keys(), [3])
def test_can_handle_multiple_partitions(self): # Create the user partitions self.course.user_partitions = [ UserPartition( id=0, name='Cohort user partition', scheme=UserPartition.get_scheme('cohort'), description='Cohorted user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), UserPartition( id=1, name='Random user partition', scheme=UserPartition.get_scheme('random'), description='Random user partition', groups=[ Group(id=0, name="Group A"), Group(id=1, name="Group B"), ], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) # Assign group access rules for multiple partitions, one of which is a cohorted partition __, problem = self._create_problem_with_content_group(0, 1) problem.group_access = { 0: [0], 1: [1], } self.store.update_item(problem, ModuleStoreEnum.UserID.test) # This used to cause an exception since the code assumed that # only one partition would be available. actual = GroupConfiguration.get_content_groups_usage_info(self.store, self.course) self.assertEqual(actual.keys(), [0]) actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course) self.assertEqual(actual.keys(), [0])
def test_can_get_correct_usage_info_for_unit(self): """ When group access is set on the unit level, the usage info should return a url to the unit, not the sequential parent of the unit. """ self.course.user_partitions = [ UserPartition( id=0, name='User Partition', scheme=UserPartition.get_scheme('cohort'), description='User Partition', groups=[ Group(id=0, name="Group") ], ), ] vertical, __ = self._create_problem_with_content_group( cid=0, group_id=0, name_suffix='0' ) self.client.ajax_post( reverse_usage_url("xblock_handler", vertical.location), data={'metadata': {'group_access': {0: [0]}}} ) actual = self._get_user_partition('cohort') # order of usage list is arbitrary, sort for reliable comparison actual['groups'][0]['usage'].sort(key=itemgetter('label')) expected = { 'id': 0, 'name': 'User Partition', 'scheme': 'cohort', 'description': 'User Partition', 'version': UserPartition.VERSION, 'groups': [ {'id': 0, 'name': 'Group', 'version': 1, 'usage': [ { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Subsection 0 / Test Unit 0" }, { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Unit 0 / Test Problem 0" } ]}, ], u'parameters': {}, u'active': True, } self.maxDiff = None assert actual == expected
def create_enrollment_track_partition(course): """ Create an EnrollmentTrackUserPartition instance for the given course. """ enrollment_track_scheme = UserPartition.get_scheme("enrollment_track") partition = enrollment_track_scheme.create_user_partition( id=1, name="Test Enrollment Track Partition", description="Test partition for segmenting users by enrollment track", parameters={"course_id": unicode(course.id)} ) return partition
def test_can_get_correct_usage_info_for_unit(self): """ When group access is set on the unit level, the usage info should return a url to the unit, not the sequential parent of the unit. """ self.course.user_partitions = [ UserPartition( id=0, name='User Partition', scheme=UserPartition.get_scheme('cohort'), description='User Partition', groups=[ Group(id=0, name="Group") ], ), ] vertical, __ = self._create_problem_with_content_group( cid=0, group_id=0, name_suffix='0' ) self.client.ajax_post( reverse_usage_url("xblock_handler", vertical.location), data={'metadata': {'group_access': {0: [0]}}} ) actual = self._get_user_partition('cohort') # order of usage list is arbitrary, sort for reliable comparison actual['groups'][0]['usage'].sort(key=itemgetter('label')) expected = { 'id': 0, 'name': 'User Partition', 'scheme': 'cohort', 'description': 'User Partition', 'version': UserPartition.VERSION, 'groups': [ {'id': 0, 'name': 'Group', 'version': 1, 'usage': [ { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Subsection 0 / Test Unit 0" }, { 'url': u"/container/{}".format(vertical.location), 'label': u"Test Unit 0 / Test Problem 0" } ]}, ], u'parameters': {}, u'active': True, } self.maxDiff = None assert actual == expected
class PartitionTestCase(TestCase): """Base class for test cases that require partitions""" TEST_ID = 0 TEST_NAME = "Mock Partition" TEST_DESCRIPTION = "for testing purposes" TEST_GROUPS = [Group(0, 'Group 1'), Group(1, 'Group 2')] TEST_SCHEME_NAME = "mock" def setUp(self): super(PartitionTestCase, self).setUp() # Set up two user partition schemes: mock and random self.non_random_scheme = MockUserPartitionScheme(self.TEST_SCHEME_NAME) self.random_scheme = MockUserPartitionScheme("random") extensions = [ Extension( self.non_random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.non_random_scheme, None ), Extension( self.random_scheme.name, USER_PARTITION_SCHEME_NAMESPACE, self.random_scheme, None ), ] UserPartition.scheme_extensions = ExtensionManager.make_test_instance( extensions, namespace=USER_PARTITION_SCHEME_NAMESPACE ) # Create a test partition self.user_partition = UserPartition( self.TEST_ID, self.TEST_NAME, self.TEST_DESCRIPTION, self.TEST_GROUPS, extensions[0].plugin ) # Make sure the names are set on the schemes (which happens normally in code, but may not happen in tests). self.user_partition.get_scheme(self.non_random_scheme.name) self.user_partition.get_scheme(self.random_scheme.name)
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ enabled_for_course = ContentTypeGatingConfig.enabled_for_course( course_key=course.id) studio_override_for_course = ContentTypeGatingConfig.current( course_key=course.id).studio_override_enabled if not (enabled_for_course or studio_override_for_course): return None try: content_gate_scheme = UserPartition.get_scheme( CONTENT_TYPE_GATING_SCHEME) except UserPartitionError: LOG.warning( u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.", CONTENT_TYPE_GATING_SCHEME) return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( u"Can't add %r partition, as ID %r is assigned to %r in course %s.", CONTENT_TYPE_GATING_SCHEME, CONTENT_GATING_PARTITION_ID, _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, six.text_type(course.id), ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, # Content gating partition name should not be marked for translations # edX mobile apps expect it in english name=u"Feature-based Enrollments", description=_( u"Partition for segmenting users by access to gated content types" ), parameters={"course_id": six.text_type(course.id)}) return partition
def test_can_get_correct_usage_info_for_split_test(self): """ When a split test is created and content group access is set for a problem within a group, the usage info should return a url to the split test, not to the group. """ # Create user partition for groups in the split test, # and another partition to set group access for the problem within the split test. self._add_user_partitions(count=1) self.course.user_partitions += [ UserPartition( id=1, name='Cohort User Partition', scheme=UserPartition.get_scheme('cohort'), description='Cohort User Partition', groups=[ Group(id=3, name="Problem Group") ], ), ] self.store.update_item(self.course, ModuleStoreEnum.UserID.test) __, split_test, problem = self._create_content_experiment(cid=0, name_suffix='0', group_id=3, cid_for_problem=1) expected = { 'id': 1, 'name': 'Cohort User Partition', 'scheme': 'cohort', 'description': 'Cohort User Partition', 'version': UserPartition.VERSION, 'groups': [ {'id': 3, 'name': 'Problem Group', 'version': 1, 'usage': [ { 'url': '/container/{}'.format(split_test.location), 'label': 'Condition 1 vertical / Test Problem' } ]}, ], u'parameters': {}, u'active': True, } actual = self._get_user_partition('cohort') self.assertEqual(actual, expected)
def create_verification_user_partitions(self, checkpoint_names): """ Create user partitions for verification checkpoints. """ scheme = UserPartition.get_scheme("verification") self.course.user_partitions = [ UserPartition( id=0, name=checkpoint_name, description="Verification checkpoint", scheme=scheme, groups=[ Group(scheme.ALLOW, "Completed verification at {}".format(checkpoint_name)), Group(scheme.DENY, "Did not complete verification at {}".format(checkpoint_name)), ], ) for checkpoint_name in checkpoint_names ] self.store.update_item(self.course, self.user.id)
def create_enrollment_track_partition_with_course_id(course_id): """ Create and return the dynamic enrollment track user partition based only on course_id. If it cannot be created, None is returned. """ if not FEATURES.get('ENABLE_ENROLLMENT_TRACK_USER_PARTITION'): return None try: enrollment_track_scheme = UserPartition.get_scheme("enrollment_track") except UserPartitionError: log.warning("No 'enrollment_track' scheme registered, EnrollmentTrackUserPartition will not be created.") return None partition = enrollment_track_scheme.create_user_partition( id=ENROLLMENT_TRACK_PARTITION_ID, name=_("Enrollment Track Groups"), description=_("Partition for segmenting users by enrollment track"), parameters={"course_id": str(course_id)} ) return partition
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ enabled_for_course = ContentTypeGatingConfig.enabled_for_course(course_key=course.id) studio_override_for_course = ContentTypeGatingConfig.current(course_key=course.id).studio_override_enabled if not (enabled_for_course or studio_override_for_course): return None try: content_gate_scheme = UserPartition.get_scheme(CONTENT_TYPE_GATING_SCHEME) except UserPartitionError: LOG.warning( u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.", CONTENT_TYPE_GATING_SCHEME ) return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( u"Can't add %r partition, as ID %r is assigned to %r in course %s.", CONTENT_TYPE_GATING_SCHEME, CONTENT_GATING_PARTITION_ID, _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, unicode(course.id), ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, name=_(u"Feature-based Enrollments"), description=_(u"Partition for segmenting users by access to gated content types"), parameters={"course_id": unicode(course.id)} ) return partition
def create_verification_user_partitions(self, checkpoint_names): """ Create user partitions for verification checkpoints. """ scheme = UserPartition.get_scheme("verification") self.course.user_partitions = [ UserPartition( id=0, name=checkpoint_name, description="Verification checkpoint", scheme=scheme, groups=[ Group( scheme.ALLOW, "Completed verification at {}".format( checkpoint_name)), Group( scheme.DENY, "Did not complete verification at {}".format( checkpoint_name)), ], ) for checkpoint_name in checkpoint_names ] self.store.update_item(self.course, self.user.id)
def test_singular_deleted_group(self): """ Verify that a partition with only one deleted group is shown in the partition info with the group marked as deleted """ self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[], ), ]) self._set_group_access({0: [1]}) partitions = self._get_partition_info() groups = partitions[0]["groups"] self.assertEqual(len(groups), 1) self.assertEqual(groups[0], { "id": 1, "name": "Deleted Group", "selected": True, "deleted": True, })
def test_singular_deleted_group(self): """ Verify that a partition with only one deleted group is shown in the partition info with the group marked as deleted """ self._set_partitions([ UserPartition( id=0, name="Cohort user partition", scheme=UserPartition.get_scheme("cohort"), description="Cohorted user partition", groups=[], ), ]) self._set_group_access({0: [1]}) partitions = self._get_partition_info() groups = partitions[0]["groups"] self.assertEqual(len(groups), 1) self.assertEqual(groups[0], { "id": 1, "name": "Deleted Group", "selected": True, "deleted": True, })
def test_get_scheme(self): self.assertEqual(UserPartition.get_scheme('cohort'), CohortPartitionScheme) with self.assertRaisesRegexp(UserPartitionError, 'Unrecognized scheme'): UserPartition.get_scheme('other')
def _set_verification_partitions(course_key, icrv_blocks): """ Create or update user partitions in the course. Ensures that each ICRV block in the course has an associated user partition with the groups ALLOW and DENY. Arguments: course_key (CourseKey): Identifier for the course. icrv_blocks (list of XBlock): In-course reverification blocks, e.g. reverification checkpoints. Returns: list of UserPartition """ scheme = UserPartition.get_scheme(VERIFICATION_SCHEME_NAME) if scheme is None: log.error("Could not retrieve user partition scheme with ID %s", VERIFICATION_SCHEME_NAME) return [] course = modulestore().get_course(course_key) if course is None: log.error("Could not find course %s", course_key) return [] verified_partitions = course.get_user_partitions_for_scheme(scheme) partition_id_for_location = { p.parameters["location"]: p.id for p in verified_partitions if "location" in p.parameters } partitions = [] for block in icrv_blocks: partition = UserPartition( id=partition_id_for_location.get(unicode(block.location), _unique_partition_id(course)), name=block.related_assessment, description=u"Verification checkpoint at {}".format( block.related_assessment), scheme=scheme, parameters={"location": unicode(block.location)}, groups=[ Group( scheme.ALLOW, "Completed verification at {}".format( block.related_assessment)), Group( scheme.DENY, "Did not complete verification at {}".format( block.related_assessment)), ]) partitions.append(partition) log.info(( "Configured partition %s for course %s using a verified partition scheme " "for the in-course-reverification checkpoint at location %s"), partition.id, course_key, partition.parameters["location"]) # Preserve existing, non-verified partitions from the course # Mark partitions for deleted in-course reverification as disabled. partitions += _other_partitions(verified_partitions, partitions, course_key) course.set_user_partitions_for_scheme(partitions, scheme) modulestore().update_item(course, ModuleStoreEnum.UserID.system) log.info("Saved updated partitions for the course %s", course_key) return partitions
def test_get_scheme(self): self.assertEqual(UserPartition.get_scheme('random'), RandomUserPartitionScheme) with self.assertRaisesRegex(UserPartitionError, 'Unrecognized scheme'): UserPartition.get_scheme('other')
def setUp(self): super(GroupAccessTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments 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)
def test_get_scheme(self): self.assertEqual(UserPartition.get_scheme("cohort"), CohortPartitionScheme) with self.assertRaisesRegexp(UserPartitionError, "Unrecognized scheme"): UserPartition.get_scheme("other")
def test_get_scheme(self): """ Ensure that the scheme extension is correctly plugged in (via entry point in setup.py) """ self.assertEquals(UserPartition.get_scheme('enrollment_track'), EnrollmentTrackPartitionScheme)
def test_get_scheme(self): self.assertEqual(UserPartition.get_scheme('random'), RandomUserPartitionScheme) with self.assertRaisesRegexp(UserPartitionError, 'Unrecognized scheme'): UserPartition.get_scheme('other')
def test_get_scheme(self): assert UserPartition.get_scheme('cohort') == CohortPartitionScheme with self.assertRaisesRegex(UserPartitionError, 'Unrecognized scheme'): UserPartition.get_scheme('other')
def test_get_scheme(self): """ Ensure that the scheme extension is correctly plugged in (via entry point in setup.py) """ self.assertEquals(UserPartition.get_scheme('enrollment_track'), EnrollmentTrackPartitionScheme)
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)
def _set_verification_partitions(course_key, icrv_blocks): """ Create or update user partitions in the course. Ensures that each ICRV block in the course has an associated user partition with the groups ALLOW and DENY. Arguments: course_key (CourseKey): Identifier for the course. icrv_blocks (list of XBlock): In-course reverification blocks, e.g. reverification checkpoints. Returns: list of UserPartition """ scheme = UserPartition.get_scheme(VERIFICATION_SCHEME_NAME) if scheme is None: log.error("Could not retrieve user partition scheme with ID %s", VERIFICATION_SCHEME_NAME) return [] course = modulestore().get_course(course_key) if course is None: log.error("Could not find course %s", course_key) return [] verified_partitions = course.get_user_partitions_for_scheme(scheme) partition_id_for_location = { p.parameters["location"]: p.id for p in verified_partitions if "location" in p.parameters } partitions = [] for block in icrv_blocks: partition = UserPartition( id=partition_id_for_location.get( unicode(block.location), _unique_partition_id(course) ), name=block.related_assessment, description=u"Verification checkpoint at {}".format(block.related_assessment), scheme=scheme, parameters={"location": unicode(block.location)}, groups=[ Group(scheme.ALLOW, "Completed verification at {}".format(block.related_assessment)), Group(scheme.DENY, "Did not complete verification at {}".format(block.related_assessment)), ] ) partitions.append(partition) log.info( ( "Configured partition %s for course %s using a verified partition scheme " "for the in-course-reverification checkpoint at location %s" ), partition.id, course_key, partition.parameters["location"] ) # Preserve existing, non-verified partitions from the course # Mark partitions for deleted in-course reverification as disabled. partitions += _other_partitions(verified_partitions, partitions, course_key) course.set_user_partitions_for_scheme(partitions, scheme) modulestore().update_item(course, ModuleStoreEnum.UserID.system) log.info("Saved updated partitions for the course %s", course_key) return partitions