Merge pull request #11691 from edx/jeskew/shared_modulestore_test_case_conversion
WIP: Lots of Python unittest speedups
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
"""
|
||||
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)
|
||||
@@ -1,65 +0,0 @@
|
||||
"""
|
||||
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.
|
||||
|
||||
39
common/djangoapps/monkey_patch/django_db_models_options.py
Normal file
39
common/djangoapps/monkey_patch/django_db_models_options.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
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,15 +270,50 @@ 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()
|
||||
clear_existing_modulestores()
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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,12 +132,13 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
|
||||
|
||||
def test_publication_api(self):
|
||||
"""verify that we can correctly discern a published item by ccx key"""
|
||||
for expected in self.blocks:
|
||||
block_key = self.ccx_locator.make_usage_key(
|
||||
expected.location.block_type, expected.location.block_id
|
||||
)
|
||||
self.assertTrue(self.store.has_published_version(expected))
|
||||
self.store.unpublish(block_key, self.user.id)
|
||||
self.assertFalse(self.store.has_published_version(expected))
|
||||
self.store.publish(block_key, self.user.id)
|
||||
self.assertTrue(self.store.has_published_version(expected))
|
||||
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
|
||||
)
|
||||
self.assertTrue(self.store.has_published_version(expected))
|
||||
self.store.unpublish(block_key, self.user.id)
|
||||
self.assertFalse(self.store.has_published_version(expected))
|
||||
self.store.publish(block_key, self.user.id)
|
||||
self.assertTrue(self.store.has_published_version(expected))
|
||||
|
||||
@@ -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,56 +11,68 @@ 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()
|
||||
@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()
|
||||
|
||||
self.test_course = CourseFactory.create()
|
||||
self.course = CourseFactory.create()
|
||||
self.chapter0 = ItemFactory.create(parent=self.course,
|
||||
display_name='Overview')
|
||||
self.chapter9 = ItemFactory.create(parent=self.course,
|
||||
display_name='factory_chapter')
|
||||
self.section0 = ItemFactory.create(parent=self.chapter0,
|
||||
display_name='Welcome')
|
||||
self.section9 = ItemFactory.create(parent=self.chapter9,
|
||||
display_name='factory_section')
|
||||
self.unit0 = ItemFactory.create(parent=self.section0,
|
||||
display_name='New Unit')
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.chapter0 = ItemFactory.create(parent=cls.course,
|
||||
display_name='Overview')
|
||||
cls.chapter9 = ItemFactory.create(parent=cls.course,
|
||||
display_name='factory_chapter')
|
||||
cls.section0 = ItemFactory.create(parent=cls.chapter0,
|
||||
display_name='Welcome')
|
||||
cls.section9 = ItemFactory.create(parent=cls.chapter9,
|
||||
display_name='factory_section')
|
||||
cls.unit0 = ItemFactory.create(parent=cls.section0,
|
||||
display_name='New Unit')
|
||||
|
||||
self.chapterchrome = ItemFactory.create(parent=self.course,
|
||||
display_name='Chrome')
|
||||
self.chromelesssection = ItemFactory.create(parent=self.chapterchrome,
|
||||
display_name='chromeless',
|
||||
chrome='none')
|
||||
self.accordionsection = ItemFactory.create(parent=self.chapterchrome,
|
||||
display_name='accordion',
|
||||
chrome='accordion')
|
||||
self.tabssection = ItemFactory.create(parent=self.chapterchrome,
|
||||
display_name='tabs',
|
||||
chrome='tabs')
|
||||
self.defaultchromesection = ItemFactory.create(
|
||||
parent=self.chapterchrome,
|
||||
cls.chapterchrome = ItemFactory.create(parent=cls.course,
|
||||
display_name='Chrome')
|
||||
cls.chromelesssection = ItemFactory.create(parent=cls.chapterchrome,
|
||||
display_name='chromeless',
|
||||
chrome='none')
|
||||
cls.accordionsection = ItemFactory.create(parent=cls.chapterchrome,
|
||||
display_name='accordion',
|
||||
chrome='accordion')
|
||||
cls.tabssection = ItemFactory.create(parent=cls.chapterchrome,
|
||||
display_name='tabs',
|
||||
chrome='tabs')
|
||||
cls.defaultchromesection = ItemFactory.create(
|
||||
parent=cls.chapterchrome,
|
||||
display_name='defaultchrome',
|
||||
)
|
||||
self.fullchromesection = ItemFactory.create(parent=self.chapterchrome,
|
||||
display_name='fullchrome',
|
||||
chrome='accordion,tabs')
|
||||
self.tabtest = ItemFactory.create(parent=self.chapterchrome,
|
||||
display_name='progress_tab',
|
||||
default_tab='progress')
|
||||
cls.fullchromesection = ItemFactory.create(parent=cls.chapterchrome,
|
||||
display_name='fullchrome',
|
||||
chrome='accordion,tabs')
|
||||
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)):
|
||||
@@ -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,10 +370,7 @@ 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)
|
||||
return self.import_mongo_course(gitloc, branch)
|
||||
|
||||
def import_mongo_course(self, gitloc, branch):
|
||||
"""
|
||||
@@ -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')
|
||||
@@ -115,297 +113,6 @@ class SysadminBaseTestCase(ModuleStoreTestCase):
|
||||
self.addCleanup(shutil.rmtree, path)
|
||||
|
||||
|
||||
@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'),
|
||||
|
||||
@@ -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):
|
||||
|
||||
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(CreateThreadUnicodeTestCase, cls).setUpClassAndTestData():
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super(CreateThreadUnicodeTestCase, 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,):
|
||||
@@ -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):
|
||||
|
||||
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(UpdateThreadUnicodeTestCase, cls).setUpClassAndTestData():
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@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):
|
||||
|
||||
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(CreateCommentUnicodeTestCase, cls).setUpClassAndTestData():
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
@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)
|
||||
]
|
||||
|
||||
@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'}]
|
||||
}
|
||||
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
|
||||
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.
|
||||
cls.team_commentable_id = "team_discussion_id"
|
||||
cls.team = CourseTeamFactory.create(
|
||||
name=u'The Only Team',
|
||||
course_id=cls.course.id,
|
||||
topic_id='topic_id',
|
||||
discussion_topic_id=cls.team_commentable_id
|
||||
)
|
||||
|
||||
cls.team.add_user(cls.student_in_team)
|
||||
|
||||
# Dummy commentable ID not linked to a team
|
||||
cls.course_commentable_id = "course_level_commentable"
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super(TeamsPermissionsTestCase, self).setUp()
|
||||
self.password = "test password"
|
||||
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)
|
||||
|
||||
# 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))
|
||||
|
||||
# Create a team.
|
||||
self.team_commentable_id = "team_discussion_id"
|
||||
self.team = CourseTeamFactory.create(
|
||||
name=u'The Only Team',
|
||||
course_id=self.course.id,
|
||||
topic_id='topic_id',
|
||||
discussion_topic_id=self.team_commentable_id
|
||||
)
|
||||
|
||||
self.team.add_user(self.student_in_team)
|
||||
|
||||
# Dummy commentable ID not linked to a team
|
||||
self.course_commentable_id = "course_level_commentable"
|
||||
|
||||
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,89 +200,90 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TeamAPITestCase, cls).setUpClass()
|
||||
teams_configuration_1 = {
|
||||
'topics':
|
||||
[
|
||||
{
|
||||
'id': 'topic_{}'.format(i),
|
||||
'name': name,
|
||||
'description': 'Description for topic {}.'.format(i)
|
||||
} for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power'])
|
||||
]
|
||||
}
|
||||
cls.test_course_1 = CourseFactory.create(
|
||||
org='TestX',
|
||||
course='TS101',
|
||||
display_name='Test Course',
|
||||
teams_configuration=teams_configuration_1
|
||||
)
|
||||
with super(TeamAPITestCase, cls).setUpClassAndTestData():
|
||||
teams_configuration_1 = {
|
||||
'topics':
|
||||
[
|
||||
{
|
||||
'id': 'topic_{}'.format(i),
|
||||
'name': name,
|
||||
'description': 'Description for topic {}.'.format(i)
|
||||
} for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power'])
|
||||
]
|
||||
}
|
||||
cls.test_course_1 = CourseFactory.create(
|
||||
org='TestX',
|
||||
course='TS101',
|
||||
display_name='Test Course',
|
||||
teams_configuration=teams_configuration_1
|
||||
)
|
||||
|
||||
teams_configuration_2 = {
|
||||
'topics':
|
||||
[
|
||||
{
|
||||
'id': 'topic_5',
|
||||
'name': 'Other Interests',
|
||||
'description': 'Description for topic 5.'
|
||||
},
|
||||
{
|
||||
'id': 'topic_6',
|
||||
'name': 'Public Profiles',
|
||||
'description': 'Description for topic 6.'
|
||||
},
|
||||
{
|
||||
'id': 'Topic_6.5',
|
||||
'name': 'Test Accessibility Topic',
|
||||
'description': 'Description for Topic_6.5'
|
||||
},
|
||||
],
|
||||
'max_team_size': 1
|
||||
}
|
||||
cls.test_course_2 = CourseFactory.create(
|
||||
org='MIT',
|
||||
course='6.002x',
|
||||
display_name='Circuits',
|
||||
teams_configuration=teams_configuration_2
|
||||
)
|
||||
teams_configuration_2 = {
|
||||
'topics':
|
||||
[
|
||||
{
|
||||
'id': 'topic_5',
|
||||
'name': 'Other Interests',
|
||||
'description': 'Description for topic 5.'
|
||||
},
|
||||
{
|
||||
'id': 'topic_6',
|
||||
'name': 'Public Profiles',
|
||||
'description': 'Description for topic 6.'
|
||||
},
|
||||
{
|
||||
'id': 'Topic_6.5',
|
||||
'name': 'Test Accessibility Topic',
|
||||
'description': 'Description for Topic_6.5'
|
||||
},
|
||||
],
|
||||
'max_team_size': 1
|
||||
}
|
||||
cls.test_course_2 = CourseFactory.create(
|
||||
org='MIT',
|
||||
course='6.002x',
|
||||
display_name='Circuits',
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user