""" Tests for the maintenance app views. """ import ddt import json from django.conf import settings from django.core.urlresolvers import reverse from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from contentstore.management.commands.utils import get_course_versions from student.tests.factories import AdminFactory, UserFactory from .views import COURSE_KEY_ERROR_MESSAGES, MAINTENANCE_VIEWS # This list contains URLs of all maintenance app views. MAINTENANCE_URLS = [reverse(view['url']) for view in MAINTENANCE_VIEWS.values()] class TestMaintenanceIndex(ModuleStoreTestCase): """ Tests for maintenance index view. """ def setUp(self): super(TestMaintenanceIndex, self).setUp() self.user = AdminFactory() login_success = self.client.login(username=self.user.username, password='test') self.assertTrue(login_success) self.view_url = reverse('maintenance:maintenance_index') def test_maintenance_index(self): """ Test that maintenance index view lists all the maintenance app views. """ response = self.client.get(self.view_url) self.assertContains(response, 'Maintenance', status_code=200) # Check that all the expected links appear on the index page. for url in MAINTENANCE_URLS: self.assertContains(response, url, status_code=200) @ddt.ddt class MaintenanceViewTestCase(ModuleStoreTestCase): """ Base class for maintenance view tests. """ view_url = '' def setUp(self): super(MaintenanceViewTestCase, self).setUp() self.user = AdminFactory() login_success = self.client.login(username=self.user.username, password='test') self.assertTrue(login_success) def verify_error_message(self, data, error_message): """ Verify the response contains error message. """ response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertContains(response, error_message, status_code=200) def tearDown(self): """ Reverse the setup. """ self.client.logout() super(MaintenanceViewTestCase, self).tearDown() @ddt.ddt class MaintenanceViewAccessTests(MaintenanceViewTestCase): """ Tests for access control of maintenance views. """ @ddt.data(MAINTENANCE_URLS) @ddt.unpack def test_require_login(self, url): """ Test that maintenance app requires user login. """ # Log out then try to retrieve the page self.client.logout() response = self.client.get(url) # Expect a redirect to the login page redirect_url = '{login_url}?next={original_url}'.format( login_url=reverse('login'), original_url=url, ) self.assertRedirects(response, redirect_url) @ddt.data(MAINTENANCE_URLS) @ddt.unpack def test_global_staff_access(self, url): """ Test that all maintenance app views are accessible to global staff user. """ response = self.client.get(url) self.assertEqual(response.status_code, 200) @ddt.data(MAINTENANCE_URLS) @ddt.unpack def test_non_global_staff_access(self, url): """ Test that all maintenance app views are not accessible to non-global-staff user. """ user = UserFactory(username='test', email='test@example.com', password='test') login_success = self.client.login(username=user.username, password='test') self.assertTrue(login_success) response = self.client.get(url) self.assertContains( response, u'Must be {platform_name} staff to perform this action.'.format(platform_name=settings.PLATFORM_NAME), status_code=403 ) @ddt.ddt class TestForcePublish(MaintenanceViewTestCase): """ Tests for the force publish view. """ def setUp(self): super(TestForcePublish, self).setUp() self.view_url = reverse('maintenance:force_publish_course') def setup_test_course(self): """ Creates the course and add some changes to it. Returns: course: a course object """ course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) # Add some changes to course chapter = ItemFactory.create(category='chapter', parent_location=course.location) self.store.create_child( self.user.id, # pylint: disable=no-member chapter.location, 'html', block_id='html_component' ) # verify that course has changes. self.assertTrue(self.store.has_changes(self.store.get_item(course.location))) return course @ddt.data( ('', COURSE_KEY_ERROR_MESSAGES['empty_course_key']), ('edx', COURSE_KEY_ERROR_MESSAGES['invalid_course_key']), ('course-v1:e+d+X', COURSE_KEY_ERROR_MESSAGES['course_key_not_found']), ) @ddt.unpack def test_invalid_course_key_messages(self, course_key, error_message): """ Test all error messages for invalid course keys. """ # validate that course key contains error message self.verify_error_message( data={'course-id': course_key}, error_message=error_message ) def test_mongo_course(self): """ Test that we get a error message on old mongo courses. """ # validate non split error message course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo) self.verify_error_message( data={'course-id': unicode(course.id)}, error_message='Force publishing course is not supported with old mongo courses.' ) def test_mongo_course_with_split_course_key(self): """ Test that we get an error message `course_key_not_found` for a provided split course key if we already have an old mongo course. """ # validate non split error message course = CourseFactory.create(org='e', number='d', run='X', default_store=ModuleStoreEnum.Type.mongo) self.verify_error_message( data={'course-id': unicode(course.id)}, error_message='Force publishing course is not supported with old mongo courses.' ) # Now search for the course key in split version. self.verify_error_message( data={'course-id': 'course-v1:e+d+X'}, error_message=COURSE_KEY_ERROR_MESSAGES['course_key_not_found'] ) def test_already_published(self): """ Test that when a course is forcefully publish, we get a 'course is already published' message. """ course = self.setup_test_course() # publish the course source_store = modulestore()._get_modulestore_for_courselike(course.id) # pylint: disable=protected-access source_store.force_publish_course(course.id, self.user.id, commit=True) # pylint: disable=no-member # now course is published, we should get `already published course` error. self.verify_error_message( data={'course-id': unicode(course.id)}, error_message='Course is already in published state.' ) def verify_versions_are_different(self, course): """ Verify draft and published versions point to different locations. Arguments: course (object): a course object. """ # get draft and publish branch versions versions = get_course_versions(unicode(course.id)) # verify that draft and publish point to different versions self.assertNotEqual(versions['draft-branch'], versions['published-branch']) def get_force_publish_course_response(self, course): """ Get force publish the course response. Arguments: course (object): a course object. Returns: response : response from force publish post view. """ # Verify versions point to different locations initially self.verify_versions_are_different(course) # force publish course view data = { 'course-id': unicode(course.id) } response = self.client.post(self.view_url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest') response_data = json.loads(response.content) return response_data def test_force_publish_dry_run(self): """ Test that dry run does not publishes the course but shows possible outcome if force published is executed. """ course = self.setup_test_course() response = self.get_force_publish_course_response(course) self.assertIn('current_versions', response) # verify that course still has changes as we just dry ran force publish course. self.assertTrue(self.store.has_changes(self.store.get_item(course.location))) # verify that both branch versions are still different self.verify_versions_are_different(course)