diff --git a/.gitignore b/.gitignore index eb1c8904f85487b08056c8ab9a7eceeb249bda42..10cc4812a8b9d962a1a7b2735aedb67559035892 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ coverage.xml cover/ cover_html/ reports/ +jscover.log jscover.log.* ### Installation artifacts diff --git a/cms/djangoapps/contentstore/management/commands/delete_split_course.py b/cms/djangoapps/contentstore/management/commands/delete_split_course.py deleted file mode 100644 index 68230bc1b75e1f5e3c0deb87bb19e9bf7976350c..0000000000000000000000000000000000000000 --- a/cms/djangoapps/contentstore/management/commands/delete_split_course.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Django management command to rollback a migration to split. The way to do this -is to delete the course from the split mongo datastore. -""" -from django.core.management.base import BaseCommand, CommandError -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError -from xmodule.modulestore.locator import CourseLocator - - -class Command(BaseCommand): - "Delete a course from the split Mongo datastore" - - help = "Delete a course from the split Mongo datastore" - args = "locator" - - def handle(self, *args, **options): - if len(args) < 1: - raise CommandError( - "delete_split_course requires at least one argument (locator)" - ) - - try: - locator = CourseLocator(url=args[0]) - except ValueError: - raise CommandError("Invalid locator string {}".format(args[0])) - - try: - modulestore('split').delete_course(locator.package_id) - except ItemNotFoundError: - raise CommandError("No course found with locator {}".format(locator)) diff --git a/cms/djangoapps/contentstore/management/commands/rollback_split_course.py b/cms/djangoapps/contentstore/management/commands/rollback_split_course.py new file mode 100644 index 0000000000000000000000000000000000000000..3681ebf282efcc180d586571dd0433623a0cb0e0 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/rollback_split_course.py @@ -0,0 +1,51 @@ +""" +Django management command to rollback a migration to split. The way to do this +is to delete the course from the split mongo datastore. +""" +from django.core.management.base import BaseCommand, CommandError +from xmodule.modulestore.django import modulestore, loc_mapper +from xmodule.modulestore.exceptions import ItemNotFoundError, InsufficientSpecificationError +from xmodule.modulestore.locator import CourseLocator + + +class Command(BaseCommand): + "Rollback a course that was migrated to the split Mongo datastore" + + help = "Rollback a course that was migrated to the split Mongo datastore" + args = "locator" + + def handle(self, *args, **options): + if len(args) < 1: + raise CommandError( + "rollback_split_course requires at least one argument (locator)" + ) + + try: + locator = CourseLocator(url=args[0]) + except ValueError: + raise CommandError("Invalid locator string {}".format(args[0])) + + location = loc_mapper().translate_locator_to_location(locator, get_course=True) + if not location: + raise CommandError( + "This course does not exist in the old Mongo store. " + "This command is designed to rollback a course, not delete " + "it entirely." + ) + old_mongo_course = modulestore('direct').get_item(location) + if not old_mongo_course: + raise CommandError( + "This course does not exist in the old Mongo store. " + "This command is designed to rollback a course, not delete " + "it entirely." + ) + + try: + modulestore('split').delete_course(locator.package_id) + except ItemNotFoundError: + raise CommandError("No course found with locator {}".format(locator)) + + print( + 'Course rolled back successfully. To delete this course entirely, ' + 'call the "delete_course" management command.' + ) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_split_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_split_course.py deleted file mode 100644 index f8411d09673ee936c3cbd6c41fdeee5fdaa97ac6..0000000000000000000000000000000000000000 --- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_split_course.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Unittests for deleting a split mongo course -""" -import unittest - -from django.core.management import CommandError, call_command -from django.test.utils import override_settings -from contentstore.management.commands.delete_split_course import Command -from contentstore.tests.modulestore_config import TEST_MODULESTORE -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.persistent_factories import PersistentCourseFactory -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.exceptions import ItemNotFoundError -# pylint: disable=E1101 - - -class TestArgParsing(unittest.TestCase): - """ - Tests for parsing arguments for the `delete_split_course` management command - """ - def setUp(self): - self.command = Command() - - def test_no_args(self): - errstring = "delete_split_course requires at least one argument" - with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle() - - def test_invalid_locator(self): - errstring = "Invalid locator string !?!" - with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle("!?!") - - def test_nonexistant_locator(self): - errstring = "No course found with locator course/branch/name" - with self.assertRaisesRegexp(CommandError, errstring): - self.command.handle("course/branch/name") - - -@override_settings(MODULESTORE=TEST_MODULESTORE) -class TestDeleteSplitCourse(ModuleStoreTestCase): - """ - Unit tests for deleting a split-mongo course from command line - """ - - def setUp(self): - super(TestDeleteSplitCourse, self).setUp() - self.course = PersistentCourseFactory() - - def test_happy_path(self): - locator = self.course.location - call_command( - "delete_split_course", - str(locator), - ) - with self.assertRaises(ItemNotFoundError): - modulestore('split').get_course(locator) diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py new file mode 100644 index 0000000000000000000000000000000000000000..e30f9324fe21baab69791b5c5a8d8f6a2fa4f17e --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/tests/test_rollback_split_course.py @@ -0,0 +1,108 @@ +""" +Unittests for deleting a split mongo course +""" +import unittest +from StringIO import StringIO +from mock import patch + +from django.contrib.auth.models import User +from django.core.management import CommandError, call_command +from django.test.utils import override_settings +from contentstore.management.commands.rollback_split_course import Command +from contentstore.tests.modulestore_config import TEST_MODULESTORE +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.persistent_factories import PersistentCourseFactory +from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.django import modulestore, loc_mapper +from xmodule.modulestore.exceptions import ItemNotFoundError +from xmodule.modulestore.split_migrator import SplitMigrator +# pylint: disable=E1101 + + +class TestArgParsing(unittest.TestCase): + """ + Tests for parsing arguments for the `rollback_split_course` management command + """ + def setUp(self): + self.command = Command() + + def test_no_args(self): + errstring = "rollback_split_course requires at least one argument" + with self.assertRaisesRegexp(CommandError, errstring): + self.command.handle() + + def test_invalid_locator(self): + errstring = "Invalid locator string !?!" + with self.assertRaisesRegexp(CommandError, errstring): + self.command.handle("!?!") + + +@override_settings(MODULESTORE=TEST_MODULESTORE) +class TestRollbackSplitCourseNoOldMongo(ModuleStoreTestCase): + """ + Unit tests for rolling back a split-mongo course from command line + """ + + def setUp(self): + super(TestRollbackSplitCourseNoOldMongo, self).setUp() + self.course = PersistentCourseFactory() + + def test_no_old_course(self): + locator = self.course.location + errstring = "course does not exist in the old Mongo store" + with self.assertRaisesRegexp(CommandError, errstring): + Command().handle(str(locator)) + +@override_settings(MODULESTORE=TEST_MODULESTORE) +class TestRollbackSplitCourseNoSplitMongo(ModuleStoreTestCase): + """ + Unit tests for rolling back a split-mongo course from command line + """ + + def setUp(self): + super(TestRollbackSplitCourseNoSplitMongo, self).setUp() + self.old_course = CourseFactory() + + def test_nonexistent_locator(self): + locator = loc_mapper().translate_location(self.old_course.id, self.old_course.location) + errstring = "No course found with locator" + with self.assertRaisesRegexp(CommandError, errstring): + Command().handle(str(locator)) + + +@override_settings(MODULESTORE=TEST_MODULESTORE) +class TestRollbackSplitCourse(ModuleStoreTestCase): + """ + Unit tests for rolling back a split-mongo course from command line + """ + def setUp(self): + super(TestRollbackSplitCourse, self).setUp() + self.old_course = CourseFactory() + uname = 'testuser' + email = 'test+courses@edx.org' + password = 'foo' + self.user = User.objects.create_user(uname, email, password) + + # migrate old course to split + migrator = SplitMigrator( + draft_modulestore=modulestore('default'), + direct_modulestore=modulestore('direct'), + split_modulestore=modulestore('split'), + loc_mapper=loc_mapper(), + ) + migrator.migrate_mongo_course(self.old_course.location, self.user) + locator = loc_mapper().translate_location(self.old_course.id, self.old_course.location) + self.course = modulestore('split').get_course(locator) + + @patch("sys.stdout", new_callable=StringIO) + def test_happy_path(self, mock_stdout): + locator = self.course.location + call_command( + "rollback_split_course", + str(locator), + ) + with self.assertRaises(ItemNotFoundError): + modulestore('split').get_course(locator) + + self.assertIn("Course rolled back successfully", mock_stdout.getvalue()) +