From 58f36d3e9332a4755d61d807eaabd3c4558da734 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 24 Jul 2015 19:20:29 -0400 Subject: [PATCH] Enable faster ModuleStore tests with SharedModuleStoreTestCase. This is a new TestCase base class, intended for situations where you only want to initialize course content in the setUpClass(), as opposed to setUp(). This is done for performance reasons, since many test cases only use courses in a read-only fashion, particularly in LMS tests. This commit also converts the following modules to use this new base class: lms/djangoapps/ccx/tests/test_ccx_modulestore.py (38s -> 4s) lms/djangoapps/discussion_api/tests/test_api.py (2m45s -> 51s) lms/djangoapps/teams/tests/test_views.py (1m17s -> 33s) --- .../xmodule/modulestore/tests/django_utils.py | 101 +++++-- .../ccx/tests/test_ccx_modulestore.py | 57 ++-- .../discussion_api/tests/test_api.py | 252 ++++++++++++------ lms/djangoapps/teams/tests/test_views.py | 31 ++- 4 files changed, 300 insertions(+), 141 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 971e784d9f..293c49c556 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -159,6 +159,23 @@ def xml_store_config(data_dir, source_dirs=None): return store + +@patch('xmodule.modulestore.django.create_modulestore_instance') +def drop_mongo_collections(mock_create): + """ + If using a Mongo-backed modulestore & contentstore, drop the collections. + """ + # Do not create the modulestore if it does not exist. + mock_create.return_value = None + + module_store = modulestore() + if hasattr(module_store, '_drop_database'): + module_store._drop_database() # pylint: disable=protected-access + _CONTENTSTORE.clear() + if hasattr(module_store, 'close_connections'): + module_store.close_connections() + + TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT # This is an XML only modulestore with only the toy course loaded @@ -198,6 +215,71 @@ TEST_DATA_SPLIT_MODULESTORE = mixed_store_config( ) +class SharedModuleStoreTestCase(TestCase): + """ + 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. + If your tests modify contents in the ModuleStore, you should use + ModuleStoreTestCase instead. + + How to use:: + + from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase + from student.tests.factories import CourseEnrollmentFactory, UserFactory + + class MyModuleStoreTestCase(SharedModuleStoreTestCase): + @classmethod + def setUpClass(cls): + super(MyModuleStoreTestCase, cls).setUpClass() + cls.course = CourseFactory.create() + + def setUp(self): + super(MyModuleStoreTestCase, self).setUp() + self.user = UserFactory.create() + CourseEnrollmentFactory.create( + user=self.user, course_id=self.course.id + ) + + Important things to note: + + 1. You're creating the course in setUpClass(), *not* in setUp(). + 2. Any Django ORM operations should still happen in setUp(). Models created + in setUpClass() will *not* be cleaned up, and will leave side-effects + that can break other, completely unrelated test cases. + + 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(), {}, include_xml=False) + + @classmethod + def setUpClass(cls): + super(SharedModuleStoreTestCase, cls).setUpClass() + + cls._settings_override = override_settings(MODULESTORE=cls.MODULESTORE) + cls._settings_override.__enter__() + XMODULE_FACTORY_LOCK.enable() + clear_existing_modulestores() + cls.store = modulestore() + + @classmethod + def tearDownClass(cls): + drop_mongo_collections() # pylint: disable=no-value-for-parameter + RequestCache().clear_request_cache() + XMODULE_FACTORY_LOCK.disable() + cls._settings_override.__exit__(None, None, None) + + super(SharedModuleStoreTestCase, cls).tearDownClass() + + def setUp(self): + # OverrideFieldData.provider_classes is always reset to `None` so + # that they're recalculated for every test + OverrideFieldData.provider_classes = None + super(SharedModuleStoreTestCase, self).setUp() + + class ModuleStoreTestCase(TestCase): """ Subclass for any test case that uses a ModuleStore. @@ -254,8 +336,7 @@ class ModuleStoreTestCase(TestCase): # which will cause them to be re-created clear_existing_modulestores() - self.addCleanup(self.drop_mongo_collections) - + self.addCleanup(drop_mongo_collections) self.addCleanup(RequestCache().clear_request_cache) # Enable XModuleFactories for the space of this test (and its setUp). @@ -317,22 +398,6 @@ class ModuleStoreTestCase(TestCase): updated_course = self.store.get_course(course.id) return updated_course - @staticmethod - @patch('xmodule.modulestore.django.create_modulestore_instance') - def drop_mongo_collections(mock_create): - """ - If using a Mongo-backed modulestore & contentstore, drop the collections. - """ - # Do not create the modulestore if it does not exist. - mock_create.return_value = None - - module_store = modulestore() - if hasattr(module_store, '_drop_database'): - module_store._drop_database() # pylint: disable=protected-access - _CONTENTSTORE.clear() - if hasattr(module_store, 'close_connections'): - module_store.close_connections() - def create_sample_course(self, org, course, run, block_info_tree=None, course_fields=None): """ create a course in the default modulestore from the collection of BlockInfo diff --git a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py index 8e642595b2..b46e0fabbc 100644 --- a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py +++ b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py @@ -8,57 +8,64 @@ from itertools import izip_longest, chain import pytz from student.tests.factories import AdminFactory from xmodule.modulestore.tests.django_utils import ( - ModuleStoreTestCase, + SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE ) +from student.tests.factories import UserFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from ..models import CustomCourseForEdX -class TestCCXModulestoreWrapper(ModuleStoreTestCase): +class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): """tests for a modulestore wrapped by CCXModulestoreWrapper """ MODULESTORE = TEST_DATA_SPLIT_MODULESTORE + @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 + ) + # Create a course outline + cls.chapters = chapters = [ + ItemFactory.create(start=start, parent=course) for _ in xrange(2) + ] + cls.sequentials = sequentials = [ + ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters + ] + cls.verticals = verticals = [ + ItemFactory.create( + due=due, parent=s, graded=True, format='Homework' + ) for _ in xrange(2) for s in sequentials + ] + cls.blocks = [ + ItemFactory.create(parent=v) for _ in xrange(2) for v in verticals + ] + def setUp(self): """ Set up tests """ super(TestCCXModulestoreWrapper, self).setUp() - self.course = course = CourseFactory.create() + self.user = UserFactory.create() # Create instructor account coach = AdminFactory.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 = chapters = [ - ItemFactory.create(start=start, parent=course) for _ in xrange(2) - ] - self.sequentials = sequentials = [ - ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters - ] - self.verticals = verticals = [ - ItemFactory.create( - due=due, parent=s, graded=True, format='Homework' - ) for _ in xrange(2) for s in sequentials - ] - self.blocks = [ - ItemFactory.create(parent=v) for _ in xrange(2) for v in verticals - ] - self.ccx = ccx = CustomCourseForEdX( - course_id=course.id, + course_id=self.course.id, display_name='Test CCX', coach=coach ) ccx.save() - self.ccx_locator = CCXLocator.from_course_locator(course.id, ccx.id) # pylint: disable=no-member + self.ccx_locator = CCXLocator.from_course_locator(self.course.id, ccx.id) # pylint: disable=no-member def get_all_children_bf(self, block): """traverse the children of block in a breadth-first order""" diff --git a/lms/djangoapps/discussion_api/tests/test_api.py b/lms/djangoapps/discussion_api/tests/test_api.py index 8768858fde..21f6c59b01 100644 --- a/lms/djangoapps/discussion_api/tests/test_api.py +++ b/lms/djangoapps/discussion_api/tests/test_api.py @@ -49,7 +49,7 @@ from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory from util.testing import UrlResetMixin from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.partitions.partitions import Group, UserPartition @@ -64,18 +64,36 @@ def _remove_discussion_tab(course, user_id): modulestore().update_item(course, user_id) +def _discussion_disabled_course_for(user): + """ + Create and return a course with discussions disabled. + + The user passed in will be enrolled in the course. + """ + course_with_disabled_forums = CourseFactory.create() + CourseEnrollmentFactory.create(user=user, course_id=course_with_disabled_forums.id) + _remove_discussion_tab(course_with_disabled_forums, user.id) + + return course_with_disabled_forums + + @ddt.ddt -class GetCourseTest(UrlResetMixin, ModuleStoreTestCase): +class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase): """Test for get_course""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(GetCourseTest, cls).setUpClass() + cls.course = CourseFactory.create(org="x", course="y", run="z") + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(GetCourseTest, self).setUp() - self.course = CourseFactory.create(org="x", course="y", run="z") self.user = UserFactory.create() + CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.request = RequestFactory().get("/dummy") self.request.user = self.user - CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) def test_nonexistent_course(self): with self.assertRaises(Http404): @@ -88,9 +106,8 @@ class GetCourseTest(UrlResetMixin, ModuleStoreTestCase): get_course(self.request, self.course.id) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) with self.assertRaises(Http404): - get_course(self.request, self.course.id) + get_course(self.request, _discussion_disabled_course_for(self.user).id) def test_basic(self): self.assertEqual( @@ -248,16 +265,17 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): self.assertEqual(actual, expected) def test_many(self): - self.course.discussion_topics = { - "A": {"id": "non-courseware-1"}, - "B": {"id": "non-courseware-2"}, - } - modulestore().update_item(self.course, self.user.id) - self.make_discussion_module("courseware-1", "A", "1") - self.make_discussion_module("courseware-2", "A", "2") - self.make_discussion_module("courseware-3", "B", "1") - self.make_discussion_module("courseware-4", "B", "2") - self.make_discussion_module("courseware-5", "C", "1") + with self.store.bulk_operations(self.course.id, emit_signals=False): + self.course.discussion_topics = { + "A": {"id": "non-courseware-1"}, + "B": {"id": "non-courseware-2"}, + } + self.store.update_item(self.course, self.user.id) + self.make_discussion_module("courseware-1", "A", "1") + self.make_discussion_module("courseware-2", "A", "2") + self.make_discussion_module("courseware-3", "B", "1") + self.make_discussion_module("courseware-4", "B", "2") + self.make_discussion_module("courseware-5", "C", "1") actual = self.get_course_topics() expected = { "courseware_topics": [ @@ -291,20 +309,22 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): self.assertEqual(actual, expected) def test_sort_key(self): - self.course.discussion_topics = { - "W": {"id": "non-courseware-1", "sort_key": "Z"}, - "X": {"id": "non-courseware-2"}, - "Y": {"id": "non-courseware-3", "sort_key": "Y"}, - "Z": {"id": "non-courseware-4", "sort_key": "W"}, - } - modulestore().update_item(self.course, self.user.id) - self.make_discussion_module("courseware-1", "First", "A", sort_key="D") - self.make_discussion_module("courseware-2", "First", "B", sort_key="B") - self.make_discussion_module("courseware-3", "First", "C", sort_key="E") - self.make_discussion_module("courseware-4", "Second", "A", sort_key="F") - self.make_discussion_module("courseware-5", "Second", "B", sort_key="G") - self.make_discussion_module("courseware-6", "Second", "C") - self.make_discussion_module("courseware-7", "Second", "D", sort_key="A") + with self.store.bulk_operations(self.course.id, emit_signals=False): + self.course.discussion_topics = { + "W": {"id": "non-courseware-1", "sort_key": "Z"}, + "X": {"id": "non-courseware-2"}, + "Y": {"id": "non-courseware-3", "sort_key": "Y"}, + "Z": {"id": "non-courseware-4", "sort_key": "W"}, + } + self.store.update_item(self.course, self.user.id) + self.make_discussion_module("courseware-1", "First", "A", sort_key="D") + self.make_discussion_module("courseware-2", "First", "B", sort_key="B") + self.make_discussion_module("courseware-3", "First", "C", sort_key="E") + self.make_discussion_module("courseware-4", "Second", "A", sort_key="F") + self.make_discussion_module("courseware-5", "Second", "B", sort_key="G") + self.make_discussion_module("courseware-6", "Second", "C") + self.make_discussion_module("courseware-7", "Second", "D", sort_key="A") + actual = self.get_course_topics() expected = { "courseware_topics": [ @@ -364,26 +384,27 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): group_id=self.partition.groups[group_idx].id ) - self.make_discussion_module("courseware-1", "First", "Everybody") - self.make_discussion_module( - "courseware-2", - "First", - "Cohort A", - group_access={self.partition.id: [self.partition.groups[0].id]} - ) - self.make_discussion_module( - "courseware-3", - "First", - "Cohort B", - group_access={self.partition.id: [self.partition.groups[1].id]} - ) - self.make_discussion_module("courseware-4", "Second", "Staff Only", visible_to_staff_only=True) - self.make_discussion_module( - "courseware-5", - "Second", - "Future Start Date", - start=datetime.now(UTC) + timedelta(days=1) - ) + with self.store.bulk_operations(self.course.id, emit_signals=False): + self.make_discussion_module("courseware-1", "First", "Everybody") + self.make_discussion_module( + "courseware-2", + "First", + "Cohort A", + group_access={self.partition.id: [self.partition.groups[0].id]} + ) + self.make_discussion_module( + "courseware-3", + "First", + "Cohort B", + group_access={self.partition.id: [self.partition.groups[1].id]} + ) + self.make_discussion_module("courseware-4", "Second", "Staff Only", visible_to_staff_only=True) + self.make_discussion_module( + "courseware-5", + "Second", + "Future Start Date", + start=datetime.now(UTC) + timedelta(days=1) + ) student_actual = self.get_course_topics() student_expected = { @@ -456,8 +477,15 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase): @ddt.ddt -class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Test for get_thread_list""" + + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(GetThreadListTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(GetThreadListTest, self).setUp() @@ -469,7 +497,6 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.author = UserFactory.create() self.cohort = CohortFactory.create(course_id=self.course.id) @@ -502,9 +529,8 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.get_thread_list([]) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) with self.assertRaises(Http404): - self.get_thread_list([]) + self.get_thread_list([], course=_discussion_disabled_course_for(self.user)) def test_empty(self): self.assertEqual( @@ -812,8 +838,15 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest @ddt.ddt -class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase): +class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase): """Test for get_comment_list""" + + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(GetCommentListTest, cls).setUpClass() + cls.course = CourseFactory.create() + def setUp(self): super(GetCommentListTest, self).setUp() httpretty.reset() @@ -824,7 +857,6 @@ class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase): self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.author = UserFactory.create() @@ -861,9 +893,13 @@ class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase): self.get_comment_list(self.make_minimal_cs_thread()) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) + disabled_course = _discussion_disabled_course_for(self.user) with self.assertRaises(Http404): - self.get_comment_list(self.make_minimal_cs_thread()) + self.get_comment_list( + self.make_minimal_cs_thread( + overrides={"course_id": unicode(disabled_course.id)} + ) + ) @ddt.data( *itertools.product( @@ -1224,8 +1260,14 @@ class GetCommentListTest(CommentsServiceMockMixin, ModuleStoreTestCase): @ddt.ddt -class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for create_thread""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(CreateThreadTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(CreateThreadTest, self).setUp() @@ -1236,7 +1278,6 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.minimal_data = { "course_id": unicode(self.course.id), @@ -1447,7 +1488,8 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC self.assertEqual(assertion.exception.message_dict, {"course_id": ["Invalid value."]}) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) + disabled_course = _discussion_disabled_course_for(self.user) + self.minimal_data["course_id"] = unicode(disabled_course.id) with self.assertRaises(ValidationError) as assertion: create_thread(self.request, self.minimal_data) self.assertEqual(assertion.exception.message_dict, {"course_id": ["Invalid value."]}) @@ -1460,8 +1502,14 @@ class CreateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC @ddt.ddt -class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for create_comment""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(CreateCommentTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(CreateCommentTest, self).setUp() @@ -1472,7 +1520,6 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.register_get_thread_response( make_minimal_cs_thread({ @@ -1654,7 +1701,14 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.assertEqual(assertion.exception.message_dict, {"thread_id": ["Invalid value."]}) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) + disabled_course = _discussion_disabled_course_for(self.user) + self.register_get_thread_response( + make_minimal_cs_thread({ + "id": "test_thread", + "course_id": unicode(disabled_course.id), + "commentable_id": "test_topic", + }) + ) with self.assertRaises(ValidationError) as assertion: create_comment(self.request, self.minimal_data) self.assertEqual(assertion.exception.message_dict, {"thread_id": ["Invalid value."]}) @@ -1713,8 +1767,14 @@ class CreateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest @ddt.ddt -class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for update_thread""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(UpdateThreadTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(UpdateThreadTest, self).setUp() @@ -1725,7 +1785,6 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) def register_thread(self, overrides=None): @@ -1825,8 +1884,8 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC update_thread(self.request, "test_thread", {}) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) - self.register_thread() + disabled_course = _discussion_disabled_course_for(self.user) + self.register_thread(overrides={"course_id": unicode(disabled_course.id)}) with self.assertRaises(Http404): update_thread(self.request, "test_thread", {}) @@ -2015,31 +2074,41 @@ class UpdateThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC @ddt.ddt -class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for update_comment""" + + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(UpdateCommentTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(UpdateCommentTest, self).setUp() + + self.user = UserFactory.create() + CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) + httpretty.reset() httpretty.enable() self.addCleanup(httpretty.disable) - self.user = UserFactory.create() self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() - CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) - - def register_comment(self, overrides=None, thread_overrides=None): + def register_comment(self, overrides=None, thread_overrides=None, course=None): """ Make a comment with appropriate data overridden by the overrides parameter and register mock responses for both GET and PUT on its endpoint. Also mock GET for the related thread with thread_overrides. """ + if course is None: + course = self.course + cs_thread_data = make_minimal_cs_thread({ "id": "test_thread", - "course_id": unicode(self.course.id) + "course_id": unicode(course.id) }) cs_thread_data.update(thread_overrides or {}) self.register_get_thread_response(cs_thread_data) @@ -2057,6 +2126,7 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.register_get_comment_response(cs_comment_data) self.register_put_comment_response(cs_comment_data) + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def test_empty(self): """Check that an empty update does not make any modifying requests.""" self.register_comment() @@ -2118,8 +2188,7 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest update_comment(self.request, "test_comment", {}) def test_discussions_disabled(self): - _remove_discussion_tab(self.course, self.user.id) - self.register_comment() + self.register_comment(course=_discussion_disabled_course_for(self.user)) with self.assertRaises(Http404): update_comment(self.request, "test_comment", {}) @@ -2309,8 +2378,14 @@ class UpdateCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest @ddt.ddt -class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for delete_thread""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(DeleteThreadTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(DeleteThreadTest, self).setUp() @@ -2321,7 +2396,6 @@ class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() self.thread_id = "test_thread" CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) @@ -2366,8 +2440,8 @@ class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC delete_thread(self.request, self.thread_id) def test_discussions_disabled(self): - self.register_thread() - _remove_discussion_tab(self.course, self.user.id) + disabled_course = _discussion_disabled_course_for(self.user) + self.register_thread(overrides={"course_id": unicode(disabled_course.id)}) with self.assertRaises(Http404): delete_thread(self.request, self.thread_id) @@ -2436,8 +2510,14 @@ class DeleteThreadTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestC @ddt.ddt -class DeleteCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTestCase): +class DeleteCommentTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase): """Tests for delete_comment""" + @classmethod + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + def setUpClass(cls): + super(DeleteCommentTest, cls).setUpClass() + cls.course = CourseFactory.create() + @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): super(DeleteCommentTest, self).setUp() @@ -2448,7 +2528,6 @@ class DeleteCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest self.register_get_user_response(self.user) self.request = RequestFactory().get("/test_path") self.request.user = self.user - self.course = CourseFactory.create() self.thread_id = "test_thread" self.comment_id = "test_comment" CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) @@ -2504,8 +2583,11 @@ class DeleteCommentTest(CommentsServiceMockMixin, UrlResetMixin, ModuleStoreTest delete_comment(self.request, self.comment_id) def test_discussions_disabled(self): - self.register_comment_and_thread() - _remove_discussion_tab(self.course, self.user.id) + disabled_course = _discussion_disabled_course_for(self.user) + self.register_comment_and_thread( + thread_overrides={"course_id": unicode(disabled_course.id)}, + overrides={"course_id": unicode(disabled_course.id)} + ) with self.assertRaises(Http404): delete_comment(self.request, self.comment_id) diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 177c558d4c..32d76b76e1 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -15,22 +15,26 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF from student.models import CourseEnrollment from xmodule.modulestore.tests.factories import CourseFactory from .factories import CourseTeamFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @attr('shard_1') -class TestDashboard(ModuleStoreTestCase): +class TestDashboard(SharedModuleStoreTestCase): """Tests for the Teams dashboard.""" test_password = "test" + @classmethod + def setUpClass(cls): + super(TestDashboard, cls).setUpClass() + cls.course = CourseFactory.create( + teams_configuration={"max_team_size": 10, "topics": [{"name": "foo", "id": 0, "description": "test topic"}]} + ) + def setUp(self): """ Set up tests """ super(TestDashboard, self).setUp() - self.course = CourseFactory.create( - teams_configuration={"max_team_size": 10, "topics": [{"name": "foo", "id": 0, "description": "test topic"}]} - ) # will be assigned to self.client by default self.user = UserFactory.create(password=self.test_password) self.teams_url = reverse('teams_dashboard', args=[self.course.id]) @@ -96,14 +100,14 @@ class TestDashboard(ModuleStoreTestCase): self.assertEqual(404, response.status_code) -class TeamAPITestCase(APITestCase, ModuleStoreTestCase): +class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): """Base class for Team API test cases.""" test_password = 'password' - def setUp(self): - super(TeamAPITestCase, self).setUp() - + @classmethod + def setUpClass(cls): + super(TeamAPITestCase, cls).setUpClass() teams_configuration = { 'topics': [ @@ -114,16 +118,17 @@ class TeamAPITestCase(APITestCase, ModuleStoreTestCase): } for i, name in enumerate([u'sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) ] } - self.topics_count = 4 - - self.test_course_1 = CourseFactory.create( + cls.test_course_1 = CourseFactory.create( org='TestX', course='TS101', display_name='Test Course', teams_configuration=teams_configuration ) - self.test_course_2 = CourseFactory.create(org='MIT', course='6.002x', display_name='Circuits') + cls.test_course_2 = CourseFactory.create(org='MIT', course='6.002x', display_name='Circuits') + def setUp(self): + super(TeamAPITestCase, self).setUp() + self.topics_count = 4 self.users = { 'student_unenrolled': UserFactory.create(password=self.test_password), 'student_enrolled': UserFactory.create(password=self.test_password),