Add a TestCase mixin for enabling caches in tests
By default, disable all caching in tests, to preserve test independence. In order to enable caching, inherit from CacheSetupMixin, and specify which cache configuration is needed. [EV-32]
This commit is contained in:
@@ -4,7 +4,7 @@ Tests for the force_publish management command
|
||||
import mock
|
||||
from django.core.management import call_command, CommandError
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from contentstore.management.commands.force_publish import Command
|
||||
from contentstore.management.commands.utils import get_course_versions
|
||||
@@ -62,7 +62,19 @@ class TestForcePublish(SharedModuleStoreTestCase):
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
call_command('force_publish', unicode(course.id))
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
|
||||
class TestForcePublishModifications(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the force_publish management command that modify the courseware
|
||||
during the test.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestForcePublishModifications, self).setUp()
|
||||
self.course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
|
||||
self.test_user_id = ModuleStoreEnum.UserID.test
|
||||
self.command = Command()
|
||||
|
||||
def test_force_publish(self):
|
||||
"""
|
||||
Test 'force_publish' command
|
||||
|
||||
@@ -88,8 +88,9 @@ class ContentStoreTestCase(ModuleStoreTestCase):
|
||||
|
||||
class AuthTestCase(ContentStoreTestCase):
|
||||
"""Check that various permissions-related things work"""
|
||||
|
||||
|
||||
CREATE_USER = False
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(AuthTestCase, self).setUp()
|
||||
|
||||
@@ -46,6 +46,8 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
|
||||
class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
|
||||
"""Test the embargo API calls to determine whether a user has access. """
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(EmbargoCheckAccessApiTests, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
@@ -239,16 +241,13 @@ class EmbargoMessageUrlApiTests(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test the embargo API calls for retrieving the blocking message URLs. """
|
||||
|
||||
URLCONF_MODULES = ['embargo']
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def setUp(self):
|
||||
super(EmbargoMessageUrlApiTests, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
def tearDown(self):
|
||||
super(EmbargoMessageUrlApiTests, self).tearDown()
|
||||
cache.clear()
|
||||
|
||||
@ddt.data(
|
||||
('enrollment', '/embargo/blocked-message/enrollment/embargo/'),
|
||||
('courseware', '/embargo/blocked-message/courseware/embargo/')
|
||||
|
||||
@@ -8,9 +8,14 @@ from embargo.models import (
|
||||
Country, CountryAccessRule, CourseAccessRuleHistory
|
||||
)
|
||||
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
class EmbargoModelsTest(TestCase):
|
||||
|
||||
class EmbargoModelsTest(CacheIsolationTestCase):
|
||||
"""Test each of the 3 models in embargo.models"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def test_course_embargo(self):
|
||||
course_id = CourseLocator('abc', '123', 'doremi')
|
||||
# Test that course is not authorized by default
|
||||
@@ -101,9 +106,11 @@ class EmbargoModelsTest(TestCase):
|
||||
self.assertFalse('1.2.0.0' in cblacklist)
|
||||
|
||||
|
||||
class RestrictedCourseTest(TestCase):
|
||||
class RestrictedCourseTest(CacheIsolationTestCase):
|
||||
"""Test RestrictedCourse model. """
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def test_unicode_values(self):
|
||||
course_id = CourseLocator('abc', '123', 'doremi')
|
||||
restricted_course = RestrictedCourse.objects.create(course_key=course_id)
|
||||
@@ -162,8 +169,9 @@ class CountryTest(TestCase):
|
||||
self.assertEquals(unicode(country), "New Zealand (NZ)")
|
||||
|
||||
|
||||
class CountryAccessRuleTest(TestCase):
|
||||
class CountryAccessRuleTest(CacheIsolationTestCase):
|
||||
"""Test CountryAccessRule model. """
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def test_unicode_values(self):
|
||||
course_id = CourseLocator('abc', '123', 'doremi')
|
||||
|
||||
@@ -10,11 +10,12 @@ import ddt
|
||||
|
||||
from util.testing import UrlResetMixin
|
||||
from embargo import messages
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@ddt.ddt
|
||||
class CourseAccessMessageViewTest(UrlResetMixin, TestCase):
|
||||
class CourseAccessMessageViewTest(CacheIsolationTestCase, UrlResetMixin):
|
||||
"""Tests for the courseware access message view.
|
||||
|
||||
These end-points serve static content.
|
||||
@@ -32,6 +33,8 @@ class CourseAccessMessageViewTest(UrlResetMixin, TestCase):
|
||||
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
URLCONF_MODULES = ['embargo']
|
||||
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
|
||||
@@ -15,22 +15,24 @@ from course_modes.models import CourseMode
|
||||
from enrollment import api
|
||||
from enrollment.errors import EnrollmentApiLoadError, EnrollmentNotFoundError, CourseModeNotFoundError
|
||||
from enrollment.tests import fake_data_api
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(ENROLLMENT_DATA_API="enrollment.tests.fake_data_api")
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentTest(TestCase):
|
||||
class EnrollmentTest(CacheIsolationTestCase):
|
||||
"""
|
||||
Test student enrollment, especially with different course modes.
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
COURSE_ID = "some/great/course"
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(EnrollmentTest, self).setUp()
|
||||
fake_data_api.reset()
|
||||
cache.clear()
|
||||
|
||||
@ddt.data(
|
||||
# Default (no course modes in the database)
|
||||
|
||||
@@ -141,6 +141,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
|
||||
OTHER_USERNAME = "Jane"
|
||||
OTHER_EMAIL = "jane@example.com"
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
""" Create a course and user, then log in. """
|
||||
super(EnrollmentTest, self).setUp()
|
||||
|
||||
@@ -23,11 +23,12 @@ from mock import patch
|
||||
from nose.plugins.attrib import attr
|
||||
from urllib import urlencode
|
||||
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.views import create_account, change_enrollment
|
||||
from student.models import UserProfile, CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
|
||||
|
||||
@@ -76,11 +77,14 @@ def gen_all_identities():
|
||||
@attr('shard_3')
|
||||
@ddt
|
||||
@override_settings(SESSION_ENGINE='django.contrib.sessions.backends.cache')
|
||||
class ShibSPTest(SharedModuleStoreTestCase):
|
||||
class ShibSPTest(CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the Shibboleth SP, which communicates via request.META
|
||||
(Apache environment variables set by mod_shib)
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
request_factory = RequestFactory()
|
||||
|
||||
def setUp(self):
|
||||
@@ -377,8 +381,23 @@ class ShibSPTest(SharedModuleStoreTestCase):
|
||||
self.assertEqual(profile.name, request2.session['ExternalAuthMap'].external_name)
|
||||
self.assertEqual(profile.name, identity.get('displayName').decode('utf-8'))
|
||||
|
||||
|
||||
@ddt
|
||||
@override_settings(SESSION_ENGINE='django.contrib.sessions.backends.cache')
|
||||
class ShibSPTestModifiedCourseware(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the Shibboleth SP which modify the courseware
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
request_factory = RequestFactory()
|
||||
|
||||
def setUp(self):
|
||||
super(ShibSPTestModifiedCourseware, self).setUp()
|
||||
self.test_user_id = ModuleStoreEnum.UserID.test
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
@data(None, "", "shib:https://idp.stanford.edu/")
|
||||
def test_course_specific_login_and_reg(self, domain):
|
||||
"""
|
||||
@@ -457,7 +476,6 @@ class ShibSPTest(SharedModuleStoreTestCase):
|
||||
'&enrollment_action=enroll')
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_enrollment_limit_by_domain(self):
|
||||
"""
|
||||
Tests that the enrollmentDomain setting is properly limiting enrollment to those who have
|
||||
@@ -525,7 +543,6 @@ class ShibSPTest(SharedModuleStoreTestCase):
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))
|
||||
|
||||
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_shib_login_enrollment(self):
|
||||
"""
|
||||
A functionality test that a student with an existing shib login
|
||||
|
||||
@@ -17,6 +17,7 @@ from mock import patch
|
||||
from social.apps.django_app.default.models import UserSocialAuth
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory, RegistrationFactory, UserProfileFactory
|
||||
from student.views import login_oauth_token
|
||||
from third_party_auth.tests.utils import (
|
||||
@@ -28,11 +29,13 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
|
||||
class LoginTest(TestCase):
|
||||
class LoginTest(CacheIsolationTestCase):
|
||||
'''
|
||||
Test student.views.login_user() view
|
||||
'''
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(LoginTest, self).setUp()
|
||||
# Create one user and save it to the database
|
||||
|
||||
@@ -158,7 +158,7 @@ class RegisterFormTest(ThirdPartyAuthTestMixin, UrlResetMixin, SharedModuleStore
|
||||
"""Test rendering of the registration form. """
|
||||
|
||||
URLCONF_MODULES = ['lms.urls']
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RegisterFormTest, cls).setUpClass()
|
||||
|
||||
@@ -19,6 +19,7 @@ from django.utils.http import urlsafe_base64_encode, base36_to_int, int_to_base3
|
||||
from mock import Mock, patch
|
||||
import ddt
|
||||
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.views import password_reset, password_reset_confirm_wrapper, SETTING_CHANGE_INITIATED
|
||||
from student.tests.factories import UserFactory
|
||||
from student.tests.test_email import mock_render_to_string
|
||||
@@ -28,11 +29,13 @@ from .test_microsite import fake_microsite_get_value
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ResetPasswordTests(EventTestMixin, TestCase):
|
||||
class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase):
|
||||
""" Tests that clicking reset password sends email, and doesn't activate the user
|
||||
"""
|
||||
request_factory = RequestFactory()
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(ResetPasswordTests, self).setUp('student.views.tracker')
|
||||
self.user = UserFactory.create()
|
||||
|
||||
@@ -7,14 +7,17 @@ from django.test import TestCase
|
||||
from student.models import UserProfile
|
||||
from student.tests.factories import UserFactory
|
||||
from django.core.cache import cache
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class UserProfilePropertiesTest(TestCase):
|
||||
class UserProfilePropertiesTest(CacheIsolationTestCase):
|
||||
"""Unit tests for age, gender_display, and level_of_education_display properties ."""
|
||||
|
||||
password = "test"
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(UserProfilePropertiesTest, self).setUp()
|
||||
self.user = UserFactory.create(password=self.password)
|
||||
|
||||
@@ -13,7 +13,6 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from request_cache.middleware import RequestCache
|
||||
|
||||
from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error
|
||||
from openedx.core.lib.tempdir import mkdtemp_clean
|
||||
@@ -25,6 +24,7 @@ from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOS
|
||||
from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK
|
||||
|
||||
from openedx.core.djangoapps.bookmarks.signals import trigger_update_xblocks_cache_task
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase
|
||||
|
||||
|
||||
class StoreConstructors(object):
|
||||
@@ -170,23 +170,64 @@ TEST_DATA_SPLIT_MODULESTORE = mixed_store_config(
|
||||
)
|
||||
|
||||
|
||||
def clear_all_caches():
|
||||
"""Clear all caches so that cache info doesn't leak across test cases."""
|
||||
# This will no longer be necessary when Django adds (in Django 1.10?):
|
||||
# https://code.djangoproject.com/ticket/11505
|
||||
for cache in django.core.cache.caches.all():
|
||||
cache.clear()
|
||||
class ModuleStoreIsolationMixin(CacheIsolationMixin):
|
||||
"""
|
||||
A mixin to be used by TestCases that want to isolate their use of the
|
||||
Modulestore.
|
||||
|
||||
RequestCache().clear_request_cache()
|
||||
How to use::
|
||||
|
||||
class MyTestCase(ModuleStoreMixin, TestCase):
|
||||
|
||||
MODULESTORE = <settings for the modulestore to test>
|
||||
|
||||
def my_test(self):
|
||||
self.start_modulestore_isolation()
|
||||
self.addCleanup(self.end_modulestore_isolation)
|
||||
|
||||
modulestore.create_course(...)
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
MODULESTORE = mixed_store_config(mkdtemp_clean(), {})
|
||||
ENABLED_CACHES = ['mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@classmethod
|
||||
def start_modulestore_isolation(cls):
|
||||
"""
|
||||
Isolate uses of the modulestore after this call. Once
|
||||
:py:meth:`end_modulestore_isolation` is called, this modulestore will
|
||||
be flushed (all content will be deleted).
|
||||
"""
|
||||
cls.start_cache_isolation()
|
||||
cls.__settings_override = override_settings(
|
||||
MODULESTORE=cls.MODULESTORE,
|
||||
)
|
||||
cls.__settings_override.__enter__()
|
||||
XMODULE_FACTORY_LOCK.enable()
|
||||
clear_existing_modulestores()
|
||||
cls.store = modulestore()
|
||||
|
||||
@classmethod
|
||||
def end_modulestore_isolation(cls):
|
||||
"""
|
||||
Delete all content in the Modulestore, and reset the Modulestore
|
||||
settings from before :py:meth:`start_modulestore_isolation` was
|
||||
called.
|
||||
"""
|
||||
drop_mongo_collections() # pylint: disable=no-value-for-parameter
|
||||
XMODULE_FACTORY_LOCK.disable()
|
||||
cls.__settings_override.__exit__(None, None, None)
|
||||
cls.end_cache_isolation()
|
||||
|
||||
|
||||
class SharedModuleStoreTestCase(TestCase):
|
||||
class SharedModuleStoreTestCase(ModuleStoreIsolationMixin, CacheIsolationTestCase):
|
||||
"""
|
||||
Subclass for any test case that uses a ModuleStore that can be shared
|
||||
between individual tests. This class ensures that the ModuleStore is cleaned
|
||||
before/after the entire test case has run. Use this class if your tests
|
||||
set up one or a small number of courses that individual tests do not modify
|
||||
(or modify extermely rarely -- see @modifies_courseware).
|
||||
set up one or a small number of courses that individual tests do not modify.
|
||||
If your tests modify contents in the ModuleStore, you should use
|
||||
ModuleStoreTestCase instead.
|
||||
|
||||
@@ -218,41 +259,26 @@ class SharedModuleStoreTestCase(TestCase):
|
||||
In Django 1.8, we will be able to use setUpTestData() to do class level init
|
||||
for Django ORM models that will get cleaned up properly.
|
||||
"""
|
||||
MODULESTORE = mixed_store_config(mkdtemp_clean(), {})
|
||||
# Tell Django to clean out all databases, not just default
|
||||
multi_db = True
|
||||
|
||||
@classmethod
|
||||
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()
|
||||
cls.start_modulestore_isolation()
|
||||
# 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().
|
||||
@@ -261,19 +287,15 @@ class SharedModuleStoreTestCase(TestCase):
|
||||
@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().
|
||||
Start modulestore isolation, and then load modulestore specific
|
||||
test data.
|
||||
"""
|
||||
super(SharedModuleStoreTestCase, cls).setUpClass()
|
||||
cls._setUpModuleStore()
|
||||
cls.start_modulestore_isolation()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
drop_mongo_collections() # pylint: disable=no-value-for-parameter
|
||||
clear_all_caches()
|
||||
XMODULE_FACTORY_LOCK.disable()
|
||||
cls._settings_override.__exit__(None, None, None)
|
||||
|
||||
cls.end_modulestore_isolation()
|
||||
super(SharedModuleStoreTestCase, cls).tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
@@ -282,69 +304,8 @@ class SharedModuleStoreTestCase(TestCase):
|
||||
OverrideFieldData.provider_classes = None
|
||||
super(SharedModuleStoreTestCase, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
"""Reset caches."""
|
||||
clear_all_caches()
|
||||
super(SharedModuleStoreTestCase, self).tearDown()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Manually run tearDownClass/setUpClass again.
|
||||
|
||||
This is so that if you have a mostly read-only course that you're just
|
||||
modifying in one test, you can write `self.reset()` at the
|
||||
end of that test and reset the state of the world for other tests in
|
||||
the class.
|
||||
"""
|
||||
self.tearDownClass()
|
||||
self.setUpClass()
|
||||
|
||||
@staticmethod
|
||||
def modifies_courseware(f):
|
||||
"""
|
||||
Decorator to place around tests that modify course content.
|
||||
|
||||
For performance reasons, SharedModuleStoreTestCase intentionally does
|
||||
not reset the modulestore between individual tests. However, sometimes
|
||||
you might have a test case where the vast majority of tests treat a
|
||||
course as read-only, but one or two want to modify it. In that case, you
|
||||
can do this:
|
||||
|
||||
class MyTestCase(SharedModuleStoreTestCase):
|
||||
# ...
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_that_edits_modulestore(self):
|
||||
do_something()
|
||||
|
||||
This is equivalent to calling `self.reset()` at the end of
|
||||
your test.
|
||||
|
||||
If you find yourself using this functionality a lot, it might indicate
|
||||
that you should be using ModuleStoreTestCase instead, or that you should
|
||||
break up your tests into different TestCases.
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""Call the object method, and reset the test case afterwards."""
|
||||
try:
|
||||
# Attempt execution of the test.
|
||||
return_val = f(*args, **kwargs)
|
||||
except:
|
||||
# If the test raises an exception, re-raise it.
|
||||
raise
|
||||
else:
|
||||
# Otherwise, return the test's return value.
|
||||
return return_val
|
||||
finally:
|
||||
# In either case, call SharedModuleStoreTestCase.reset() "on the way out."
|
||||
# For more, see here: https://docs.python.org/2/tutorial/errors.html#defining-clean-up-actions.
|
||||
obj = args[0]
|
||||
obj.reset()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class ModuleStoreTestCase(TestCase):
|
||||
class ModuleStoreTestCase(ModuleStoreIsolationMixin, TestCase):
|
||||
"""
|
||||
Subclass for any test case that uses a ModuleStore.
|
||||
Ensures that the ModuleStore is cleaned before/after each test.
|
||||
@@ -382,8 +343,6 @@ class ModuleStoreTestCase(TestCase):
|
||||
your `setUp()` method.
|
||||
"""
|
||||
|
||||
MODULESTORE = mixed_store_config(mkdtemp_clean(), {})
|
||||
|
||||
CREATE_USER = True
|
||||
|
||||
# Tell Django to clean out all databases, not just default
|
||||
@@ -394,20 +353,9 @@ class ModuleStoreTestCase(TestCase):
|
||||
Creates a test User if `self.CREATE_USER` is True.
|
||||
Sets the password as self.user_password.
|
||||
"""
|
||||
settings_override = override_settings(MODULESTORE=self.MODULESTORE)
|
||||
settings_override.__enter__()
|
||||
self.addCleanup(settings_override.__exit__, None, None, None)
|
||||
self.start_modulestore_isolation()
|
||||
|
||||
# Clear out any existing modulestores,
|
||||
# which will cause them to be re-created
|
||||
clear_existing_modulestores()
|
||||
|
||||
self.addCleanup(drop_mongo_collections)
|
||||
self.addCleanup(clear_all_caches)
|
||||
|
||||
# Enable XModuleFactories for the space of this test (and its setUp).
|
||||
self.addCleanup(XMODULE_FACTORY_LOCK.disable)
|
||||
XMODULE_FACTORY_LOCK.enable()
|
||||
self.addCleanup(self.end_modulestore_isolation)
|
||||
|
||||
# When testing CCX, we should make sure that
|
||||
# OverrideFieldData.provider_classes is always reset to `None` so
|
||||
@@ -436,7 +384,6 @@ class ModuleStoreTestCase(TestCase):
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
|
||||
def create_non_staff_user(self):
|
||||
"""
|
||||
Creates a non-staff test user.
|
||||
|
||||
@@ -44,6 +44,8 @@ class TestEmailErrors(ModuleStoreTestCase):
|
||||
Test that errors from sending email are handled properly.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(TestEmailErrors, self).setUp()
|
||||
course_title = u"ẗëṡẗ title イ乇丂イ ᄊ乇丂丂ムg乇 キo尺 ムレレ тэѕт мэѕѕаБэ"
|
||||
|
||||
@@ -52,6 +52,8 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
|
||||
# TEST_DATA must be overridden by subclasses
|
||||
TEST_DATA = None
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create a test client, course, and user.
|
||||
|
||||
@@ -68,6 +68,8 @@ from lms.djangoapps.ccx.tests.utils import (
|
||||
from lms.djangoapps.ccx.utils import is_email
|
||||
from lms.djangoapps.ccx.views import get_date
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
def intercept_renderer(path, context):
|
||||
"""
|
||||
@@ -184,10 +186,6 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
|
||||
Tests for Custom Courses views.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCoachDashboard, cls).setUpClass()
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up tests
|
||||
@@ -206,31 +204,6 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
|
||||
allow_access(self.course, instructor, 'instructor')
|
||||
self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor))
|
||||
|
||||
def assert_elements_in_schedule(self, url, n_chapters=2, n_sequentials=4, n_verticals=8):
|
||||
"""
|
||||
Helper function to count visible elements in the schedule
|
||||
"""
|
||||
response = self.client.get(url)
|
||||
# the schedule contains chapters
|
||||
chapters = json.loads(response.mako_context['schedule']) # pylint: disable=no-member
|
||||
sequentials = flatten([chapter.get('children', []) for chapter in chapters])
|
||||
verticals = flatten([sequential.get('children', []) for sequential in sequentials])
|
||||
# check that the numbers of nodes at different level are the expected ones
|
||||
self.assertEqual(n_chapters, len(chapters))
|
||||
self.assertEqual(n_sequentials, len(sequentials))
|
||||
self.assertEqual(n_verticals, len(verticals))
|
||||
# extract the locations of all the nodes
|
||||
all_elements = chapters + sequentials + verticals
|
||||
return [elem['location'] for elem in all_elements if 'location' in elem]
|
||||
|
||||
def hide_node(self, node):
|
||||
"""
|
||||
Helper function to set the node `visible_to_staff_only` property
|
||||
to True and save the change
|
||||
"""
|
||||
node.visible_to_staff_only = True
|
||||
self.mstore.update_item(node, self.coach.id)
|
||||
|
||||
def test_not_a_coach(self):
|
||||
"""
|
||||
User is not a coach, should get Forbidden response.
|
||||
@@ -352,43 +325,6 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
|
||||
self.assertEqual(get_date(ccx, unit, 'start', parent_node=subsection), self.mooc_start)
|
||||
self.assertEqual(get_date(ccx, unit, 'due', parent_node=subsection), self.mooc_due)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
@patch('ccx.views.render_to_response', intercept_renderer)
|
||||
@patch('ccx.views.TODAY')
|
||||
def test_get_ccx_schedule(self, today):
|
||||
"""
|
||||
Gets CCX schedule and checks number of blocks in it.
|
||||
Hides nodes at a different depth and checks that these nodes
|
||||
are not in the schedule.
|
||||
"""
|
||||
today.return_value = datetime.datetime(2014, 11, 25, tzinfo=pytz.UTC)
|
||||
self.make_coach()
|
||||
ccx = self.make_ccx()
|
||||
url = reverse(
|
||||
'ccx_coach_dashboard',
|
||||
kwargs={
|
||||
'course_id': CCXLocator.from_course_locator(
|
||||
self.course.id, ccx.id)
|
||||
}
|
||||
)
|
||||
# all the elements are visible
|
||||
self.assert_elements_in_schedule(url)
|
||||
# hide a vertical
|
||||
vertical = self.verticals[0]
|
||||
self.hide_node(vertical)
|
||||
locations = self.assert_elements_in_schedule(url, n_verticals=7)
|
||||
self.assertNotIn(unicode(vertical.location), locations)
|
||||
# hide a sequential
|
||||
sequential = self.sequentials[0]
|
||||
self.hide_node(sequential)
|
||||
locations = self.assert_elements_in_schedule(url, n_sequentials=3, n_verticals=6)
|
||||
self.assertNotIn(unicode(sequential.location), locations)
|
||||
# hide a chapter
|
||||
chapter = self.chapters[0]
|
||||
self.hide_node(chapter)
|
||||
locations = self.assert_elements_in_schedule(url, n_chapters=1, n_sequentials=2, n_verticals=4)
|
||||
self.assertNotIn(unicode(chapter.location), locations)
|
||||
|
||||
@patch('ccx.views.render_to_response', intercept_renderer)
|
||||
@patch('ccx.views.TODAY')
|
||||
def test_edit_schedule(self, today):
|
||||
@@ -842,6 +778,134 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
|
||||
)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class TestCoachDashboardSchedule(CcxTestCase, LoginEnrollmentTestCase, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests of the CCX Coach Dashboard which need to modify the course content.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_inheritance_cache', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCoachDashboardSchedule, self).setUp()
|
||||
self.course = course = CourseFactory.create()
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
self.chapters = [
|
||||
ItemFactory.create(start=start, parent=course) for _ in xrange(2)
|
||||
]
|
||||
self.sequentials = flatten([
|
||||
[
|
||||
ItemFactory.create(parent=chapter) for _ in xrange(2)
|
||||
] for chapter in self.chapters
|
||||
])
|
||||
self.verticals = flatten([
|
||||
[
|
||||
ItemFactory.create(
|
||||
start=start, due=due, parent=sequential, graded=True, format='Homework', category=u'vertical'
|
||||
) for _ in xrange(2)
|
||||
] for sequential in self.sequentials
|
||||
])
|
||||
|
||||
# Trying to wrap the whole thing in a bulk operation fails because it
|
||||
# doesn't find the parents. But we can at least wrap this part...
|
||||
with self.store.bulk_operations(course.id, emit_signals=False):
|
||||
blocks = flatten([ # pylint: disable=unused-variable
|
||||
[
|
||||
ItemFactory.create(parent=vertical) for _ in xrange(2)
|
||||
] for vertical in self.verticals
|
||||
])
|
||||
|
||||
# Create instructor account
|
||||
self.coach = UserFactory.create()
|
||||
# create an instance of modulestore
|
||||
self.mstore = modulestore()
|
||||
|
||||
# Login with the instructor account
|
||||
self.client.login(username=self.coach.username, password="test")
|
||||
|
||||
# adding staff to master course.
|
||||
staff = UserFactory()
|
||||
allow_access(self.course, staff, 'staff')
|
||||
self.assertTrue(CourseStaffRole(self.course.id).has_user(staff))
|
||||
|
||||
# adding instructor to master course.
|
||||
instructor = UserFactory()
|
||||
allow_access(self.course, instructor, 'instructor')
|
||||
self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor))
|
||||
|
||||
self.assertTrue(modulestore().has_course(self.course.id))
|
||||
|
||||
def assert_elements_in_schedule(self, url, n_chapters=2, n_sequentials=4, n_verticals=8):
|
||||
"""
|
||||
Helper function to count visible elements in the schedule
|
||||
"""
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# the schedule contains chapters
|
||||
chapters = json.loads(response.mako_context['schedule']) # pylint: disable=no-member
|
||||
sequentials = flatten([chapter.get('children', []) for chapter in chapters])
|
||||
verticals = flatten([sequential.get('children', []) for sequential in sequentials])
|
||||
# check that the numbers of nodes at different level are the expected ones
|
||||
self.assertEqual(n_chapters, len(chapters))
|
||||
self.assertEqual(n_sequentials, len(sequentials))
|
||||
self.assertEqual(n_verticals, len(verticals))
|
||||
# extract the locations of all the nodes
|
||||
all_elements = chapters + sequentials + verticals
|
||||
return [elem['location'] for elem in all_elements if 'location' in elem]
|
||||
|
||||
def hide_node(self, node):
|
||||
"""
|
||||
Helper function to set the node `visible_to_staff_only` property
|
||||
to True and save the change
|
||||
"""
|
||||
node.visible_to_staff_only = True
|
||||
self.mstore.update_item(node, self.coach.id)
|
||||
|
||||
@patch('ccx.views.render_to_response', intercept_renderer)
|
||||
@patch('ccx.views.TODAY')
|
||||
def test_get_ccx_schedule(self, today):
|
||||
"""
|
||||
Gets CCX schedule and checks number of blocks in it.
|
||||
Hides nodes at a different depth and checks that these nodes
|
||||
are not in the schedule.
|
||||
"""
|
||||
today.return_value = datetime.datetime(2014, 11, 25, tzinfo=pytz.UTC)
|
||||
self.make_coach()
|
||||
ccx = self.make_ccx()
|
||||
url = reverse(
|
||||
'ccx_coach_dashboard',
|
||||
kwargs={
|
||||
'course_id': CCXLocator.from_course_locator(
|
||||
self.course.id, ccx.id)
|
||||
}
|
||||
)
|
||||
# all the elements are visible
|
||||
self.assert_elements_in_schedule(url)
|
||||
# hide a vertical
|
||||
vertical = self.verticals[0]
|
||||
self.hide_node(vertical)
|
||||
locations = self.assert_elements_in_schedule(url, n_verticals=7)
|
||||
self.assertNotIn(unicode(vertical.location), locations)
|
||||
# hide a sequential
|
||||
sequential = self.sequentials[0]
|
||||
self.hide_node(sequential)
|
||||
locations = self.assert_elements_in_schedule(url, n_sequentials=3, n_verticals=6)
|
||||
self.assertNotIn(unicode(sequential.location), locations)
|
||||
# hide a chapter
|
||||
chapter = self.chapters[0]
|
||||
self.hide_node(chapter)
|
||||
locations = self.assert_elements_in_schedule(url, n_chapters=1, n_sequentials=2, n_verticals=4)
|
||||
self.assertNotIn(unicode(chapter.location), locations)
|
||||
|
||||
|
||||
GET_CHILDREN = XModuleMixin.get_children
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from nose.plugins.attrib import attr
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
from certificates.api import get_certificate_url
|
||||
from certificates.models import (
|
||||
@@ -38,7 +39,7 @@ FEATURES_WITH_CUSTOM_CERTS_ENABLED.update(FEATURES_WITH_CERTS_ENABLED)
|
||||
|
||||
@attr('shard_1')
|
||||
@ddt.ddt
|
||||
class UpdateExampleCertificateViewTest(TestCase):
|
||||
class UpdateExampleCertificateViewTest(CacheIsolationTestCase):
|
||||
"""Tests for the XQueue callback that updates example certificates. """
|
||||
|
||||
COURSE_KEY = CourseLocator(org='test', course='test', run='test')
|
||||
@@ -48,6 +49,8 @@ class UpdateExampleCertificateViewTest(TestCase):
|
||||
DOWNLOAD_URL = 'http://www.example.com'
|
||||
ERROR_REASON = 'Kaboom!'
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(UpdateExampleCertificateViewTest, self).setUp()
|
||||
self.cert_set = ExampleCertificateSet.objects.create(course_key=self.COURSE_KEY)
|
||||
|
||||
@@ -144,13 +144,24 @@ class TestGetCourseList(CourseListTestMixin, SharedModuleStoreTestCase):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
self._make_api_call(anonuser, self.staff_user)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
|
||||
class TestGetCourseListMultipleCourses(CourseListTestMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Test the behavior of the `list_courses` api function (with tests that
|
||||
modify the courseware).
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestGetCourseListMultipleCourses, self).setUp()
|
||||
self.course = self.create_course()
|
||||
self.staff_user = self.create_user("staff", is_staff=True)
|
||||
self.honor_user = self.create_user("honor", is_staff=False)
|
||||
|
||||
def test_multiple_courses(self):
|
||||
self.create_course(course='second')
|
||||
courses = self._make_api_call(self.honor_user, self.honor_user)
|
||||
self.assertEqual(len(courses), 2)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_filter_by_org(self):
|
||||
"""Verify that courses are filtered by the provided org key."""
|
||||
# Create a second course to be filtered out of queries.
|
||||
@@ -173,7 +184,6 @@ class TestGetCourseList(CourseListTestMixin, SharedModuleStoreTestCase):
|
||||
all(course.org == self.course.org for course in filtered_courses)
|
||||
)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_filter(self):
|
||||
# Create a second course to be filtered out of queries.
|
||||
alternate_course = self.create_course(course='mobile', mobile_available=True)
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.test import RequestFactory
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, ModuleStoreTestCase
|
||||
from .mixins import CourseApiFactoryMixin, TEST_PASSWORD
|
||||
from ..views import CourseDetailView
|
||||
|
||||
@@ -91,7 +91,24 @@ class CourseListViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase):
|
||||
response_to_missing_username = self.verify_response(expected_status_code=200)
|
||||
self.assertIsNotNone(response_to_missing_username.data) # pylint: disable=no-member
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_not_logged_in(self):
|
||||
self.client.logout()
|
||||
self.verify_response()
|
||||
|
||||
|
||||
class CourseListViewTestCaseMultipleCourses(CourseApiTestViewMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Test responses returned from CourseListView (with tests that modify the
|
||||
courseware).
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CourseListViewTestCaseMultipleCourses, self).setUp()
|
||||
self.course = self.create_course()
|
||||
self.url = reverse('course-list')
|
||||
self.staff_user = self.create_user(username='staff', is_staff=True)
|
||||
self.honor_user = self.create_user(username='honor', is_staff=False)
|
||||
|
||||
def test_filter_by_org(self):
|
||||
"""Verify that CourseOverviews are filtered by the provided org key."""
|
||||
self.setup_user(self.staff_user)
|
||||
@@ -116,7 +133,6 @@ class CourseListViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase):
|
||||
all(course['org'] == self.course.org for course in filtered_response.data['results']) # pylint: disable=no-member
|
||||
)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_filter(self):
|
||||
self.setup_user(self.staff_user)
|
||||
|
||||
@@ -139,10 +155,6 @@ class CourseListViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase):
|
||||
"testing course_api.views.CourseListView with filter_={}".format(filter_),
|
||||
)
|
||||
|
||||
def test_not_logged_in(self):
|
||||
self.client.logout()
|
||||
self.verify_response()
|
||||
|
||||
|
||||
class CourseDetailViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
@@ -15,6 +15,8 @@ class TestGenerateCourseBlocks(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests generate course blocks management command.
|
||||
"""
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create courses in modulestore.
|
||||
|
||||
@@ -15,6 +15,7 @@ class CourseBlocksSignalTest(EnableTransformerRegistryMixin, ModuleStoreTestCase
|
||||
"""
|
||||
Tests for the Course Blocks signal
|
||||
"""
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(CourseBlocksSignalTest, self).setUp()
|
||||
|
||||
@@ -36,6 +36,8 @@ class CourseStructureTestCase(TransformerRegistryTestMixin, ModuleStoreTestCase)
|
||||
"""
|
||||
Helper for test cases that need to build course structures.
|
||||
"""
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create users.
|
||||
|
||||
@@ -36,6 +36,7 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the Course Info page
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CourseInfoTestCase, cls).setUpClass()
|
||||
@@ -263,6 +264,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
|
||||
"""
|
||||
Tests for the info page of self-paced courses.
|
||||
"""
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -151,6 +151,8 @@ class TestMaxScoresCache(SharedModuleStoreTestCase):
|
||||
Tests for the MaxScoresCache
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMaxScoresCache, cls).setUpClass()
|
||||
|
||||
@@ -127,6 +127,8 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
|
||||
COURSE_SLUG = "100"
|
||||
COURSE_NAME = "test_course"
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(TestSubmittingProblems, self).setUp()
|
||||
|
||||
|
||||
@@ -970,6 +970,8 @@ class ProgressPageTests(ModuleStoreTestCase):
|
||||
Tests that verify that the progress page works correctly.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_modulestore_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(ProgressPageTests, self).setUp()
|
||||
self.request_factory = RequestFactory()
|
||||
|
||||
@@ -51,6 +51,8 @@ class TestGitAddCourse(SharedModuleStoreTestCase):
|
||||
TEST_BRANCH_COURSE = SlashSeparatedCourseKey('MITx', 'edx4edx_branch', 'edx4edx')
|
||||
GIT_REPO_DIR = settings.GIT_REPO_DIR
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def assertCommandFailureRegexp(self, regex, *args):
|
||||
"""
|
||||
Convenience function for testing command failures
|
||||
|
||||
@@ -10,7 +10,6 @@ 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
|
||||
from request_cache.middleware import RequestCache
|
||||
from mock import patch, ANY, Mock
|
||||
from nose.tools import assert_true, assert_equal
|
||||
from nose.plugins.attrib import attr
|
||||
@@ -24,8 +23,9 @@ from django_comment_client.tests.utils import CohortedTestCase
|
||||
from django_comment_client.tests.unicode import UnicodeTestMixin
|
||||
from django_comment_common.models import Role
|
||||
from django_comment_common.utils import seed_permissions_roles, ThreadContext
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory, CourseAccessRoleFactory
|
||||
|
||||
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory, CourseAccessRoleFactory
|
||||
from util.testing import UrlResetMixin
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
|
||||
@@ -351,17 +351,12 @@ class ViewsTestCaseMixin(object):
|
||||
class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSetupMixin, ViewsTestCaseMixin):
|
||||
|
||||
CREATE_USER = False
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def setUp(self):
|
||||
super(ViewsQueryCountTestCase, self).setUp()
|
||||
|
||||
def clear_caches(self):
|
||||
"""Clears caches so that query count numbers are accurate."""
|
||||
for cache in settings.CACHES:
|
||||
caches[cache].clear()
|
||||
RequestCache.clear_request_cache()
|
||||
|
||||
def count_queries(func): # pylint: disable=no-self-argument
|
||||
"""
|
||||
Decorates test methods to count mongo and SQL calls for a
|
||||
|
||||
@@ -51,7 +51,7 @@ from student.models import (
|
||||
)
|
||||
from student.tests.factories import UserFactory, CourseModeFactory, AdminFactory
|
||||
from student.roles import CourseBetaTesterRole, CourseSalesAdminRole, CourseFinanceAdminRole, CourseInstructorRole
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.fields import Date
|
||||
|
||||
@@ -3968,27 +3968,6 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
})
|
||||
self.assertEqual(response.status_code, 400, response.content)
|
||||
|
||||
@SharedModuleStoreTestCase.modifies_courseware
|
||||
def test_reset_extension_to_deleted_date(self):
|
||||
"""
|
||||
Test that we can delete a due date extension after deleting the normal
|
||||
due date, without causing an error.
|
||||
"""
|
||||
self.test_change_due_date()
|
||||
self.week1.due = None
|
||||
self.week1 = self.store.update_item(self.week1, self.user1.id)
|
||||
# Now, week1's normal due date is deleted but the extension still exists.
|
||||
url = reverse('reset_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.get(url, {
|
||||
'student': self.user1.username,
|
||||
'url': self.week1.location.to_deprecated_string(),
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(
|
||||
None,
|
||||
get_extended_due(self.course, self.week1, self.user1)
|
||||
)
|
||||
|
||||
def test_show_unit_extensions(self):
|
||||
self.test_change_due_date()
|
||||
url = reverse('show_unit_extensions',
|
||||
@@ -4017,6 +3996,114 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.user1.profile.name, self.user1.username)})
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
def setUp(self):
|
||||
"""
|
||||
Fixtures.
|
||||
"""
|
||||
super(TestDueDateExtensionsDeletedDate, self).setUp()
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
self.due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=utc)
|
||||
|
||||
with self.store.bulk_operations(self.course.id, emit_signals=False):
|
||||
self.week1 = ItemFactory.create(due=self.due)
|
||||
self.week2 = ItemFactory.create(due=self.due)
|
||||
self.week3 = ItemFactory.create() # No due date
|
||||
self.course.children = [
|
||||
self.week1.location.to_deprecated_string(),
|
||||
self.week2.location.to_deprecated_string(),
|
||||
self.week3.location.to_deprecated_string()
|
||||
]
|
||||
self.homework = ItemFactory.create(
|
||||
parent_location=self.week1.location,
|
||||
due=self.due
|
||||
)
|
||||
self.week1.children = [self.homework.location.to_deprecated_string()]
|
||||
|
||||
user1 = UserFactory.create()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user1.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.week1.location).save()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user1.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.week2.location).save()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user1.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.week3.location).save()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user1.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.homework.location).save()
|
||||
|
||||
user2 = UserFactory.create()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user2.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.week1.location).save()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user2.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.homework.location).save()
|
||||
|
||||
user3 = UserFactory.create()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user3.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.week1.location).save()
|
||||
StudentModule(
|
||||
state='{}',
|
||||
student_id=user3.id,
|
||||
course_id=self.course.id,
|
||||
module_state_key=self.homework.location).save()
|
||||
|
||||
self.user1 = user1
|
||||
self.user2 = user2
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
def test_reset_extension_to_deleted_date(self):
|
||||
"""
|
||||
Test that we can delete a due date extension after deleting the normal
|
||||
due date, without causing an error.
|
||||
"""
|
||||
|
||||
url = reverse('change_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.get(url, {
|
||||
'student': self.user1.username,
|
||||
'url': self.week1.location.to_deprecated_string(),
|
||||
'due_datetime': '12/30/2013 00:00'
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(datetime.datetime(2013, 12, 30, 0, 0, tzinfo=utc),
|
||||
get_extended_due(self.course, self.week1, self.user1))
|
||||
|
||||
self.week1.due = None
|
||||
self.week1 = self.store.update_item(self.week1, self.user1.id)
|
||||
# Now, week1's normal due date is deleted but the extension still exists.
|
||||
url = reverse('reset_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.get(url, {
|
||||
'student': self.user1.username,
|
||||
'url': self.week1.location.to_deprecated_string(),
|
||||
})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(
|
||||
None,
|
||||
get_extended_due(self.course, self.week1, self.user1)
|
||||
)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
@@ -1683,6 +1683,9 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
|
||||
"""
|
||||
Test certificate generation task works.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCertificateGeneration, self).setUp()
|
||||
self.initialize_course()
|
||||
|
||||
@@ -10,18 +10,21 @@ import mock
|
||||
from pytz import UTC
|
||||
from mobile_api.middleware import AppVersionUpgrade
|
||||
from mobile_api.models import AppVersionConfig
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestAppVersionUpgradeMiddleware(TestCase):
|
||||
class TestAppVersionUpgradeMiddleware(CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for version based app upgrade middleware
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestAppVersionUpgradeMiddleware, self).setUp()
|
||||
self.middleware = AppVersionUpgrade()
|
||||
self.set_app_version_config()
|
||||
cache.clear()
|
||||
|
||||
def set_app_version_config(self):
|
||||
""" Creates configuration data for platform versions """
|
||||
|
||||
@@ -1707,6 +1707,9 @@ class RegistrationCodeRedemptionCourseEnrollment(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test suite for RegistrationCodeRedemption Course Enrollments
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RegistrationCodeRedemptionCourseEnrollment, cls).setUpClass()
|
||||
|
||||
@@ -22,6 +22,7 @@ from course_modes.models import CourseMode
|
||||
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
|
||||
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from student_account.views import account_settings_context
|
||||
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
|
||||
@@ -31,7 +32,7 @@ from openedx.core.djangoapps.theming.test_util import with_edx_domain_context
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
|
||||
""" Tests for the student account views that update the user's account information. """
|
||||
|
||||
USERNAME = u"heisenberg"
|
||||
@@ -64,6 +65,8 @@ class StudentAccountUpdateTest(UrlResetMixin, TestCase):
|
||||
|
||||
URLCONF_MODULES = ['student_accounts.urls']
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(StudentAccountUpdateTest, self).setUp()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class SoftwareSecureFakeViewTest(UrlResetMixin, TestCase):
|
||||
"""
|
||||
Base class to test the fake software secure view.
|
||||
"""
|
||||
|
||||
|
||||
URLCONF_MODULES = ['verify_student.urls']
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
|
||||
@@ -18,6 +18,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
|
||||
from lms.djangoapps.verify_student.models import (
|
||||
SoftwareSecurePhotoVerification,
|
||||
@@ -844,11 +845,13 @@ class SkippedReverificationTest(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
|
||||
class VerificationDeadlineTest(TestCase):
|
||||
class VerificationDeadlineTest(CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the VerificationDeadline model.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def test_caching(self):
|
||||
deadlines = {
|
||||
CourseKey.from_string("edX/DemoX/Fall"): datetime.now(pytz.UTC),
|
||||
|
||||
@@ -203,9 +203,7 @@ CACHES = {
|
||||
# This is the cache used for most things.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': 'edx_loc_mem_cache',
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
},
|
||||
|
||||
# The general cache is what you get if you use our util.cache. It's used for
|
||||
@@ -215,20 +213,13 @@ CACHES = {
|
||||
# push process.
|
||||
'general': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
'KEY_PREFIX': 'general',
|
||||
'VERSION': 4,
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
},
|
||||
|
||||
'mongo_metadata_inheritance': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': os.path.join(tempfile.gettempdir(), 'mongo_metadata_inheritance'),
|
||||
'TIMEOUT': 300,
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
},
|
||||
'loc_cache': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': 'edx_location_mem_cache',
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
},
|
||||
'course_structure_cache': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
|
||||
@@ -40,6 +40,8 @@ class BookmarksTestsBase(ModuleStoreTestCase):
|
||||
STORE_TYPE = ModuleStoreEnum.Type.mongo
|
||||
TEST_PASSWORD = 'test'
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(BookmarksTestsBase, self).setUp()
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class CourseStructureApiTests(ModuleStoreTestCase):
|
||||
"""
|
||||
MOCK_CACHE = "openedx.core.djangoapps.content.course_structures.api.v0.api.cache"
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Test setup
|
||||
|
||||
@@ -16,16 +16,20 @@ from openedx.core.djangoapps.credentials.utils import (
|
||||
)
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@attr('shard_2')
|
||||
class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin, CredentialsDataMixin,
|
||||
ProgramsDataMixin, TestCase):
|
||||
ProgramsDataMixin, CacheIsolationTestCase):
|
||||
""" Tests covering the retrieval of user credentials from the Credentials
|
||||
service.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCredentialsRetrieval, self).setUp()
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ class CreditApiTestBase(ModuleStoreTestCase):
|
||||
Base class for test cases of the credit API.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
PROVIDER_ID = "hogwarts"
|
||||
PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
|
||||
PROVIDER_URL = "https://credit.example.com/request"
|
||||
|
||||
@@ -31,6 +31,7 @@ class ReverificationPartitionTest(ModuleStoreTestCase):
|
||||
SUBMITTED = "submitted"
|
||||
APPROVED = "approved"
|
||||
DENIED = "denied"
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(ReverificationPartitionTest, self).setUp()
|
||||
|
||||
@@ -17,6 +17,7 @@ from openedx.core.djangoapps.programs import utils
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs.tests import factories
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
|
||||
|
||||
@@ -26,8 +27,11 @@ UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@attr('shard_2')
|
||||
class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
|
||||
CredentialsApiConfigMixin, TestCase):
|
||||
CredentialsApiConfigMixin, CacheIsolationTestCase):
|
||||
"""Tests covering the retrieval of programs from the Programs service."""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestProgramRetrieval, self).setUp()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from student.tests.factories import UserFactory
|
||||
from student.models import UserProfile, LanguageProficiency, PendingEmailChange
|
||||
from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KEY
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY
|
||||
|
||||
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
@@ -121,10 +122,13 @@ class UserAPITestCase(APITestCase):
|
||||
clear=True
|
||||
)
|
||||
@attr('shard_2')
|
||||
class TestAccountAPI(UserAPITestCase):
|
||||
class TestAccountAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
"""
|
||||
Unit tests for the Account API.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccountAPI, self).setUp()
|
||||
|
||||
@@ -244,7 +248,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=self.test_password)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(11):
|
||||
with self.assertNumQueries(14):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_full_shareable_account_response(response, account_privacy=ALL_USERS_VISIBILITY)
|
||||
|
||||
@@ -259,7 +263,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=self.test_password)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(11):
|
||||
with self.assertNumQueries(14):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_private_account_response(response, account_privacy=PRIVATE_VISIBILITY)
|
||||
|
||||
@@ -307,11 +311,11 @@ class TestAccountAPI(UserAPITestCase):
|
||||
Test that a client (logged in) can get her own account information (using default legacy profile information,
|
||||
as created by the test UserFactory).
|
||||
"""
|
||||
def verify_get_own_information():
|
||||
def verify_get_own_information(queries):
|
||||
"""
|
||||
Internal helper to perform the actual assertions
|
||||
"""
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(queries):
|
||||
response = self.send_get(self.client)
|
||||
data = response.data
|
||||
self.assertEqual(17, len(data))
|
||||
@@ -333,12 +337,12 @@ class TestAccountAPI(UserAPITestCase):
|
||||
self.assertEqual(False, data["accomplishments_shared"])
|
||||
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
verify_get_own_information()
|
||||
verify_get_own_information(12)
|
||||
|
||||
# Now make sure that the user can get the same information, even if not active
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
verify_get_own_information()
|
||||
verify_get_own_information(9)
|
||||
|
||||
def test_get_account_empty_string(self):
|
||||
"""
|
||||
@@ -352,7 +356,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
legacy_profile.save()
|
||||
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(12):
|
||||
response = self.send_get(self.client)
|
||||
for empty_field in ("level_of_education", "gender", "country", "bio"):
|
||||
self.assertIsNone(response.data[empty_field])
|
||||
|
||||
@@ -20,6 +20,7 @@ from social.apps.django_app.default.models import UserSocialAuth
|
||||
|
||||
from django_comment_common import models
|
||||
from openedx.core.lib.api.test_utils import ApiTestCase, TEST_API_KEY
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
|
||||
from third_party_auth.tests.utils import (
|
||||
@@ -334,12 +335,14 @@ class UserViewSetTest(UserApiTestCase):
|
||||
)
|
||||
|
||||
|
||||
class UserPreferenceViewSetTest(UserApiTestCase):
|
||||
class UserPreferenceViewSetTest(CacheIsolationTestCase, UserApiTestCase):
|
||||
"""
|
||||
Test cases covering the User Preference DRF view class and its various behaviors
|
||||
"""
|
||||
LIST_URI = USER_PREFERENCE_LIST_URI
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(UserPreferenceViewSetTest, self).setUp()
|
||||
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
|
||||
@@ -1725,12 +1728,16 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.ddt
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin):
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the User API registration endpoint with 3rd party authentication.
|
||||
"""
|
||||
CREATE_USER = False
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
__test__ = False
|
||||
|
||||
def setUp(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).setUp()
|
||||
self.url = reverse('user_api_registration')
|
||||
@@ -1847,6 +1854,8 @@ class TestFacebookRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Facebook authentication."""
|
||||
__test__ = True
|
||||
|
||||
def test_social_auth_exception(self):
|
||||
"""
|
||||
According to the do_auth method in social.backends.facebook.py,
|
||||
@@ -1863,6 +1872,8 @@ class TestGoogleRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Google authentication."""
|
||||
__test__ = True
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
0
openedx/core/djangolib/testing/__init__.py
Normal file
0
openedx/core/djangolib/testing/__init__.py
Normal file
122
openedx/core/djangolib/testing/utils.py
Normal file
122
openedx/core/djangolib/testing/utils.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from django.core.cache import caches
|
||||
from django.test import TestCase, override_settings
|
||||
from django.conf import settings
|
||||
from django.contrib import sites
|
||||
|
||||
from request_cache.middleware import RequestCache
|
||||
|
||||
|
||||
class CacheIsolationMixin(object):
|
||||
"""
|
||||
This class can be used to enable specific django caches for
|
||||
specific the TestCase that it's mixed into.
|
||||
|
||||
Usage:
|
||||
|
||||
Use the ENABLED_CACHES to list the names of caches that should
|
||||
be enabled in the context of this TestCase. These caches will
|
||||
use a loc_mem_cache with the default settings.
|
||||
|
||||
Set the class variable CACHES to explicitly specify the cache settings
|
||||
that should be overridden. This class will insert those values into
|
||||
django.conf.settings, and will reset all named caches before each
|
||||
test.
|
||||
|
||||
If both CACHES and ENABLED_CACHES are not None, raises an error.
|
||||
"""
|
||||
|
||||
CACHES = None
|
||||
ENABLED_CACHES = None
|
||||
__settings_override = None
|
||||
|
||||
@classmethod
|
||||
def start_cache_isolation(cls):
|
||||
"""
|
||||
Start cache isolation by overriding the settings.CACHES and
|
||||
flushing the cache.
|
||||
"""
|
||||
cache_settings = None
|
||||
if cls.CACHES is not None and cls.ENABLED_CACHES is not None:
|
||||
raise Exception(
|
||||
"Use either CACHES or ENABLED_CACHES, but not both"
|
||||
)
|
||||
|
||||
if cls.CACHES is not None:
|
||||
cache_settings = cls.CACHES
|
||||
elif cls.ENABLED_CACHES is not None:
|
||||
cache_settings = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
||||
|
||||
cache_settings.update({
|
||||
cache_name: {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': cache_name,
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
} for cache_name in cls.ENABLED_CACHES
|
||||
})
|
||||
|
||||
if cache_settings is None:
|
||||
return
|
||||
|
||||
cls.__settings_override = override_settings(CACHES=cache_settings)
|
||||
cls.__settings_override.__enter__()
|
||||
|
||||
# Start with empty caches
|
||||
cls.clear_caches()
|
||||
|
||||
@classmethod
|
||||
def end_cache_isolation(cls):
|
||||
"""
|
||||
End cache isolation by flushing the cache and then returning
|
||||
settings.CACHES to its original state.
|
||||
"""
|
||||
# Make sure that cache contents don't leak out after the isolation is ended
|
||||
cls.clear_caches()
|
||||
|
||||
if cls.__settings_override is not None:
|
||||
cls.__settings_override.__exit__(None, None, None)
|
||||
cls.__settings_override = None
|
||||
|
||||
@classmethod
|
||||
def clear_caches(cls):
|
||||
"""
|
||||
Clear all of the caches defined in settings.CACHES.
|
||||
"""
|
||||
# N.B. As of 2016-04-20, Django won't return any caches
|
||||
# from django.core.cache.caches.all() that haven't been
|
||||
# accessed using caches[name] previously, so we loop
|
||||
# over our list of overridden caches, instead.
|
||||
for cache in settings.CACHES:
|
||||
caches[cache].clear()
|
||||
|
||||
# The sites framework caches in a module-level dictionary.
|
||||
# Clear that.
|
||||
sites.models.SITE_CACHE.clear()
|
||||
|
||||
RequestCache.clear_request_cache()
|
||||
|
||||
|
||||
class CacheIsolationTestCase(CacheIsolationMixin, TestCase):
|
||||
"""
|
||||
A TestCase that isolates caches (as described in
|
||||
:py:class:`CacheIsolationMixin`) at class setup, and flushes the cache
|
||||
between every test.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CacheIsolationTestCase, cls).setUpClass()
|
||||
cls.start_cache_isolation()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.end_cache_isolation()
|
||||
super(CacheIsolationTestCase, cls).tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
super(CacheIsolationTestCase, self).setUp()
|
||||
|
||||
self.clear_caches()
|
||||
self.addCleanup(self.clear_caches)
|
||||
@@ -15,6 +15,7 @@ from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
@@ -24,8 +25,11 @@ LOGGER_NAME = 'openedx.core.lib.edx_api_utils'
|
||||
|
||||
@attr('shard_2')
|
||||
class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, ProgramsApiConfigMixin, ProgramsDataMixin,
|
||||
TestCase):
|
||||
CacheIsolationTestCase):
|
||||
"""Test utility for API data retrieval."""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestApiDataRetrieval, self).setUp()
|
||||
ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
|
||||
|
||||
Reference in New Issue
Block a user