""" Unit tests for course import and export Celery tasks """ from __future__ import absolute_import, division, print_function import copy import json from uuid import uuid4 import mock from django.conf import settings from django.contrib.auth.models import User from django.test.utils import override_settings from opaque_keys.edx.locator import CourseLocator from organizations.models import OrganizationCourse from organizations.tests.factories import OrganizationFactory from user_tasks.models import UserTaskArtifact, UserTaskStatus from contentstore.tasks import export_olx, rerun_course from contentstore.tests.test_libraries import LibraryTestCase from contentstore.tests.utils import CourseTestCase from course_action_state.models import CourseRerunState from openedx.core.djangoapps.embargo.models import Country, CountryAccessRule, RestrictedCourse from xmodule.modulestore.django import modulestore TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE) TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex def side_effect_exception(*args, **kwargs): # pylint: disable=unused-argument """ Side effect for mocking which raises an exception """ raise Exception('Boom!') @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class ExportCourseTestCase(CourseTestCase): """ Tests of the export_olx task applied to courses """ def test_success(self): """ Verify that a routine course export task succeeds """ key = str(self.course.location.course_key) result = export_olx.delay(self.user.id, key, u'en') status = UserTaskStatus.objects.get(task_id=result.id) self.assertEqual(status.state, UserTaskStatus.SUCCEEDED) artifacts = UserTaskArtifact.objects.filter(status=status) self.assertEqual(len(artifacts), 1) output = artifacts[0] self.assertEqual(output.name, 'Output') @mock.patch('contentstore.tasks.export_course_to_xml', side_effect=side_effect_exception) def test_exception(self, mock_export): # pylint: disable=unused-argument """ The export task should fail gracefully if an exception is thrown """ key = str(self.course.location.course_key) result = export_olx.delay(self.user.id, key, u'en') self._assert_failed(result, json.dumps({u'raw_error_msg': u'Boom!'})) def test_invalid_user_id(self): """ Verify that attempts to export a course as an invalid user fail """ user_id = User.objects.order_by(u'-id').first().pk + 100 key = str(self.course.location.course_key) result = export_olx.delay(user_id, key, u'en') self._assert_failed(result, u'Unknown User ID: {}'.format(user_id)) def test_non_course_author(self): """ Verify that users who aren't authors of the course are unable to export it """ _, nonstaff_user = self.create_non_staff_authed_user_client() key = str(self.course.location.course_key) result = export_olx.delay(nonstaff_user.id, key, u'en') self._assert_failed(result, u'Permission denied') def _assert_failed(self, task_result, error_message): """ Verify that a task failed with the specified error message """ status = UserTaskStatus.objects.get(task_id=task_result.id) self.assertEqual(status.state, UserTaskStatus.FAILED) artifacts = UserTaskArtifact.objects.filter(status=status) self.assertEqual(len(artifacts), 1) error = artifacts[0] self.assertEqual(error.name, u'Error') self.assertEqual(error.text, error_message) @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class ExportLibraryTestCase(LibraryTestCase): """ Tests of the export_olx task applied to libraries """ def test_success(self): """ Verify that a routine library export task succeeds """ key = str(self.lib_key) result = export_olx.delay(self.user.id, key, u'en') # pylint: disable=no-member status = UserTaskStatus.objects.get(task_id=result.id) self.assertEqual(status.state, UserTaskStatus.SUCCEEDED) artifacts = UserTaskArtifact.objects.filter(status=status) self.assertEqual(len(artifacts), 1) output = artifacts[0] self.assertEqual(output.name, 'Output') @override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE) class RerunCourseTaskTestCase(CourseTestCase): def _rerun_course(self, old_course_key, new_course_key): CourseRerunState.objects.initiated(old_course_key, new_course_key, self.user, 'Test Re-run') rerun_course(str(old_course_key), str(new_course_key), self.user.id) def test_success(self): """ The task should clone the OrganizationCourse and RestrictedCourse data. """ old_course_key = self.course.id new_course_key = CourseLocator(org=old_course_key.org, course=old_course_key.course, run='rerun') old_course_id = str(old_course_key) new_course_id = str(new_course_key) organization = OrganizationFactory() OrganizationCourse.objects.create(course_id=old_course_id, organization=organization) restricted_course = RestrictedCourse.objects.create(course_key=self.course.id) restricted_country = Country.objects.create(country='US') CountryAccessRule.objects.create( rule_type=CountryAccessRule.BLACKLIST_RULE, restricted_course=restricted_course, country=restricted_country ) # Run the task! self._rerun_course(old_course_key, new_course_key) # Verify the new course run exists course = modulestore().get_course(new_course_key) self.assertIsNotNone(course) # Verify the OrganizationCourse is cloned self.assertEqual(OrganizationCourse.objects.count(), 2) # This will raise an error if the OrganizationCourse object was not cloned OrganizationCourse.objects.get(course_id=new_course_id, organization=organization) # Verify the RestrictedCourse and related objects are cloned self.assertEqual(RestrictedCourse.objects.count(), 2) restricted_course = RestrictedCourse.objects.get(course_key=new_course_key) self.assertEqual(CountryAccessRule.objects.count(), 2) CountryAccessRule.objects.get( rule_type=CountryAccessRule.BLACKLIST_RULE, restricted_course=restricted_course, country=restricted_country )