diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index a417418410..ce7d9e757c 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -1,27 +1,46 @@
import json
import shutil
-from django.test import TestCase
from django.test.client import Client
from override_settings import override_settings
from django.conf import settings
from django.core.urlresolvers import reverse
from path import path
+from tempfile import mkdtemp
import json
from fs.osfs import OSFS
import copy
from mock import Mock
-import xmodule.modulestore.django
-from factories import *
-from utils import CmsTestCase
+from student.models import Registration
+from django.contrib.auth.models import User
+from cms.djangoapps.contentstore.utils import get_modulestore
+
+from utils import ModuleStoreTestCase, parse_json
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+
+from xmodule.modulestore import Location
+from xmodule.modulestore.store_utilities import clone_course
+from xmodule.modulestore.store_utilities import delete_course
+from xmodule.modulestore.django import modulestore, _MODULESTORES
+from xmodule.contentstore.django import contentstore
+from xmodule.templates import update_templates
+from xmodule.modulestore.xml_exporter import export_to_xml
+from xmodule.modulestore.xml_importer import import_from_xml
+
+from xmodule.capa_module import CapaDescriptor
+from xmodule.course_module import CourseDescriptor
+from xmodule.seq_module import SequenceDescriptor
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
-class ContentStoreTest(CmsTestCase):
-
+class ContentStoreToyCourseTest(ModuleStoreTestCase):
+ """
+ Tests that rely on the toy courses.
+ TODO: refactor using CourseFactory so they do not.
+ """
def setUp(self):
uname = 'testuser'
email = 'test+courses@edx.org'
@@ -40,109 +59,6 @@ class ContentStoreTest(CmsTestCase):
self.client = Client()
self.client.login(username=uname, password=password)
- self.course_data = {
- 'template': 'i4x://edx/templates/course/Empty',
- 'org': 'MITx',
- 'number': '999',
- 'display_name': 'Robot Super Course',
- }
-
- def test_create_course(self):
- """Test new course creation - happy path"""
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- self.assertEqual(resp.status_code, 200)
- data = parse_json(resp)
- self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
-
- def test_create_course_duplicate_course(self):
- """Test new course creation - error path"""
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- data = parse_json(resp)
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.')
-
- def test_create_course_duplicate_number(self):
- """Test new course creation - error path"""
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- self.course_data['display_name'] = 'Robot Super Course Two'
-
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- data = parse_json(resp)
-
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(data['ErrMsg'],
- 'There is already a course defined with the same organization and course number.')
-
- def test_create_course_with_bad_organization(self):
- """Test new course creation - error path for bad organization name"""
- self.course_data['org'] = 'University of California, Berkeley'
- resp = self.client.post(reverse('create_new_course'), self.course_data)
- data = parse_json(resp)
-
- self.assertEqual(resp.status_code, 200)
- self.assertEqual(data['ErrMsg'],
- "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.")
-
- def test_course_index_view_with_no_courses(self):
- """Test viewing the index page with no courses"""
- # Create a course so there is something to view
- resp = self.client.get(reverse('index'))
- self.assertContains(resp,
- '
My Courses
',
- status_code=200,
- html=True)
-
- def test_course_factory(self):
- course = CourseFactory.create()
- self.assertIsInstance(course, CourseDescriptor)
-
- def test_item_factory(self):
- course = CourseFactory.create()
- item = ItemFactory.create(parent_location=course.location)
- self.assertIsInstance(item, SequenceDescriptor)
-
- def test_course_index_view_with_course(self):
- """Test viewing the index page with an existing course"""
- CourseFactory.create(display_name='Robot Super Educational Course')
- resp = self.client.get(reverse('index'))
- self.assertContains(resp,
- 'Robot Super Educational Course',
- status_code=200,
- html=True)
-
- def test_course_overview_view_with_course(self):
- """Test viewing the course overview page with an existing course"""
- CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
-
- data = {
- 'org': 'MITx',
- 'course': '999',
- 'name': Location.clean('Robot Super Course'),
- }
-
- resp = self.client.get(reverse('course_index', kwargs=data))
- self.assertContains(resp,
- 'Robot Super Course',
- status_code=200,
- html=True)
-
- def test_clone_item(self):
- """Test cloning an item. E.g. creating a new section"""
- CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
-
- section_data = {
- 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course',
- 'template' : 'i4x://edx/templates/chapter/Empty',
- 'display_name': 'Section One',
- }
-
- resp = self.client.post(reverse('clone_item'), section_data)
-
- self.assertEqual(resp.status_code, 200)
- data = parse_json(resp)
- self.assertRegexpMatches(data['id'],
- '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$')
def check_edit_unit(self, test_course_name):
import_from_xml(modulestore(), 'common/test/data/', [test_course_name])
@@ -183,9 +99,6 @@ class ContentStoreTest(CmsTestCase):
self.assertEqual(reverse_tabs, course_tabs)
-
-
-
def test_about_overrides(self):
'''
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
@@ -210,11 +123,18 @@ class ContentStoreTest(CmsTestCase):
course = ms.get_item(source_location)
self.assertNotIn('hide_progress_tab', course.metadata)
-
def test_clone_course(self):
+
+ course_data = {
+ 'template': 'i4x://edx/templates/course/Empty',
+ 'org': 'MITx',
+ 'number': '999',
+ 'display_name': 'Robot Super Course',
+ }
+
import_from_xml(modulestore(), 'common/test/data/', ['full'])
- resp = self.client.post(reverse('create_new_course'), self.course_data)
+ resp = self.client.post(reverse('create_new_course'), course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
@@ -302,7 +222,6 @@ class ContentStoreTest(CmsTestCase):
shutil.rmtree(root_dir)
-
def test_course_handouts_rewrites(self):
ms = modulestore('direct')
cs = contentstore()
@@ -323,6 +242,141 @@ class ContentStoreTest(CmsTestCase):
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
+class ContentStoreTest(ModuleStoreTestCase):
+ """
+ Tests for the CMS ContentStore application.
+ """
+ def setUp(self):
+ """
+ These tests need a user in the DB so that the django Test Client
+ can log them in.
+ They inherit from the ModuleStoreTestCase class so that the mongodb collection
+ will be cleared out before each test case execution and deleted
+ afterwards.
+ """
+ uname = 'testuser'
+ email = 'test+courses@edx.org'
+ password = 'foo'
+
+ # Create the use so we can log them in.
+ self.user = User.objects.create_user(uname, email, password)
+
+ # Note that we do not actually need to do anything
+ # for registration if we directly mark them active.
+ self.user.is_active = True
+ # Staff has access to view all courses
+ self.user.is_staff = True
+ self.user.save()
+
+ self.client = Client()
+ self.client.login(username=uname, password=password)
+
+ self.course_data = {
+ 'template': 'i4x://edx/templates/course/Empty',
+ 'org': 'MITx',
+ 'number': '999',
+ 'display_name': 'Robot Super Course',
+ }
+
+ def test_create_course(self):
+ """Test new course creation - happy path"""
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ self.assertEqual(resp.status_code, 200)
+ data = parse_json(resp)
+ self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
+
+ def test_create_course_duplicate_course(self):
+ """Test new course creation - error path"""
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ data = parse_json(resp)
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(data['ErrMsg'], 'There is already a course defined with this name.')
+
+ def test_create_course_duplicate_number(self):
+ """Test new course creation - error path"""
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ self.course_data['display_name'] = 'Robot Super Course Two'
+
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ data = parse_json(resp)
+
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(data['ErrMsg'],
+ 'There is already a course defined with the same organization and course number.')
+
+ def test_create_course_with_bad_organization(self):
+ """Test new course creation - error path for bad organization name"""
+ self.course_data['org'] = 'University of California, Berkeley'
+ resp = self.client.post(reverse('create_new_course'), self.course_data)
+ data = parse_json(resp)
+
+ self.assertEqual(resp.status_code, 200)
+ self.assertEqual(data['ErrMsg'],
+ "Unable to create course 'Robot Super Course'.\n\nInvalid characters in 'University of California, Berkeley'.")
+
+ def test_course_index_view_with_no_courses(self):
+ """Test viewing the index page with no courses"""
+ # Create a course so there is something to view
+ resp = self.client.get(reverse('index'))
+ self.assertContains(resp,
+ 'My Courses
',
+ status_code=200,
+ html=True)
+
+ def test_course_factory(self):
+ """Test that the course factory works correctly."""
+ course = CourseFactory.create()
+ self.assertIsInstance(course, CourseDescriptor)
+
+ def test_item_factory(self):
+ """Test that the item factory works correctly."""
+ course = CourseFactory.create()
+ item = ItemFactory.create(parent_location=course.location)
+ self.assertIsInstance(item, SequenceDescriptor)
+
+ def test_course_index_view_with_course(self):
+ """Test viewing the index page with an existing course"""
+ CourseFactory.create(display_name='Robot Super Educational Course')
+ resp = self.client.get(reverse('index'))
+ self.assertContains(resp,
+ 'Robot Super Educational Course',
+ status_code=200,
+ html=True)
+
+ def test_course_overview_view_with_course(self):
+ """Test viewing the course overview page with an existing course"""
+ CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
+
+ data = {
+ 'org': 'MITx',
+ 'course': '999',
+ 'name': Location.clean('Robot Super Course'),
+ }
+
+ resp = self.client.get(reverse('course_index', kwargs=data))
+ self.assertContains(resp,
+ 'Robot Super Course',
+ status_code=200,
+ html=True)
+
+ def test_clone_item(self):
+ """Test cloning an item. E.g. creating a new section"""
+ CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
+
+ section_data = {
+ 'parent_location' : 'i4x://MITx/999/course/Robot_Super_Course',
+ 'template' : 'i4x://edx/templates/chapter/Empty',
+ 'display_name': 'Section One',
+ }
+
+ resp = self.client.post(reverse('clone_item'), section_data)
+
+ self.assertEqual(resp.status_code, 200)
+ data = parse_json(resp)
+ self.assertRegexpMatches(data['id'],
+ '^i4x:\/\/MITx\/999\/chapter\/([0-9]|[a-f]){32}$')
+
def test_capa_module(self):
"""Test that a problem treats markdown specially."""
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py
index 74eff6e9cc..0191a97f6c 100644
--- a/cms/djangoapps/contentstore/tests/test_course_settings.py
+++ b/cms/djangoapps/contentstore/tests/test_course_settings.py
@@ -1,21 +1,27 @@
-from django.test.testcases import TestCase
import datetime
import time
+import json
+import calendar
+import copy
+from util import converters
+from util.converters import jsdate_to_time
+
+from django.test.testcases import TestCase
from django.contrib.auth.models import User
-import xmodule
from django.test.client import Client
from django.core.urlresolvers import reverse
-from xmodule.modulestore import Location
-from cms.djangoapps.models.settings.course_details import CourseDetails,\
- CourseSettingsEncoder
-import json
-from util import converters
-import calendar
-from util.converters import jsdate_to_time
from django.utils.timezone import UTC
+
+import xmodule
+from xmodule.modulestore import Location
+from cms.djangoapps.models.settings.course_details import (CourseDetails,
+ CourseSettingsEncoder)
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
from cms.djangoapps.contentstore.utils import get_modulestore
-import copy
+
+from utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
+
# YYYY-MM-DDThh:mm:ss.s+/-HH:MM
class ConvertersTestCase(TestCase):
@@ -36,8 +42,15 @@ class ConvertersTestCase(TestCase):
self.compare_dates(converters.jsdate_to_time("2013-01-01T00:00:00"), converters.jsdate_to_time("2012-12-31T23:59:59"), datetime.timedelta(seconds=1))
-class CourseTestCase(TestCase):
+class CourseTestCase(ModuleStoreTestCase):
def setUp(self):
+ """
+ These tests need a user in the DB so that the django Test Client
+ can log them in.
+ They inherit from the ModuleStoreTestCase class so that the mongodb collection
+ will be cleared out before each test case execution and deleted
+ afterwards.
+ """
uname = 'testuser'
email = 'test+courses@edx.org'
password = 'foo'
@@ -52,36 +65,15 @@ class CourseTestCase(TestCase):
self.user.is_staff = True
self.user.save()
- # Flush and initialize the module store
- # It needs the templates because it creates new records
- # by cloning from the template.
- # Note that if your test module gets in some weird state
- # (though it shouldn't), do this manually
- # from the bash shell to drop it:
- # $ mongo test_xmodule --eval "db.dropDatabase()"
- xmodule.modulestore.django._MODULESTORES = {}
- xmodule.modulestore.django.modulestore().collection.drop()
- xmodule.templates.update_templates()
-
self.client = Client()
self.client.login(username=uname, password=password)
- self.course_data = {
- 'template': 'i4x://edx/templates/course/Empty',
- 'org': 'MITx',
- 'number': '999',
- 'display_name': 'Robot Super Course',
- }
- self.course_location = Location('i4x', 'MITx', '999', 'course', 'Robot_Super_Course')
- self.create_course()
-
- def tearDown(self):
- xmodule.modulestore.django._MODULESTORES = {}
- xmodule.modulestore.django.modulestore().collection.drop()
-
- def create_course(self):
- """Create new course"""
- self.client.post(reverse('create_new_course'), self.course_data)
+ t='i4x://edx/templates/course/Empty'
+ o='MITx'
+ n='999'
+ dn='Robot Super Course'
+ self.course_location = Location('i4x', o, n, 'course', 'Robot_Super_Course')
+ CourseFactory.create(template=t, org=o, number=n, display_name=dn)
class CourseDetailsTestCase(CourseTestCase):
def test_virgin_fetch(self):
@@ -145,7 +137,6 @@ class CourseDetailsViewTest(CourseTestCase):
return datetime.isoformat("T")
else:
return None
-
def test_update_and_fetch(self):
details = CourseDetails.fetch(self.course_location)
@@ -271,5 +262,3 @@ class CourseGradingTest(CourseTestCase):
test_grader.graders[1]['drop_count'] = test_grader.graders[1].get('drop_count') + 1
altered_grader = CourseGradingModel.update_grader_from_json(test_grader.course_location, test_grader.graders[1])
self.assertDictEqual(test_grader.graders[1], altered_grader, "drop_count[1] + 2")
-
-
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index 8c8ee07264..1f09aee578 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -11,8 +11,6 @@ import json
from fs.osfs import OSFS
import copy
-from student.models import Registration
-from django.contrib.auth.models import User
from cms.djangoapps.contentstore.utils import get_modulestore
from xmodule.modulestore import Location
@@ -29,22 +27,7 @@ from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
-from .utils import CmsTestCase
-
-def parse_json(response):
- """Parse response, which is assumed to be json"""
- return json.loads(response.content)
-
-
-def user(email):
- """look up a user by email"""
- return User.objects.get(email=email)
-
-
-def registration(email):
- """look up registration object by email"""
- return Registration.objects.get(user__email=email)
-
+from utils import ModuleStoreTestCase, parse_json, user, registration
class ContentStoreTestCase(TestCase):
def _login(self, email, pw):
diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py
index c800ee936c..bcf0471820 100644
--- a/cms/djangoapps/contentstore/tests/utils.py
+++ b/cms/djangoapps/contentstore/tests/utils.py
@@ -1,15 +1,20 @@
from django.test import TestCase
-from xmodule.modulestore.django import modulestore, _MODULESTORES
+import json
+
+from student.models import Registration
+from django.contrib.auth.models import User
+
+import xmodule.modulestore.django
from xmodule.templates import update_templates
# Subclass TestCase and use to initialize the contentstore
-class CmsTestCase(TestCase):
+class ModuleStoreTestCase(TestCase):
""" Subclass for any test case that uses the mongodb
module store. This clears it out before running the TestCase
and reinitilizes it with the templates afterwards. """
def _pre_setup(self):
- super(CmsTestCase, self)._pre_setup()
+ super(ModuleStoreTestCase, self)._pre_setup()
# Flush and initialize the module store
# It needs the templates because it creates new records
# by cloning from the template.
@@ -17,16 +22,27 @@ class CmsTestCase(TestCase):
# (though it shouldn't), do this manually
# from the bash shell to drop it:
# $ mongo test_xmodule --eval "db.dropDatabase()"
- _MODULESTORES = {}
- modulestore().collection.drop()
+ xmodule.modulestore.django._MODULESTORES = {}
+ xmodule.modulestore.django.modulestore().collection.drop()
update_templates()
def _post_teardown(self):
# Make sure you flush out the test modulestore after the end
- # of the last test because otherwise on the next run
- # cms/djangoapps/contentstore/__init__.py
- # update_templates() will try to update the templates
- # via upsert and it sometimes seems to be messing things up.
- _MODULESTORES = {}
- modulestore().collection.drop()
- super(CmsTestCase, self)._post_teardown()
\ No newline at end of file
+ # of the last test so the collection will be deleted.
+ # Otherwise there will be lingering collections leftover
+ # from executing the tests.
+ xmodule.modulestore.django._MODULESTORES = {}
+ xmodule.modulestore.django.modulestore().collection.drop()
+ super(ModuleStoreTestCase, self)._post_teardown()
+
+def parse_json(response):
+ """Parse response, which is assumed to be json"""
+ return json.loads(response.content)
+
+def user(email):
+ """look up a user by email"""
+ return User.objects.get(email=email)
+
+def registration(email):
+ """look up registration object by email"""
+ return Registration.objects.get(user__email=email)
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 7ebb2648ec..fb63cb34ed 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -1240,6 +1240,11 @@ def edge(request):
@login_required
@expect_json
def create_new_course(request):
+ # This logic is repeated in xmodule/modulestore/tests/factories.py
+ # so if you change anything here, you need to also change it there.
+ # TODO: write a test that creates two courses, one with the factory and
+ # the other with this method, then compare them to make sure they are
+ # equivalent.
template = Location(request.POST['template'])
org = request.POST.get('org')
number = request.POST.get('number')
@@ -1289,8 +1294,11 @@ def initialize_course_tabs(course):
# at least a list populated with the minimal times
# @TODO: I don't like the fact that the presentation tier is away of these data related constraints, let's find a better
# place for this. Also rather than using a simple list of dictionaries a nice class model would be helpful here
- course.tabs = [{"type": "courseware"},
- {"type": "course_info", "name": "Course Info"},
+
+ # This logic is repeated in xmodule/modulestore/tests/factories.py
+ # so if you change anything here, you need to also change it there.
+ course.tabs = [{"type": "courseware"},
+ {"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
{"type": "wiki", "name": "Wiki"},
{"type": "progress", "name": "Progress"}]
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
index 987dbca09b..b4264b30c9 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py
@@ -1,5 +1,4 @@
from factory import Factory
-# from datetime import datetime
from time import gmtime
from uuid import uuid4
from xmodule.modulestore import Location
@@ -22,7 +21,8 @@ class XModuleCourseFactory(Factory):
@classmethod
def _create(cls, target_class, *args, **kwargs):
-
+ # This logic was taken from the create_new_course method in
+ # cms/djangoapps/contentstore/views.py
template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
org = kwargs.get('org')
number = kwargs.get('number')
@@ -41,6 +41,7 @@ class XModuleCourseFactory(Factory):
new_course.metadata['data_dir'] = uuid4().hex
new_course.metadata['start'] = stringify_time(gmtime())
+
new_course.tabs = [{"type": "courseware"},
{"type": "course_info", "name": "Course Info"},
{"type": "discussion", "name": "Discussion"},
@@ -114,4 +115,4 @@ class ItemFactory(XModuleItemFactory):
parent_location = 'i4x://MITx/999/course/Robot_Super_Course'
template = 'i4x://edx/templates/chapter/Empty'
- display_name = 'Section One'
\ No newline at end of file
+ display_name = 'Section One'