Re-running a course now clones the OrganizationCourse and RestrictedCourse data from the old run to the new run. This will allow administrators to avoid the tedium of manually recreating this data.
164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
"""
|
|
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 xmodule.modulestore.django import modulestore
|
|
|
|
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 RestrictedCourse, CountryAccessRule, Country
|
|
|
|
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
|
|
)
|