Commit d4817685 by Renzo Lucioni

Merge pull request #11691 from edx/jeskew/shared_modulestore_test_case_conversion

WIP: Lots of Python unittest speedups
parents be7be407 b02d1c12
"""
Script for converting a tar.gz file representing an exported course
to the archive format used by a different version of export.
Sample invocation: ./manage.py export_convert_format mycourse.tar.gz ~/newformat/
"""
import os
from path import Path as path
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from tempfile import mkdtemp
import tarfile
import shutil
from openedx.core.lib.extract_tar import safetar_extractall
from xmodule.modulestore.xml_exporter import convert_between_versions
class Command(BaseCommand):
"""
Convert between export formats.
"""
help = 'Convert between versions 0 and 1 of the course export format'
args = '<tar.gz archive file> <output path>'
def handle(self, *args, **options):
"Execute the command"
if len(args) != 2:
raise CommandError("export requires two arguments: <tar.gz file> <output path>")
source_archive = args[0]
output_path = args[1]
# Create temp directories to extract the source and create the target archive.
temp_source_dir = mkdtemp(dir=settings.DATA_DIR)
temp_target_dir = mkdtemp(dir=settings.DATA_DIR)
try:
extract_source(source_archive, temp_source_dir)
desired_version = convert_between_versions(temp_source_dir, temp_target_dir)
# New zip up the target directory.
parts = os.path.basename(source_archive).split('.')
archive_name = path(output_path) / "{source_name}_version_{desired_version}.tar.gz".format(
source_name=parts[0], desired_version=desired_version
)
with open(archive_name, "w"):
tar_file = tarfile.open(archive_name, mode='w:gz')
try:
for item in os.listdir(temp_target_dir):
tar_file.add(path(temp_target_dir) / item, arcname=item)
finally:
tar_file.close()
print "Created archive {0}".format(archive_name)
except ValueError as err:
raise CommandError(err)
finally:
shutil.rmtree(temp_source_dir)
shutil.rmtree(temp_target_dir)
def extract_source(source_archive, target):
"""
Extract the archive into the given target directory.
"""
with tarfile.open(source_archive) as tar_file:
safetar_extractall(tar_file, target)
"""
Test for export_convert_format.
"""
from unittest import TestCase
from django.core.management import call_command, CommandError
from django.conf import settings
from tempfile import mkdtemp
import shutil
from path import Path as path
from contentstore.management.commands.export_convert_format import Command, extract_source
from xmodule.tests.helpers import directories_equal
class ConvertExportFormat(TestCase):
"""
Tests converting between export formats.
"""
def setUp(self):
""" Common setup. """
super(ConvertExportFormat, self).setUp()
self.temp_dir = mkdtemp(dir=settings.DATA_DIR)
self.addCleanup(shutil.rmtree, self.temp_dir)
self.data_dir = path(__file__).realpath().parent / 'data'
self.version0 = self.data_dir / "Version0_drafts.tar.gz"
self.version1 = self.data_dir / "Version1_drafts.tar.gz"
self.command = Command()
def test_no_args(self):
""" Test error condition of no arguments. """
errstring = "export requires two arguments"
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle()
def test_version1_archive(self):
"""
Smoke test for creating a version 1 archive from a version 0.
"""
call_command('export_convert_format', self.version0, self.temp_dir)
output = path(self.temp_dir) / 'Version0_drafts_version_1.tar.gz'
self.assertTrue(self._verify_archive_equality(output, self.version1))
def test_version0_archive(self):
"""
Smoke test for creating a version 0 archive from a version 1.
"""
call_command('export_convert_format', self.version1, self.temp_dir)
output = path(self.temp_dir) / 'Version1_drafts_version_0.tar.gz'
self.assertTrue(self._verify_archive_equality(output, self.version0))
def _verify_archive_equality(self, file1, file2):
"""
Helper function for determining if 2 archives are equal.
"""
temp_dir_1 = mkdtemp(dir=settings.DATA_DIR)
temp_dir_2 = mkdtemp(dir=settings.DATA_DIR)
try:
extract_source(file1, temp_dir_1)
extract_source(file2, temp_dir_2)
return directories_equal(temp_dir_1, temp_dir_2)
finally:
shutil.rmtree(temp_dir_1)
shutil.rmtree(temp_dir_2)
......@@ -29,8 +29,9 @@ from opaque_keys.edx.locations import CourseLocator
from xmodule.error_module import ErrorDescriptor
from course_action_state.models import CourseRerunState
TOTAL_COURSES_COUNT = 500
USER_COURSES_COUNT = 50
TOTAL_COURSES_COUNT = 10
USER_COURSES_COUNT = 1
@ddt.ddt
......@@ -157,8 +158,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
self.assertEqual(courses_list_by_groups, [])
@ddt.data(
(ModuleStoreEnum.Type.split, 5),
(ModuleStoreEnum.Type.mongo, 3)
(ModuleStoreEnum.Type.split, 3),
(ModuleStoreEnum.Type.mongo, 2)
)
@ddt.unpack
def test_staff_course_listing(self, default_store, mongo_calls):
......@@ -265,8 +266,8 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
)
@ddt.data(
(ModuleStoreEnum.Type.split, 150, 505),
(ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 3)
(ModuleStoreEnum.Type.split, 3, 13),
(ModuleStoreEnum.Type.mongo, USER_COURSES_COUNT, 2)
)
@ddt.unpack
def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls):
......
......@@ -9,7 +9,7 @@ settings.INSTALLED_APPS # pylint: disable=pointless-statement
from openedx.core.lib.django_startup import autostartup
import django
from monkey_patch import third_party_auth
from monkey_patch import third_party_auth, django_db_models_options
import xmodule.x_module
import cms.lib.xblock.runtime
......@@ -22,6 +22,7 @@ def run():
Executed during django startup
"""
third_party_auth.patch()
django_db_models_options.patch()
# Comprehensive theming needs to be set up before django startup,
# because modifying django template paths after startup has no effect.
......
"""
Monkey patch implementation of the following _expire_cache performance improvement:
https://github.com/django/django/commit/7628f87e2b1ab4b8a881f06c8973be4c368aaa3d
Remove once we upgrade to a version of django which includes this fix natively!
NOTE: This is on django's master branch but is NOT currently part of any django 1.8 or 1.9 release.
"""
from django.db.models.options import Options
def patch():
"""
Monkey-patch the Options class.
"""
def _expire_cache(self, forward=True, reverse=True):
# pylint: disable=missing-docstring
# This method is usually called by apps.cache_clear(), when the
# registry is finalized, or when a new field is added.
if forward:
for cache_key in self.FORWARD_PROPERTIES:
if cache_key in self.__dict__:
delattr(self, cache_key)
if reverse and not self.abstract:
for cache_key in self.REVERSE_PROPERTIES:
if cache_key in self.__dict__:
delattr(self, cache_key)
self._get_fields_cache = {} # pylint: disable=protected-access
# Patch constants as a set instead of a list.
Options.FORWARD_PROPERTIES = {'fields', 'many_to_many', 'concrete_fields',
'local_concrete_fields', '_forward_fields_map'}
Options.REVERSE_PROPERTIES = {'related_objects', 'fields_map', '_relation_tree'}
# Patch the expire_cache method to utilize constant's new set data structure.
Options._expire_cache = _expire_cache # pylint: disable=protected-access
......@@ -4,6 +4,7 @@ Modulestore configuration for test cases.
"""
import functools
from uuid import uuid4
from contextlib import contextmanager
from mock import patch
......@@ -269,9 +270,10 @@ class SharedModuleStoreTestCase(TestCase):
multi_db = True
@classmethod
def setUpClass(cls):
super(SharedModuleStoreTestCase, cls).setUpClass()
def _setUpModuleStore(cls): # pylint: disable=invalid-name
"""
Set up the modulestore for an entire test class.
"""
cls._settings_override = override_settings(MODULESTORE=cls.MODULESTORE)
cls._settings_override.__enter__()
XMODULE_FACTORY_LOCK.enable()
......@@ -279,6 +281,40 @@ class SharedModuleStoreTestCase(TestCase):
cls.store = modulestore()
@classmethod
@contextmanager
def setUpClassAndTestData(cls): # pylint: disable=invalid-name
"""
For use when the test class has a setUpTestData() method that uses variables
that are setup during setUpClass() of the same test class.
Use it like so:
@classmethod
def setUpClass(cls):
with super(MyTestClass, cls).setUpClassAndTestData():
<all the cls.setUpClass() setup code that performs modulestore setup...>
@classmethod
def setUpTestData(cls):
<all the setup code that creates Django models per test class...>
<these models can use variables (courses) setup in setUpClass() above>
"""
cls._setUpModuleStore()
# Now yield to allow the test class to run its setUpClass() setup code.
yield
# Now call the base class, which calls back into the test class's setUpTestData().
super(SharedModuleStoreTestCase, cls).setUpClass()
@classmethod
def setUpClass(cls):
"""
For use when the test class has no setUpTestData() method -or-
when that method does not use variable set up in setUpClass().
"""
super(SharedModuleStoreTestCase, cls).setUpClass()
cls._setUpModuleStore()
@classmethod
def tearDownClass(cls):
drop_mongo_collections() # pylint: disable=no-value-for-parameter
clear_all_caches()
......
......@@ -24,8 +24,6 @@ from opaque_keys.edx.locator import CourseLocator, LibraryLocator
DRAFT_DIR = "drafts"
PUBLISHED_DIR = "published"
EXPORT_VERSION_FILE = "format.json"
EXPORT_VERSION_KEY = "export_format"
DEFAULT_CONTENT_FIELDS = ['metadata', 'data']
......@@ -408,90 +406,3 @@ def export_extra_content(export_fs, modulestore, source_course_key, dest_course_
# export content fields other then metadata and data in json format in current directory
_export_field_content(item, item_dir)
def convert_between_versions(source_dir, target_dir):
"""
Converts a version 0 export format to version 1, and vice versa.
@param source_dir: the directory structure with the course export that should be converted.
The contents of source_dir will not be altered.
@param target_dir: the directory where the converted export should be written.
@return: the version number of the converted export.
"""
def convert_to_version_1():
""" Convert a version 0 archive to version 0 """
os.mkdir(copy_root)
with open(copy_root / EXPORT_VERSION_FILE, 'w') as f:
f.write('{{"{export_key}": 1}}\n'.format(export_key=EXPORT_VERSION_KEY))
# If a drafts folder exists, copy it over.
copy_drafts()
# Now copy everything into the published directory
published_dir = copy_root / PUBLISHED_DIR
shutil.copytree(path(source_dir) / course_name, published_dir)
# And delete the nested drafts directory, if it exists.
nested_drafts_dir = published_dir / DRAFT_DIR
if nested_drafts_dir.isdir():
shutil.rmtree(nested_drafts_dir)
def convert_to_version_0():
""" Convert a version 1 archive to version 0 """
# Copy everything in "published" up to the top level.
published_dir = path(source_dir) / course_name / PUBLISHED_DIR
if not published_dir.isdir():
raise ValueError("a version 1 archive must contain a published branch")
shutil.copytree(published_dir, copy_root)
# If there is a DRAFT branch, copy it. All other branches are ignored.
copy_drafts()
def copy_drafts():
"""
Copy drafts directory from the old archive structure to the new.
"""
draft_dir = path(source_dir) / course_name / DRAFT_DIR
if draft_dir.isdir():
shutil.copytree(draft_dir, copy_root / DRAFT_DIR)
root = os.listdir(source_dir)
if len(root) != 1 or (path(source_dir) / root[0]).isfile():
raise ValueError("source archive does not have single course directory at top level")
course_name = root[0]
# For this version of the script, we simply convert back and forth between version 0 and 1.
original_version = get_version(path(source_dir) / course_name)
if original_version not in [0, 1]:
raise ValueError("unknown version: " + str(original_version))
desired_version = 1 if original_version is 0 else 0
copy_root = path(target_dir) / course_name
if desired_version == 1:
convert_to_version_1()
else:
convert_to_version_0()
return desired_version
def get_version(course_path):
"""
Return the export format version number for the given
archive directory structure (represented as a path instance).
If the archived file does not correspond to a known export
format, None will be returned.
"""
format_file = course_path / EXPORT_VERSION_FILE
if not format_file.isfile():
return 0
with open(format_file, "r") as f:
data = json.load(f)
if EXPORT_VERSION_KEY in data:
return data[EXPORT_VERSION_KEY]
return None
......@@ -25,9 +25,6 @@ from xblock.test.tools import blocks_are_equivalent
from opaque_keys.edx.locations import Location
from xmodule.modulestore import EdxJSONEncoder
from xmodule.modulestore.xml import XMLModuleStore
from xmodule.modulestore.xml_exporter import (
convert_between_versions, get_version
)
from xmodule.tests import DATA_DIR
from xmodule.tests.helpers import directories_equal
from xmodule.x_module import XModuleMixin
......@@ -214,173 +211,3 @@ class TestEdxJsonEncoder(unittest.TestCase):
with self.assertRaises(TypeError):
self.encoder.default({})
class ConvertExportFormat(unittest.TestCase):
"""
Tests converting between export formats.
"""
def setUp(self):
""" Common setup. """
super(ConvertExportFormat, self).setUp()
# Directory for expanding all the test archives
self.temp_dir = mkdtemp()
self.addCleanup(shutil.rmtree, self.temp_dir)
# Directory where new archive will be created
self.result_dir = path(self.temp_dir) / uuid.uuid4().hex
os.mkdir(self.result_dir)
# Expand all the test archives and store their paths.
self.data_dir = path(__file__).realpath().parent / 'data'
self._version0_nodrafts = None
self._version1_nodrafts = None
self._version0_drafts = None
self._version1_drafts = None
self._version1_drafts_extra_branch = None
self._no_version = None
@property
def version0_nodrafts(self):
"lazily expand this"
if self._version0_nodrafts is None:
self._version0_nodrafts = self._expand_archive('Version0_nodrafts.tar.gz')
return self._version0_nodrafts
@property
def version1_nodrafts(self):
"lazily expand this"
if self._version1_nodrafts is None:
self._version1_nodrafts = self._expand_archive('Version1_nodrafts.tar.gz')
return self._version1_nodrafts
@property
def version0_drafts(self):
"lazily expand this"
if self._version0_drafts is None:
self._version0_drafts = self._expand_archive('Version0_drafts.tar.gz')
return self._version0_drafts
@property
def version1_drafts(self):
"lazily expand this"
if self._version1_drafts is None:
self._version1_drafts = self._expand_archive('Version1_drafts.tar.gz')
return self._version1_drafts
@property
def version1_drafts_extra_branch(self):
"lazily expand this"
if self._version1_drafts_extra_branch is None:
self._version1_drafts_extra_branch = self._expand_archive('Version1_drafts_extra_branch.tar.gz')
return self._version1_drafts_extra_branch
@property
def no_version(self):
"lazily expand this"
if self._no_version is None:
self._no_version = self._expand_archive('NoVersionNumber.tar.gz')
return self._no_version
def _expand_archive(self, name):
""" Expand archive into a directory and return the directory. """
target = path(self.temp_dir) / uuid.uuid4().hex
os.mkdir(target)
with tarfile.open(self.data_dir / name) as tar_file:
tar_file.extractall(path=target)
return target
def test_no_version(self):
""" Test error condition of no version number specified. """
errstring = "unknown version"
with self.assertRaisesRegexp(ValueError, errstring):
convert_between_versions(self.no_version, self.result_dir)
def test_no_published(self):
""" Test error condition of a version 1 archive with no published branch. """
errstring = "version 1 archive must contain a published branch"
no_published = self._expand_archive('Version1_nopublished.tar.gz')
with self.assertRaisesRegexp(ValueError, errstring):
convert_between_versions(no_published, self.result_dir)
def test_empty_course(self):
""" Test error condition of a version 1 archive with no published branch. """
errstring = "source archive does not have single course directory at top level"
empty_course = self._expand_archive('EmptyCourse.tar.gz')
with self.assertRaisesRegexp(ValueError, errstring):
convert_between_versions(empty_course, self.result_dir)
def test_convert_to_1_nodrafts(self):
"""
Test for converting from version 0 of export format to version 1 in a course with no drafts.
"""
self._verify_conversion(self.version0_nodrafts, self.version1_nodrafts)
def test_convert_to_1_drafts(self):
"""
Test for converting from version 0 of export format to version 1 in a course with drafts.
"""
self._verify_conversion(self.version0_drafts, self.version1_drafts)
def test_convert_to_0_nodrafts(self):
"""
Test for converting from version 1 of export format to version 0 in a course with no drafts.
"""
self._verify_conversion(self.version1_nodrafts, self.version0_nodrafts)
def test_convert_to_0_drafts(self):
"""
Test for converting from version 1 of export format to version 0 in a course with drafts.
"""
self._verify_conversion(self.version1_drafts, self.version0_drafts)
def test_convert_to_0_extra_branch(self):
"""
Test for converting from version 1 of export format to version 0 in a course
with drafts and an extra branch.
"""
self._verify_conversion(self.version1_drafts_extra_branch, self.version0_drafts)
def test_equality_function(self):
"""
Check equality function returns False for unequal directories.
"""
self.assertFalse(directories_equal(self.version1_nodrafts, self.version0_nodrafts))
self.assertFalse(directories_equal(self.version1_drafts_extra_branch, self.version1_drafts))
def test_version_0(self):
"""
Check that get_version correctly identifies a version 0 archive (old format).
"""
self.assertEqual(0, self._version_test(self.version0_nodrafts))
def test_version_1(self):
"""
Check that get_version correctly identifies a version 1 archive (new format).
"""
self.assertEqual(1, self._version_test(self.version1_nodrafts))
def test_version_missing(self):
"""
Check that get_version returns None if no version number is specified,
and the archive is not version 0.
"""
self.assertIsNone(self._version_test(self.no_version))
def _version_test(self, archive_dir):
"""
Helper function for version tests.
"""
root = os.listdir(archive_dir)
course_directory = archive_dir / root[0]
return get_version(course_directory)
def _verify_conversion(self, source_archive, comparison_archive):
"""
Helper function for conversion tests.
"""
convert_between_versions(source_archive, self.result_dir)
self.assertTrue(directories_equal(self.result_dir, comparison_archive))
......@@ -25,16 +25,12 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(TestCCXModulestoreWrapper, cls).setUpClass()
cls.course = course = CourseFactory.create()
cls.mooc_start = start = datetime.datetime(
2010, 5, 12, 2, 42, tzinfo=pytz.UTC
)
cls.mooc_due = due = datetime.datetime(
2010, 7, 7, 0, 0, tzinfo=pytz.UTC
)
cls.course = CourseFactory.create()
start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
# Create a course outline
cls.chapters = chapters = [
ItemFactory.create(start=start, parent=course) for _ in xrange(2)
ItemFactory.create(start=start, parent=cls.course) for _ in xrange(2)
]
cls.sequentials = sequentials = [
ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters
......@@ -48,20 +44,24 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
ItemFactory.create(parent=v, category='html') for _ in xrange(2) for v in verticals
]
@classmethod
def setUpTestData(cls):
"""
Set up models for the whole TestCase.
"""
cls.user = UserFactory.create()
# Create instructor account
cls.coach = AdminFactory.create()
def setUp(self):
"""
Set up tests
"""
super(TestCCXModulestoreWrapper, self).setUp()
self.user = UserFactory.create()
# Create instructor account
coach = AdminFactory.create()
self.ccx = ccx = CustomCourseForEdX(
course_id=self.course.id,
display_name='Test CCX',
coach=coach
coach=self.coach
)
ccx.save()
......@@ -132,6 +132,7 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
def test_publication_api(self):
"""verify that we can correctly discern a published item by ccx key"""
with self.store.bulk_operations(self.ccx_locator):
for expected in self.blocks:
block_key = self.ccx_locator.make_usage_key(
expected.location.block_type, expected.location.block_id
......
......@@ -13,7 +13,7 @@ from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_ov
from request_cache.middleware import RequestCache
from student.tests.factories import AdminFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
SharedModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE)
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -26,26 +26,25 @@ from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks
@attr('shard_1')
@override_settings(FIELD_OVERRIDE_PROVIDERS=(
'ccx.overrides.CustomCoursesForEdxOverrideProvider',))
class TestFieldOverrides(ModuleStoreTestCase):
class TestFieldOverrides(SharedModuleStoreTestCase):
"""
Make sure field overrides behave in the expected manner.
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
def setUp(self):
@classmethod
def setUpClass(cls):
"""
Set up tests
Course is created here and shared by all the class's tests.
"""
super(TestFieldOverrides, self).setUp()
self.course = course = CourseFactory.create()
self.course.enable_ccx = True
super(TestFieldOverrides, cls).setUpClass()
cls.course = CourseFactory.create()
cls.course.enable_ccx = True
# Create a course outline
self.mooc_start = start = datetime.datetime(
2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
self.mooc_due = due = datetime.datetime(
2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
chapters = [ItemFactory.create(start=start, parent=course)
start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
chapters = [ItemFactory.create(start=start, parent=cls.course)
for _ in xrange(2)]
sequentials = flatten([
[ItemFactory.create(parent=chapter) for _ in xrange(2)]
......@@ -57,8 +56,14 @@ class TestFieldOverrides(ModuleStoreTestCase):
[ItemFactory.create(parent=vertical) for _ in xrange(2)]
for vertical in verticals])
def setUp(self):
"""
Set up tests
"""
super(TestFieldOverrides, self).setUp()
self.ccx = ccx = CustomCourseForEdX(
course_id=course.id,
course_id=self.course.id,
display_name='Test CCX',
coach=AdminFactory.create())
ccx.save()
......@@ -70,7 +75,7 @@ class TestFieldOverrides(ModuleStoreTestCase):
self.addCleanup(RequestCache.clear_request_cache)
inject_field_overrides(iter_blocks(ccx.course), course, AdminFactory.create())
inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create())
def cleanup_provider_classes():
"""
......
......@@ -7,7 +7,7 @@ from nose.plugins.attrib import attr
from django.test.utils import override_settings
from xblock.field_data import DictFieldData
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from ..field_overrides import (
disable_overrides,
......@@ -23,14 +23,21 @@ TESTUSER = "testuser"
@attr('shard_1')
@override_settings(FIELD_OVERRIDE_PROVIDERS=(
'courseware.tests.test_field_overrides.TestOverrideProvider',))
class OverrideFieldDataTests(ModuleStoreTestCase):
class OverrideFieldDataTests(SharedModuleStoreTestCase):
"""
Tests for `OverrideFieldData`.
"""
@classmethod
def setUpClass(cls):
"""
Course is created here and shared by all the class's tests.
"""
super(OverrideFieldDataTests, cls).setUpClass()
cls.course = CourseFactory.create(enable_ccx=True)
def setUp(self):
super(OverrideFieldDataTests, self).setUp()
self.course = CourseFactory.create(enable_ccx=True)
OverrideFieldData.provider_classes = None
def tearDown(self):
......
......@@ -11,57 +11,69 @@ from django.test.utils import override_settings
from courseware.tests.helpers import LoginEnrollmentTestCase
from courseware.tests.factories import GlobalStaffFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.django import modulestore
@attr('shard_1')
class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Check that navigation state is saved properly.
"""
STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')]
def setUp(self):
super(TestNavigation, self).setUp()
self.test_course = CourseFactory.create()
self.course = CourseFactory.create()
self.chapter0 = ItemFactory.create(parent=self.course,
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(TestNavigation, cls).setUpClassAndTestData():
cls.test_course = CourseFactory.create()
cls.test_course_proctored = CourseFactory.create()
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
cls.chapter0 = ItemFactory.create(parent=cls.course,
display_name='Overview')
self.chapter9 = ItemFactory.create(parent=self.course,
cls.chapter9 = ItemFactory.create(parent=cls.course,
display_name='factory_chapter')
self.section0 = ItemFactory.create(parent=self.chapter0,
cls.section0 = ItemFactory.create(parent=cls.chapter0,
display_name='Welcome')
self.section9 = ItemFactory.create(parent=self.chapter9,
cls.section9 = ItemFactory.create(parent=cls.chapter9,
display_name='factory_section')
self.unit0 = ItemFactory.create(parent=self.section0,
cls.unit0 = ItemFactory.create(parent=cls.section0,
display_name='New Unit')
self.chapterchrome = ItemFactory.create(parent=self.course,
cls.chapterchrome = ItemFactory.create(parent=cls.course,
display_name='Chrome')
self.chromelesssection = ItemFactory.create(parent=self.chapterchrome,
cls.chromelesssection = ItemFactory.create(parent=cls.chapterchrome,
display_name='chromeless',
chrome='none')
self.accordionsection = ItemFactory.create(parent=self.chapterchrome,
cls.accordionsection = ItemFactory.create(parent=cls.chapterchrome,
display_name='accordion',
chrome='accordion')
self.tabssection = ItemFactory.create(parent=self.chapterchrome,
cls.tabssection = ItemFactory.create(parent=cls.chapterchrome,
display_name='tabs',
chrome='tabs')
self.defaultchromesection = ItemFactory.create(
parent=self.chapterchrome,
cls.defaultchromesection = ItemFactory.create(
parent=cls.chapterchrome,
display_name='defaultchrome',
)
self.fullchromesection = ItemFactory.create(parent=self.chapterchrome,
cls.fullchromesection = ItemFactory.create(parent=cls.chapterchrome,
display_name='fullchrome',
chrome='accordion,tabs')
self.tabtest = ItemFactory.create(parent=self.chapterchrome,
cls.tabtest = ItemFactory.create(parent=cls.chapterchrome,
display_name='progress_tab',
default_tab='progress')
cls.staff_user = GlobalStaffFactory()
cls.user = UserFactory()
def setUp(self):
super(TestNavigation, self).setUp()
# Create student accounts and activate them.
for i in range(len(self.STUDENT_INFO)):
email, password = self.STUDENT_INFO[i]
......@@ -69,8 +81,6 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.create_account(username, email, password)
self.activate_user(email)
self.staff_user = GlobalStaffFactory()
def assertTabActive(self, tabname, response):
''' Check if the progress tab is active in the tab set '''
for line in response.content.split('\n'):
......@@ -278,9 +288,9 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
email, password = self.STUDENT_INFO[0]
self.login(email, password)
self.enroll(self.test_course, True)
self.enroll(self.test_course_proctored, True)
test_course_id = self.test_course.id.to_deprecated_string()
test_course_id = self.test_course_proctored.id.to_deprecated_string()
with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': False}):
url = reverse(
......@@ -302,10 +312,10 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
# now set up a course which is proctored enabled
self.test_course.enable_proctored_exams = True
self.test_course.save()
self.test_course_proctored.enable_proctored_exams = True
self.test_course_proctored.save()
modulestore().update_item(self.test_course, self.user.id)
modulestore().update_item(self.test_course_proctored, self.user.id)
resp = self.client.get(url)
......
......@@ -18,7 +18,7 @@ import dashboard.git_import as git_import
from dashboard.git_import import GitImportError
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
......@@ -37,7 +37,7 @@ FEATURES_WITH_SSL_AUTH['AUTH_USE_CERTIFICATES'] = True
@override_settings(MONGODB_LOG=TEST_MONGODB_LOG)
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'),
"ENABLE_SYSADMIN_DASHBOARD not set")
class TestGitAddCourse(ModuleStoreTestCase):
class TestGitAddCourse(SharedModuleStoreTestCase):
"""
Tests the git_add_course management command for proper functions.
"""
......
......@@ -59,10 +59,6 @@ class SysadminDashboardView(TemplateView):
"""
self.def_ms = modulestore()
self.is_using_mongo = True
if self.def_ms.get_modulestore_type(None) == 'xml':
self.is_using_mongo = False
self.msg = u''
self.datatable = []
super(SysadminDashboardView, self).__init__(**kwargs)
......@@ -374,11 +370,8 @@ class Courses(SysadminDashboardView):
return _("The git repo location should end with '.git', "
"and be a valid url")
if self.is_using_mongo:
return self.import_mongo_course(gitloc, branch)
return self.import_xml_course(gitloc, branch)
def import_mongo_course(self, gitloc, branch):
"""
Imports course using management command and captures logging output
......@@ -429,80 +422,6 @@ class Courses(SysadminDashboardView):
msg += u"<pre>{0}</pre>".format(escape(ret))
return msg
def import_xml_course(self, gitloc, branch):
"""Imports a git course into the XMLModuleStore"""
msg = u''
if not getattr(settings, 'GIT_IMPORT_WITH_XMLMODULESTORE', False):
# Translators: "GIT_IMPORT_WITH_XMLMODULESTORE" is a variable name.
# "XMLModuleStore" and "MongoDB" are database systems. You should not
# translate these names.
return _('Refusing to import. GIT_IMPORT_WITH_XMLMODULESTORE is '
'not turned on, and it is generally not safe to import '
'into an XMLModuleStore with multithreaded. We '
'recommend you enable the MongoDB based module store '
'instead, unless this is a development environment.')
cdir = (gitloc.rsplit('/', 1)[1])[:-4]
gdir = settings.DATA_DIR / cdir
if os.path.exists(gdir):
msg += _("The course {0} already exists in the data directory! "
"(reloading anyway)").format(cdir)
cmd = ['git', 'pull', ]
cwd = gdir
else:
cmd = ['git', 'clone', gitloc, ]
cwd = settings.DATA_DIR
cwd = os.path.abspath(cwd)
try:
cmd_output = escape(
subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd)
)
except subprocess.CalledProcessError as ex:
log.exception('Git pull or clone output was: %r', ex.output)
# Translators: unable to download the course content from
# the source git repository. Clone occurs if this is brand
# new, and pull is when it is being updated from the
# source.
return _('Unable to clone or pull repository. Please check '
'your url. Output was: {0!r}').format(ex.output)
msg += u'<pre>{0}</pre>'.format(cmd_output)
if not os.path.exists(gdir):
msg += _('Failed to clone repository to {directory_name}').format(directory_name=gdir)
return msg
# Change branch if specified
if branch:
try:
git_import.switch_branch(branch, gdir)
except GitImportError as ex:
return str(ex)
# Translators: This is a git repository branch, which is a
# specific version of a courses content
msg += u'<p>{0}</p>'.format(
_('Successfully switched to branch: '
'{branch_name}').format(branch_name=branch))
self.def_ms.try_load_course(os.path.abspath(gdir))
errlog = self.def_ms.errored_courses.get(cdir, '')
if errlog:
msg += u'<hr width="50%"><pre>{0}</pre>'.format(escape(errlog))
else:
course = self.def_ms.courses[os.path.abspath(gdir)]
msg += _('Loaded course {course_name}<br/>Errors:').format(
course_name="{} {}".format(cdir, course.display_name)
)
errors = self.def_ms.get_course_errors(course.id)
if not errors:
msg += u'None'
else:
msg += u'<ul>'
for (summary, err) in errors:
msg += u'<li><pre>{0}: {1}</pre></li>'.format(escape(summary),
escape(err))
msg += u'</ul>'
return msg
def make_datatable(self):
"""Creates course information datatable"""
......
......@@ -20,8 +20,6 @@ from django.utils.translation import ugettext as _
import mongoengine
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.tests.django_utils import TEST_DATA_XML_MODULESTORE
from dashboard.models import CourseImportLog
from dashboard.sysadmin import Users
from dashboard.git_import import GitImportError
......@@ -30,7 +28,7 @@ from external_auth.models import ExternalAuthMap
from student.roles import CourseStaffRole, GlobalStaff
from student.tests.factories import UserFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
......@@ -46,7 +44,7 @@ FEATURES_WITH_SSL_AUTH = settings.FEATURES.copy()
FEATURES_WITH_SSL_AUTH['AUTH_USE_CERTIFICATES'] = True
class SysadminBaseTestCase(ModuleStoreTestCase):
class SysadminBaseTestCase(SharedModuleStoreTestCase):
"""
Base class with common methods used in XML and Mongo tests
"""
......@@ -57,7 +55,7 @@ class SysadminBaseTestCase(ModuleStoreTestCase):
def setUp(self):
"""Setup test case by adding primary user."""
super(SysadminBaseTestCase, self).setUp(create_user=False)
super(SysadminBaseTestCase, self).setUp()
self.user = UserFactory.create(username='test_user',
email='test_user+sysadmin@edx.org',
password='foo')
......@@ -116,297 +114,6 @@ class SysadminBaseTestCase(ModuleStoreTestCase):
@attr('shard_1')
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'),
"ENABLE_SYSADMIN_DASHBOARD not set")
@override_settings(GIT_IMPORT_WITH_XMLMODULESTORE=True)
class TestSysadmin(SysadminBaseTestCase):
"""
Test sysadmin dashboard features using XMLModuleStore
"""
MODULESTORE = TEST_DATA_XML_MODULESTORE
def test_staff_access(self):
"""Test access controls."""
test_views = ['sysadmin', 'sysadmin_courses', 'sysadmin_staffing', ]
for view in test_views:
response = self.client.get(reverse(view))
self.assertEqual(response.status_code, 302)
self.user.is_staff = False
self.user.save()
logged_in = self.client.login(username=self.user.username,
password='foo')
self.assertTrue(logged_in)
for view in test_views:
response = self.client.get(reverse(view))
self.assertEqual(response.status_code, 404)
response = self.client.get(reverse('gitlogs'))
self.assertEqual(response.status_code, 404)
self.user.is_staff = True
self.user.save()
self.client.logout()
self.client.login(username=self.user.username, password='foo')
for view in test_views:
response = self.client.get(reverse(view))
self.assertTrue(response.status_code, 200)
response = self.client.get(reverse('gitlogs'))
self.assertTrue(response.status_code, 200)
def test_user_mod(self):
"""Create and delete a user"""
self._setstaff_login()
self.client.login(username=self.user.username, password='foo')
# Create user tests
# No uname
response = self.client.post(reverse('sysadmin'),
{'action': 'create_user',
'student_fullname': 'blah',
'student_password': 'foozor', })
self.assertIn('Must provide username', response.content.decode('utf-8'))
# no full name
response = self.client.post(reverse('sysadmin'),
{'action': 'create_user',
'student_uname': 'test_cuser+sysadmin@edx.org',
'student_password': 'foozor', })
self.assertIn('Must provide full name', response.content.decode('utf-8'))
# Test create valid user
self.client.post(reverse('sysadmin'),
{'action': 'create_user',
'student_uname': 'test_cuser+sysadmin@edx.org',
'student_fullname': 'test cuser',
'student_password': 'foozor', })
self.assertIsNotNone(
User.objects.get(username='test_cuser+sysadmin@edx.org',
email='test_cuser+sysadmin@edx.org'))
# login as new user to confirm
self.assertTrue(self.client.login(
username='test_cuser+sysadmin@edx.org', password='foozor'))
self.client.logout()
self.client.login(username=self.user.username, password='foo')
# Delete user tests
# Try no username
response = self.client.post(reverse('sysadmin'),
{'action': 'del_user', })
self.assertIn('Must provide username', response.content.decode('utf-8'))
# Try bad usernames
response = self.client.post(reverse('sysadmin'),
{'action': 'del_user',
'student_uname': 'flabbergast@example.com',
'student_fullname': 'enigma jones', })
self.assertIn('Cannot find user with email address', response.content.decode('utf-8'))
response = self.client.post(reverse('sysadmin'),
{'action': 'del_user',
'student_uname': 'flabbergast',
'student_fullname': 'enigma jones', })
self.assertIn('Cannot find user with username', response.content.decode('utf-8'))
self.client.post(reverse('sysadmin'),
{'action': 'del_user',
'student_uname': 'test_cuser+sysadmin@edx.org',
'student_fullname': 'test cuser', })
self.assertEqual(0, len(User.objects.filter(
username='test_cuser+sysadmin@edx.org',
email='test_cuser+sysadmin@edx.org')))
self.assertEqual(1, len(User.objects.all()))
def test_user_csv(self):
"""Download and validate user CSV"""
num_test_users = 100
self._setstaff_login()
# Stuff full of users to test streaming
for user_num in xrange(num_test_users):
Users().create_user('testingman_with_long_name{}'.format(user_num),
'test test')
response = self.client.post(reverse('sysadmin'),
{'action': 'download_users', })
self.assertIn('attachment', response['Content-Disposition'])
self.assertEqual('text/csv', response['Content-Type'])
self.assertIn('test_user', response.content)
self.assertTrue(num_test_users + 2, len(response.content.splitlines()))
# Clean up
User.objects.filter(
username__startswith='testingman_with_long_name').delete()
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH)
def test_authmap_repair(self):
"""Run authmap check and repair"""
self._setstaff_login()
Users().create_user('test0', 'test test')
# Will raise exception, so no assert needed
eamap = ExternalAuthMap.objects.get(external_name='test test')
mitu = User.objects.get(username='test0')
self.assertTrue(check_password(eamap.internal_password, mitu.password))
mitu.set_password('not autogenerated')
mitu.save()
self.assertFalse(check_password(eamap.internal_password, mitu.password))
# Create really non user AuthMap
ExternalAuthMap(external_id='ll',
external_domain='ll',
external_credentials='{}',
external_email='a@b.c',
external_name='c',
internal_password='').save()
response = self.client.post(reverse('sysadmin'),
{'action': 'repair_eamap', })
self.assertIn('{0} test0'.format('Failed in authenticating'),
response.content)
self.assertIn('fixed password', response.content.decode('utf-8'))
self.assertTrue(self.client.login(username='test0',
password=eamap.internal_password))
# Check for all OK
self._setstaff_login()
response = self.client.post(reverse('sysadmin'),
{'action': 'repair_eamap', })
self.assertIn('All ok!', response.content.decode('utf-8'))
def test_xml_course_add_delete(self):
"""add and delete course from xml module store"""
self._setstaff_login()
# Try bad git repo
response = self.client.post(reverse('sysadmin_courses'), {
'repo_location': 'github.com/mitocw/edx4edx_lite',
'action': 'add_course', })
self.assertIn(_("The git repo location should end with '.git', "
"and be a valid url"), response.content.decode('utf-8'))
response = self.client.post(reverse('sysadmin_courses'), {
'repo_location': 'http://example.com/not_real.git',
'action': 'add_course', })
self.assertIn('Unable to clone or pull repository',
response.content.decode('utf-8'))
# Create git loaded course
response = self._add_edx4edx()
def_ms = modulestore()
self.assertEqual('xml', def_ms.get_modulestore_type(None))
course = def_ms.courses.get('{0}/edx4edx_lite'.format(
os.path.abspath(settings.DATA_DIR)), None)
self.assertIsNotNone(course)
# Delete a course
self._rm_edx4edx()
course = def_ms.courses.get('{0}/edx4edx_lite'.format(
os.path.abspath(settings.DATA_DIR)), None)
self.assertIsNone(course)
# Load a bad git branch
response = self._add_edx4edx('asdfasdfasdf')
self.assertIn(GitImportError.REMOTE_BRANCH_MISSING,
response.content.decode('utf-8'))
# Load a course from a git branch
self._add_edx4edx(self.TEST_BRANCH)
course = def_ms.courses.get('{0}/edx4edx_lite'.format(
os.path.abspath(settings.DATA_DIR)), None)
self.assertIsNotNone(course)
self.assertEqual(self.TEST_BRANCH_COURSE, course.id)
self._rm_edx4edx()
# Try and delete a non-existent course
response = self.client.post(reverse('sysadmin_courses'),
{'course_id': 'foobar/foo/blah',
'action': 'del_course', })
self.assertIn('Error - cannot get course with ID',
response.content.decode('utf-8'))
@override_settings(GIT_IMPORT_WITH_XMLMODULESTORE=False)
def test_xml_safety_flag(self):
"""Make sure the settings flag to disable xml imports is working"""
self._setstaff_login()
response = self._add_edx4edx()
self.assertIn('GIT_IMPORT_WITH_XMLMODULESTORE', response.content)
def_ms = modulestore()
course = def_ms.courses.get('{0}/edx4edx_lite'.format(
os.path.abspath(settings.DATA_DIR)), None)
self.assertIsNone(course)
def test_git_pull(self):
"""Make sure we can pull"""
self._setstaff_login()
response = self._add_edx4edx()
response = self._add_edx4edx()
self.assertIn(_("The course {0} already exists in the data directory! "
"(reloading anyway)").format('edx4edx_lite'),
response.content.decode('utf-8'))
self._rm_edx4edx()
def test_staff_csv(self):
"""Download and validate staff CSV"""
self._setstaff_login()
self._add_edx4edx()
def_ms = modulestore()
course = def_ms.get_course(SlashSeparatedCourseKey('MITx', 'edx4edx', 'edx4edx'))
CourseStaffRole(course.id).add_users(self.user)
response = self.client.post(reverse('sysadmin_staffing'),
{'action': 'get_staff_csv', })
self.assertIn('attachment', response['Content-Disposition'])
self.assertEqual('text/csv', response['Content-Type'])
columns = ['course_id', 'role', 'username',
'email', 'full_name', ]
self.assertIn(','.join('"' + c + '"' for c in columns),
response.content)
self._rm_edx4edx()
def test_enrollment_page(self):
"""
Adds a course and makes sure that it shows up on the staffing and
enrollment page
"""
self._setstaff_login()
self._add_edx4edx()
response = self.client.get(reverse('sysadmin_staffing'))
self.assertIn('edx4edx', response.content)
self._rm_edx4edx()
@attr('shard_1')
@override_settings(MONGODB_LOG=TEST_MONGODB_LOG)
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'),
"ENABLE_SYSADMIN_DASHBOARD not set")
......
......@@ -6,7 +6,7 @@ import ddt
from django.conf import settings
from django.core.cache import caches
from django.test.client import Client, RequestFactory
from django.test.client import RequestFactory
from django.contrib.auth.models import User
from django.core.management import call_command
from django.core.urlresolvers import reverse
......@@ -27,7 +27,7 @@ from student.tests.factories import CourseEnrollmentFactory, UserFactory, Course
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import check_mongo_calls
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
......@@ -177,12 +177,6 @@ class ThreadActionGroupIdTestCase(
class ViewsTestCaseMixin(object):
"""
This class is used by both ViewsQueryCountTestCase and ViewsTestCase. By
breaking out set_up_course into its own method, ViewsQueryCountTestCase
can build a course in a particular modulestore, while ViewsTestCase can
just run it in setUp for all tests.
"""
def set_up_course(self, module_count=0):
"""
......@@ -234,7 +228,6 @@ class ViewsTestCaseMixin(object):
CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id)
self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id))
self.client = Client()
assert_true(self.client.login(username='student', password=self.password))
def _setup_mock_request(self, mock_request, include_depth=False):
......@@ -379,9 +372,7 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 4, 26),
(ModuleStoreEnum.Type.mongo, 20, 4, 26),
(ModuleStoreEnum.Type.split, 3, 13, 26),
(ModuleStoreEnum.Type.split, 20, 13, 26),
)
@ddt.unpack
@count_queries
......@@ -390,9 +381,7 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet
@ddt.data(
(ModuleStoreEnum.Type.mongo, 3, 3, 20),
(ModuleStoreEnum.Type.mongo, 20, 3, 20),
(ModuleStoreEnum.Type.split, 3, 10, 20),
(ModuleStoreEnum.Type.split, 20, 10, 20),
)
@ddt.unpack
@count_queries
......@@ -404,19 +393,62 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
class ViewsTestCase(
UrlResetMixin,
ModuleStoreTestCase,
SharedModuleStoreTestCase,
MockRequestSetupMixin,
ViewsTestCaseMixin,
MockSignalHandlerMixin
):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(ViewsTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create(
org='MITx', course='999',
discussion_topics={"Some Topic": {"id": "some_topic"}},
display_name='Robot Super Course',
)
@classmethod
def setUpTestData(cls):
super(ViewsTestCase, cls).setUpTestData()
cls.course_id = cls.course.id
# seed the forums permissions and roles
call_command('seed_permissions_roles', unicode(cls.course_id))
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self):
# Patching the ENABLE_DISCUSSION_SERVICE value affects the contents of urls.py,
# so we need to call super.setUp() which reloads urls.py (because
# of the UrlResetMixin)
super(ViewsTestCase, self).setUp(create_user=False)
self.set_up_course()
super(ViewsTestCase, self).setUp()
# Patch the comment client user save method so it does not try
# to create a new cc user when creating a django user
with patch('student.models.cc.User.save'):
uname = 'student'
email = 'student@edx.org'
self.password = 'test' # pylint: disable=attribute-defined-outside-init
# Create the user and make them active so we can log them in.
self.student = User.objects.create_user(uname, email, self.password) # pylint: disable=attribute-defined-outside-init
self.student.is_active = True
self.student.save()
# Add a discussion moderator
self.moderator = UserFactory.create(password=self.password) # pylint: disable=attribute-defined-outside-init
# Enroll the student in the course
CourseEnrollmentFactory(user=self.student,
course_id=self.course_id)
# Enroll the moderator and give them the appropriate roles
CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id)
self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id))
assert_true(self.client.login(username='student', password=self.password))
@contextmanager
def assert_discussion_signals(self, signal, user=None):
......@@ -986,18 +1018,32 @@ class ViewsTestCase(
@patch("lms.lib.comment_client.utils.requests.request", autospec=True)
@disable_signal(views, 'comment_endorsed')
class ViewPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin):
class ViewPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(ViewPermissionsTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(ViewPermissionsTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.password = "test password"
cls.student = UserFactory.create(password=cls.password)
cls.moderator = UserFactory.create(password=cls.password)
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
CourseEnrollmentFactory(user=cls.moderator, course_id=cls.course.id)
cls.moderator.roles.add(Role.objects.get(name="Moderator", course_id=cls.course.id))
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self):
super(ViewPermissionsTestCase, self).setUp()
self.password = "test password"
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create(password=self.password)
self.moderator = UserFactory.create(password=self.password)
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id)
self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id))
def test_pin_thread_as_student(self, mock_request):
self._set_mock_request_data(mock_request, {})
......@@ -1079,14 +1125,21 @@ class ViewPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet
self.assertEqual(response.status_code, 200)
class CreateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
def setUp(self):
super(CreateThreadUnicodeTestCase, self).setUp()
class CreateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(CreateThreadUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(CreateThreadUnicodeTestCase, cls).setUpTestData()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request,):
......@@ -1108,14 +1161,21 @@ class CreateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockReq
@disable_signal(views, 'thread_edited')
class UpdateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
def setUp(self):
super(UpdateThreadUnicodeTestCase, self).setUp()
class UpdateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(UpdateThreadUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpTestData(cls):
super(UpdateThreadUnicodeTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('django_comment_client.utils.get_discussion_categories_ids', return_value=["test_commentable"])
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
......@@ -1138,14 +1198,21 @@ class UpdateThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockReq
@disable_signal(views, 'comment_created')
class CreateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
def setUp(self):
super(CreateCommentUnicodeTestCase, self).setUp()
class CreateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(CreateCommentUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpTestData(cls):
super(CreateCommentUnicodeTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1173,14 +1240,21 @@ class CreateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRe
@disable_signal(views, 'comment_edited')
class UpdateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
def setUp(self):
super(UpdateCommentUnicodeTestCase, self).setUp()
class UpdateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(UpdateCommentUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(UpdateCommentUnicodeTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1199,17 +1273,23 @@ class UpdateCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRe
@disable_signal(views, 'comment_created')
class CreateSubCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
class CreateSubCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
"""
Make sure comments under a response can handle unicode.
"""
def setUp(self):
super(CreateSubCommentUnicodeTestCase, self).setUp()
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(CreateSubCommentUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpTestData(cls):
super(CreateSubCommentUnicodeTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1245,7 +1325,7 @@ class CreateSubCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, Moc
@disable_signal(views, 'comment_created')
@disable_signal(views, 'comment_voted')
@disable_signal(views, 'comment_deleted')
class TeamsPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin):
class TeamsPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
# Most of the test points use the same ddt data.
# args: user, commentable_id, status_code
ddt_permissions_args = [
......@@ -1261,38 +1341,48 @@ class TeamsPermissionsTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSe
('moderator', 'team_commentable_id', 200)
]
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self):
super(TeamsPermissionsTestCase, self).setUp()
self.password = "test password"
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(TeamsPermissionsTestCase, cls).setUpClassAndTestData():
teams_configuration = {
'topics': [{'id': "topic_id", 'name': 'Solar Power', 'description': 'Solar power is hot'}]
}
self.course = CourseFactory.create(teams_configuration=teams_configuration)
seed_permissions_roles(self.course.id)
cls.course = CourseFactory.create(teams_configuration=teams_configuration)
@classmethod
def setUpTestData(cls):
super(TeamsPermissionsTestCase, cls).setUpTestData()
cls.password = "test password"
seed_permissions_roles(cls.course.id)
# Create 3 users-- student in team, student not in team, discussion moderator
self.student_in_team = UserFactory.create(password=self.password)
self.student_not_in_team = UserFactory.create(password=self.password)
self.moderator = UserFactory.create(password=self.password)
CourseEnrollmentFactory(user=self.student_in_team, course_id=self.course.id)
CourseEnrollmentFactory(user=self.student_not_in_team, course_id=self.course.id)
CourseEnrollmentFactory(user=self.moderator, course_id=self.course.id)
self.moderator.roles.add(Role.objects.get(name="Moderator", course_id=self.course.id))
cls.student_in_team = UserFactory.create(password=cls.password)
cls.student_not_in_team = UserFactory.create(password=cls.password)
cls.moderator = UserFactory.create(password=cls.password)
CourseEnrollmentFactory(user=cls.student_in_team, course_id=cls.course.id)
CourseEnrollmentFactory(user=cls.student_not_in_team, course_id=cls.course.id)
CourseEnrollmentFactory(user=cls.moderator, course_id=cls.course.id)
cls.moderator.roles.add(Role.objects.get(name="Moderator", course_id=cls.course.id))
# Create a team.
self.team_commentable_id = "team_discussion_id"
self.team = CourseTeamFactory.create(
cls.team_commentable_id = "team_discussion_id"
cls.team = CourseTeamFactory.create(
name=u'The Only Team',
course_id=self.course.id,
course_id=cls.course.id,
topic_id='topic_id',
discussion_topic_id=self.team_commentable_id
discussion_topic_id=cls.team_commentable_id
)
self.team.add_user(self.student_in_team)
cls.team.add_user(cls.student_in_team)
# Dummy commentable ID not linked to a team
self.course_commentable_id = "course_level_commentable"
cls.course_commentable_id = "course_level_commentable"
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self):
super(TeamsPermissionsTestCase, self).setUp()
def _setup_mock(self, user, mock_request, data):
user = getattr(self, user)
......@@ -1501,18 +1591,26 @@ TEAM_COMMENTABLE_ID = 'test-team-discussion'
@disable_signal(views, 'comment_created')
@ddt.ddt
class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
class ForumEventTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin):
"""
Forum actions are expected to launch analytics events. Test these here.
"""
def setUp(self):
super(ForumEventTestCase, self).setUp()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
self.student.roles.add(Role.objects.get(name="Student", course_id=self.course.id))
CourseAccessRoleFactory(course_id=self.course.id, user=self.student, role='Wizard')
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(ForumEventTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(ForumEventTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
cls.student.roles.add(Role.objects.get(name="Student", course_id=cls.course.id))
CourseAccessRoleFactory(course_id=cls.course.id, user=cls.student, role='Wizard')
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
......@@ -1676,7 +1774,24 @@ class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
self.assertEqual(event['vote_value'], 'up')
class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
class UsersEndpointTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(UsersEndpointTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(UsersEndpointTestCase, cls).setUpTestData()
seed_permissions_roles(cls.course.id)
cls.student = UserFactory.create()
cls.enrollment = CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
cls.other_user = UserFactory.create(username="other")
CourseEnrollmentFactory(user=cls.other_user, course_id=cls.course.id)
def set_post_counts(self, mock_request, threads_count=1, comments_count=1):
"""
......@@ -1687,16 +1802,6 @@ class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
"comments_count": comments_count,
})
def setUp(self):
super(UsersEndpointTestCase, self).setUp()
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
self.enrollment = CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
self.other_user = UserFactory.create(username="other")
CourseEnrollmentFactory(user=self.other_user, course_id=self.course.id)
def make_request(self, method='get', course_id=None, **kwargs):
course_id = course_id or self.course.id
request = getattr(RequestFactory(), method)("dummy_url", kwargs)
......
......@@ -26,7 +26,8 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
TEST_DATA_MONGO_MODULESTORE
SharedModuleStoreTestCase,
TEST_DATA_MONGO_MODULESTORE,
)
from xmodule.modulestore.tests.factories import check_mongo_calls, CourseFactory, ItemFactory
......@@ -1282,13 +1283,20 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
self.assert_all_calls_have_header(mock_request, "X-Edx-Api-Key", "test_api_key")
class InlineDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(InlineDiscussionUnicodeTestCase, self).setUp()
class InlineDiscussionUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
self.course = CourseFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(InlineDiscussionUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(InlineDiscussionUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1305,13 +1313,19 @@ class InlineDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
self.assertEqual(response_data["discussion_data"][0]["body"], text)
class ForumFormDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(ForumFormDiscussionUnicodeTestCase, self).setUp()
class ForumFormDiscussionUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(ForumFormDiscussionUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
self.course = CourseFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpTestData(cls):
super(ForumFormDiscussionUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1377,13 +1391,20 @@ class ForumDiscussionXSSTestCase(UrlResetMixin, ModuleStoreTestCase):
self.assertNotIn(malicious_code, resp.content)
class ForumDiscussionSearchUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(ForumDiscussionSearchUnicodeTestCase, self).setUp()
class ForumDiscussionSearchUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
self.course = CourseFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(ForumDiscussionSearchUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(ForumDiscussionSearchUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1403,13 +1424,20 @@ class ForumDiscussionSearchUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin
self.assertEqual(response_data["discussion_data"][0]["body"], text)
class SingleThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(SingleThreadUnicodeTestCase, self).setUp()
class SingleThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
self.course = CourseFactory.create(discussion_topics={'dummy_discussion_id': {'id': 'dummy_discussion_id'}})
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(SingleThreadUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create(discussion_topics={'dummy_discussion_id': {'id': 'dummy_discussion_id'}})
@classmethod
def setUpTestData(cls):
super(SingleThreadUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1426,13 +1454,20 @@ class SingleThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
self.assertEqual(response_data["content"]["body"], text)
class UserProfileUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(UserProfileUnicodeTestCase, self).setUp()
class UserProfileUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
self.course = CourseFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(UserProfileUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(UserProfileUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......@@ -1448,13 +1483,20 @@ class UserProfileUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
self.assertEqual(response_data["discussion_data"][0]["body"], text)
class FollowedThreadsUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
def setUp(self):
super(FollowedThreadsUnicodeTestCase, self).setUp()
class FollowedThreadsUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin):
self.course = CourseFactory.create()
self.student = UserFactory.create()
CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
@classmethod
def setUpClass(cls):
# pylint: disable=super-method-not-called
with super(FollowedThreadsUnicodeTestCase, cls).setUpClassAndTestData():
cls.course = CourseFactory.create()
@classmethod
def setUpTestData(cls):
super(FollowedThreadsUnicodeTestCase, cls).setUpTestData()
cls.student = UserFactory.create()
CourseEnrollmentFactory(user=cls.student, course_id=cls.course.id)
@patch('lms.lib.comment_client.utils.requests.request', autospec=True)
def _test_unicode_data(self, text, mock_request):
......
......@@ -200,7 +200,7 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(TeamAPITestCase, cls).setUpClass()
with super(TeamAPITestCase, cls).setUpClassAndTestData():
teams_configuration_1 = {
'topics':
[
......@@ -246,43 +246,44 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
teams_configuration=teams_configuration_2
)
def setUp(self):
super(TeamAPITestCase, self).setUp()
self.topics_count = 4
self.users = {
'staff': AdminFactory.create(password=self.test_password),
'course_staff': StaffFactory.create(course_key=self.test_course_1.id, password=self.test_password)
@classmethod
def setUpTestData(cls):
super(TeamAPITestCase, cls).setUpTestData()
cls.topics_count = 4
cls.users = {
'staff': AdminFactory.create(password=cls.test_password),
'course_staff': StaffFactory.create(course_key=cls.test_course_1.id, password=cls.test_password)
}
self.create_and_enroll_student(username='student_enrolled')
self.create_and_enroll_student(username='student_enrolled_not_on_team')
self.create_and_enroll_student(username='student_unenrolled', courses=[])
cls.create_and_enroll_student(username='student_enrolled')
cls.create_and_enroll_student(username='student_enrolled_not_on_team')
cls.create_and_enroll_student(username='student_unenrolled', courses=[])
# Make this student a community TA.
self.create_and_enroll_student(username='community_ta')
seed_permissions_roles(self.test_course_1.id)
community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=self.test_course_1.id)
community_ta_role.users.add(self.users['community_ta'])
cls.create_and_enroll_student(username='community_ta')
seed_permissions_roles(cls.test_course_1.id)
community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=cls.test_course_1.id)
community_ta_role.users.add(cls.users['community_ta'])
# This student is enrolled in both test courses and is a member of a team in each course, but is not on the
# same team as student_enrolled.
self.create_and_enroll_student(
courses=[self.test_course_1, self.test_course_2],
cls.create_and_enroll_student(
courses=[cls.test_course_1, cls.test_course_2],
username='student_enrolled_both_courses_other_team'
)
# Make this student have a public profile
self.create_and_enroll_student(
courses=[self.test_course_2],
cls.create_and_enroll_student(
courses=[cls.test_course_2],
username='student_enrolled_public_profile'
)
profile = self.users['student_enrolled_public_profile'].profile
profile = cls.users['student_enrolled_public_profile'].profile
profile.year_of_birth = 1970
profile.save()
# This student is enrolled in the other course, but not yet a member of a team. This is to allow
# course_2 to use a max_team_size of 1 without breaking other tests on course_1
self.create_and_enroll_student(
courses=[self.test_course_2],
cls.create_and_enroll_student(
courses=[cls.test_course_2],
username='student_enrolled_other_course_not_on_team'
)
......@@ -292,58 +293,58 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
sender=CourseTeam,
dispatch_uid='teams.signals.course_team_post_save_callback'
):
self.solar_team = CourseTeamFactory.create(
cls.solar_team = CourseTeamFactory.create(
name=u'Sólar team',
course_id=self.test_course_1.id,
course_id=cls.test_course_1.id,
topic_id='topic_0'
)
self.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=self.test_course_1.id)
self.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id)
self.another_team = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id)
self.public_profile_team = CourseTeamFactory.create(
cls.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=cls.test_course_1.id)
cls.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=cls.test_course_1.id)
cls.another_team = CourseTeamFactory.create(name='Another Team', course_id=cls.test_course_2.id)
cls.public_profile_team = CourseTeamFactory.create(
name='Public Profile Team',
course_id=self.test_course_2.id,
course_id=cls.test_course_2.id,
topic_id='topic_6'
)
self.search_team = CourseTeamFactory.create(
cls.search_team = CourseTeamFactory.create(
name='Search',
description='queryable text',
country='GS',
language='to',
course_id=self.test_course_2.id,
course_id=cls.test_course_2.id,
topic_id='topic_7'
)
self.chinese_team = CourseTeamFactory.create(
cls.chinese_team = CourseTeamFactory.create(
name=u'著文企臺個',
description=u'共樣地面較,件展冷不護者這與民教過住意,國制銀產物助音是勢一友',
country='CN',
language='zh_HANS',
course_id=self.test_course_2.id,
course_id=cls.test_course_2.id,
topic_id='topic_7'
)
self.test_team_name_id_map = {team.name: team for team in (
self.solar_team,
self.wind_team,
self.nuclear_team,
self.another_team,
self.public_profile_team,
self.search_team,
self.chinese_team,
cls.test_team_name_id_map = {team.name: team for team in (
cls.solar_team,
cls.wind_team,
cls.nuclear_team,
cls.another_team,
cls.public_profile_team,
cls.search_team,
cls.chinese_team,
)}
for user, course in [('staff', self.test_course_1), ('course_staff', self.test_course_1)]:
for user, course in [('staff', cls.test_course_1), ('course_staff', cls.test_course_1)]:
CourseEnrollment.enroll(
self.users[user], course.id, check_access=True
cls.users[user], course.id, check_access=True
)
# Django Rest Framework v3 requires us to pass a request to serializers
# that have URL fields. Since we're invoking this code outside the context
# of a request, we need to simulate that there's a request.
self.solar_team.add_user(self.users['student_enrolled'])
self.nuclear_team.add_user(self.users['student_enrolled_both_courses_other_team'])
self.another_team.add_user(self.users['student_enrolled_both_courses_other_team'])
self.public_profile_team.add_user(self.users['student_enrolled_public_profile'])
cls.solar_team.add_user(cls.users['student_enrolled'])
cls.nuclear_team.add_user(cls.users['student_enrolled_both_courses_other_team'])
cls.another_team.add_user(cls.users['student_enrolled_both_courses_other_team'])
cls.public_profile_team.add_user(cls.users['student_enrolled_public_profile'])
def build_membership_data_raw(self, username, team):
"""Assembles a membership creation payload based on the raw values provided."""
......@@ -353,21 +354,22 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
"""Assembles a membership creation payload based on the username and team model provided."""
return self.build_membership_data_raw(self.users[username].username, team.team_id)
def create_and_enroll_student(self, courses=None, username=None):
@classmethod
def create_and_enroll_student(cls, courses=None, username=None):
""" Creates a new student and enrolls that student in the course.
Adds the new user to the self.users dictionary with the username as the key.
Adds the new user to the cls.users dictionary with the username as the key.
Returns the username once the user has been created.
"""
if username is not None:
user = UserFactory.create(password=self.test_password, username=username)
user = UserFactory.create(password=cls.test_password, username=username)
else:
user = UserFactory.create(password=self.test_password)
courses = courses if courses is not None else [self.test_course_1]
user = UserFactory.create(password=cls.test_password)
courses = courses if courses is not None else [cls.test_course_1]
for course in courses:
CourseEnrollment.enroll(user, course.id, check_access=True)
self.users[user.username] = user
cls.users[user.username] = user
return user.username
......
......@@ -12,7 +12,7 @@ from openedx.core.lib.django_startup import autostartup
import edxmako
import logging
import analytics
from monkey_patch import third_party_auth
from monkey_patch import third_party_auth, django_db_models_options
import xmodule.x_module
......@@ -29,6 +29,7 @@ def run():
Executed during django startup
"""
third_party_auth.patch()
django_db_models_options.patch()
# To override the settings before executing the autostartup() for python-social-auth
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment