""" Unit tests for course import and export Celery tasks """ 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') 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 )