def test_add_organization_course(self): """ Unit Test: test_add_organization_course """ with self.assertNumQueries(2): api.add_organization_course( self.test_organization, self.test_course_key )
def test_add_organization_course_bogus_course_key(self): """ Unit Test: test_add_organization_course_bogus_course_key """ with self.assertNumQueries(0): with self.assertRaises(exceptions.InvalidCourseKeyException): api.add_organization_course(self.test_organization, '12345667avßßß') with self.assertNumQueries(0): with self.assertRaises(exceptions.InvalidCourseKeyException): api.add_organization_course(self.test_organization, None)
def test_get_course_organizations(self): """ Unit Test: test_get_course_organizations """ api.add_organization_course( self.test_organization, self.test_course_key ) with self.assertNumQueries(1): course_organizations = api.get_course_organizations(self.test_course_key) self.assertEqual(len(course_organizations), 1)
def test_add_organization_course_active_exists(self): """ Unit Test: test_add_organization_course_active_exists """ api.add_organization_course( self.test_organization, self.test_course_key ) with self.assertNumQueries(1): api.add_organization_course( self.test_organization, self.test_course_key )
def test_add_organization_course_inactive_to_active(self): """ Unit Test: test_add_organization_course_inactive_to_active """ api.add_organization_course( self.test_organization, self.test_course_key ) api.remove_organization_course(self.test_organization, self.test_course_key) with self.assertNumQueries(3): api.add_organization_course( self.test_organization, self.test_course_key )
def test_remove_organization_course(self): """ Unit Test: test_remove_organization_course """ api.add_organization_course( self.test_organization, self.test_course_key ) organizations = api.get_course_organizations(self.test_course_key) self.assertEqual(len(organizations), 1) with self.assertNumQueries(3): api.remove_organization_course(self.test_organization, self.test_course_key) organizations = api.get_course_organizations(self.test_course_key) self.assertEqual(len(organizations), 0)
def test_remove_organization_course_missing_course(self): """ Unit Test: test_remove_organization_course_missing_organization """ api.add_organization_course( self.test_organization, 'edX/DemoX/Demo_Course' ) organizations = api.get_course_organizations('edX/DemoX/Demo_Course') self.assertEqual(len(organizations), 1) with self.assertNumQueries(1): api.remove_organization_course(self.test_organization, self.test_course_key) organizations = api.get_course_organizations(self.test_course_key) self.assertEqual(len(organizations), 0)
def test_remove_course_references(self): """ Unit Test: test_remove_course_references """ # Add a course dependency on the test organization api.add_organization_course( self.test_organization, self.test_course_key ) self.assertEqual(len(api.get_organization_courses(self.test_organization)), 1) # Remove the course dependency with self.assertNumQueries(2): api.remove_course_references(self.test_course_key) self.assertEqual(len(api.get_organization_courses(self.test_organization)), 0)
def test_add_organization_inactive_organization_with_relationships(self): """ Unit Test: test_add_organization_inactive_organization_with_relationships""" organization_data = { 'name': 'local_organizationßßß', 'description': 'Local Organization Descriptionßßß' } organization = api.add_organization(organization_data) api.add_organization_course( organization, self.test_course_key ) with self.assertNumQueries(1): organization = api.add_organization(organization_data)
def add_organization_course(organization_data, course_id): """ Client API operation adapter/wrapper """ if not organizations_enabled(): return None from organizations import api as organizations_api return organizations_api.add_organization_course(organization_data=organization_data, course_key=course_id)
def add_organization_course(organization_data, course_id): """ Client API operation adapter/wrapper """ if not settings.FEATURES.get('ORGANIZATIONS_APP', False): return None from organizations import api as organizations_api return organizations_api.add_organization_course(organization_data=organization_data, course_key=course_id)
def add_organization_course(organization_data, course_id): """ Client API operation adapter/wrapper """ if not settings.FEATURES.get('ORGANIZATIONS_APP', False): return None from organizations import api as organizations_api return organizations_api.add_organization_course( organization_data=organization_data, course_key=course_id)
def test_add_several_organization_courses(self): """ Test that the query_count of bulk_add_organization_courses does not increase when given more organization-course linkages to add. """ org_a = api.add_organization(self.make_organization_data("org_a")) org_b = api.add_organization(self.make_organization_data("org_b")) org_c = api.add_organization(self.make_organization_data("org_c")) course_key_x = CourseKey.from_string("course-v1:x+x+x") course_key_y = CourseKey.from_string("course-v1:y+y+y") course_key_z = CourseKey.from_string("course-v1:z+z+z") # Add linkage A->X. api.add_organization_course(org_a, course_key_x) # Add linkage A->Y, and then remove (actually: deactivate). api.add_organization_course(org_a, course_key_y) api.remove_organization_course(org_a, course_key_y) # 1 query to load list of existing linkages, # 1 query to fetch organizations for existing linkages, # 1 query to ensure all existing linkages active, # 1 query to get organiations for new linkages, # 1 query to create new linkages. with self.assertNumQueries(5): api.bulk_add_organization_courses([ (org_a, course_key_x), # Already existing. (org_a, course_key_x), # Already existing. (org_a, course_key_y), # Reactivation. (org_a, course_key_z), # The rest are new. (org_b, course_key_x), (org_b, course_key_y), (org_b, course_key_z), (org_c, course_key_x), (org_c, course_key_y), (org_c, course_key_z), (org_c, course_key_z), # Redundant. (org_c, course_key_z), # Redundant. ]) assert len(api.get_organization_courses(org_a)) == 3 assert len(api.get_organization_courses(org_b)) == 3 assert len(api.get_organization_courses(org_c)) == 3
def test_get_course_organization_multi_linked_orgs(self): """ Test that when a course is linked to multiple organizations, ``get_course_organization`` and ``get_course_organization_id`` return the first-linked one. """ # Use non-alphabetically-ordered org names to test that the # returned org was the first *linked*, not just the first *alphabetically*. api.add_organization_course( api.add_organization({ 'short_name': 'orgW', 'name': 'Org West' }), self.test_course_key, ) api.add_organization_course( api.add_organization({ 'short_name': 'orgN', 'name': 'Org North' }), self.test_course_key, ) api.add_organization_course( api.add_organization({ 'short_name': 'orgS', 'name': 'Org South' }), self.test_course_key, ) org_result = api.get_course_organization(self.test_course_key) assert org_result['short_name'] == 'orgW' org_id_result = api.get_course_organization_id(self.test_course_key) assert org_id_result assert org_id_result == org_result['id']
def rerun_course(source_course_key_string, destination_course_key_string, user_id, fields=None): """ Reruns a course in a new celery task. """ # import here, at top level this import prevents the celery workers from starting up correctly from edxval.api import copy_course_videos source_course_key = CourseKey.from_string(source_course_key_string) destination_course_key = CourseKey.from_string( destination_course_key_string) try: # deserialize the payload fields = deserialize_fields(fields) if fields else None # use the split modulestore as the store for the rerun course, # as the Mongo modulestore doesn't support multiple runs of the same course. store = modulestore() with store.default_store('split'): store.clone_course(source_course_key, destination_course_key, user_id, fields=fields) # set initial permissions for the user to access the course. initialize_permissions(destination_course_key, User.objects.get(id=user_id)) # update state: Succeeded CourseRerunState.objects.succeeded(course_key=destination_course_key) # call edxval to attach videos to the rerun copy_course_videos(source_course_key, destination_course_key) # Copy OrganizationCourse organization_course = OrganizationCourse.objects.filter( course_id=source_course_key_string).first() if organization_course: clone_instance(organization_course, {'course_id': destination_course_key_string}) # Copy RestrictedCourse restricted_course = RestrictedCourse.objects.filter( course_key=source_course_key).first() if restricted_course: country_access_rules = CountryAccessRule.objects.filter( restricted_course=restricted_course) new_restricted_course = clone_instance( restricted_course, {'course_key': destination_course_key}) for country_access_rule in country_access_rules: clone_instance(country_access_rule, {'restricted_course': new_restricted_course}) org_data = ensure_organization(source_course_key.org) add_organization_course(org_data, destination_course_key) return "succeeded" except DuplicateCourseError: # do NOT delete the original course, only update the status CourseRerunState.objects.failed(course_key=destination_course_key) LOGGER.exception('Course Rerun Error') return "duplicate course" # catch all exceptions so we can update the state and properly cleanup the course. except Exception as exc: # pylint: disable=broad-except # update state: Failed CourseRerunState.objects.failed(course_key=destination_course_key) LOGGER.exception('Course Rerun Error') try: # cleanup any remnants of the course modulestore().delete_course(destination_course_key, user_id) except ItemNotFoundError: # it's possible there was an error even before the course module was created pass return "exception: " + str(exc)
def test_add_organization_course(self): """ Unit Test: test_add_organization_course """ with self.assertNumQueries(2): api.add_organization_course(self.test_organization, self.test_course_key)
def test_end_to_end(self): """ Test the happy path of the backfill command without any mocking. """ # org_A: already existing, with courses and a library. org_a = add_organization({"short_name": "org_A", "name": "Org A"}) course_a1_key = CourseOverviewFactory(org="org_A", run="1").id CourseOverviewFactory(org="org_A", run="2") LibraryFactory(org="org_A") # Write linkage for org_a->course_a1. # (Linkage for org_a->course_a2 is purposefully left out here; # it should be created by the backfill). add_organization_course(org_a, course_a1_key) # org_B: already existing, but has no content. add_organization({"short_name": "org_B", "name": "Org B"}) # org_C: has a couple courses; should be created. CourseOverviewFactory(org="org_C", run="1") CourseOverviewFactory(org="org_C", run="2") # org_D: has both a course and a library; should be created. CourseOverviewFactory(org="org_D", run="1") LibraryFactory(org="org_D") # org_E: just has a library; should be created. LibraryFactory(org="org_E") # Confirm starting condition: # Only orgs are org_A and org_B, and only linkage is org_a->course_a1. assert set(org["short_name"] for org in get_organizations()) == {"org_A", "org_B"} assert len( get_organization_courses( get_organization_by_short_name('org_A'))) == 1 assert len( get_organization_courses( get_organization_by_short_name('org_B'))) == 0 # Run the backfill. call_command("backfill_orgs_and_org_courses", "--apply") # Confirm ending condition: # All five orgs present. Each org a has expected number of org-course linkages. assert set(org["short_name"] for org in get_organizations()) == { "org_A", "org_B", "org_C", "org_D", "org_E" } assert len( get_organization_courses( get_organization_by_short_name('org_A'))) == 2 assert len( get_organization_courses( get_organization_by_short_name('org_B'))) == 0 assert len( get_organization_courses( get_organization_by_short_name('org_C'))) == 2 assert len( get_organization_courses( get_organization_by_short_name('org_D'))) == 1 assert len( get_organization_courses( get_organization_by_short_name('org_E'))) == 0
def test_end_to_end(self, run_type): """ Test the happy path of the backfill command without any mocking. """ # org_A: already existing, with courses and a library. org_a = add_organization({"short_name": "org_A", "name": "Org A"}) course_a1_key = CourseOverviewFactory(org="org_A", run="1").id CourseOverviewFactory(org="org_A", run="2") LibraryFactory(org="org_A") # Write linkage for org_a->course_a1. # (Linkage for org_a->course_a2 is purposefully left out here; # it should be created by the backfill). add_organization_course(org_a, course_a1_key) # org_B: already existing, but has no content. add_organization({"short_name": "org_B", "name": "Org B"}) # org_C: has a few courses; should be created. CourseOverviewFactory(org="org_C", run="1") CourseOverviewFactory(org="org_C", run="2") # Include an Old Mongo Modulestore -style deprecated course key. # This can be safely removed when Old Mongo Modulestore support is # removed. CourseOverviewFactory( id=CourseLocator.from_string("org_C/toy/3"), org="org_C", run="3", ) # org_D: has both a course and a library; should be created. CourseOverviewFactory(org="org_D", run="1") LibraryFactory(org="org_D") # org_E: just has a library; should be created. LibraryFactory(org="org_E") # Confirm starting condition: # Only orgs are org_A and org_B, and only linkage is org_a->course_a1. assert set( org["short_name"] for org in get_organizations() ) == { "org_A", "org_B" } assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 1 assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0 # Run the backfill. call_command("backfill_orgs_and_org_courses", run_type) if run_type == "--dry": # Confirm ending conditions are the same as the starting conditions. assert set( org["short_name"] for org in get_organizations() ) == { "org_A", "org_B" } assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 1 assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0 else: # Confirm ending condition: # All five orgs present. Each org a has expected number of org-course linkages. assert set( org["short_name"] for org in get_organizations() ) == { "org_A", "org_B", "org_C", "org_D", "org_E" } assert len(get_organization_courses(get_organization_by_short_name('org_A'))) == 2 assert len(get_organization_courses(get_organization_by_short_name('org_B'))) == 0 assert len(get_organization_courses(get_organization_by_short_name('org_C'))) == 3 assert len(get_organization_courses(get_organization_by_short_name('org_D'))) == 1 assert len(get_organization_courses(get_organization_by_short_name('org_E'))) == 0
def test_edge_cases(self, mock_log_info): """ Test that bulk_add_organization_courses handles a few edge cases as expected. """ org_a = api.add_organization(self.make_organization_data("org_a")) org_b = api.add_organization(self.make_organization_data("org_b")) course_key_x = CourseKey.from_string("course-v1:x+x+x") course_key_y = CourseKey.from_string("course-v1:y+y+y") course_key_z = CourseKey.from_string("course-v1:z+z+z") # Add linkage A->X api.add_organization_course(org_a, course_key_x) # Add and then remove (under the hood: deactivate) linkage between A->Y. api.add_organization_course(org_a, course_key_y) api.remove_organization_course(org_a, course_key_y) # Add and then remove (under the hood: deactivate) linkage between A->Z. # This should NOT be reactivated, as we don't include it in the bulk_add call. api.add_organization_course(org_a, course_key_z) api.remove_organization_course(org_a, course_key_z) # 1 query to load list of existing linkages, # 1 query to fetch organizations for existing linkages, # 1 query to ensure all existing linkages active, # 1 query to get organiations for new linkages, # 1 query to create new linkages. with self.assertNumQueries(5): api.bulk_add_organization_courses([ # A->X: Existing linkage, should be a no-op. (org_a, course_key_x), # B->Y: Should create new linkage. (org_b, course_key_y), # A->Y: Is an inactive linkage; should be re-activated. (org_a, course_key_y), # B->Y: Is already in this list; shouldn't affect anything. (org_b, course_key_y), # B->Z: Adding with a stringified course id; should work as if we # used the course key object. (org_b, str(course_key_z)), # B->Z: Adding again with the course key object; should be a no-op. (org_b, course_key_z), ]) # Org A was linked to courses X and Y. # Org A also has an inactive link to course Z that we never re-activated. org_a_courses = api.get_organization_courses(org_a) assert {org_course["course_id"] for org_course in org_a_courses } == {"course-v1:x+x+x", "course-v1:y+y+y"} # Org B was linked to courses Y and Z. org_b_courses = api.get_organization_courses(org_b) assert {org_course["course_id"] for org_course in org_b_courses } == {"course-v1:y+y+y", "course-v1:z+z+z"} # Based on logging messages, make sure the expected breakdown of # created vs. reactivated vs. not touched # is true for the org-course linkages passed to `bulk_add_organization_courses`. logged_linkages_to_reactivate = mock_log_info.call_args_list[0][0][2] assert set(logged_linkages_to_reactivate) == { ("org_a", str(course_key_y)), } logged_linkages_to_create = mock_log_info.call_args_list[1][0][2] assert set(logged_linkages_to_create) == { ("org_b", str(course_key_y)), ("org_b", str(course_key_z)), }