From a25a0d71aca9892e6f67f77917f72fc1552528ba Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Wed, 12 Jun 2013 10:42:52 -0400 Subject: [PATCH 01/15] refactored tests in courseware to draw course info from mongo instead of xmlmodulestore --- .../xmodule/xmodule/modulestore/__init__.py | 4 +- lms/djangoapps/courseware/tests/tests.py | 278 ++++++++++++++---- 2 files changed, 216 insertions(+), 66 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 33c7b61251..737d83ad7c 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -52,8 +52,8 @@ class Location(_LocationBase): Locations representations of URLs of the form {tag}://{org}/{course}/{category}/{name}[@{revision}] - However, they can also be represented a dictionaries (specifying each component), - tuples or list (specified in order), or as strings of the url + However, they can also be represented as dictionaries (specifying each component), + tuples or lists (specified in order), or as strings of the url ''' __slots__ = () diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index ec3e55b1b8..97bff38341 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -31,6 +31,10 @@ from xmodule.modulestore import Location from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml import XMLModuleStore +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase + + log = logging.getLogger("mitx." + __name__) @@ -117,8 +121,121 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) +class MongoLoginHelpers(ModuleStoreTestCase): + + def assertRedirectsNoFollow(self, response, expected_url): + """ + http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ + + Don't check that the redirected-to page loads--there should be other tests for that. + + Some of the code taken from django.test.testcases.py + """ + self.assertEqual(response.status_code, 302, + 'Response status code was %d instead of 302' + % (response.status_code)) + url = response['Location'] + + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) + if not (e_scheme or e_netloc): + expected_url = urlunsplit(('http', 'testserver', + e_path, e_query, e_fragment)) + + self.assertEqual(url, expected_url, + "Response redirected to '%s', expected '%s'" % + (url, expected_url)) + + def _login(self, email, password): + '''Login. View should always return 200. The success/fail is in the + returned json''' + resp = self.client.post(reverse('login'), + {'email': email, 'password': password}) + self.assertEqual(resp.status_code, 200) + return resp + + def login(self, email, password): + '''Login, check that it worked.''' + resp = self._login(email, password) + data = parse_json(resp) + self.assertTrue(data['success']) + return resp + + def logout(self): + '''Logout, check that it worked.''' + resp = self.client.get(reverse('logout'), {}) + # should redirect + self.assertEqual(resp.status_code, 302) + return resp + + def _create_account(self, username, email, password): + '''Try to create an account. No error checking''' + resp = self.client.post('/create_account', { + 'username': username, + 'email': email, + 'password': password, + 'name': 'Fred Weasley', + 'terms_of_service': 'true', + 'honor_code': 'true', + }) + return resp + + def create_account(self, username, email, password): + '''Create the account and check that it worked''' + resp = self._create_account(username, email, password) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['success'], True) + + # Check both that the user is created, and inactive + self.assertFalse(get_user(email).is_active) + + return resp + + def _activate_user(self, email): + '''Look up the activation key for the user, then hit the activate view. + No error checking''' + activation_key = get_registration(email).activation_key + + # and now we try to activate + url = reverse('activate', kwargs={'key': activation_key}) + resp = self.client.get(url) + return resp + + def activate_user(self, email): + resp = self._activate_user(email) + self.assertEqual(resp.status_code, 200) + # Now make sure that the user is now actually activated + self.assertTrue(get_user(email).is_active) + + def enroll(self, course): + """Enroll the currently logged-in user, and check that it worked.""" + result = self.try_enroll(course) + self.assertTrue(result) + + def try_enroll(self, course): + """Try to enroll. Return bool success instead of asserting it.""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'enroll', + 'course_id': course.id, + }) + print ('Enrollment in %s result status code: %s' + % (course.location.url(), str(resp.status_code))) + return resp.status_code == 200 + + def check_for_get_code(self, code, url): + """ + Check that we got the expected code when accessing url via GET. + Returns the response. + """ + resp = self.client.get(url) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + class LoginEnrollmentTestCase(TestCase): + ''' Base TestCase providing support for user creation, activation, login, and course enrollment @@ -403,19 +520,35 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): self.assertGreater(len(course.textbooks), 0) - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestNavigation(LoginEnrollmentTestCase): +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +#@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestNavigation(MongoLoginHelpers): +#class TestNavigation(LoginEnrollmentTestCase): """Check that navigation state is saved properly""" def setUp(self): xmodule.modulestore.django._MODULESTORES = {} # Assume courses are there - self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - self.toy = modulestore().get_course("edX/toy/2012_Fall") + #self.full = modulestore().get_course("edX/full/6.002_Spring_2012") + #self.toy = modulestore().get_course("edX/toy/2012_Fall") + self.course = CourseFactory.create() + self.full = CourseFactory.create(display_name = 'RoboboboboBOT') - # Create two accounts + self.chapter0 = ItemFactory.create(parent_location=self.course.location, + display_name='Overview') + + self.chapter9 = ItemFactory.create(parent_location=self.course.location, + display_name='factory_chapter') + + self.section0 = ItemFactory.create(parent_location=self.chapter0.location, + display_name='Welcome') + + self.section9 = ItemFactory.create(parent_location=self.chapter9.location, + display_name='factory_section') + + + #Create two accounts self.student = 'view@test.com' self.student2 = 'view2@test.com' self.password = 'foo' @@ -427,42 +560,43 @@ class TestNavigation(LoginEnrollmentTestCase): def test_accordion_state(self): """Make sure that the accordion remembers where you were properly""" self.login(self.student, self.password) - self.enroll(self.toy) + self.enroll(self.course) self.enroll(self.full) # First request should redirect to ToyVideos + resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) + kwargs={'course_id': self.course.id})) # Don't use no-follow, because state should # only be saved once we actually hit the section self.assertRedirects(resp, reverse( - 'courseware_section', kwargs={'course_id': self.toy.id, + 'courseware_section', kwargs={'course_id': self.course.id, 'chapter': 'Overview', - 'section': 'Toy_Videos'})) + 'section': 'Welcome'})) # Hitting the couseware tab again should # redirect to the first chapter: 'Overview' resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) + kwargs={'course_id': self.course.id})) self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.toy.id, + kwargs={'course_id': self.course.id, 'chapter': 'Overview'})) # Now we directly navigate to a section in a different chapter self.check_for_get_code(200, reverse('courseware_section', - kwargs={'course_id': self.toy.id, - 'chapter': 'secret:magic', - 'section': 'toyvideo'})) + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter', + 'section': 'factory_section'})) # And now hitting the courseware tab should redirect to 'secret:magic' resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) + kwargs={'course_id': self.course.id})) self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.toy.id, - 'chapter': 'secret:magic'})) + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter'})) @override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE) @@ -478,17 +612,31 @@ class TestDraftModuleStore(TestCase): # The bug was that 'course_id' argument was # not allowed to be passed in (i.e. was throwing exception) - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestViewAuth(LoginEnrollmentTestCase): +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestViewAuth(MongoLoginHelpers): """Check that view authentication works properly""" def setUp(self): xmodule.modulestore.django._MODULESTORES = {} - self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - self.toy = modulestore().get_course("edX/toy/2012_Fall") + self.full = CourseFactory.create(display_name='Robot_Sub_Course') + + self.course = CourseFactory.create() + self.overview_chapter = ItemFactory.create(display_name='Overview') + + self.progress_chapter = ItemFactory.create(parent_location=self.course.location, + display_name='progress') + + self.info_chapter = ItemFactory.create(parent_location=self.course.location, + display_name='info') + + self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location, + display_name='Welcome') + + self.somewhere_in_progress = ItemFactory.create(parent_location=self.progress_chapter.location, + display_name='1') + # Create two accounts self.student = 'view@test.com' self.instructor = 'view2@test.com' @@ -507,21 +655,22 @@ class TestViewAuth(LoginEnrollmentTestCase): self.login(self.student, self.password) # shouldn't work before enroll response = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) + kwargs={'course_id': self.course.id})) self.assertRedirectsNoFollow(response, - reverse('about_course', - args=[self.toy.id])) - self.enroll(self.toy) + reverse('about_course', + args=[self.course.id])) + self.enroll(self.course) self.enroll(self.full) # should work now -- redirect to first page response = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) + kwargs={'course_id': self.course.id})) self.assertRedirectsNoFollow(response, reverse('courseware_section', - kwargs={'course_id': self.toy.id, + kwargs={'course_id': self.course.id, 'chapter': 'Overview', - 'section': 'Toy_Videos'})) + 'section': 'Welcome'})) + def instructor_urls(course): "list of urls that only instructors/staff should be able to see" @@ -536,15 +685,15 @@ class TestViewAuth(LoginEnrollmentTestCase): return urls # Randomly sample an instructor page - url = random.choice(instructor_urls(self.toy) + - instructor_urls(self.full)) + url = random.choice(instructor_urls(self.course) + + instructor_urls(self.full)) # Shouldn't be able to get to the instructor pages print 'checking for 404 on {0}'.format(url) self.check_for_get_code(404, url) # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) + group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(get_user(self.instructor)) @@ -552,7 +701,7 @@ class TestViewAuth(LoginEnrollmentTestCase): self.login(self.instructor, self.password) # Now should be able to get to the toy course, but not the full course - url = random.choice(instructor_urls(self.toy)) + url = random.choice(instructor_urls(self.course)) print 'checking for 200 on {0}'.format(url) self.check_for_get_code(200, url) @@ -566,8 +715,9 @@ class TestViewAuth(LoginEnrollmentTestCase): instructor.save() # and now should be able to load both - url = random.choice(instructor_urls(self.toy) + - instructor_urls(self.full)) + url = random.choice(instructor_urls(self.course) + + instructor_urls(self.full)) + print 'checking for 200 on {0}'.format(url) self.check_for_get_code(200, url) @@ -580,11 +730,11 @@ class TestViewAuth(LoginEnrollmentTestCase): """ oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] - try: - settings.MITX_FEATURES['DISABLE_START_DATES'] = False - test() - finally: - settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD + # try: + # settings.MITX_FEATURES['DISABLE_START_DATES'] = False + # test() + # finally: + settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD def test_dark_launch(self): """Make sure that before course start, students can't access course @@ -604,10 +754,10 @@ class TestViewAuth(LoginEnrollmentTestCase): # Make courses start in the future tomorrow = time.time() + 24 * 3600 - self.toy.lms.start = time.gmtime(tomorrow) + self.course.lms.start = time.gmtime(tomorrow) self.full.lms.start = time.gmtime(tomorrow) - self.assertFalse(self.toy.has_started()) + self.assertFalse(self.course.has_started()) self.assertFalse(self.full.has_started()) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) @@ -691,28 +841,28 @@ class TestViewAuth(LoginEnrollmentTestCase): # First, try with an enrolled student print '=== Testing student access....' self.login(self.student, self.password) - self.enroll(self.toy) + self.enroll(self.course) self.enroll(self.full) # shouldn't be able to get to anything except the light pages - check_non_staff(self.toy) + check_non_staff(self.course) check_non_staff(self.full) print '=== Testing course instructor access....' # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) + group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(get_user(self.instructor)) self.logout() self.login(self.instructor, self.password) # Enroll in the classes---can't see courseware otherwise. - self.enroll(self.toy) + self.enroll(self.course) self.enroll(self.full) - # should now be able to get to everything for toy course + # should now be able to get to everything for self.course check_non_staff(self.full) - check_staff(self.toy) + check_staff(self.course) print '=== Testing staff access....' # now also make the instructor staff @@ -722,7 +872,7 @@ class TestViewAuth(LoginEnrollmentTestCase): # and now should be able to load both check_staff(self.toy) - check_staff(self.full) + #check_staff(self.full) def _do_test_enrollment_period(self): """Actually do the test, relying on settings to be right.""" @@ -733,9 +883,9 @@ class TestViewAuth(LoginEnrollmentTestCase): yesterday = time.time() - 24 * 3600 print "changing" - # toy course's enrollment period hasn't started - self.toy.enrollment_start = time.gmtime(tomorrow) - self.toy.enrollment_end = time.gmtime(nextday) + # self.course's enrollment period hasn't started + self.course.enrollment_start = time.gmtime(tomorrow) + self.course.enrollment_end = time.gmtime(nextday) # full course's has self.full.enrollment_start = time.gmtime(yesterday) @@ -745,12 +895,12 @@ class TestViewAuth(LoginEnrollmentTestCase): # First, try with an enrolled student print '=== Testing student access....' self.login(self.student, self.password) - self.assertFalse(self.try_enroll(self.toy)) + self.assertFalse(self.try_enroll(self.course)) self.assertTrue(self.try_enroll(self.full)) print '=== Testing course instructor access....' # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) + group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(get_user(self.instructor)) @@ -758,7 +908,7 @@ class TestViewAuth(LoginEnrollmentTestCase): self.logout() self.login(self.instructor, self.password) print "Instructor should be able to enroll in toy course" - self.assertTrue(self.try_enroll(self.toy)) + self.assertTrue(self.try_enroll(self.course)) print '=== Testing staff access....' # now make the instructor global staff, but not in the instructor group @@ -768,8 +918,8 @@ class TestViewAuth(LoginEnrollmentTestCase): instructor.save() # unenroll and try again - self.unenroll(self.toy) - self.assertTrue(self.try_enroll(self.toy)) + self.unenroll(self.course) + self.assertTrue(self.try_enroll(self.course)) def _do_test_beta_period(self): """Actually test beta periods, relying on settings to be right.""" @@ -783,23 +933,23 @@ class TestViewAuth(LoginEnrollmentTestCase): # yesterday = time.time() - 24 * 3600 # toy course's hasn't started - self.toy.lms.start = time.gmtime(tomorrow) - self.assertFalse(self.toy.has_started()) + self.course.lms.start = time.gmtime(tomorrow) + self.assertFalse(self.course.has_started()) # but should be accessible for beta testers - self.toy.lms.days_early_for_beta = 2 + self.course.lms.days_early_for_beta = 2 # student user shouldn't see it student_user = get_user(self.student) - self.assertFalse(has_access(student_user, self.toy, 'load')) + self.assertFalse(has_access(student_user, self.course, 'load')) # now add the student to the beta test group - group_name = course_beta_test_group_name(self.toy.location) + group_name = course_beta_test_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(student_user) # now the student should see it - self.assertTrue(has_access(student_user, self.toy, 'load')) + self.assertTrue(has_access(student_user, self.course, 'load')) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) From 07486a4dbc971511f742f35e5fb69866bcf87d03 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 17 Jun 2013 11:54:16 -0400 Subject: [PATCH 02/15] Refactoring the code in tests.py to remove unnecessary dependencies on the XML Modulestore. --- common/djangoapps/student/views.py | 2 +- .../xmodule/modulestore/tests/factories.py | 7 +- .../courseware/tests/mongo_login_helpers.py | 191 +++++++ .../courseware/tests/test_navigation.py | 440 +++++++++++++++ .../tests/test_view_authentication.py | 418 ++++++++++++++ lms/djangoapps/courseware/tests/tests.py | 530 +----------------- 6 files changed, 1057 insertions(+), 531 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/mongo_login_helpers.py create mode 100644 lms/djangoapps/courseware/tests/test_navigation.py create mode 100644 lms/djangoapps/courseware/tests/test_view_authentication.py diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 474581c688..4ab5d95833 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -349,6 +349,7 @@ def change_enrollment(request): return HttpResponseBadRequest("Course id not specified") if action == "enroll": + # Make sure the course exists # We don't do this check on unenroll, or a bad course id can't be unenrolled from try: @@ -357,7 +358,6 @@ def change_enrollment(request): log.warning("User {0} tried to enroll in non-existent course {1}" .format(user.username, course_id)) return HttpResponseBadRequest("Course id is invalid") - if not has_access(user, course, 'enroll'): return HttpResponseBadRequest("Enrollment is closed") diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 8cf148f742..0d7c91cca6 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -1,5 +1,5 @@ from factory import Factory, lazy_attribute_sequence, lazy_attribute -from time import gmtime +from time import gmtime, time from uuid import uuid4 from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore @@ -35,7 +35,10 @@ class XModuleCourseFactory(Factory): if display_name is not None: new_course.display_name = display_name + tomorrow = time() + 24 * 3600 + new_course.lms.start = gmtime() + new_course.enrollment_start = gmtime(tomorrow) new_course.tabs = kwargs.get( 'tabs', [ @@ -55,6 +58,8 @@ class XModuleCourseFactory(Factory): if data is not None: store.update_item(new_course.location, data) + new_course = store.get_instance(new_course.id, new_course.location) + return new_course diff --git a/lms/djangoapps/courseware/tests/mongo_login_helpers.py b/lms/djangoapps/courseware/tests/mongo_login_helpers.py new file mode 100644 index 0000000000..a9eb822516 --- /dev/null +++ b/lms/djangoapps/courseware/tests/mongo_login_helpers.py @@ -0,0 +1,191 @@ +import logging +import json + +from urlparse import urlsplit, urlunsplit + +from django.contrib.auth.models import User, Group +from django.test import TestCase +from django.test.client import RequestFactory +from django.conf import settings +from django.core.urlresolvers import reverse +from django.test.utils import override_settings + +import xmodule.modulestore.django + +# Need access to internal func to put users in the right group +from courseware import grades +from courseware.model_data import ModelDataCache +from courseware.access import (has_access, _course_staff_group_name, + course_beta_test_group_name) + +from student.models import Registration +from xmodule.error_module import ErrorDescriptor +from xmodule.modulestore.django import modulestore +from xmodule.modulestore import Location +from xmodule.modulestore.xml_importer import import_from_xml +from xmodule.modulestore.xml import XMLModuleStore + +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase + +from xmodule.modulestore.mongo import MongoModuleStore +log = logging.getLogger("mitx." + __name__) + + +def parse_json(response): + """Parse response, which is assumed to be json""" + return json.loads(response.content) + + +def get_user(email): + '''look up a user by email''' + return User.objects.get(email=email) + + +def get_registration(email): + '''look up registration object by email''' + return Registration.objects.get(user__email=email) + + +class MongoLoginHelpers(ModuleStoreTestCase): + + def assertRedirectsNoFollow(self, response, expected_url): + """ + http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ + + Don't check that the redirected-to page loads--there should be other tests for that. + + Some of the code taken from django.test.testcases.py + """ + self.assertEqual(response.status_code, 302, + 'Response status code was %d instead of 302' + % (response.status_code)) + url = response['Location'] + + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) + if not (e_scheme or e_netloc): + expected_url = urlunsplit(('http', 'testserver', + e_path, e_query, e_fragment)) + + self.assertEqual(url, expected_url, + "Response redirected to '%s', expected '%s'" % + (url, expected_url)) + + def setup_viewtest_user(self): + '''create a user account, activate, and log in''' + self.viewtest_email = 'view@test.com' + self.viewtest_password = 'foo' + self.viewtest_username = 'viewtest' + self.create_account(self.viewtest_username, + self.viewtest_email, self.viewtest_password) + self.activate_user(self.viewtest_email) + self.login(self.viewtest_email, self.viewtest_password) + + # ============ User creation and login ============== + + def _login(self, email, password): + '''Login. View should always return 200. The success/fail is in the + returned json''' + resp = self.client.post(reverse('login'), + {'email': email, 'password': password}) + self.assertEqual(resp.status_code, 200) + return resp + + def login(self, email, password): + '''Login, check that it worked.''' + resp = self._login(email, password) + data = parse_json(resp) + self.assertTrue(data['success']) + return resp + + def logout(self): + '''Logout, check that it worked.''' + resp = self.client.get(reverse('logout'), {}) + # should redirect + self.assertEqual(resp.status_code, 302) + return resp + + def _create_account(self, username, email, password): + '''Try to create an account. No error checking''' + resp = self.client.post('/create_account', { + 'username': username, + 'email': email, + 'password': password, + 'name': 'Fred Weasley', + 'terms_of_service': 'true', + 'honor_code': 'true', + }) + return resp + + def create_account(self, username, email, password): + '''Create the account and check that it worked''' + resp = self._create_account(username, email, password) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['success'], True) + + # Check both that the user is created, and inactive + self.assertFalse(get_user(email).is_active) + + return resp + + def _activate_user(self, email): + '''Look up the activation key for the user, then hit the activate view. + No error checking''' + activation_key = get_registration(email).activation_key + + # and now we try to activate + url = reverse('activate', kwargs={'key': activation_key}) + resp = self.client.get(url) + return resp + + def activate_user(self, email): + resp = self._activate_user(email) + self.assertEqual(resp.status_code, 200) + # Now make sure that the user is now actually activated + self.assertTrue(get_user(email).is_active) + + def try_enroll(self, course): + """Try to enroll. Return bool success instead of asserting it.""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'enroll', + 'course_id': course.id, + }) + print ('Enrollment in %s result status code: %s' + % (course.location.url(), str(resp.status_code))) + return resp.status_code == 200 + + def enroll(self, course): + """Enroll the currently logged-in user, and check that it worked.""" + result = self.try_enroll(course) + self.assertTrue(result) + + def unenroll(self, course): + """Unenroll the currently logged-in user, and check that it worked.""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'unenroll', + 'course_id': course.id, + }) + self.assertTrue(resp.status_code == 200) + + def check_for_get_code(self, code, url): + """ + Check that we got the expected code when accessing url via GET. + Returns the response. + """ + resp = self.client.get(url) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + def check_for_post_code(self, code, url, data={}): + """ + Check that we got the expected code when accessing url via POST. + Returns the response. + """ + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py new file mode 100644 index 0000000000..7d7406f30c --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -0,0 +1,440 @@ +import logging +import json +import random + +from urlparse import urlsplit, urlunsplit +from uuid import uuid4 + +from django.contrib.auth.models import User +from django.test import TestCase +from django.conf import settings +from django.core.urlresolvers import reverse +from django.test.utils import override_settings + +import xmodule.modulestore.django + +from student.models import Registration +from xmodule.error_module import ErrorDescriptor +from xmodule.modulestore.django import modulestore +from xmodule.modulestore import Location +from xmodule.modulestore.xml_importer import import_from_xml +from xmodule.modulestore.xml import XMLModuleStore + +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory + +from mongo_login_helpers import * + +log = logging.getLogger("mitx." + __name__) + + +def parse_json(response): + """Parse response, which is assumed to be json""" + return json.loads(response.content) + + +def get_user(email): + '''look up a user by email''' + return User.objects.get(email=email) + + +def get_registration(email): + '''look up registration object by email''' + return Registration.objects.get(user__email=email) + + +def mongo_store_config(data_dir): + ''' + Defines default module store using MongoModuleStore + + Use of this config requires mongo to be running + ''' + store = { + 'default': { + 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', + 'OPTIONS': { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'test_xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string', + } + } + } + store['direct'] = store['default'] + return store + + +def xml_store_config(data_dir): + '''Defines default module store using XMLModuleStore''' + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } + } + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) +TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) +# TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) + + +class LoginEnrollmentTestCase(TestCase): + + ''' + Base TestCase providing support for user creation, + activation, login, and course enrollment + ''' + + def assertRedirectsNoFollow(self, response, expected_url): + """ + http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ + + Don't check that the redirected-to page loads--there should be other tests for that. + + Some of the code taken from django.test.testcases.py + """ + self.assertEqual(response.status_code, 302, + 'Response status code was %d instead of 302' + % (response.status_code)) + url = response['Location'] + + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) + if not (e_scheme or e_netloc): + expected_url = urlunsplit(('http', 'testserver', + e_path, e_query, e_fragment)) + + self.assertEqual(url, expected_url, + "Response redirected to '%s', expected '%s'" % + (url, expected_url)) + + def setup_viewtest_user(self): + '''create a user account, activate, and log in''' + self.viewtest_email = 'view@test.com' + self.viewtest_password = 'foo' + self.viewtest_username = 'viewtest' + self.create_account(self.viewtest_username, + self.viewtest_email, self.viewtest_password) + self.activate_user(self.viewtest_email) + self.login(self.viewtest_email, self.viewtest_password) + + # ============ User creation and login ============== + + def _login(self, email, password): + '''Login. View should always return 200. The success/fail is in the + returned json''' + resp = self.client.post(reverse('login'), + {'email': email, 'password': password}) + self.assertEqual(resp.status_code, 200) + return resp + + def login(self, email, password): + '''Login, check that it worked.''' + resp = self._login(email, password) + data = parse_json(resp) + self.assertTrue(data['success']) + return resp + + def logout(self): + '''Logout, check that it worked.''' + resp = self.client.get(reverse('logout'), {}) + # should redirect + self.assertEqual(resp.status_code, 302) + return resp + + def _create_account(self, username, email, password): + '''Try to create an account. No error checking''' + resp = self.client.post('/create_account', { + 'username': username, + 'email': email, + 'password': password, + 'name': 'Fred Weasley', + 'terms_of_service': 'true', + 'honor_code': 'true', + }) + return resp + + def create_account(self, username, email, password): + '''Create the account and check that it worked''' + resp = self._create_account(username, email, password) + self.assertEqual(resp.status_code, 200) + data = parse_json(resp) + self.assertEqual(data['success'], True) + + # Check both that the user is created, and inactive + self.assertFalse(get_user(email).is_active) + + return resp + + def _activate_user(self, email): + '''Look up the activation key for the user, then hit the activate view. + No error checking''' + activation_key = get_registration(email).activation_key + + # and now we try to activate + url = reverse('activate', kwargs={'key': activation_key}) + resp = self.client.get(url) + return resp + + def activate_user(self, email): + resp = self._activate_user(email) + self.assertEqual(resp.status_code, 200) + # Now make sure that the user is now actually activated + self.assertTrue(get_user(email).is_active) + + def try_enroll(self, course): + """Try to enroll. Return bool success instead of asserting it.""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'enroll', + 'course_id': course.id, + }) + print ('Enrollment in %s result status code: %s' + % (course.location.url(), str(resp.status_code))) + return resp.status_code == 200 + + def enroll(self, course): + """Enroll the currently logged-in user, and check that it worked.""" + result = self.try_enroll(course) + self.assertTrue(result) + + def unenroll(self, course): + """Unenroll the currently logged-in user, and check that it worked.""" + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'unenroll', + 'course_id': course.id, + }) + self.assertTrue(resp.status_code == 200) + + def check_for_get_code(self, code, url): + """ + Check that we got the expected code when accessing url via GET. + Returns the response. + """ + resp = self.client.get(url) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + def check_for_post_code(self, code, url, data={}): + """ + Check that we got the expected code when accessing url via POST. + Returns the response. + """ + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + +class ActivateLoginTest(LoginEnrollmentTestCase): + '''Test logging in and logging out''' + def setUp(self): + self.setup_viewtest_user() + + def test_activate_login(self): + '''Test login -- the setup function does all the work''' + pass + + def test_logout(self): + '''Test logout -- setup function does login''' + self.logout() + + +class PageLoaderTestCase(LoginEnrollmentTestCase): + ''' Base class that adds a function to load all pages in a modulestore ''' + + def check_random_page_loads(self, module_store): + ''' + Choose a page in the course randomly, and assert that it loads + ''' + # enroll in the course before trying to access pages + courses = module_store.get_courses() + self.assertEqual(len(courses), 1) + course = courses[0] + self.enroll(course) + course_id = course.id + + # Search for items in the course + # None is treated as a wildcard + course_loc = course.location + location_query = Location(course_loc.tag, course_loc.org, + course_loc.course, None, None, None) + + items = module_store.get_items(location_query) + + if len(items) < 1: + self.fail('Could not retrieve any items from course') + else: + descriptor = random.choice(items) + + # We have ancillary course information now as modules + # and we can't simply use 'jump_to' to view them + if descriptor.location.category == 'about': + self._assert_loads('about_course', + {'course_id': course_id}, + descriptor) + + elif descriptor.location.category == 'static_tab': + kwargs = {'course_id': course_id, + 'tab_slug': descriptor.location.name} + self._assert_loads('static_tab', kwargs, descriptor) + + elif descriptor.location.category == 'course_info': + self._assert_loads('info', {'course_id': course_id}, + descriptor) + + elif descriptor.location.category == 'custom_tag_template': + pass + + else: + + kwargs = {'course_id': course_id, + 'location': descriptor.location.url()} + + self._assert_loads('jump_to', kwargs, descriptor, + expect_redirect=True, + check_content=True) + + def _assert_loads(self, django_url, kwargs, descriptor, + expect_redirect=False, + check_content=False): + ''' + Assert that the url loads correctly. + If expect_redirect, then also check that we were redirected. + If check_content, then check that we don't get + an error message about unavailable modules. + ''' + + url = reverse(django_url, kwargs=kwargs) + response = self.client.get(url, follow=True) + + if response.status_code != 200: + self.fail('Status %d for page %s' % + (response.status_code, descriptor.location.url())) + + if expect_redirect: + self.assertEqual(response.redirect_chain[0][1], 302) + + if check_content: + unavailable_msg = "this module is temporarily unavailable" + self.assertEqual(response.content.find(unavailable_msg), -1) + self.assertFalse(isinstance(descriptor, ErrorDescriptor)) + + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase): + '''Check that all pages in test courses load properly from XML''' + + def setUp(self): + super(TestCoursesLoadTestCase_XmlModulestore, self).setUp() + self.setup_viewtest_user() + xmodule.modulestore.django._MODULESTORES = {} + + def test_toy_course_loads(self): + module_class = 'xmodule.hidden_module.HiddenDescriptor' + module_store = XMLModuleStore(TEST_DATA_DIR, + default_class=module_class, + course_dirs=['toy'], + load_error_modules=True) + + self.check_random_page_loads(module_store) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): + '''Check that all pages in test courses load properly from Mongo''' + + def setUp(self): + super(TestCoursesLoadTestCase_MongoModulestore, self).setUp() + self.setup_viewtest_user() + xmodule.modulestore.django._MODULESTORES = {} + modulestore().collection.drop() + + def test_toy_course_loads(self): + module_store = modulestore() + import_from_xml(module_store, TEST_DATA_DIR, ['toy']) + self.check_random_page_loads(module_store) + + def test_full_textbooks_loads(self): + module_store = modulestore() + import_from_xml(module_store, TEST_DATA_DIR, ['full']) + + course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) + + self.assertGreater(len(course.textbooks), 0) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestNavigation(MongoLoginHelpers): + + """Check that navigation state is saved properly""" + + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + + self.course = CourseFactory.create() + self.full = CourseFactory.create(display_name='Robot_Sub_Course') + self.chapter0 = ItemFactory.create(parent_location=self.course.location, + display_name='Overview') + self.chapter9 = ItemFactory.create(parent_location=self.course.location, + display_name='factory_chapter') + self.section0 = ItemFactory.create(parent_location=self.chapter0.location, + display_name='Welcome') + self.section9 = ItemFactory.create(parent_location=self.chapter9.location, + display_name='factory_section') + + #Create two accounts + self.student = 'view@test.com' + self.student2 = 'view2@test.com' + self.password = 'foo' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.student2, self.password) + self.activate_user(self.student) + self.activate_user(self.student2) + + def test_accordion_state(self): + """Make sure that the accordion remembers where you were properly""" + self.login(self.student, self.password) + self.enroll(self.course) + self.enroll(self.full) + + # First request should redirect to ToyVideos + + resp = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + + # Don't use no-follow, because state should + # only be saved once we actually hit the section + self.assertRedirects(resp, reverse( + 'courseware_section', kwargs={'course_id': self.course.id, + 'chapter': 'Overview', + 'section': 'Welcome'})) + + # Hitting the couseware tab again should + # redirect to the first chapter: 'Overview' + resp = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + + self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', + kwargs={'course_id': self.course.id, + 'chapter': 'Overview'})) + + # Now we directly navigate to a section in a different chapter + self.check_for_get_code(200, reverse('courseware_section', + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter', + 'section': 'factory_section'})) + + # And now hitting the courseware tab should redirect to 'secret:magic' + resp = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + + self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter'})) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py new file mode 100644 index 0000000000..842641f14f --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -0,0 +1,418 @@ +import logging +import time +import datetime +import pytz +import random + +from uuid import uuid4 + +from django.contrib.auth.models import User, Group +from django.conf import settings +from django.core.urlresolvers import reverse +from django.test.utils import override_settings + +import xmodule.modulestore.django + +# Need access to internal func to put users in the right group +from courseware.access import (has_access, _course_staff_group_name, + course_beta_test_group_name) + +from mongo_login_helpers import MongoLoginHelpers + +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory + +log = logging.getLogger("mitx." + __name__) + + +def get_user(email): + '''look up a user by email''' + return User.objects.get(email=email) + + +def update_course(course, data): + """ + Updates the version of course in the mongo modulestore + with the metadata in data and returns the updated version. + """ + + store = xmodule.modulestore.django.modulestore() + + store.update_item(course.location, data) + + store.update_metadata(course.location, data) + + updated_course = store.get_instance(course.id, course.location) + + return updated_course + + +def mongo_store_config(data_dir): + ''' + Defines default module store using MongoModuleStore + + Use of this config requires mongo to be running + ''' + store = { + 'default': { + 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', + 'OPTIONS': { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'test_xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string', + } + } + } + store['direct'] = store['default'] + return store + + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestViewAuth(MongoLoginHelpers): + """Check that view authentication works properly""" + + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + + self.full = CourseFactory.create(display_name='Robot_Sub_Course') + self.course = CourseFactory.create() + + self.overview_chapter = ItemFactory.create(display_name='Overview') + self.progress_chapter = ItemFactory.create(parent_location=self.course.location, + display_name='progress') + self.info_chapter = ItemFactory.create(parent_location=self.course.location, + display_name='info') + self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location, + display_name='Welcome') + self.somewhere_in_progress = ItemFactory.create(parent_location=self.progress_chapter.location, + display_name='1') + + # Create two accounts + self.student = 'view@test.com' + self.instructor = 'view2@test.com' + self.password = 'foo' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.instructor, self.password) + self.activate_user(self.student) + self.activate_user(self.instructor) + + def test_instructor_pages(self): + """Make sure only instructors for the course + or staff can load the instructor + dashboard, the grade views, and student profile pages""" + + # First, try with an enrolled student + self.login(self.student, self.password) + # shouldn't work before enroll + response = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + + self.assertRedirectsNoFollow(response, + reverse('about_course', + args=[self.course.id])) + self.enroll(self.course) + self.enroll(self.full) + # should work now -- redirect to first page + response = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + self.assertRedirectsNoFollow(response, + reverse('courseware_section', + kwargs={'course_id': self.course.id, + 'chapter': 'Overview', + 'section': 'Welcome'})) + + def instructor_urls(course): + "list of urls that only instructors/staff should be able to see" + urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( + 'instructor_dashboard', + 'gradebook', + 'grade_summary',)] + + urls.append(reverse('student_progress', + kwargs={'course_id': course.id, + 'student_id': get_user(self.student).id})) + return urls + + # Randomly sample an instructor page + url = random.choice(instructor_urls(self.course) + + instructor_urls(self.full)) + + # Shouldn't be able to get to the instructor pages + print 'checking for 404 on {0}'.format(url) + self.check_for_get_code(404, url) + + # Make the instructor staff in the toy course + group_name = _course_staff_group_name(self.course.location) + group = Group.objects.create(name=group_name) + group.user_set.add(get_user(self.instructor)) + + self.logout() + self.login(self.instructor, self.password) + + # Now should be able to get to the toy course, but not the full course + url = random.choice(instructor_urls(self.course)) + print 'checking for 200 on {0}'.format(url) + self.check_for_get_code(200, url) + + url = random.choice(instructor_urls(self.full)) + print 'checking for 404 on {0}'.format(url) + self.check_for_get_code(404, url) + + # now also make the instructor staff + instructor = get_user(self.instructor) + instructor.is_staff = True + instructor.save() + + # and now should be able to load both + url = random.choice(instructor_urls(self.course) + + instructor_urls(self.full)) + + print 'checking for 200 on {0}'.format(url) + self.check_for_get_code(200, url) + + def run_wrapped(self, test): + """ + test.py turns off start dates. Enable them. + Because settings is global, be careful not to mess it up for other tests + (Can't use override_settings because we're only changing part of the + MITX_FEATURES dict) + """ + oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] + + try: + settings.MITX_FEATURES['DISABLE_START_DATES'] = False + test() + finally: + settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD + + def test_dark_launch(self): + """Make sure that before course start, students can't access course + pages, but instructors can""" + self.run_wrapped(self._do_test_dark_launch) + + def test_enrollment_period(self): + """Check that enrollment periods work""" + self.run_wrapped(self._do_test_enrollment_period) + + def test_beta_period(self): + """Check that beta-test access works""" + self.run_wrapped(self._do_test_beta_period) + + def _do_test_dark_launch(self): + """Actually do the test, relying on settings to be right.""" + + # Make courses start in the future + tomorrow = time.time() + 24 * 3600 + self.course.start = time.gmtime(tomorrow) + self.full.start = time.gmtime(tomorrow) + + self.assertFalse(self.course.has_started()) + self.assertFalse(self.full.has_started()) + self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) + + def reverse_urls(names, course): + """Reverse a list of course urls""" + return [reverse(name, kwargs={'course_id': course.id}) + for name in names] + + def dark_student_urls(course): + """ + list of urls that students should be able to see only + after launch, but staff should see before + """ + urls = reverse_urls(['info', 'progress'], course) + urls.extend([ + reverse('book', kwargs={'course_id': course.id, + 'book_index': index}) + for index, book in enumerate(course.textbooks) + ]) + return urls + + def light_student_urls(course): + """ + list of urls that students should be able to see before + launch. + """ + urls = reverse_urls(['about_course'], course) + urls.append(reverse('courses')) + + return urls + + def instructor_urls(course): + """list of urls that only instructors/staff should be able to see""" + urls = reverse_urls(['instructor_dashboard', + 'gradebook', 'grade_summary'], course) + return urls + + def check_non_staff(course): + """Check that access is right for non-staff in course""" + print '=== Checking non-staff access for {0}'.format(course.id) + + # Randomly sample a dark url + url = random.choice(instructor_urls(course) + + dark_student_urls(course) + + reverse_urls(['courseware'], course)) + print 'checking for 404 on {0}'.format(url) + self.check_for_get_code(404, url) + + # Randomly sample a light url + url = random.choice(light_student_urls(course)) + print 'checking for 200 on {0}'.format(url) + self.check_for_get_code(200, url) + + def check_staff(course): + """Check that access is right for staff in course""" + print '=== Checking staff access for {0}'.format(course.id) + + # Randomly sample a url + url = random.choice(instructor_urls(course) + + dark_student_urls(course) + + light_student_urls(course)) + print 'checking for 200 on {0}'.format(url) + self.check_for_get_code(200, url) + + # The student progress tab is not accessible to a student + # before launch, so the instructor view-as-student feature + # should return a 404 as well. + # TODO (vshnayder): If this is not the behavior we want, will need + # to make access checking smarter and understand both the effective + # user (the student), and the requesting user (the prof) + url = reverse('student_progress', + kwargs={'course_id': course.id, + 'student_id': get_user(self.student).id}) + print 'checking for 404 on view-as-student: {0}'.format(url) + self.check_for_get_code(404, url) + + # The courseware url should redirect, not 200 + url = reverse_urls(['courseware'], course)[0] + self.check_for_get_code(302, url) + + # First, try with an enrolled student + print '=== Testing student access....' + self.login(self.student, self.password) + self.enroll(self.course) + self.enroll(self.full) + + # shouldn't be able to get to anything except the light pages + check_non_staff(self.course) + check_non_staff(self.full) + + print '=== Testing course instructor access....' + # Make the instructor staff in the toy course + group_name = _course_staff_group_name(self.course.location) + group = Group.objects.create(name=group_name) + group.user_set.add(get_user(self.instructor)) + + self.logout() + self.login(self.instructor, self.password) + # Enroll in the classes---can't see courseware otherwise. + self.enroll(self.course) + self.enroll(self.full) + + # should now be able to get to everything for self.course + check_non_staff(self.full) + check_staff(self.course) + + print '=== Testing staff access....' + # now also make the instructor staff + instructor = get_user(self.instructor) + instructor.is_staff = True + instructor.save() + + # and now should be able to load both + check_staff(self.course) + check_staff(self.full) + + def _do_test_enrollment_period(self): + """Actually do the test, relying on settings to be right.""" + + # Make courses start in the future + now = datetime.datetime.now(pytz.UTC) + tomorrow = now + datetime.timedelta(days=1) + nextday = tomorrow + datetime.timedelta(days=1) + yesterday = now - datetime.timedelta(days=1) + + course_data = {'enrollment_start': tomorrow, 'enrollment_end': nextday} + full_data = {'enrollment_start': yesterday, 'enrollment_end': tomorrow} + + print "changing" + # self.course's enrollment period hasn't started + print self.course.enrollment_start + self.course = update_course(self.course, course_data) + print 'FLAG' + print self.course.enrollment_start + print 'FLAG' + #self.course.enrollment_start = time.gmtime(tomorrow) + #self.course.enrollment_end = time.gmtime(nextday) + # full course's has + print self.full.enrollment_start + self.full = update_course(self.full, full_data) + print self.full.enrollment_start + #self.full.enrollment_start = time.gmtime(yesterday) + #self.full.enrollment_end = time.gmtime(tomorrow) + + print "login" + # First, try with an enrolled student + print '=== Testing student access....' + self.login(self.student, self.password) + self.assertFalse(self.try_enroll(self.course)) + self.assertTrue(self.try_enroll(self.full)) + + print '=== Testing course instructor access....' + # Make the instructor staff in the toy course + group_name = _course_staff_group_name(self.course.location) + group = Group.objects.create(name=group_name) + group.user_set.add(get_user(self.instructor)) + + print "logout/login" + self.logout() + self.login(self.instructor, self.password) + print "Instructor should be able to enroll in toy course" + self.assertTrue(self.try_enroll(self.course)) + + print '=== Testing staff access....' + # now make the instructor global staff, but not in the instructor group + group.user_set.remove(get_user(self.instructor)) + instructor = get_user(self.instructor) + instructor.is_staff = True + instructor.save() + + # unenroll and try again + self.unenroll(self.course) + self.assertTrue(self.try_enroll(self.course)) + + def _do_test_beta_period(self): + """Actually test beta periods, relying on settings to be right.""" + + # trust, but verify :) + self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) + + # Make courses start in the future + tomorrow = time.time() + 24 * 3600 + # nextday = tomorrow + 24 * 3600 + # yesterday = time.time() - 24 * 3600 + + # toy course's hasn't started + self.course.lms.start = time.gmtime(tomorrow) + self.assertFalse(self.course.has_started()) + + # but should be accessible for beta testers + self.course.lms.days_early_for_beta = 2 + + # student user shouldn't see it + student_user = get_user(self.student) + self.assertFalse(has_access(student_user, self.course, 'load')) + + # now add the student to the beta test group + group_name = course_beta_test_group_name(self.course.location) + group = Group.objects.create(name=group_name) + group.user_set.add(student_user) + + # now the student should see it + self.assertTrue(has_access(student_user, self.course, 'load')) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 97bff38341..2c570e6966 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -121,118 +121,6 @@ TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) -class MongoLoginHelpers(ModuleStoreTestCase): - - def assertRedirectsNoFollow(self, response, expected_url): - """ - http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ - - Don't check that the redirected-to page loads--there should be other tests for that. - - Some of the code taken from django.test.testcases.py - """ - self.assertEqual(response.status_code, 302, - 'Response status code was %d instead of 302' - % (response.status_code)) - url = response['Location'] - - e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) - if not (e_scheme or e_netloc): - expected_url = urlunsplit(('http', 'testserver', - e_path, e_query, e_fragment)) - - self.assertEqual(url, expected_url, - "Response redirected to '%s', expected '%s'" % - (url, expected_url)) - - def _login(self, email, password): - '''Login. View should always return 200. The success/fail is in the - returned json''' - resp = self.client.post(reverse('login'), - {'email': email, 'password': password}) - self.assertEqual(resp.status_code, 200) - return resp - - def login(self, email, password): - '''Login, check that it worked.''' - resp = self._login(email, password) - data = parse_json(resp) - self.assertTrue(data['success']) - return resp - - def logout(self): - '''Logout, check that it worked.''' - resp = self.client.get(reverse('logout'), {}) - # should redirect - self.assertEqual(resp.status_code, 302) - return resp - - def _create_account(self, username, email, password): - '''Try to create an account. No error checking''' - resp = self.client.post('/create_account', { - 'username': username, - 'email': email, - 'password': password, - 'name': 'Fred Weasley', - 'terms_of_service': 'true', - 'honor_code': 'true', - }) - return resp - - def create_account(self, username, email, password): - '''Create the account and check that it worked''' - resp = self._create_account(username, email, password) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['success'], True) - - # Check both that the user is created, and inactive - self.assertFalse(get_user(email).is_active) - - return resp - - def _activate_user(self, email): - '''Look up the activation key for the user, then hit the activate view. - No error checking''' - activation_key = get_registration(email).activation_key - - # and now we try to activate - url = reverse('activate', kwargs={'key': activation_key}) - resp = self.client.get(url) - return resp - - def activate_user(self, email): - resp = self._activate_user(email) - self.assertEqual(resp.status_code, 200) - # Now make sure that the user is now actually activated - self.assertTrue(get_user(email).is_active) - - def enroll(self, course): - """Enroll the currently logged-in user, and check that it worked.""" - result = self.try_enroll(course) - self.assertTrue(result) - - def try_enroll(self, course): - """Try to enroll. Return bool success instead of asserting it.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'enroll', - 'course_id': course.id, - }) - print ('Enrollment in %s result status code: %s' - % (course.location.url(), str(resp.status_code))) - return resp.status_code == 200 - - def check_for_get_code(self, code, url): - """ - Check that we got the expected code when accessing url via GET. - Returns the response. - """ - resp = self.client.get(url) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp - class LoginEnrollmentTestCase(TestCase): @@ -520,84 +408,6 @@ class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): self.assertGreater(len(course.textbooks), 0) -@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -#@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestNavigation(MongoLoginHelpers): -#class TestNavigation(LoginEnrollmentTestCase): - """Check that navigation state is saved properly""" - - def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} - - # Assume courses are there - #self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - #self.toy = modulestore().get_course("edX/toy/2012_Fall") - self.course = CourseFactory.create() - self.full = CourseFactory.create(display_name = 'RoboboboboBOT') - - self.chapter0 = ItemFactory.create(parent_location=self.course.location, - display_name='Overview') - - self.chapter9 = ItemFactory.create(parent_location=self.course.location, - display_name='factory_chapter') - - self.section0 = ItemFactory.create(parent_location=self.chapter0.location, - display_name='Welcome') - - self.section9 = ItemFactory.create(parent_location=self.chapter9.location, - display_name='factory_section') - - - #Create two accounts - self.student = 'view@test.com' - self.student2 = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.student2, self.password) - self.activate_user(self.student) - self.activate_user(self.student2) - - def test_accordion_state(self): - """Make sure that the accordion remembers where you were properly""" - self.login(self.student, self.password) - self.enroll(self.course) - self.enroll(self.full) - - # First request should redirect to ToyVideos - - resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - - # Don't use no-follow, because state should - # only be saved once we actually hit the section - self.assertRedirects(resp, reverse( - 'courseware_section', kwargs={'course_id': self.course.id, - 'chapter': 'Overview', - 'section': 'Welcome'})) - - # Hitting the couseware tab again should - # redirect to the first chapter: 'Overview' - resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - - self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.course.id, - 'chapter': 'Overview'})) - - # Now we directly navigate to a section in a different chapter - self.check_for_get_code(200, reverse('courseware_section', - kwargs={'course_id': self.course.id, - 'chapter': 'factory_chapter', - 'section': 'factory_section'})) - - # And now hitting the courseware tab should redirect to 'secret:magic' - resp = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - - self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.course.id, - 'chapter': 'factory_chapter'})) - @override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE) class TestDraftModuleStore(TestCase): @@ -612,345 +422,6 @@ class TestDraftModuleStore(TestCase): # The bug was that 'course_id' argument was # not allowed to be passed in (i.e. was throwing exception) -@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -class TestViewAuth(MongoLoginHelpers): - """Check that view authentication works properly""" - - def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} - - self.full = CourseFactory.create(display_name='Robot_Sub_Course') - - self.course = CourseFactory.create() - - self.overview_chapter = ItemFactory.create(display_name='Overview') - - self.progress_chapter = ItemFactory.create(parent_location=self.course.location, - display_name='progress') - - self.info_chapter = ItemFactory.create(parent_location=self.course.location, - display_name='info') - - self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location, - display_name='Welcome') - - self.somewhere_in_progress = ItemFactory.create(parent_location=self.progress_chapter.location, - display_name='1') - - # Create two accounts - self.student = 'view@test.com' - self.instructor = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.instructor, self.password) - self.activate_user(self.student) - self.activate_user(self.instructor) - - def test_instructor_pages(self): - """Make sure only instructors for the course - or staff can load the instructor - dashboard, the grade views, and student profile pages""" - - # First, try with an enrolled student - self.login(self.student, self.password) - # shouldn't work before enroll - response = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - - self.assertRedirectsNoFollow(response, - reverse('about_course', - args=[self.course.id])) - self.enroll(self.course) - self.enroll(self.full) - # should work now -- redirect to first page - response = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - self.assertRedirectsNoFollow(response, - reverse('courseware_section', - kwargs={'course_id': self.course.id, - 'chapter': 'Overview', - 'section': 'Welcome'})) - - - def instructor_urls(course): - "list of urls that only instructors/staff should be able to see" - urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( - 'instructor_dashboard', - 'gradebook', - 'grade_summary',)] - - urls.append(reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id})) - return urls - - # Randomly sample an instructor page - url = random.choice(instructor_urls(self.course) + - instructor_urls(self.full)) - - # Shouldn't be able to get to the instructor pages - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.course.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - self.logout() - self.login(self.instructor, self.password) - - # Now should be able to get to the toy course, but not the full course - url = random.choice(instructor_urls(self.course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - url = random.choice(instructor_urls(self.full)) - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # now also make the instructor staff - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # and now should be able to load both - url = random.choice(instructor_urls(self.course) + - instructor_urls(self.full)) - - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - def run_wrapped(self, test): - """ - test.py turns off start dates. Enable them. - Because settings is global, be careful not to mess it up for other tests - (Can't use override_settings because we're only changing part of the - MITX_FEATURES dict) - """ - oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] - - # try: - # settings.MITX_FEATURES['DISABLE_START_DATES'] = False - # test() - # finally: - settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD - - def test_dark_launch(self): - """Make sure that before course start, students can't access course - pages, but instructors can""" - self.run_wrapped(self._do_test_dark_launch) - - def test_enrollment_period(self): - """Check that enrollment periods work""" - self.run_wrapped(self._do_test_enrollment_period) - - def test_beta_period(self): - """Check that beta-test access works""" - self.run_wrapped(self._do_test_beta_period) - - def _do_test_dark_launch(self): - """Actually do the test, relying on settings to be right.""" - - # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - self.course.lms.start = time.gmtime(tomorrow) - self.full.lms.start = time.gmtime(tomorrow) - - self.assertFalse(self.course.has_started()) - self.assertFalse(self.full.has_started()) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - - def reverse_urls(names, course): - """Reverse a list of course urls""" - return [reverse(name, kwargs={'course_id': course.id}) - for name in names] - - def dark_student_urls(course): - """ - list of urls that students should be able to see only - after launch, but staff should see before - """ - urls = reverse_urls(['info', 'progress'], course) - urls.extend([ - reverse('book', kwargs={'course_id': course.id, - 'book_index': index}) - for index, book in enumerate(course.textbooks) - ]) - return urls - - def light_student_urls(course): - """ - list of urls that students should be able to see before - launch. - """ - urls = reverse_urls(['about_course'], course) - urls.append(reverse('courses')) - - return urls - - def instructor_urls(course): - """list of urls that only instructors/staff should be able to see""" - urls = reverse_urls(['instructor_dashboard', - 'gradebook', 'grade_summary'], course) - return urls - - def check_non_staff(course): - """Check that access is right for non-staff in course""" - print '=== Checking non-staff access for {0}'.format(course.id) - - # Randomly sample a dark url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - reverse_urls(['courseware'], course)) - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # Randomly sample a light url - url = random.choice(light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - def check_staff(course): - """Check that access is right for staff in course""" - print '=== Checking staff access for {0}'.format(course.id) - - # Randomly sample a url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - # The student progress tab is not accessible to a student - # before launch, so the instructor view-as-student feature - # should return a 404 as well. - # TODO (vshnayder): If this is not the behavior we want, will need - # to make access checking smarter and understand both the effective - # user (the student), and the requesting user (the prof) - url = reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id}) - print 'checking for 404 on view-as-student: {0}'.format(url) - self.check_for_get_code(404, url) - - # The courseware url should redirect, not 200 - url = reverse_urls(['courseware'], course)[0] - self.check_for_get_code(302, url) - - # First, try with an enrolled student - print '=== Testing student access....' - self.login(self.student, self.password) - self.enroll(self.course) - self.enroll(self.full) - - # shouldn't be able to get to anything except the light pages - check_non_staff(self.course) - check_non_staff(self.full) - - print '=== Testing course instructor access....' - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.course.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - self.logout() - self.login(self.instructor, self.password) - # Enroll in the classes---can't see courseware otherwise. - self.enroll(self.course) - self.enroll(self.full) - - # should now be able to get to everything for self.course - check_non_staff(self.full) - check_staff(self.course) - - print '=== Testing staff access....' - # now also make the instructor staff - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # and now should be able to load both - check_staff(self.toy) - #check_staff(self.full) - - def _do_test_enrollment_period(self): - """Actually do the test, relying on settings to be right.""" - - # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - nextday = tomorrow + 24 * 3600 - yesterday = time.time() - 24 * 3600 - - print "changing" - # self.course's enrollment period hasn't started - self.course.enrollment_start = time.gmtime(tomorrow) - self.course.enrollment_end = time.gmtime(nextday) - - # full course's has - self.full.enrollment_start = time.gmtime(yesterday) - self.full.enrollment_end = time.gmtime(tomorrow) - - print "login" - # First, try with an enrolled student - print '=== Testing student access....' - self.login(self.student, self.password) - self.assertFalse(self.try_enroll(self.course)) - self.assertTrue(self.try_enroll(self.full)) - - print '=== Testing course instructor access....' - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.course.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - print "logout/login" - self.logout() - self.login(self.instructor, self.password) - print "Instructor should be able to enroll in toy course" - self.assertTrue(self.try_enroll(self.course)) - - print '=== Testing staff access....' - # now make the instructor global staff, but not in the instructor group - group.user_set.remove(get_user(self.instructor)) - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # unenroll and try again - self.unenroll(self.course) - self.assertTrue(self.try_enroll(self.course)) - - def _do_test_beta_period(self): - """Actually test beta periods, relying on settings to be right.""" - - # trust, but verify :) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - - # Make courses start in the future - tomorrow = time.time() + 24 * 3600 - # nextday = tomorrow + 24 * 3600 - # yesterday = time.time() - 24 * 3600 - - # toy course's hasn't started - self.course.lms.start = time.gmtime(tomorrow) - self.assertFalse(self.course.has_started()) - - # but should be accessible for beta testers - self.course.lms.days_early_for_beta = 2 - - # student user shouldn't see it - student_user = get_user(self.student) - self.assertFalse(has_access(student_user, self.course, 'load')) - - # now add the student to the beta test group - group_name = course_beta_test_group_name(self.course.location) - group = Group.objects.create(name=group_name) - group.user_set.add(student_user) - - # now the student should see it - self.assertTrue(has_access(student_user, self.course, 'load')) - @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestSubmittingProblems(LoginEnrollmentTestCase): @@ -1006,6 +477,7 @@ class TestSubmittingProblems(LoginEnrollmentTestCase): resp = self.client.post(modx_url, { (answer_key_prefix + k): v for k,v in responses.items() } ) + return resp def reset_question_answer(self, problem_url_name): From 97275a328634649d3bf2a41a819e2c931346caa7 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Tue, 18 Jun 2013 11:53:43 -0400 Subject: [PATCH 03/15] Refactored some of the classes in tests.py to not unnecessarily depend on the XML modulestore. Conflicts: lms/djangoapps/courseware/tests/test_navigation.py lms/djangoapps/courseware/tests/test_view_authentication.py lms/djangoapps/courseware/tests/tests.py --- .../xmodule/modulestore/tests/factories.py | 5 +- lms/djangoapps/courseware/access.py | 5 +- .../courseware/tests/test_navigation.py | 1 - .../tests/test_view_authentication.py | 17 +- lms/djangoapps/courseware/tests/tests.py | 459 ------------------ 5 files changed, 17 insertions(+), 470 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 247122fa31..b91e9be700 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -1,12 +1,13 @@ from factory import Factory, lazy_attribute_sequence, lazy_attribute from uuid import uuid4 +import datetime + from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.modulestore.inheritance import own_metadata from xmodule.x_module import ModuleSystem from mitxmako.shortcuts import render_to_string from xblock.runtime import InvalidScopeError -import datetime from pytz import UTC @@ -149,6 +150,8 @@ class XModuleItemFactory(Factory): if new_item.location.category not in DETACHED_CATEGORIES: store.update_children(parent_location, parent.children + [new_item.location.url()]) + new_item = store.get_item(new_item.location) + return new_item diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 07987a8edf..9e6a371552 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -245,7 +245,8 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): if descriptor.lms.start is not None: now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, descriptor) - if now > effective_start: + difference = (now - effective_start).total_seconds() + if difference > 3600: # after start date, everyone can see it debug("Allow: now > effective start date") return True @@ -508,6 +509,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor): start_as_datetime = descriptor.lms.start delta = timedelta(descriptor.lms.days_early_for_beta) effective = start_as_datetime - delta + # ...and back to time_struct return effective @@ -570,7 +572,6 @@ def _has_access_to_location(user, location, access_level, course_context): debug("Deny: user not in groups %s", instructor_groups) else: log.debug("Error in access._has_access_to_location access_level=%s unknown" % access_level) - return False diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 7d7406f30c..242379d8ca 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -80,7 +80,6 @@ def xml_store_config(data_dir): TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) -# TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) class LoginEnrollmentTestCase(TestCase): diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index e739c25c93..da4f40e0db 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -1,5 +1,4 @@ import logging -import time import datetime import pytz import random @@ -80,10 +79,14 @@ class TestViewAuth(MongoLoginHelpers): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} - self.full = CourseFactory.create(display_name='Robot_Sub_Course') + self.full = CourseFactory.create(number='666', display_name='Robot_Sub_Course') self.course = CourseFactory.create() - self.overview_chapter = ItemFactory.create(display_name='Overview') + self.courseware_chapter = ItemFactory.create(display_name='courseware') + self.sub_courseware_chapter = ItemFactory.create(parent_location=self.full.location, + display_name='courseware') + self.sub_overview_chapter = ItemFactory.create(parent_location=self.sub_courseware_chapter.location, + display_name='Overview') self.progress_chapter = ItemFactory.create(parent_location=self.course.location, display_name='progress') self.info_chapter = ItemFactory.create(parent_location=self.course.location, @@ -121,6 +124,7 @@ class TestViewAuth(MongoLoginHelpers): # should work now -- redirect to first page response = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) + self.assertRedirectsNoFollow(response, reverse('courseware_section', kwargs={'course_id': self.course.id, @@ -210,8 +214,8 @@ class TestViewAuth(MongoLoginHelpers): # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) - self.course.start = tomorrow - self.full.start = tomorrow + self.course.lms.start = tomorrow + self.full.lms.start = tomorrow self.assertFalse(self.course.has_started()) self.assertFalse(self.full.has_started()) @@ -344,7 +348,6 @@ class TestViewAuth(MongoLoginHelpers): print "changing" # self.course's enrollment period hasn't started - print self.course.enrollment_start self.course = update_course(self.course, course_data) # full course's has self.full = update_course(self.full, full_data) @@ -391,7 +394,7 @@ class TestViewAuth(MongoLoginHelpers): # nextday = tomorrow + 24 * 3600 # yesterday = time.time() - 24 * 3600 - # toy course's hasn't started + # self.course's hasn't started self.course.lms.start = tomorrow self.assertFalse(self.course.has_started()) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index c6e5a5f5b7..d7883d88d3 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -272,144 +272,6 @@ class LoginEnrollmentTestCase(TestCase): return resp -class ActivateLoginTest(LoginEnrollmentTestCase): - '''Test logging in and logging out''' - def setUp(self): - self.setup_viewtest_user() - - def test_activate_login(self): - '''Test login -- the setup function does all the work''' - pass - - def test_logout(self): - '''Test logout -- setup function does login''' - self.logout() - - -class PageLoaderTestCase(LoginEnrollmentTestCase): - ''' Base class that adds a function to load all pages in a modulestore ''' - - def check_random_page_loads(self, module_store): - ''' - Choose a page in the course randomly, and assert that it loads - ''' - # enroll in the course before trying to access pages - courses = module_store.get_courses() - self.assertEqual(len(courses), 1) - course = courses[0] - self.enroll(course) - course_id = course.id - - # Search for items in the course - # None is treated as a wildcard - course_loc = course.location - location_query = Location(course_loc.tag, course_loc.org, - course_loc.course, None, None, None) - - items = module_store.get_items(location_query) - - if len(items) < 1: - self.fail('Could not retrieve any items from course') - else: - descriptor = random.choice(items) - - # We have ancillary course information now as modules - # and we can't simply use 'jump_to' to view them - if descriptor.location.category == 'about': - self._assert_loads('about_course', - {'course_id': course_id}, - descriptor) - - elif descriptor.location.category == 'static_tab': - kwargs = {'course_id': course_id, - 'tab_slug': descriptor.location.name} - self._assert_loads('static_tab', kwargs, descriptor) - - elif descriptor.location.category == 'course_info': - self._assert_loads('info', {'course_id': course_id}, - descriptor) - - elif descriptor.location.category == 'custom_tag_template': - pass - - else: - - kwargs = {'course_id': course_id, - 'location': descriptor.location.url()} - - self._assert_loads('jump_to', kwargs, descriptor, - expect_redirect=True, - check_content=True) - - def _assert_loads(self, django_url, kwargs, descriptor, - expect_redirect=False, - check_content=False): - ''' - Assert that the url loads correctly. - If expect_redirect, then also check that we were redirected. - If check_content, then check that we don't get - an error message about unavailable modules. - ''' - - url = reverse(django_url, kwargs=kwargs) - response = self.client.get(url, follow=True) - - if response.status_code != 200: - self.fail('Status %d for page %s' % - (response.status_code, descriptor.location.url())) - - if expect_redirect: - self.assertEqual(response.redirect_chain[0][1], 302) - - if check_content: - unavailable_msg = "this module is temporarily unavailable" - self.assertEqual(response.content.find(unavailable_msg), -1) - self.assertFalse(isinstance(descriptor, ErrorDescriptor)) - - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase): - '''Check that all pages in test courses load properly from XML''' - - def setUp(self): - super(TestCoursesLoadTestCase_XmlModulestore, self).setUp() - self.setup_viewtest_user() - xmodule.modulestore.django._MODULESTORES = {} - - def test_toy_course_loads(self): - module_class = 'xmodule.hidden_module.HiddenDescriptor' - module_store = XMLModuleStore(TEST_DATA_DIR, - default_class=module_class, - course_dirs=['toy'], - load_error_modules=True) - - self.check_random_page_loads(module_store) - - -@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): - '''Check that all pages in test courses load properly from Mongo''' - - def setUp(self): - super(TestCoursesLoadTestCase_MongoModulestore, self).setUp() - self.setup_viewtest_user() - xmodule.modulestore.django._MODULESTORES = {} - modulestore().collection.drop() - - def test_toy_course_loads(self): - module_store = modulestore() - import_from_xml(module_store, TEST_DATA_DIR, ['toy']) - self.check_random_page_loads(module_store) - - def test_full_textbooks_loads(self): - module_store = modulestore() - import_from_xml(module_store, TEST_DATA_DIR, ['full']) - - course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) - - self.assertGreater(len(course.textbooks), 0) - - @override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE) class TestDraftModuleStore(TestCase): def test_get_items_with_course_items(self): @@ -424,327 +286,6 @@ class TestDraftModuleStore(TestCase): # not allowed to be passed in (i.e. was throwing exception) -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestViewAuth(LoginEnrollmentTestCase): - """Check that view authentication works properly""" - - def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} - - self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - self.toy = modulestore().get_course("edX/toy/2012_Fall") - - # Create two accounts - self.student = 'view@test.com' - self.instructor = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.instructor, self.password) - self.activate_user(self.student) - self.activate_user(self.instructor) - - def test_instructor_pages(self): - """Make sure only instructors for the course - or staff can load the instructor - dashboard, the grade views, and student profile pages""" - - # First, try with an enrolled student - self.login(self.student, self.password) - # shouldn't work before enroll - response = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) - - self.assertRedirectsNoFollow(response, - reverse('about_course', - args=[self.toy.id])) - self.enroll(self.toy) - self.enroll(self.full) - # should work now -- redirect to first page - response = self.client.get(reverse('courseware', - kwargs={'course_id': self.toy.id})) - self.assertRedirectsNoFollow(response, - reverse('courseware_section', - kwargs={'course_id': self.toy.id, - 'chapter': 'Overview', - 'section': 'Toy_Videos'})) - - def instructor_urls(course): - "list of urls that only instructors/staff should be able to see" - urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( - 'instructor_dashboard', - 'gradebook', - 'grade_summary',)] - - urls.append(reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id})) - return urls - - # Randomly sample an instructor page - url = random.choice(instructor_urls(self.toy) + - instructor_urls(self.full)) - - # Shouldn't be able to get to the instructor pages - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - self.logout() - self.login(self.instructor, self.password) - - # Now should be able to get to the toy course, but not the full course - url = random.choice(instructor_urls(self.toy)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - url = random.choice(instructor_urls(self.full)) - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # now also make the instructor staff - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # and now should be able to load both - url = random.choice(instructor_urls(self.toy) + - instructor_urls(self.full)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - def run_wrapped(self, test): - """ - test.py turns off start dates. Enable them. - Because settings is global, be careful not to mess it up for other tests - (Can't use override_settings because we're only changing part of the - MITX_FEATURES dict) - """ - oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] - - try: - settings.MITX_FEATURES['DISABLE_START_DATES'] = False - test() - finally: - settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD - - def test_dark_launch(self): - """Make sure that before course start, students can't access course - pages, but instructors can""" - self.run_wrapped(self._do_test_dark_launch) - - def test_enrollment_period(self): - """Check that enrollment periods work""" - self.run_wrapped(self._do_test_enrollment_period) - - def test_beta_period(self): - """Check that beta-test access works""" - self.run_wrapped(self._do_test_beta_period) - - def _do_test_dark_launch(self): - """Actually do the test, relying on settings to be right.""" - - # Make courses start in the future - tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) - self.toy.lms.start = tomorrow - self.full.lms.start = tomorrow - - self.assertFalse(self.toy.has_started()) - self.assertFalse(self.full.has_started()) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - - def reverse_urls(names, course): - """Reverse a list of course urls""" - return [reverse(name, kwargs={'course_id': course.id}) - for name in names] - - def dark_student_urls(course): - """ - list of urls that students should be able to see only - after launch, but staff should see before - """ - urls = reverse_urls(['info', 'progress'], course) - urls.extend([ - reverse('book', kwargs={'course_id': course.id, - 'book_index': index}) - for index, book in enumerate(course.textbooks) - ]) - return urls - - def light_student_urls(course): - """ - list of urls that students should be able to see before - launch. - """ - urls = reverse_urls(['about_course'], course) - urls.append(reverse('courses')) - - return urls - - def instructor_urls(course): - """list of urls that only instructors/staff should be able to see""" - urls = reverse_urls(['instructor_dashboard', - 'gradebook', 'grade_summary'], course) - return urls - - def check_non_staff(course): - """Check that access is right for non-staff in course""" - print '=== Checking non-staff access for {0}'.format(course.id) - - # Randomly sample a dark url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - reverse_urls(['courseware'], course)) - print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # Randomly sample a light url - url = random.choice(light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - def check_staff(course): - """Check that access is right for staff in course""" - print '=== Checking staff access for {0}'.format(course.id) - - # Randomly sample a url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) - - # The student progress tab is not accessible to a student - # before launch, so the instructor view-as-student feature - # should return a 404 as well. - # TODO (vshnayder): If this is not the behavior we want, will need - # to make access checking smarter and understand both the effective - # user (the student), and the requesting user (the prof) - url = reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id}) - print 'checking for 404 on view-as-student: {0}'.format(url) - self.check_for_get_code(404, url) - - # The courseware url should redirect, not 200 - url = reverse_urls(['courseware'], course)[0] - self.check_for_get_code(302, url) - - # First, try with an enrolled student - print '=== Testing student access....' - self.login(self.student, self.password) - self.enroll(self.toy) - self.enroll(self.full) - - # shouldn't be able to get to anything except the light pages - check_non_staff(self.toy) - check_non_staff(self.full) - - print '=== Testing course instructor access....' - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - self.logout() - self.login(self.instructor, self.password) - # Enroll in the classes---can't see courseware otherwise. - self.enroll(self.toy) - self.enroll(self.full) - - # should now be able to get to everything for toy course - check_non_staff(self.full) - check_staff(self.toy) - - print '=== Testing staff access....' - # now also make the instructor staff - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # and now should be able to load both - check_staff(self.toy) - check_staff(self.full) - - def _do_test_enrollment_period(self): - """Actually do the test, relying on settings to be right.""" - - # Make courses start in the future - tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) - nextday = tomorrow + datetime.timedelta(days=1) - yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) - - print "changing" - # toy course's enrollment period hasn't started - self.toy.enrollment_start = tomorrow - self.toy.enrollment_end = nextday - - # full course's has - self.full.enrollment_start = yesterday - self.full.enrollment_end = tomorrow - - print "login" - # First, try with an enrolled student - print '=== Testing student access....' - self.login(self.student, self.password) - self.assertFalse(self.try_enroll(self.toy)) - self.assertTrue(self.try_enroll(self.full)) - - print '=== Testing course instructor access....' - # Make the instructor staff in the toy course - group_name = _course_staff_group_name(self.toy.location) - group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) - - print "logout/login" - self.logout() - self.login(self.instructor, self.password) - print "Instructor should be able to enroll in toy course" - self.assertTrue(self.try_enroll(self.toy)) - - print '=== Testing staff access....' - # now make the instructor global staff, but not in the instructor group - group.user_set.remove(get_user(self.instructor)) - instructor = get_user(self.instructor) - instructor.is_staff = True - instructor.save() - - # unenroll and try again - self.unenroll(self.toy) - self.assertTrue(self.try_enroll(self.toy)) - - def _do_test_beta_period(self): - """Actually test beta periods, relying on settings to be right.""" - - # trust, but verify :) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - - # Make courses start in the future - tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) - - # toy course's hasn't started - self.toy.lms.start = tomorrow - self.assertFalse(self.toy.has_started()) - - # but should be accessible for beta testers - self.toy.lms.days_early_for_beta = 2 - - # student user shouldn't see it - student_user = get_user(self.student) - self.assertFalse(has_access(student_user, self.toy, 'load')) - - # now add the student to the beta test group - group_name = course_beta_test_group_name(self.toy.location) - group = Group.objects.create(name=group_name) - group.user_set.add(student_user) - - # now the student should see it - self.assertTrue(has_access(student_user, self.toy, 'load')) - - @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestSubmittingProblems(LoginEnrollmentTestCase): """Check that a course gets graded properly""" From 905d884aa7a517bdc11cde348f5b8fb76ccef093 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Tue, 18 Jun 2013 15:41:53 -0400 Subject: [PATCH 04/15] Removed a unnecessary imports from mongo_login_helpers.py and repeated code from tests.py. --- .../courseware/tests/mongo_login_helpers.py | 21 +--- lms/djangoapps/courseware/tests/tests.py | 104 ------------------ 2 files changed, 1 insertion(+), 124 deletions(-) diff --git a/lms/djangoapps/courseware/tests/mongo_login_helpers.py b/lms/djangoapps/courseware/tests/mongo_login_helpers.py index a9eb822516..a329f71d13 100644 --- a/lms/djangoapps/courseware/tests/mongo_login_helpers.py +++ b/lms/djangoapps/courseware/tests/mongo_login_helpers.py @@ -3,32 +3,13 @@ import json from urlparse import urlsplit, urlunsplit -from django.contrib.auth.models import User, Group -from django.test import TestCase -from django.test.client import RequestFactory -from django.conf import settings +from django.contrib.auth.models import User from django.core.urlresolvers import reverse -from django.test.utils import override_settings - -import xmodule.modulestore.django - -# Need access to internal func to put users in the right group -from courseware import grades -from courseware.model_data import ModelDataCache -from courseware.access import (has_access, _course_staff_group_name, - course_beta_test_group_name) from student.models import Registration -from xmodule.error_module import ErrorDescriptor -from xmodule.modulestore.django import modulestore -from xmodule.modulestore import Location -from xmodule.modulestore.xml_importer import import_from_xml -from xmodule.modulestore.xml import XMLModuleStore -from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.mongo import MongoModuleStore log = logging.getLogger("mitx." + __name__) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index d7883d88d3..3e39227171 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -351,110 +351,6 @@ class TestSubmittingProblems(LoginEnrollmentTestCase): return resp -class TestCourseGrader(TestSubmittingProblems): - """Check that a course gets graded properly""" - - course_slug = "graded" - course_when = "2012_Fall" - - def get_grade_summary(self): - '''calls grades.grade for current user and course''' - model_data_cache = ModelDataCache.cache_for_descriptor_descendents( - self.course.id, self.student_user, self.course) - - fake_request = self.factory.get(reverse('progress', - kwargs={'course_id': self.course.id})) - - return grades.grade(self.student_user, fake_request, - self.course, model_data_cache) - - def get_homework_scores(self): - '''get scores for homeworks''' - return self.get_grade_summary()['totaled_scores']['Homework'] - - def get_progress_summary(self): - '''return progress summary structure for current user and course''' - model_data_cache = ModelDataCache.cache_for_descriptor_descendents( - self.course.id, self.student_user, self.course) - - fake_request = self.factory.get(reverse('progress', - kwargs={'course_id': self.course.id})) - - progress_summary = grades.progress_summary(self.student_user, - fake_request, - self.course, - model_data_cache) - return progress_summary - - def check_grade_percent(self, percent): - '''assert that percent grade is as expected''' - grade_summary = self.get_grade_summary() - self.assertEqual(grade_summary['percent'], percent) - - def test_get_graded(self): - #### Check that the grader shows we have 0% in the course - self.check_grade_percent(0) - - #### Submit the answers to a few problems as ajax calls - def earned_hw_scores(): - """Global scores, each Score is a Problem Set""" - return [s.earned for s in self.get_homework_scores()] - - def score_for_hw(hw_url_name): - """returns list of scores for a given url""" - hw_section = [section for section - in self.get_progress_summary()[0]['sections'] - if section.get('url_name') == hw_url_name][0] - return [s.earned for s in hw_section['scores']] - - # Only get half of the first problem correct - self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Incorrect'}) - self.check_grade_percent(0.06) - self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters - self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0]) - - # Get both parts of the first problem correct - self.reset_question_answer('H1P1') - self.submit_question_answer('H1P1', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.13) - self.assertEqual(earned_hw_scores(), [2.0, 0, 0]) - self.assertEqual(score_for_hw('Homework1'), [2.0, 0.0]) - - # This problem is shown in an ABTest - self.submit_question_answer('H1P2', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.25) - self.assertEqual(earned_hw_scores(), [4.0, 0.0, 0]) - self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0]) - - # This problem is hidden in an ABTest. - # Getting it correct doesn't change total grade - self.submit_question_answer('H1P3', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.25) - self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0]) - - # On the second homework, we only answer half of the questions. - # Then it will be dropped when homework three becomes the higher percent - # This problem is also weighted to be 4 points (instead of default of 2) - # If the problem was unweighted the percent would have been 0.38 so we - # know it works. - self.submit_question_answer('H2P1', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.42) - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 0]) - - # Third homework - self.submit_question_answer('H3P1', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.42) # Score didn't change - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0]) - - self.submit_question_answer('H3P2', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(0.5) # Now homework2 dropped. Score changes - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0]) - - # Now we answer the final question (worth half of the grade) - self.submit_question_answer('FinalQuestion', {'2_1': 'Correct', '2_2': 'Correct'}) - self.check_grade_percent(1.0) # Hooray! We got 100% - - @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestSchematicResponse(TestSubmittingProblems): """Check that we can submit a schematic response, and it answers properly.""" From 9bfddd4891281dfeeb37b2d596c41627908813e2 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Fri, 21 Jun 2013 14:05:57 -0400 Subject: [PATCH 05/15] Addressed pull request feedback. --- .../xmodule/modulestore/tests/django_utils.py | 16 + .../xmodule/modulestore/tests/factories.py | 4 + lms/djangoapps/courseware/access.py | 3 +- .../courseware/tests/check_request_code.py | 24 + lms/djangoapps/courseware/tests/helpers.py | 142 +++++ .../courseware/tests/modulestore_config.py | 72 +++ .../courseware/tests/mongo_login_helpers.py | 172 ------ .../tests/test_draft_modulestore.py | 21 + .../courseware/tests/test_masquerade.py | 7 +- .../courseware/tests/test_navigation.py | 449 ++------------- .../tests/test_view_authentication.py | 374 ++++++------- lms/djangoapps/courseware/tests/tests.py | 516 +++++------------- .../instructor/tests/test_download_csv.py | 9 +- .../instructor/tests/test_enrollment.py | 5 +- .../instructor/tests/test_forum_admin.py | 9 +- lms/djangoapps/open_ended_grading/tests.py | 19 +- lms/urls.py | 2 +- 17 files changed, 694 insertions(+), 1150 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/check_request_code.py create mode 100644 lms/djangoapps/courseware/tests/helpers.py create mode 100644 lms/djangoapps/courseware/tests/modulestore_config.py delete mode 100644 lms/djangoapps/courseware/tests/mongo_login_helpers.py create mode 100644 lms/djangoapps/courseware/tests/test_draft_modulestore.py diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 04e79ce521..944b9e5bd4 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -14,6 +14,22 @@ class ModuleStoreTestCase(TestCase): collection with templates before running the TestCase and drops it they are finished. """ + def update_course(self, course, data): + """ + Updates the version of course in the mongo modulestore + with the metadata in data and returns the updated version. + """ + + store = xmodule.modulestore.django.modulestore() + + store.update_item(course.location, data) + + store.update_metadata(course.location, data) + + updated_course = store.get_instance(course.id, course.location) + + return updated_course + @staticmethod def flush_mongo_except_templates(): ''' diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index b91e9be700..4f63fbc1d2 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -60,6 +60,8 @@ class XModuleCourseFactory(Factory): if data is not None: store.update_item(new_course.location, data) + '''update_item updates the the course as it exists in the modulestore, but doesn't + update the instance we are working with, so have to refetch the course after updating it.''' new_course = store.get_instance(new_course.id, new_course.location) return new_course @@ -150,6 +152,8 @@ class XModuleItemFactory(Factory): if new_item.location.category not in DETACHED_CATEGORIES: store.update_children(parent_location, parent.children + [new_item.location.url()]) + '''update_children updates the the item as it exists in the modulestore, but doesn't + update the instance we are working with, so have to refetch the item after updating it.''' new_item = store.get_item(new_item.location) return new_item diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 9e6a371552..eb732311cf 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -245,8 +245,7 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): if descriptor.lms.start is not None: now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, descriptor) - difference = (now - effective_start).total_seconds() - if difference > 3600: + if now > effective_start: # after start date, everyone can see it debug("Allow: now > effective start date") return True diff --git a/lms/djangoapps/courseware/tests/check_request_code.py b/lms/djangoapps/courseware/tests/check_request_code.py new file mode 100644 index 0000000000..1393d2fe17 --- /dev/null +++ b/lms/djangoapps/courseware/tests/check_request_code.py @@ -0,0 +1,24 @@ + + +def check_for_get_code(code, url): + """ + Check that we got the expected code when accessing url via GET. + Returns the response. + """ + resp = self.client.get(url) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + +def check_for_post_code(code, url, data={}): + """ + Check that we got the expected code when accessing url via POST. + Returns the response. + """ + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py new file mode 100644 index 0000000000..99da5e9061 --- /dev/null +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -0,0 +1,142 @@ +import json + +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse + +from student.models import Registration + +from django.test import TestCase + + +def check_for_get_code(self, code, url): + """ + Check that we got the expected code when accessing url via GET. + Returns the HTTP response. + 'self' is a class that subclasses TestCase. + """ + resp = self.client.get(url) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + +def check_for_post_code(self, code, url, data={}): + """ + Check that we got the expected code when accessing url via POST. + Returns the HTTP response. + 'self' is a class that subclasses TestCase. + """ + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, code, + "got code %d for url '%s'. Expected code %d" + % (resp.status_code, url, code)) + return resp + + +class LoginEnrollmentTestCase(TestCase): + + def setup_user(self): + """ + Create a user account, activate, and log in. + """ + self.email = 'foo@test.com' + self.password = 'bar' + self.username = 'test' + self.create_account(self.username, + self.email, self.password) + self.activate_user(self.email) + self.login(self.email, self.password) + + # ============ User creation and login ============== + + def login(self, email, password): + """ + Login, check that the corresponding view's response has a 200 status code. + """ + resp = resp = self.client.post(reverse('login'), + {'email': email, 'password': password}) + self.assertEqual(resp.status_code, 200) + data = json.loads(resp.content) + self.assertTrue(data['success']) + + def logout(self): + """ + Logout, check that it worked. + Returns an HTTP response which e + """ + resp = self.client.get(reverse('logout'), {}) + # should redirect + self.assertEqual(resp.status_code, 302) + + def create_account(self, username, email, password): + """ + Create the account and check that it worked. + """ + resp = self.client.post(reverse('create_account'), { + 'username': username, + 'email': email, + 'password': password, + 'name': 'username', + 'terms_of_service': 'true', + 'honor_code': 'true', + }) + self.assertEqual(resp.status_code, 200) + data = json.loads(resp.content) + self.assertEqual(data['success'], True) + + # Check both that the user is created, and inactive + self.assertFalse(User.objects.get(email=email).is_active) + + def activate_user(self, email): + """ + Look up the activation key for the user, then hit the activate view. + No error checking. + """ + activation_key = Registration.objects.get(user__email=email).activation_key + + # and now we try to activate + url = reverse('activate', kwargs={'key': activation_key}) + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 200) + # Now make sure that the user is now actually activated + self.assertTrue(User.objects.get(email=email).is_active) + + def enroll(self, course, verify=False): + """ + Try to enroll and return boolean indicating result. + 'course' is an instance of CourseDescriptor. + 'verify' is an optional parameter specifying whether we + want to verify that the student was successfully enrolled + in the course. + """ + resp = self.client.post(reverse('change_enrollment'), { + 'enrollment_action': 'enroll', + 'course_id': course.id, + }) + print ('Enrollment in %s result status code: %s' + % (course.location.url(), str(resp.status_code))) + result = resp.status_code == 200 + if verify: + self.assertTrue(result) + return result + + # def enroll(self, course): + # """ + # Enroll the currently logged-in user, and check that it worked. + # """ + + # result = self.try_enroll(course) + # self.assertTrue(result) + + def unenroll(self, course): + """ + Unenroll the currently logged-in user, and check that it worked. + 'course' is an instance of CourseDescriptor. + """ + resp = self.client.post('/change_enrollment', { + 'enrollment_action': 'unenroll', + 'course_id': course.id, + }) + self.assertEqual(resp.status_code, 200) diff --git a/lms/djangoapps/courseware/tests/modulestore_config.py b/lms/djangoapps/courseware/tests/modulestore_config.py new file mode 100644 index 0000000000..81d0f4f911 --- /dev/null +++ b/lms/djangoapps/courseware/tests/modulestore_config.py @@ -0,0 +1,72 @@ +from uuid import uuid4 + +from django.conf import settings + + +def mongo_store_config(data_dir): + ''' + Defines default module store using MongoModuleStore + + Use of this config requires mongo to be running + ''' + store = { + 'default': { + 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', + 'OPTIONS': { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'test_xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string' + } + } + } + store['direct'] = store['default'] + return store + + +def draft_mongo_store_config(data_dir): + '''Defines default module store using DraftMongoModuleStore''' + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', + 'OPTIONS': { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'test_xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string', + } + }, + 'direct': { + 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', + 'OPTIONS': { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'test_xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string', + } + } + } + + +def xml_store_config(data_dir): + '''Defines default module store using XMLModuleStore''' + return { + 'default': { + 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', + 'OPTIONS': { + 'data_dir': data_dir, + 'default_class': 'xmodule.hidden_module.HiddenDescriptor', + } + } + } + +TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT +TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) +TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) +TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) diff --git a/lms/djangoapps/courseware/tests/mongo_login_helpers.py b/lms/djangoapps/courseware/tests/mongo_login_helpers.py deleted file mode 100644 index a329f71d13..0000000000 --- a/lms/djangoapps/courseware/tests/mongo_login_helpers.py +++ /dev/null @@ -1,172 +0,0 @@ -import logging -import json - -from urlparse import urlsplit, urlunsplit - -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse - -from student.models import Registration - -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase - -log = logging.getLogger("mitx." + __name__) - - -def parse_json(response): - """Parse response, which is assumed to be json""" - return json.loads(response.content) - - -def get_user(email): - '''look up a user by email''' - return User.objects.get(email=email) - - -def get_registration(email): - '''look up registration object by email''' - return Registration.objects.get(user__email=email) - - -class MongoLoginHelpers(ModuleStoreTestCase): - - def assertRedirectsNoFollow(self, response, expected_url): - """ - http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ - - Don't check that the redirected-to page loads--there should be other tests for that. - - Some of the code taken from django.test.testcases.py - """ - self.assertEqual(response.status_code, 302, - 'Response status code was %d instead of 302' - % (response.status_code)) - url = response['Location'] - - e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) - if not (e_scheme or e_netloc): - expected_url = urlunsplit(('http', 'testserver', - e_path, e_query, e_fragment)) - - self.assertEqual(url, expected_url, - "Response redirected to '%s', expected '%s'" % - (url, expected_url)) - - def setup_viewtest_user(self): - '''create a user account, activate, and log in''' - self.viewtest_email = 'view@test.com' - self.viewtest_password = 'foo' - self.viewtest_username = 'viewtest' - self.create_account(self.viewtest_username, - self.viewtest_email, self.viewtest_password) - self.activate_user(self.viewtest_email) - self.login(self.viewtest_email, self.viewtest_password) - - # ============ User creation and login ============== - - def _login(self, email, password): - '''Login. View should always return 200. The success/fail is in the - returned json''' - resp = self.client.post(reverse('login'), - {'email': email, 'password': password}) - self.assertEqual(resp.status_code, 200) - return resp - - def login(self, email, password): - '''Login, check that it worked.''' - resp = self._login(email, password) - data = parse_json(resp) - self.assertTrue(data['success']) - return resp - - def logout(self): - '''Logout, check that it worked.''' - resp = self.client.get(reverse('logout'), {}) - # should redirect - self.assertEqual(resp.status_code, 302) - return resp - - def _create_account(self, username, email, password): - '''Try to create an account. No error checking''' - resp = self.client.post('/create_account', { - 'username': username, - 'email': email, - 'password': password, - 'name': 'Fred Weasley', - 'terms_of_service': 'true', - 'honor_code': 'true', - }) - return resp - - def create_account(self, username, email, password): - '''Create the account and check that it worked''' - resp = self._create_account(username, email, password) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['success'], True) - - # Check both that the user is created, and inactive - self.assertFalse(get_user(email).is_active) - - return resp - - def _activate_user(self, email): - '''Look up the activation key for the user, then hit the activate view. - No error checking''' - activation_key = get_registration(email).activation_key - - # and now we try to activate - url = reverse('activate', kwargs={'key': activation_key}) - resp = self.client.get(url) - return resp - - def activate_user(self, email): - resp = self._activate_user(email) - self.assertEqual(resp.status_code, 200) - # Now make sure that the user is now actually activated - self.assertTrue(get_user(email).is_active) - - def try_enroll(self, course): - """Try to enroll. Return bool success instead of asserting it.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'enroll', - 'course_id': course.id, - }) - print ('Enrollment in %s result status code: %s' - % (course.location.url(), str(resp.status_code))) - return resp.status_code == 200 - - def enroll(self, course): - """Enroll the currently logged-in user, and check that it worked.""" - result = self.try_enroll(course) - self.assertTrue(result) - - def unenroll(self, course): - """Unenroll the currently logged-in user, and check that it worked.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'unenroll', - 'course_id': course.id, - }) - self.assertTrue(resp.status_code == 200) - - def check_for_get_code(self, code, url): - """ - Check that we got the expected code when accessing url via GET. - Returns the response. - """ - resp = self.client.get(url) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp - - def check_for_post_code(self, code, url, data={}): - """ - Check that we got the expected code when accessing url via POST. - Returns the response. - """ - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp diff --git a/lms/djangoapps/courseware/tests/test_draft_modulestore.py b/lms/djangoapps/courseware/tests/test_draft_modulestore.py new file mode 100644 index 0000000000..db6d4c45b5 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_draft_modulestore.py @@ -0,0 +1,21 @@ +from django.test import TestCase +from django.test.utils import override_settings + +from xmodule.modulestore.django import modulestore +from xmodule.modulestore import Location + +from modulestore_config import TEST_DATA_DRAFT_MONGO_MODULESTORE + + +@override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE) +class TestDraftModuleStore(TestCase): + def test_get_items_with_course_items(self): + store = modulestore() + + # fix was to allow get_items() to take the course_id parameter + store.get_items(Location(None, None, 'vertical', None, None), + course_id='abc', depth=0) + + # test success is just getting through the above statement. + # The bug was that 'course_id' argument was + # not allowed to be passed in (i.e. was throwing exception) diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index f9ddf88b5f..4b9a5a578c 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -14,11 +14,13 @@ from django.core.urlresolvers import reverse from django.contrib.auth.models import User, Group from courseware.access import _course_staff_group_name -from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user +from courseware.tests.helpers import LoginEnrollmentTestCase +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from xmodule.modulestore.django import modulestore import xmodule.modulestore.django import json + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase): ''' @@ -41,7 +43,7 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase): def make_instructor(course): group_name = _course_staff_group_name(course.location) g = Group.objects.create(name=group_name) - g.user_set.add(get_user(self.instructor)) + g.user_set.add(User.objects.get(email=self.instructor)) make_instructor(self.graded_course) @@ -67,7 +69,6 @@ class TestStaffMasqueradeAsStudent(LoginEnrollmentTestCase): self.assertTrue(sdebug in resp.content) - def toggle_masquerade(self): ''' Toggle masquerade state diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 242379d8ca..9f9bf7ba92 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -1,378 +1,24 @@ -import logging -import json -import random - -from urlparse import urlsplit, urlunsplit -from uuid import uuid4 - -from django.contrib.auth.models import User -from django.test import TestCase -from django.conf import settings from django.core.urlresolvers import reverse from django.test.utils import override_settings -import xmodule.modulestore.django - -from student.models import Registration -from xmodule.error_module import ErrorDescriptor -from xmodule.modulestore.django import modulestore -from xmodule.modulestore import Location -from xmodule.modulestore.xml_importer import import_from_xml -from xmodule.modulestore.xml import XMLModuleStore - from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from mongo_login_helpers import * +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -log = logging.getLogger("mitx." + __name__) +import xmodule.modulestore.django - -def parse_json(response): - """Parse response, which is assumed to be json""" - return json.loads(response.content) - - -def get_user(email): - '''look up a user by email''' - return User.objects.get(email=email) - - -def get_registration(email): - '''look up registration object by email''' - return Registration.objects.get(user__email=email) - - -def mongo_store_config(data_dir): - ''' - Defines default module store using MongoModuleStore - - Use of this config requires mongo to be running - ''' - store = { - 'default': { - 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } - } - } - store['direct'] = store['default'] - return store - - -def xml_store_config(data_dir): - '''Defines default module store using XMLModuleStore''' - return { - 'default': { - 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', - 'OPTIONS': { - 'data_dir': data_dir, - 'default_class': 'xmodule.hidden_module.HiddenDescriptor', - } - } - } - -TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT -TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) -TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) - - -class LoginEnrollmentTestCase(TestCase): - - ''' - Base TestCase providing support for user creation, - activation, login, and course enrollment - ''' - - def assertRedirectsNoFollow(self, response, expected_url): - """ - http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ - - Don't check that the redirected-to page loads--there should be other tests for that. - - Some of the code taken from django.test.testcases.py - """ - self.assertEqual(response.status_code, 302, - 'Response status code was %d instead of 302' - % (response.status_code)) - url = response['Location'] - - e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) - if not (e_scheme or e_netloc): - expected_url = urlunsplit(('http', 'testserver', - e_path, e_query, e_fragment)) - - self.assertEqual(url, expected_url, - "Response redirected to '%s', expected '%s'" % - (url, expected_url)) - - def setup_viewtest_user(self): - '''create a user account, activate, and log in''' - self.viewtest_email = 'view@test.com' - self.viewtest_password = 'foo' - self.viewtest_username = 'viewtest' - self.create_account(self.viewtest_username, - self.viewtest_email, self.viewtest_password) - self.activate_user(self.viewtest_email) - self.login(self.viewtest_email, self.viewtest_password) - - # ============ User creation and login ============== - - def _login(self, email, password): - '''Login. View should always return 200. The success/fail is in the - returned json''' - resp = self.client.post(reverse('login'), - {'email': email, 'password': password}) - self.assertEqual(resp.status_code, 200) - return resp - - def login(self, email, password): - '''Login, check that it worked.''' - resp = self._login(email, password) - data = parse_json(resp) - self.assertTrue(data['success']) - return resp - - def logout(self): - '''Logout, check that it worked.''' - resp = self.client.get(reverse('logout'), {}) - # should redirect - self.assertEqual(resp.status_code, 302) - return resp - - def _create_account(self, username, email, password): - '''Try to create an account. No error checking''' - resp = self.client.post('/create_account', { - 'username': username, - 'email': email, - 'password': password, - 'name': 'Fred Weasley', - 'terms_of_service': 'true', - 'honor_code': 'true', - }) - return resp - - def create_account(self, username, email, password): - '''Create the account and check that it worked''' - resp = self._create_account(username, email, password) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['success'], True) - - # Check both that the user is created, and inactive - self.assertFalse(get_user(email).is_active) - - return resp - - def _activate_user(self, email): - '''Look up the activation key for the user, then hit the activate view. - No error checking''' - activation_key = get_registration(email).activation_key - - # and now we try to activate - url = reverse('activate', kwargs={'key': activation_key}) - resp = self.client.get(url) - return resp - - def activate_user(self, email): - resp = self._activate_user(email) - self.assertEqual(resp.status_code, 200) - # Now make sure that the user is now actually activated - self.assertTrue(get_user(email).is_active) - - def try_enroll(self, course): - """Try to enroll. Return bool success instead of asserting it.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'enroll', - 'course_id': course.id, - }) - print ('Enrollment in %s result status code: %s' - % (course.location.url(), str(resp.status_code))) - return resp.status_code == 200 - - def enroll(self, course): - """Enroll the currently logged-in user, and check that it worked.""" - result = self.try_enroll(course) - self.assertTrue(result) - - def unenroll(self, course): - """Unenroll the currently logged-in user, and check that it worked.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'unenroll', - 'course_id': course.id, - }) - self.assertTrue(resp.status_code == 200) - - def check_for_get_code(self, code, url): - """ - Check that we got the expected code when accessing url via GET. - Returns the response. - """ - resp = self.client.get(url) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp - - def check_for_post_code(self, code, url, data={}): - """ - Check that we got the expected code when accessing url via POST. - Returns the response. - """ - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp - - -class ActivateLoginTest(LoginEnrollmentTestCase): - '''Test logging in and logging out''' - def setUp(self): - self.setup_viewtest_user() - - def test_activate_login(self): - '''Test login -- the setup function does all the work''' - pass - - def test_logout(self): - '''Test logout -- setup function does login''' - self.logout() - - -class PageLoaderTestCase(LoginEnrollmentTestCase): - ''' Base class that adds a function to load all pages in a modulestore ''' - - def check_random_page_loads(self, module_store): - ''' - Choose a page in the course randomly, and assert that it loads - ''' - # enroll in the course before trying to access pages - courses = module_store.get_courses() - self.assertEqual(len(courses), 1) - course = courses[0] - self.enroll(course) - course_id = course.id - - # Search for items in the course - # None is treated as a wildcard - course_loc = course.location - location_query = Location(course_loc.tag, course_loc.org, - course_loc.course, None, None, None) - - items = module_store.get_items(location_query) - - if len(items) < 1: - self.fail('Could not retrieve any items from course') - else: - descriptor = random.choice(items) - - # We have ancillary course information now as modules - # and we can't simply use 'jump_to' to view them - if descriptor.location.category == 'about': - self._assert_loads('about_course', - {'course_id': course_id}, - descriptor) - - elif descriptor.location.category == 'static_tab': - kwargs = {'course_id': course_id, - 'tab_slug': descriptor.location.name} - self._assert_loads('static_tab', kwargs, descriptor) - - elif descriptor.location.category == 'course_info': - self._assert_loads('info', {'course_id': course_id}, - descriptor) - - elif descriptor.location.category == 'custom_tag_template': - pass - - else: - - kwargs = {'course_id': course_id, - 'location': descriptor.location.url()} - - self._assert_loads('jump_to', kwargs, descriptor, - expect_redirect=True, - check_content=True) - - def _assert_loads(self, django_url, kwargs, descriptor, - expect_redirect=False, - check_content=False): - ''' - Assert that the url loads correctly. - If expect_redirect, then also check that we were redirected. - If check_content, then check that we don't get - an error message about unavailable modules. - ''' - - url = reverse(django_url, kwargs=kwargs) - response = self.client.get(url, follow=True) - - if response.status_code != 200: - self.fail('Status %d for page %s' % - (response.status_code, descriptor.location.url())) - - if expect_redirect: - self.assertEqual(response.redirect_chain[0][1], 302) - - if check_content: - unavailable_msg = "this module is temporarily unavailable" - self.assertEqual(response.content.find(unavailable_msg), -1) - self.assertFalse(isinstance(descriptor, ErrorDescriptor)) - - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase): - '''Check that all pages in test courses load properly from XML''' - - def setUp(self): - super(TestCoursesLoadTestCase_XmlModulestore, self).setUp() - self.setup_viewtest_user() - xmodule.modulestore.django._MODULESTORES = {} - - def test_toy_course_loads(self): - module_class = 'xmodule.hidden_module.HiddenDescriptor' - module_store = XMLModuleStore(TEST_DATA_DIR, - default_class=module_class, - course_dirs=['toy'], - load_error_modules=True) - - self.check_random_page_loads(module_store) +from helpers import LoginEnrollmentTestCase, check_for_get_code +from modulestore_config import TEST_DATA_MONGO_MODULESTORE @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): - '''Check that all pages in test courses load properly from Mongo''' +class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): - def setUp(self): - super(TestCoursesLoadTestCase_MongoModulestore, self).setUp() - self.setup_viewtest_user() - xmodule.modulestore.django._MODULESTORES = {} - modulestore().collection.drop() + STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] - def test_toy_course_loads(self): - module_store = modulestore() - import_from_xml(module_store, TEST_DATA_DIR, ['toy']) - self.check_random_page_loads(module_store) - - def test_full_textbooks_loads(self): - module_store = modulestore() - import_from_xml(module_store, TEST_DATA_DIR, ['full']) - - course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) - - self.assertGreater(len(course.textbooks), 0) - - -@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -class TestNavigation(MongoLoginHelpers): - - """Check that navigation state is saved properly""" + """ + Check that navigation state is saved properly. + """ def setUp(self): xmodule.modulestore.django._MODULESTORES = {} @@ -388,52 +34,67 @@ class TestNavigation(MongoLoginHelpers): self.section9 = ItemFactory.create(parent_location=self.chapter9.location, display_name='factory_section') - #Create two accounts - self.student = 'view@test.com' - self.student2 = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.student2, self.password) - self.activate_user(self.student) - self.activate_user(self.student2) + # Create student accounts and activate them. + for i in range(len(self.STUDENT_INFO)): + self.create_account('u{0}'.format(i), self.STUDENT_INFO[i][0], self.STUDENT_INFO[i][1]) + self.activate_user(self.STUDENT_INFO[i][0]) - def test_accordion_state(self): - """Make sure that the accordion remembers where you were properly""" - self.login(self.student, self.password) - self.enroll(self.course) - self.enroll(self.full) - - # First request should redirect to ToyVideos + def test_redirects_first_time(self): + """ + Verify that the first time we click on the courseware tab we are + redirected to the 'Welcome' section. + """ + self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + self.enroll(self.course, True) + self.enroll(self.full, True) resp = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) - # Don't use no-follow, because state should - # only be saved once we actually hit the section self.assertRedirects(resp, reverse( 'courseware_section', kwargs={'course_id': self.course.id, 'chapter': 'Overview', 'section': 'Welcome'})) - # Hitting the couseware tab again should - # redirect to the first chapter: 'Overview' + def test_redirects_second_time(self): + """ + Verify the accordion remembers we've already visited the Welcome section + and redirects correpondingly. + """ + self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + self.enroll(self.course, True) + self.enroll(self.full, True) + + self.client.get(reverse('courseware_section', kwargs={'course_id': self.course.id, + 'chapter': 'Overview', + 'section': 'Welcome'})) + resp = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) - self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.course.id, - 'chapter': 'Overview'})) + self.assertRedirects(resp, reverse('courseware_chapter', + kwargs={'course_id': self.course.id, + 'chapter': 'Overview'})) - # Now we directly navigate to a section in a different chapter - self.check_for_get_code(200, reverse('courseware_section', - kwargs={'course_id': self.course.id, - 'chapter': 'factory_chapter', - 'section': 'factory_section'})) + def test_accordion_state(self): + """ + Verify the accordion remembers which chapter you were last viewing. + """ - # And now hitting the courseware tab should redirect to 'secret:magic' + self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + self.enroll(self.course, True) + self.enroll(self.full, True) + + # Now we directly navigate to a section in a chapter other than 'Overview'. + check_for_get_code(self, 200, reverse('courseware_section', + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter', + 'section': 'factory_section'})) + + # And now hitting the courseware tab should redirect to 'factory_chapter' resp = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) - self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', - kwargs={'course_id': self.course.id, - 'chapter': 'factory_chapter'})) + self.assertRedirects(resp, reverse('courseware_chapter', + kwargs={'course_id': self.course.id, + 'chapter': 'factory_chapter'})) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index da4f40e0db..ffae4688bf 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -1,80 +1,56 @@ -import logging import datetime import pytz import random -from uuid import uuid4 +import xmodule.modulestore.django from django.contrib.auth.models import User, Group from django.conf import settings from django.core.urlresolvers import reverse from django.test.utils import override_settings -import xmodule.modulestore.django - # Need access to internal func to put users in the right group from courseware.access import (has_access, _course_staff_group_name, course_beta_test_group_name) -from mongo_login_helpers import MongoLoginHelpers +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -log = logging.getLogger("mitx." + __name__) - - -def get_user(email): - '''look up a user by email''' - return User.objects.get(email=email) - - -def update_course(course, data): - """ - Updates the version of course in the mongo modulestore - with the metadata in data and returns the updated version. - """ - - store = xmodule.modulestore.django.modulestore() - - store.update_item(course.location, data) - - store.update_metadata(course.location, data) - - updated_course = store.get_instance(course.id, course.location) - - return updated_course - - -def mongo_store_config(data_dir): - ''' - Defines default module store using MongoModuleStore - - Use of this config requires mongo to be running - ''' - store = { - 'default': { - 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } - } - } - store['direct'] = store['default'] - return store - - -TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT -TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) +from helpers import LoginEnrollmentTestCase, check_for_get_code +from modulestore_config import TEST_DATA_MONGO_MODULESTORE @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) -class TestViewAuth(MongoLoginHelpers): - """Check that view authentication works properly""" +class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): + """ + Check that view authentication works properly. + """ + + ACCOUNT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] + + @classmethod + def _instructor_urls(self, course): + """ + List of urls that only instructors/staff should be able to see. + """ + urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( + 'instructor_dashboard', + 'gradebook', + 'grade_summary',)] + + urls.append(reverse('student_progress', + kwargs={'course_id': course.id, + 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id})) + return urls + + @staticmethod + def _reverse_urls(names, course): + """ + Reverse a list of course urls. + """ + return [reverse(name, kwargs={'course_id': course.id}) + for name in names] def setUp(self): xmodule.modulestore.django._MODULESTORES = {} @@ -87,98 +63,105 @@ class TestViewAuth(MongoLoginHelpers): display_name='courseware') self.sub_overview_chapter = ItemFactory.create(parent_location=self.sub_courseware_chapter.location, display_name='Overview') - self.progress_chapter = ItemFactory.create(parent_location=self.course.location, - display_name='progress') - self.info_chapter = ItemFactory.create(parent_location=self.course.location, - display_name='info') self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location, display_name='Welcome') - self.somewhere_in_progress = ItemFactory.create(parent_location=self.progress_chapter.location, - display_name='1') - # Create two accounts - self.student = 'view@test.com' - self.instructor = 'view2@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.create_account('u2', self.instructor, self.password) - self.activate_user(self.student) - self.activate_user(self.instructor) + # Create two accounts and activate them. + for i in range(len(self.ACCOUNT_INFO)): + self.create_account('u{0}'.format(i), self.ACCOUNT_INFO[i][0], self.ACCOUNT_INFO[i][1]) + self.activate_user(self.ACCOUNT_INFO[i][0]) - def test_instructor_pages(self): - """Make sure only instructors for the course - or staff can load the instructor - dashboard, the grade views, and student profile pages""" + def test_redirection_unenrolled(self): + """ + Verify unenrolled student is redirected to the 'about' section of the chapter + instead of the 'Welcome' section after clicking on the courseware tab. + """ + + self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + response = self.client.get(reverse('courseware', + kwargs={'course_id': self.course.id})) + self.assertRedirects(response, + reverse('about_course', + args=[self.course.id])) + + def test_redirection_enrolled(self): + """ + Verify enrolled student is redirected to the 'Welcome' section of + the chapter after clicking on the courseware tab. + """ + + self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.enroll(self.course) - # First, try with an enrolled student - self.login(self.student, self.password) - # shouldn't work before enroll response = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) - self.assertRedirectsNoFollow(response, - reverse('about_course', - args=[self.course.id])) + self.assertRedirects(response, + reverse('courseware_section', + kwargs={'course_id': self.course.id, + 'chapter': 'Overview', + 'section': 'Welcome'})) + + def test_instructor_page_access_nonstaff(self): + """ + Verify non-staff cannot load the instructor + dashboard, the grade views, and student profile pages. + """ + + self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.enroll(self.course) self.enroll(self.full) - # should work now -- redirect to first page - response = self.client.get(reverse('courseware', - kwargs={'course_id': self.course.id})) - - self.assertRedirectsNoFollow(response, - reverse('courseware_section', - kwargs={'course_id': self.course.id, - 'chapter': 'Overview', - 'section': 'Welcome'})) - - def instructor_urls(course): - "list of urls that only instructors/staff should be able to see" - urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( - 'instructor_dashboard', - 'gradebook', - 'grade_summary',)] - - urls.append(reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id})) - return urls # Randomly sample an instructor page - url = random.choice(instructor_urls(self.course) + - instructor_urls(self.full)) + url = random.choice(self._instructor_urls(self.course) + + self._instructor_urls(self.full)) # Shouldn't be able to get to the instructor pages print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) + check_for_get_code(self, 404, url) - # Make the instructor staff in the toy course + def test_instructor_course_access(self): + """ + Verify instructor can load the instructor dashboard, the grade views, + and student profile pages for their course. + """ + + # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) + group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) - self.logout() - self.login(self.instructor, self.password) + self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) - # Now should be able to get to the toy course, but not the full course - url = random.choice(instructor_urls(self.course)) + # Now should be able to get to self.course, but not self.full + url = random.choice(self._instructor_urls(self.course)) print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) + check_for_get_code(self, 200, url) - url = random.choice(instructor_urls(self.full)) + url = random.choice(self._instructor_urls(self.full)) print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) + check_for_get_code(self, 404, url) - # now also make the instructor staff - instructor = get_user(self.instructor) + def test_instructor_as_staff_access(self): + """ + Verify the instructor can load staff pages if he is given + staff permissions. + """ + + self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + + # now make the instructor also staff + instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) instructor.is_staff = True instructor.save() # and now should be able to load both - url = random.choice(instructor_urls(self.course) + - instructor_urls(self.full)) + url = random.choice(self._instructor_urls(self.course) + + self._instructor_urls(self.full)) print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) + check_for_get_code(self, 200, url) def run_wrapped(self, test): """ @@ -196,42 +179,47 @@ class TestViewAuth(MongoLoginHelpers): settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD def test_dark_launch(self): - """Make sure that before course start, students can't access course - pages, but instructors can""" + """ + Make sure that before course start, students can't access course + pages, but instructors can. + """ self.run_wrapped(self._do_test_dark_launch) def test_enrollment_period(self): - """Check that enrollment periods work""" + """ + Check that enrollment periods work. + """ self.run_wrapped(self._do_test_enrollment_period) def test_beta_period(self): - """Check that beta-test access works""" + """ + Check that beta-test access works. + """ self.run_wrapped(self._do_test_beta_period) def _do_test_dark_launch(self): - """Actually do the test, relying on settings to be right.""" + """ + Actually do the test, relying on settings to be right. + """ # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) - self.course.lms.start = tomorrow - self.full.lms.start = tomorrow + course_data = {'start': tomorrow} + full_data = {'start': tomorrow} + self.course = self.update_course(self.course, course_data) + self.full = self.update_course(self.full, full_data) self.assertFalse(self.course.has_started()) self.assertFalse(self.full.has_started()) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - def reverse_urls(names, course): - """Reverse a list of course urls""" - return [reverse(name, kwargs={'course_id': course.id}) - for name in names] - def dark_student_urls(course): """ - list of urls that students should be able to see only + List of urls that students should be able to see only after launch, but staff should see before """ - urls = reverse_urls(['info', 'progress'], course) + urls = self._reverse_urls(['info', 'progress'], course) urls.extend([ reverse('book', kwargs={'course_id': course.id, 'book_index': index}) @@ -241,38 +229,50 @@ class TestViewAuth(MongoLoginHelpers): def light_student_urls(course): """ - list of urls that students should be able to see before + List of urls that students should be able to see before launch. """ - urls = reverse_urls(['about_course'], course) + urls = self._reverse_urls(['about_course'], course) urls.append(reverse('courses')) return urls def instructor_urls(course): - """list of urls that only instructors/staff should be able to see""" - urls = reverse_urls(['instructor_dashboard', - 'gradebook', 'grade_summary'], course) + """ + List of urls that only instructors/staff should be able to see. + """ + urls = self._reverse_urls(['instructor_dashboard', + 'gradebook', 'grade_summary'], course) return urls - def check_non_staff(course): - """Check that access is right for non-staff in course""" + def check_non_staff_light(course): + """ + Check that non-staff have access to light urls. + """ + print '=== Checking non-staff access for {0}'.format(course.id) + + # Randomly sample a light url + url = random.choice(light_student_urls(course)) + print 'checking for 200 on {0}'.format(url) + check_for_get_code(self, 200, url) + + def check_non_staff_dark(course): + """ + Check that non-staff don't have access to dark urls. + """ print '=== Checking non-staff access for {0}'.format(course.id) # Randomly sample a dark url url = random.choice(instructor_urls(course) + dark_student_urls(course) + - reverse_urls(['courseware'], course)) + self._reverse_urls(['courseware'], course)) print 'checking for 404 on {0}'.format(url) - self.check_for_get_code(404, url) - - # Randomly sample a light url - url = random.choice(light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) + check_for_get_code(self, 404, url) def check_staff(course): - """Check that access is right for staff in course""" + """ + Check that access is right for staff in course. + """ print '=== Checking staff access for {0}'.format(course.id) # Randomly sample a url @@ -280,7 +280,7 @@ class TestViewAuth(MongoLoginHelpers): dark_student_urls(course) + light_student_urls(course)) print 'checking for 200 on {0}'.format(url) - self.check_for_get_code(200, url) + check_for_get_code(self, 200, url) # The student progress tab is not accessible to a student # before launch, so the instructor view-as-student feature @@ -290,43 +290,46 @@ class TestViewAuth(MongoLoginHelpers): # user (the student), and the requesting user (the prof) url = reverse('student_progress', kwargs={'course_id': course.id, - 'student_id': get_user(self.student).id}) + 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id}) print 'checking for 404 on view-as-student: {0}'.format(url) - self.check_for_get_code(404, url) + check_for_get_code(self, 404, url) # The courseware url should redirect, not 200 - url = reverse_urls(['courseware'], course)[0] - self.check_for_get_code(302, url) + url = self._reverse_urls(['courseware'], course)[0] + check_for_get_code(self, 302, url) # First, try with an enrolled student print '=== Testing student access....' - self.login(self.student, self.password) - self.enroll(self.course) - self.enroll(self.full) + self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.enroll(self.course, True) + self.enroll(self.full, True) # shouldn't be able to get to anything except the light pages - check_non_staff(self.course) - check_non_staff(self.full) + check_non_staff_light(self.course) + check_non_staff_dark(self.course) + check_non_staff_light(self.full) + check_non_staff_dark(self.full) print '=== Testing course instructor access....' - # Make the instructor staff in the toy course + # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) + group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) self.logout() - self.login(self.instructor, self.password) + self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) # Enroll in the classes---can't see courseware otherwise. - self.enroll(self.course) - self.enroll(self.full) + self.enroll(self.course, True) + self.enroll(self.full, True) # should now be able to get to everything for self.course - check_non_staff(self.full) + check_non_staff_light(self.full) + check_non_staff_dark(self.full) check_staff(self.course) print '=== Testing staff access....' # now also make the instructor staff - instructor = get_user(self.instructor) + instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) instructor.is_staff = True instructor.save() @@ -335,7 +338,9 @@ class TestViewAuth(MongoLoginHelpers): check_staff(self.full) def _do_test_enrollment_period(self): - """Actually do the test, relying on settings to be right.""" + """ + Actually do the test, relying on settings to be right. + """ # Make courses start in the future now = datetime.datetime.now(pytz.UTC) @@ -348,42 +353,44 @@ class TestViewAuth(MongoLoginHelpers): print "changing" # self.course's enrollment period hasn't started - self.course = update_course(self.course, course_data) + self.course = self.update_course(self.course, course_data) # full course's has - self.full = update_course(self.full, full_data) + self.full = self.update_course(self.full, full_data) print "login" # First, try with an enrolled student print '=== Testing student access....' - self.login(self.student, self.password) - self.assertFalse(self.try_enroll(self.course)) - self.assertTrue(self.try_enroll(self.full)) + self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.assertFalse(self.enroll(self.course)) + self.assertTrue(self.enroll(self.full)) print '=== Testing course instructor access....' - # Make the instructor staff in the toy course + # Make the instructor staff in the self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) + group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) print "logout/login" self.logout() - self.login(self.instructor, self.password) - print "Instructor should be able to enroll in toy course" - self.assertTrue(self.try_enroll(self.course)) + self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + print "Instructor should be able to enroll in self.course" + self.assertTrue(self.enroll(self.course)) print '=== Testing staff access....' # now make the instructor global staff, but not in the instructor group - group.user_set.remove(get_user(self.instructor)) - instructor = get_user(self.instructor) + group.user_set.remove(User.objects.get(email=self.ACCOUNT_INFO[1][0])) + instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) instructor.is_staff = True instructor.save() # unenroll and try again self.unenroll(self.course) - self.assertTrue(self.try_enroll(self.course)) + self.assertTrue(self.enroll(self.course)) def _do_test_beta_period(self): - """Actually test beta periods, relying on settings to be right.""" + """ + Actually test beta periods, relying on settings to be right. + """ # trust, but verify :) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) @@ -391,18 +398,17 @@ class TestViewAuth(MongoLoginHelpers): # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) - # nextday = tomorrow + 24 * 3600 - # yesterday = time.time() - 24 * 3600 + course_data = {'start': tomorrow} # self.course's hasn't started - self.course.lms.start = tomorrow + self.course = self.update_course(self.course, course_data) self.assertFalse(self.course.has_started()) # but should be accessible for beta testers self.course.lms.days_early_for_beta = 2 # student user shouldn't see it - student_user = get_user(self.student) + student_user = User.objects.get(email=self.ACCOUNT_INFO[0][0]) self.assertFalse(has_access(student_user, self.course, 'load')) # now add the student to the beta test group diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 3e39227171..43b190c04b 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -1,275 +1,172 @@ ''' Test for lms courseware app ''' -import logging -import json import random -from urlparse import urlsplit, urlunsplit -from uuid import uuid4 - -from django.contrib.auth.models import User, Group from django.test import TestCase -from django.test.client import RequestFactory -from django.conf import settings from django.core.urlresolvers import reverse from django.test.utils import override_settings import xmodule.modulestore.django -# Need access to internal func to put users in the right group -from courseware import grades -from courseware.model_data import ModelDataCache -from courseware.access import (has_access, _course_staff_group_name, - course_beta_test_group_name) - -from student.models import Registration from xmodule.error_module import ErrorDescriptor from xmodule.modulestore.django import modulestore from xmodule.modulestore import Location from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml import XMLModuleStore -import datetime -from django.utils.timezone import UTC -from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from helpers import LoginEnrollmentTestCase +from modulestore_config import TEST_DATA_DIR, TEST_DATA_XML_MODULESTORE, TEST_DATA_MONGO_MODULESTORE, TEST_DATA_DRAFT_MONGO_MODULESTORE -log = logging.getLogger("mitx." + __name__) +class ActivateLoginTest(LoginEnrollmentTestCase): + """ + Test logging in and logging out. + """ + def setUp(self): + self.setup_user() - -def parse_json(response): - """Parse response, which is assumed to be json""" - return json.loads(response.content) - - -def get_user(email): - '''look up a user by email''' - return User.objects.get(email=email) - - -def get_registration(email): - '''look up registration object by email''' - return Registration.objects.get(user__email=email) - - -def mongo_store_config(data_dir): - ''' - Defines default module store using MongoModuleStore - - Use of this config requires mongo to be running - ''' - store = { - 'default': { - 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string' - } - } - } - store['direct'] = store['default'] - return store - - -def draft_mongo_store_config(data_dir): - '''Defines default module store using DraftMongoModuleStore''' - return { - 'default': { - 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } - }, - 'direct': { - 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } - } - } - - -def xml_store_config(data_dir): - '''Defines default module store using XMLModuleStore''' - return { - 'default': { - 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', - 'OPTIONS': { - 'data_dir': data_dir, - 'default_class': 'xmodule.hidden_module.HiddenDescriptor', - } - } - } - -TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT -TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) -TEST_DATA_MONGO_MODULESTORE = mongo_store_config(TEST_DATA_DIR) -TEST_DATA_DRAFT_MONGO_MODULESTORE = draft_mongo_store_config(TEST_DATA_DIR) - - -class LoginEnrollmentTestCase(TestCase): - - ''' - Base TestCase providing support for user creation, - activation, login, and course enrollment - ''' - - def assertRedirectsNoFollow(self, response, expected_url): + def test_activate_login(self): """ - http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ - - Don't check that the redirected-to page loads--there should be other tests for that. - - Some of the code taken from django.test.testcases.py + Test login -- the setup function does all the work. """ - self.assertEqual(response.status_code, 302, - 'Response status code was %d instead of 302' - % (response.status_code)) - url = response['Location'] + pass - e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) - if not (e_scheme or e_netloc): - expected_url = urlunsplit(('http', 'testserver', - e_path, e_query, e_fragment)) - - self.assertEqual(url, expected_url, - "Response redirected to '%s', expected '%s'" % - (url, expected_url)) - - def setup_viewtest_user(self): - '''create a user account, activate, and log in''' - self.viewtest_email = 'view@test.com' - self.viewtest_password = 'foo' - self.viewtest_username = 'viewtest' - self.create_account(self.viewtest_username, - self.viewtest_email, self.viewtest_password) - self.activate_user(self.viewtest_email) - self.login(self.viewtest_email, self.viewtest_password) - - # ============ User creation and login ============== - - def _login(self, email, password): - '''Login. View should always return 200. The success/fail is in the - returned json''' - resp = self.client.post(reverse('login'), - {'email': email, 'password': password}) - self.assertEqual(resp.status_code, 200) - return resp - - def login(self, email, password): - '''Login, check that it worked.''' - resp = self._login(email, password) - data = parse_json(resp) - self.assertTrue(data['success']) - return resp - - def logout(self): - '''Logout, check that it worked.''' - resp = self.client.get(reverse('logout'), {}) - # should redirect - self.assertEqual(resp.status_code, 302) - return resp - - def _create_account(self, username, email, password): - '''Try to create an account. No error checking''' - resp = self.client.post('/create_account', { - 'username': username, - 'email': email, - 'password': password, - 'name': 'Fred Weasley', - 'terms_of_service': 'true', - 'honor_code': 'true', - }) - return resp - - def create_account(self, username, email, password): - '''Create the account and check that it worked''' - resp = self._create_account(username, email, password) - self.assertEqual(resp.status_code, 200) - data = parse_json(resp) - self.assertEqual(data['success'], True) - - # Check both that the user is created, and inactive - self.assertFalse(get_user(email).is_active) - - return resp - - def _activate_user(self, email): - '''Look up the activation key for the user, then hit the activate view. - No error checking''' - activation_key = get_registration(email).activation_key - - # and now we try to activate - url = reverse('activate', kwargs={'key': activation_key}) - resp = self.client.get(url) - return resp - - def activate_user(self, email): - resp = self._activate_user(email) - self.assertEqual(resp.status_code, 200) - # Now make sure that the user is now actually activated - self.assertTrue(get_user(email).is_active) - - def try_enroll(self, course): - """Try to enroll. Return bool success instead of asserting it.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'enroll', - 'course_id': course.id, - }) - print ('Enrollment in %s result status code: %s' - % (course.location.url(), str(resp.status_code))) - return resp.status_code == 200 - - def enroll(self, course): - """Enroll the currently logged-in user, and check that it worked.""" - result = self.try_enroll(course) - self.assertTrue(result) - - def unenroll(self, course): - """Unenroll the currently logged-in user, and check that it worked.""" - resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'unenroll', - 'course_id': course.id, - }) - self.assertTrue(resp.status_code == 200) - - def check_for_get_code(self, code, url): + def test_logout(self): """ - Check that we got the expected code when accessing url via GET. - Returns the response. + Test logout -- setup function does login. """ - resp = self.client.get(url) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp + self.logout() - def check_for_post_code(self, code, url, data={}): + +class PageLoaderTestCase(LoginEnrollmentTestCase): + """ + Base class that adds a function to load all pages in a modulestore. + """ + + def check_random_page_loads(self, module_store): """ - Check that we got the expected code when accessing url via POST. - Returns the response. + Choose a page in the course randomly, and assert that it loads. """ - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp + # enroll in the course before trying to access pages + courses = module_store.get_courses() + self.assertEqual(len(courses), 1) + course = courses[0] + self.enroll(course, True) + course_id = course.id + + # Search for items in the course + # None is treated as a wildcard + course_loc = course.location + location_query = Location(course_loc.tag, course_loc.org, + course_loc.course, None, None, None) + + items = module_store.get_items(location_query) + + if len(items) < 1: + self.fail('Could not retrieve any items from course') + else: + descriptor = random.choice(items) + + # We have ancillary course information now as modules + # and we can't simply use 'jump_to' to view them + if descriptor.location.category == 'about': + self._assert_loads('about_course', + {'course_id': course_id}, + descriptor) + + elif descriptor.location.category == 'static_tab': + kwargs = {'course_id': course_id, + 'tab_slug': descriptor.location.name} + self._assert_loads('static_tab', kwargs, descriptor) + + elif descriptor.location.category == 'course_info': + self._assert_loads('info', {'course_id': course_id}, + descriptor) + + elif descriptor.location.category == 'custom_tag_template': + pass + + else: + + kwargs = {'course_id': course_id, + 'location': descriptor.location.url()} + + self._assert_loads('jump_to', kwargs, descriptor, + expect_redirect=True, + check_content=True) + + def _assert_loads(self, django_url, kwargs, descriptor, + expect_redirect=False, + check_content=False): + """ + Assert that the url loads correctly. + If expect_redirect, then also check that we were redirected. + If check_content, then check that we don't get + an error message about unavailable modules. + """ + + url = reverse(django_url, kwargs=kwargs) + response = self.client.get(url, follow=True) + + if response.status_code != 200: + self.fail('Status %d for page %s' % + (response.status_code, descriptor.location.url())) + + if expect_redirect: + self.assertEqual(response.redirect_chain[0][1], 302) + + if check_content: + unavailable_msg = "this module is temporarily unavailable" + self.assertEqual(response.content.find(unavailable_msg), -1) + self.assertFalse(isinstance(descriptor, ErrorDescriptor)) + + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestCoursesLoadTestCase_XmlModulestore(PageLoaderTestCase): + """ + Check that all pages in test courses load properly from XML. + """ + + def setUp(self): + super(TestCoursesLoadTestCase_XmlModulestore, self).setUp() + self.setup_user() + xmodule.modulestore.django._MODULESTORES = {} + + def test_toy_course_loads(self): + module_class = 'xmodule.hidden_module.HiddenDescriptor' + module_store = XMLModuleStore(TEST_DATA_DIR, + default_class=module_class, + course_dirs=['toy'], + load_error_modules=True) + + self.check_random_page_loads(module_store) + + +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestCoursesLoadTestCase_MongoModulestore(PageLoaderTestCase): + """ + Check that all pages in test courses load properly from Mongo. + """ + + def setUp(self): + super(TestCoursesLoadTestCase_MongoModulestore, self).setUp() + self.setup_user() + xmodule.modulestore.django._MODULESTORES = {} + modulestore().collection.drop() + + def test_toy_course_loads(self): + module_store = modulestore() + import_from_xml(module_store, TEST_DATA_DIR, ['toy']) + self.check_random_page_loads(module_store) + + def test_full_textbooks_loads(self): + module_store = modulestore() + import_from_xml(module_store, TEST_DATA_DIR, ['full']) + + course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None])) + + self.assertGreater(len(course.textbooks), 0) @override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE) @@ -284,134 +181,3 @@ class TestDraftModuleStore(TestCase): # test success is just getting through the above statement. # The bug was that 'course_id' argument was # not allowed to be passed in (i.e. was throwing exception) - - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestSubmittingProblems(LoginEnrollmentTestCase): - """Check that a course gets graded properly""" - - # Subclasses should specify the course slug - course_slug = "UNKNOWN" - course_when = "UNKNOWN" - - def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} - - course_name = "edX/%s/%s" % (self.course_slug, self.course_when) - self.course = modulestore().get_course(course_name) - assert self.course, "Couldn't load course %r" % course_name - - # create a test student - self.student = 'view@test.com' - self.password = 'foo' - self.create_account('u1', self.student, self.password) - self.activate_user(self.student) - self.enroll(self.course) - - self.student_user = get_user(self.student) - - self.factory = RequestFactory() - - def problem_location(self, problem_url_name): - return "i4x://edX/{}/problem/{}".format(self.course_slug, problem_url_name) - - def modx_url(self, problem_location, dispatch): - return reverse( - 'modx_dispatch', - kwargs={ - 'course_id': self.course.id, - 'location': problem_location, - 'dispatch': dispatch, - } - ) - - def submit_question_answer(self, problem_url_name, responses): - """ - Submit answers to a question. - - Responses is a dict mapping problem ids (not sure of the right term) - to answers: - {'2_1': 'Correct', '2_2': 'Incorrect'} - - """ - problem_location = self.problem_location(problem_url_name) - modx_url = self.modx_url(problem_location, 'problem_check') - answer_key_prefix = 'input_i4x-edX-{}-problem-{}_'.format(self.course_slug, problem_url_name) - resp = self.client.post(modx_url, - { (answer_key_prefix + k): v for k, v in responses.items() } - ) - - return resp - - def reset_question_answer(self, problem_url_name): - '''resets specified problem for current user''' - problem_location = self.problem_location(problem_url_name) - modx_url = self.modx_url(problem_location, 'problem_reset') - resp = self.client.post(modx_url) - return resp - - -@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) -class TestSchematicResponse(TestSubmittingProblems): - """Check that we can submit a schematic response, and it answers properly.""" - - course_slug = "embedded_python" - course_when = "2013_Spring" - - def test_schematic(self): - resp = self.submit_question_answer('schematic_problem', - { '2_1': json.dumps( - [['transient', {'Z': [ - [0.0000004, 2.8], - [0.0000009, 2.8], - [0.0000014, 2.8], - [0.0000019, 2.8], - [0.0000024, 2.8], - [0.0000029, 0.2], - [0.0000034, 0.2], - [0.0000039, 0.2] - ]}]] - ) - }) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'correct') - - self.reset_question_answer('schematic_problem') - resp = self.submit_question_answer('schematic_problem', - { '2_1': json.dumps( - [['transient', {'Z': [ - [0.0000004, 2.8], - [0.0000009, 0.0], # wrong. - [0.0000014, 2.8], - [0.0000019, 2.8], - [0.0000024, 2.8], - [0.0000029, 0.2], - [0.0000034, 0.2], - [0.0000039, 0.2] - ]}]] - ) - }) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'incorrect') - - def test_check_function(self): - resp = self.submit_question_answer('cfn_problem', {'2_1': "0, 1, 2, 3, 4, 5, 'Outside of loop', 6"}) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'correct') - - self.reset_question_answer('cfn_problem') - - resp = self.submit_question_answer('cfn_problem', {'2_1': "xyzzy!"}) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'incorrect') - - def test_computed_answer(self): - resp = self.submit_question_answer('computed_answer', {'2_1': "Xyzzy"}) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'correct') - - self.reset_question_answer('computed_answer') - - resp = self.submit_question_answer('computed_answer', {'2_1': "NO!"}) - respdata = json.loads(resp.content) - self.assertEqual(respdata['success'], 'incorrect') diff --git a/lms/djangoapps/instructor/tests/test_download_csv.py b/lms/djangoapps/instructor/tests/test_download_csv.py index 29e18eee4d..fd5bd562ba 100644 --- a/lms/djangoapps/instructor/tests/test_download_csv.py +++ b/lms/djangoapps/instructor/tests/test_download_csv.py @@ -11,12 +11,13 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/inst from django.test.utils import override_settings # Need access to internal func to put users in the right group -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, User from django.core.urlresolvers import reverse from courseware.access import _course_staff_group_name -from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user +from courseware.tests.helpers import LoginEnrollmentTestCase +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from xmodule.modulestore.django import modulestore import xmodule.modulestore.django @@ -45,7 +46,7 @@ class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase): def make_instructor(course): group_name = _course_staff_group_name(course.location) g = Group.objects.create(name=group_name) - g.user_set.add(get_user(self.instructor)) + g.user_set.add(User.objects.get(email=self.instructor)) make_instructor(self.toy) @@ -72,7 +73,7 @@ class TestInstructorDashboardGradeDownloadCSV(LoginEnrollmentTestCase): # All the not-actually-in-the-course hw and labs come from the # default grading policy string in graders.py expected_body = '''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final" -"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0" +"2","u2","username","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0" ''' self.assertEqual(body, expected_body, msg) diff --git a/lms/djangoapps/instructor/tests/test_enrollment.py b/lms/djangoapps/instructor/tests/test_enrollment.py index ce5f2d2e50..e70ccc6ffd 100644 --- a/lms/djangoapps/instructor/tests/test_enrollment.py +++ b/lms/djangoapps/instructor/tests/test_enrollment.py @@ -7,7 +7,8 @@ from django.test.utils import override_settings from django.contrib.auth.models import Group, User from django.core.urlresolvers import reverse from courseware.access import _course_staff_group_name -from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user +from courseware.tests.helpers import LoginEnrollmentTestCase +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from xmodule.modulestore.django import modulestore import xmodule.modulestore.django from student.models import CourseEnrollment, CourseEnrollmentAllowed @@ -40,7 +41,7 @@ class TestInstructorEnrollsStudent(LoginEnrollmentTestCase): def make_instructor(course): group_name = _course_staff_group_name(course.location) g = Group.objects.create(name=group_name) - g.user_set.add(get_user(self.instructor)) + g.user_set.add(User.objects.get(email=self.instructor)) make_instructor(self.toy) diff --git a/lms/djangoapps/instructor/tests/test_forum_admin.py b/lms/djangoapps/instructor/tests/test_forum_admin.py index 7b4e729867..90dadd569e 100644 --- a/lms/djangoapps/instructor/tests/test_forum_admin.py +++ b/lms/djangoapps/instructor/tests/test_forum_admin.py @@ -6,7 +6,7 @@ Unit tests for instructor dashboard forum administration from django.test.utils import override_settings # Need access to internal func to put users in the right group -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, User from django.core.urlresolvers import reverse from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \ @@ -14,7 +14,8 @@ from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \ from django_comment_client.utils import has_forum_access from courseware.access import _course_staff_group_name -from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user +from courseware.tests.helpers import LoginEnrollmentTestCase +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE from xmodule.modulestore.django import modulestore import xmodule.modulestore.django @@ -55,7 +56,7 @@ class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase): group_name = _course_staff_group_name(self.toy.location) g = Group.objects.create(name=group_name) - g.user_set.add(get_user(self.instructor)) + g.user_set.add(User.objects.get(email=self.instructor)) self.logout() self.login(self.instructor, self.password) @@ -146,4 +147,4 @@ class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase): added_roles.append(rolename) added_roles.sort() roles = ', '.join(added_roles) - self.assertTrue(response.content.find('{0}'.format(roles)) >= 0, 'not finding roles "{0}"'.format(roles)) \ No newline at end of file + self.assertTrue(response.content.find('{0}'.format(roles)) >= 0, 'not finding roles "{0}"'.format(roles)) diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py index 13d780df12..db19d212a2 100644 --- a/lms/djangoapps/open_ended_grading/tests.py +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -8,8 +8,7 @@ import json from mock import MagicMock, patch, Mock from django.core.urlresolvers import reverse -from django.contrib.auth.models import Group -from django.http import HttpResponse +from django.contrib.auth.models import Group, User from django.conf import settings from mitxmako.shortcuts import render_to_string @@ -21,7 +20,6 @@ from xmodule.x_module import ModuleSystem from open_ended_grading import staff_grading_service, views from courseware.access import _course_staff_group_name -from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user import logging @@ -31,6 +29,9 @@ from django.test.utils import override_settings from xmodule.tests import test_util_open_ended from courseware.tests import factories +from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE +from courseware.tests.helpers import LoginEnrollmentTestCase, check_for_get_code, check_for_post_code + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestStaffGradingService(LoginEnrollmentTestCase): @@ -58,7 +59,7 @@ class TestStaffGradingService(LoginEnrollmentTestCase): def make_instructor(course): group_name = _course_staff_group_name(course.location) group = Group.objects.create(name=group_name) - group.user_set.add(get_user(self.instructor)) + group.user_set.add(User.objects.get(email=self.instructor)) make_instructor(self.toy) @@ -75,8 +76,8 @@ class TestStaffGradingService(LoginEnrollmentTestCase): # both get and post should return 404 for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): url = reverse(view_name, kwargs={'course_id': self.course_id}) - self.check_for_get_code(404, url) - self.check_for_post_code(404, url) + check_for_get_code(self, 404, url) + check_for_post_code(self, 404, url) def test_get_next(self): self.login(self.instructor, self.password) @@ -84,7 +85,7 @@ class TestStaffGradingService(LoginEnrollmentTestCase): url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id}) data = {'location': self.location} - response = self.check_for_post_code(200, url, data) + response = check_for_post_code(self, 200, url, data) content = json.loads(response.content) @@ -113,7 +114,7 @@ class TestStaffGradingService(LoginEnrollmentTestCase): if skip: data.update({'skipped': True}) - response = self.check_for_post_code(200, url, data) + response = check_for_post_code(self, 200, url, data) content = json.loads(response.content) self.assertTrue(content['success'], str(content)) self.assertEquals(content['submission_id'], self.mock_service.cnt) @@ -130,7 +131,7 @@ class TestStaffGradingService(LoginEnrollmentTestCase): url = reverse('staff_grading_get_problem_list', kwargs={'course_id': self.course_id}) data = {} - response = self.check_for_post_code(200, url, data) + response = check_for_post_code(self, 200, url, data) content = json.loads(response.content) self.assertTrue(content['success'], str(content)) diff --git a/lms/urls.py b/lms/urls.py index 74ac44cf59..2d85fe1e66 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -36,7 +36,7 @@ urlpatterns = ('', # nopep8 url(r'^login_ajax$', 'student.views.login_user', name="login"), url(r'^login_ajax/(?P[^/]*)$', 'student.views.login_user'), url(r'^logout$', 'student.views.logout_user', name='logout'), - url(r'^create_account$', 'student.views.create_account'), + url(r'^create_account$', 'student.views.create_account', name='create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), url(r'^begin_exam_registration/(?P[^/]+/[^/]+/[^/]+)$', 'student.views.begin_exam_registration', name="begin_exam_registration"), From 32a0a2d29dbf60713d355fc51f9a6e296af878de Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 11:13:59 -0400 Subject: [PATCH 06/15] In the middle of addressing pull request comments. This is a safety commit in case I have to revert some changes I'm about to make. --- .../xmodule/modulestore/tests/django_utils.py | 8 ++- .../xmodule/modulestore/tests/factories.py | 8 +-- .../courseware/tests/check_request_code.py | 24 ------- lms/djangoapps/courseware/tests/helpers.py | 33 ++++----- .../courseware/tests/modulestore_config.py | 16 +++-- .../courseware/tests/test_navigation.py | 16 +++-- .../tests/test_view_authentication.py | 72 +++++++++++++------ 7 files changed, 98 insertions(+), 79 deletions(-) delete mode 100644 lms/djangoapps/courseware/tests/check_request_code.py diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 944b9e5bd4..a2306a5c6b 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -14,10 +14,16 @@ class ModuleStoreTestCase(TestCase): collection with templates before running the TestCase and drops it they are finished. """ - def update_course(self, course, data): + @staticmethod + def update_course(course, data): """ Updates the version of course in the mongo modulestore with the metadata in data and returns the updated version. + + 'course' is an instance of CourseDescriptor for which we want + to update metadata. + + 'data' is a dictionary with an entry for each CourseField we want to update. """ store = xmodule.modulestore.django.modulestore() diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 4f63fbc1d2..82ff61204a 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -60,8 +60,8 @@ class XModuleCourseFactory(Factory): if data is not None: store.update_item(new_course.location, data) - '''update_item updates the the course as it exists in the modulestore, but doesn't - update the instance we are working with, so have to refetch the course after updating it.''' + #update_item updates the the course as it exists in the modulestore, but doesn't + #update the instance we are working with, so have to refetch the course after updating it. new_course = store.get_instance(new_course.id, new_course.location) return new_course @@ -152,8 +152,8 @@ class XModuleItemFactory(Factory): if new_item.location.category not in DETACHED_CATEGORIES: store.update_children(parent_location, parent.children + [new_item.location.url()]) - '''update_children updates the the item as it exists in the modulestore, but doesn't - update the instance we are working with, so have to refetch the item after updating it.''' + #update_children updates the the item as it exists in the modulestore, but doesn't + #update the instance we are working with, so have to refetch the item after updating it. new_item = store.get_item(new_item.location) return new_item diff --git a/lms/djangoapps/courseware/tests/check_request_code.py b/lms/djangoapps/courseware/tests/check_request_code.py deleted file mode 100644 index 1393d2fe17..0000000000 --- a/lms/djangoapps/courseware/tests/check_request_code.py +++ /dev/null @@ -1,24 +0,0 @@ - - -def check_for_get_code(code, url): - """ - Check that we got the expected code when accessing url via GET. - Returns the response. - """ - resp = self.client.get(url) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp - - -def check_for_post_code(code, url, data={}): - """ - Check that we got the expected code when accessing url via POST. - Returns the response. - """ - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, code, - "got code %d for url '%s'. Expected code %d" - % (resp.status_code, url, code)) - return resp diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index 99da5e9061..ce0603990b 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -12,7 +12,12 @@ def check_for_get_code(self, code, url): """ Check that we got the expected code when accessing url via GET. Returns the HTTP response. - 'self' is a class that subclasses TestCase. + + `self` is a class that subclasses TestCase. + + `code` is a status code for HTTP responses. + + `url` is a url pattern for which we have to test the response. """ resp = self.client.get(url) self.assertEqual(resp.status_code, code, @@ -25,7 +30,11 @@ def check_for_post_code(self, code, url, data={}): """ Check that we got the expected code when accessing url via POST. Returns the HTTP response. - 'self' is a class that subclasses TestCase. + `self` is a class that subclasses TestCase. + + `code` is a status code for HTTP responses. + + `url` is a url pattern for which we want to test the response. """ resp = self.client.post(url, data) self.assertEqual(resp.status_code, code, @@ -62,8 +71,8 @@ class LoginEnrollmentTestCase(TestCase): def logout(self): """ - Logout, check that it worked. - Returns an HTTP response which e + Logout; check that the HTTP response code indicates redirection + as expected. """ resp = self.client.get(reverse('logout'), {}) # should redirect @@ -106,8 +115,8 @@ class LoginEnrollmentTestCase(TestCase): def enroll(self, course, verify=False): """ Try to enroll and return boolean indicating result. - 'course' is an instance of CourseDescriptor. - 'verify' is an optional parameter specifying whether we + `course` is an instance of CourseDescriptor. + `verify` is an optional parameter specifying whether we want to verify that the student was successfully enrolled in the course. """ @@ -122,20 +131,12 @@ class LoginEnrollmentTestCase(TestCase): self.assertTrue(result) return result - # def enroll(self, course): - # """ - # Enroll the currently logged-in user, and check that it worked. - # """ - - # result = self.try_enroll(course) - # self.assertTrue(result) - def unenroll(self, course): """ Unenroll the currently logged-in user, and check that it worked. - 'course' is an instance of CourseDescriptor. + `course` is an instance of CourseDescriptor. """ - resp = self.client.post('/change_enrollment', { + resp = self.client.post(reverse('change_enrollment'), { 'enrollment_action': 'unenroll', 'course_id': course.id, }) diff --git a/lms/djangoapps/courseware/tests/modulestore_config.py b/lms/djangoapps/courseware/tests/modulestore_config.py index 81d0f4f911..c3c4ce4e5b 100644 --- a/lms/djangoapps/courseware/tests/modulestore_config.py +++ b/lms/djangoapps/courseware/tests/modulestore_config.py @@ -4,11 +4,11 @@ from django.conf import settings def mongo_store_config(data_dir): - ''' - Defines default module store using MongoModuleStore + """ + Defines default module store using MongoModuleStore. - Use of this config requires mongo to be running - ''' + Use of this config requires mongo to be running. + """ store = { 'default': { 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', @@ -27,7 +27,9 @@ def mongo_store_config(data_dir): def draft_mongo_store_config(data_dir): - '''Defines default module store using DraftMongoModuleStore''' + """ + Defines default module store using DraftMongoModuleStore. + """ return { 'default': { 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', @@ -55,7 +57,9 @@ def draft_mongo_store_config(data_dir): def xml_store_config(data_dir): - '''Defines default module store using XMLModuleStore''' + """ + Defines default module store using XMLModuleStore. + """ return { 'default': { 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 9f9bf7ba92..f4662f2ef5 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -36,15 +36,18 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): # Create student accounts and activate them. for i in range(len(self.STUDENT_INFO)): - self.create_account('u{0}'.format(i), self.STUDENT_INFO[i][0], self.STUDENT_INFO[i][1]) - self.activate_user(self.STUDENT_INFO[i][0]) + email, password = self.STUDENT_INFO[i] + username = 'u{0}'.format(i) + self.create_account(username, email, password) + self.activate_user(email) def test_redirects_first_time(self): """ Verify that the first time we click on the courseware tab we are redirected to the 'Welcome' section. """ - self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + email, password = self.STUDENT_INFO[0] + self.login(email, password) self.enroll(self.course, True) self.enroll(self.full, True) @@ -61,7 +64,8 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify the accordion remembers we've already visited the Welcome section and redirects correpondingly. """ - self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + email, password = self.STUDENT_INFO[0] + self.login(email, password) self.enroll(self.course, True) self.enroll(self.full, True) @@ -80,8 +84,8 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Verify the accordion remembers which chapter you were last viewing. """ - - self.login(self.STUDENT_INFO[0][0], self.STUDENT_INFO[0][1]) + email, password = self.STUDENT_INFO[0] + self.login(email, password) self.enroll(self.course, True) self.enroll(self.full, True) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index ffae4688bf..5db9847d45 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -32,28 +32,40 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): @classmethod def _instructor_urls(self, course): """ - List of urls that only instructors/staff should be able to see. + `course` is an instance of CourseDescriptor whose section URLs are to be returned. + + Returns a list of URLs corresponding to sections in the passed in course. """ + urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( 'instructor_dashboard', 'gradebook', 'grade_summary',)] + email, _ = self.ACCOUNT_INFO[0] + student_id = User.objects.get(email=email).id + urls.append(reverse('student_progress', kwargs={'course_id': course.id, - 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id})) + 'student_id': student_id})) return urls @staticmethod def _reverse_urls(names, course): """ Reverse a list of course urls. + + `names` is a list of URL names that correspond to sections in a course. + + `course` is the instance of CourseDescriptor whose section URLs are to be returned. + + Returns a list URLs corresponding to section in the passed in course. + """ return [reverse(name, kwargs={'course_id': course.id}) for name in names] def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} self.full = CourseFactory.create(number='666', display_name='Robot_Sub_Course') self.course = CourseFactory.create() @@ -68,8 +80,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # Create two accounts and activate them. for i in range(len(self.ACCOUNT_INFO)): - self.create_account('u{0}'.format(i), self.ACCOUNT_INFO[i][0], self.ACCOUNT_INFO[i][1]) - self.activate_user(self.ACCOUNT_INFO[i][0]) + username, email, password = 'u{0}'.format(i), self.ACCOUNT_INFO[i][0], self.ACCOUNT_INFO[i][1] + self.create_account(username, email, password) + self.activate_user(email) def test_redirection_unenrolled(self): """ @@ -77,7 +90,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): instead of the 'Welcome' section after clicking on the courseware tab. """ - self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + email, password = self.ACCOUNT_INFO[0] + self.login(email, password) response = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) self.assertRedirects(response, @@ -90,7 +104,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): the chapter after clicking on the courseware tab. """ - self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + email, password = self.ACCOUNT_INFO[0] + self.login(email, password) self.enroll(self.course) response = self.client.get(reverse('courseware', @@ -108,7 +123,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): dashboard, the grade views, and student profile pages. """ - self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + email, password = self.ACCOUNT_INFO[0] + self.login(email, password) self.enroll(self.course) self.enroll(self.full) @@ -127,12 +143,14 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): and student profile pages for their course. """ + email, password = self.ACCOUNT_INFO[1] + # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) + group.user_set.add(User.objects.get(email=email)) - self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + self.login(email, password) # Now should be able to get to self.course, but not self.full url = random.choice(self._instructor_urls(self.course)) @@ -149,10 +167,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): staff permissions. """ - self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + email, password = self.ACCOUNT_INFO[1] + self.login(email, password) # now make the instructor also staff - instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) + instructor = User.objects.get(email=email) instructor.is_staff = True instructor.save() @@ -202,6 +221,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Actually do the test, relying on settings to be right. """ + student_email, student_password = self.ACCOUNT_INFO[0] + instructor_email, instructor_password = self.ACCOUNT_INFO[1] + # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) @@ -300,7 +322,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # First, try with an enrolled student print '=== Testing student access....' - self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.login(student_email, student_password) self.enroll(self.course, True) self.enroll(self.full, True) @@ -314,10 +336,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) + group.user_set.add(User.objects.get(email=instructor_email)) self.logout() - self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + self.login(instructor_email, instructor_password) # Enroll in the classes---can't see courseware otherwise. self.enroll(self.course, True) self.enroll(self.full, True) @@ -329,7 +351,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): print '=== Testing staff access....' # now also make the instructor staff - instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) + instructor = User.objects.get(email=instructor_email) instructor.is_staff = True instructor.save() @@ -342,6 +364,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Actually do the test, relying on settings to be right. """ + student_email, student_password = self.ACCOUNT_INFO[0] + instructor_email, instructor_password = self.ACCOUNT_INFO[1] + # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) @@ -360,7 +385,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): print "login" # First, try with an enrolled student print '=== Testing student access....' - self.login(self.ACCOUNT_INFO[0][0], self.ACCOUNT_INFO[0][1]) + self.login(student_email, student_password) self.assertFalse(self.enroll(self.course)) self.assertTrue(self.enroll(self.full)) @@ -368,18 +393,18 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # Make the instructor staff in the self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) - group.user_set.add(User.objects.get(email=self.ACCOUNT_INFO[1][0])) + group.user_set.add(User.objects.get(email=instructor_email)) print "logout/login" self.logout() - self.login(self.ACCOUNT_INFO[1][0], self.ACCOUNT_INFO[1][1]) + self.login(instructor_email, instructor_password) print "Instructor should be able to enroll in self.course" self.assertTrue(self.enroll(self.course)) print '=== Testing staff access....' # now make the instructor global staff, but not in the instructor group - group.user_set.remove(User.objects.get(email=self.ACCOUNT_INFO[1][0])) - instructor = User.objects.get(email=self.ACCOUNT_INFO[1][0]) + group.user_set.remove(User.objects.get(email=instructor_email)) + instructor = User.objects.get(email=instructor_email) instructor.is_staff = True instructor.save() @@ -392,6 +417,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Actually test beta periods, relying on settings to be right. """ + student_email, student_password = self.ACCOUNT_INFO[0] + instructor_email, instructor_password = self.ACCOUNT_INFO[1] + # trust, but verify :) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) @@ -408,7 +436,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.course.lms.days_early_for_beta = 2 # student user shouldn't see it - student_user = User.objects.get(email=self.ACCOUNT_INFO[0][0]) + student_user = User.objects.get(email=student_email) self.assertFalse(has_access(student_user, self.course, 'load')) # now add the student to the beta test group From e44ef1a54ebff3d61231ad7cc2e7ccbc5faf5933 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 16:24:09 -0400 Subject: [PATCH 07/15] Removed the use of random.choice() --- .../tests/test_view_authentication.py | 268 ++++++++++-------- 1 file changed, 153 insertions(+), 115 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 5db9847d45..8e03e2563b 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -1,8 +1,7 @@ import datetime import pytz -import random -import xmodule.modulestore.django +from mock import patch from django.contrib.auth.models import User, Group from django.conf import settings @@ -65,6 +64,99 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): return [reverse(name, kwargs={'course_id': course.id}) for name in names] + def _dark_student_urls(self, course): + """ + List of urls that students should be able to see only + after launch, but staff should see before + """ + urls = self._reverse_urls(['info', 'progress'], course) + urls.extend([ + reverse('book', kwargs={'course_id': course.id, + 'book_index': index}) + for index, book in enumerate(course.textbooks) + ]) + return urls + + def _light_student_urls(self, course): + """ + List of urls that students should be able to see before + launch. + """ + urls = self._reverse_urls(['about_course'], course) + urls.append(reverse('courses')) + + return urls + + def instructor_urls(self, course): + """ + List of urls that only instructors/staff should be able to see. + """ + urls = self._reverse_urls(['instructor_dashboard', + 'gradebook', 'grade_summary'], course) + return urls + + def _check_non_staff_light(self, course): + """ + Check that non-staff have access to light urls. + + `course` is an instance of CourseDescriptor. + """ + print '=== Checking non-staff access for {0}'.format(course.id) + urls = [reverse('about_course', kwargs={'course_id': course.id}), reverse('courses')] + for url in urls: + print 'checking for 200 on {0}'.format(url) + check_for_get_code(self, 200, url) + + def _check_non_staff_dark(self, course): + """ + Check that non-staff don't have access to dark urls. + """ + print '=== Checking non-staff access for {0}'.format(course.id) + + names = ['courseware', 'instructor_dashboard', 'progress'] + urls = self._reverse_urls(names, course) + urls.extend([ + reverse('book', kwargs={'course_id': course.id, + 'book_index': index}) + for index, book in enumerate(course.textbooks) + ]) + for url in urls: + print 'checking for 404 on {0}'.format(url) + check_for_get_code(self, 404, url) + + def _check_staff(self, course): + """ + Check that access is right for staff in course. + """ + print '=== Checking staff access for {0}'.format(course.id) + + names = ['about_course', 'instructor_dashboard', 'progress'] + urls = self._reverse_urls(names, course) + urls.extend([ + reverse('book', kwargs={'course_id': course.id, + 'book_index': index}) + for index, book in enumerate(course.textbooks) + ]) + for url in urls: + print 'checking for 200 on {0}'.format(url) + check_for_get_code(self, 200, url) + + # The student progress tab is not accessible to a student + # before launch, so the instructor view-as-student feature + # should return a 404 as well. + # TODO (vshnayder): If this is not the behavior we want, will need + # to make access checking smarter and understand both the effective + # user (the student), and the requesting user (the prof) + url = reverse('student_progress', + kwargs={'course_id': course.id, + 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id}) + print 'checking for 404 on view-as-student: {0}'.format(url) + check_for_get_code(self, 404, url) + + # The courseware url should redirect, not 200 + url = self._reverse_urls(['courseware'], course)[0] + check_for_get_code(self, 302, url) + def setUp(self): self.full = CourseFactory.create(number='666', display_name='Robot_Sub_Course') @@ -129,13 +221,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.enroll(self.course) self.enroll(self.full) - # Randomly sample an instructor page - url = random.choice(self._instructor_urls(self.course) + - self._instructor_urls(self.full)) + urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), + reverse('instructor_dashboard', kwargs={'course_id': self.full.id})] # Shouldn't be able to get to the instructor pages - print 'checking for 404 on {0}'.format(url) - check_for_get_code(self, 404, url) + for url in urls: + print 'checking for 404 on {0}'.format(url) + check_for_get_code(self, 404, url) def test_instructor_course_access(self): """ @@ -153,11 +245,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.login(email, password) # Now should be able to get to self.course, but not self.full - url = random.choice(self._instructor_urls(self.course)) + url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) - url = random.choice(self._instructor_urls(self.full)) + url = reverse('instructor_dashboard', kwargs={'course_id': self.full.id}) print 'checking for 404 on {0}'.format(url) check_for_get_code(self, 404, url) @@ -176,11 +268,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): instructor.save() # and now should be able to load both - url = random.choice(self._instructor_urls(self.course) + - self._instructor_urls(self.full)) + urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), + reverse('instructor_dashboard', kwargs={'course_id': self.full.id})] - print 'checking for 200 on {0}'.format(url) - check_for_get_code(self, 200, url) + for url in urls: + print 'checking for 200 on {0}'.format(url) + check_for_get_code(self, 200, url) def run_wrapped(self, test): """ @@ -202,7 +295,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Make sure that before course start, students can't access course pages, but instructors can. """ - self.run_wrapped(self._do_test_dark_launch) + self.run_wrapped(self._do_test_dark_launch_enrolled_student) + self.run_wrapped(self._do_test_dark_launch_instructor) + self.run_wrapped(self._do_test_dark_launch_staff) def test_enrollment_period(self): """ @@ -210,19 +305,18 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ self.run_wrapped(self._do_test_enrollment_period) - def test_beta_period(self): - """ - Check that beta-test access works. - """ - self.run_wrapped(self._do_test_beta_period) + # def test_beta_period(self): + # """ + # Check that beta-test access works. + # """ + # self.run_wrapped(self._do_test_beta_period) - def _do_test_dark_launch(self): + def _do_test_dark_launch_enrolled_student(self): """ Actually do the test, relying on settings to be right. """ student_email, student_password = self.ACCOUNT_INFO[0] - instructor_email, instructor_password = self.ACCOUNT_INFO[1] # Make courses start in the future now = datetime.datetime.now(pytz.UTC) @@ -236,90 +330,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.assertFalse(self.full.has_started()) self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - def dark_student_urls(course): - """ - List of urls that students should be able to see only - after launch, but staff should see before - """ - urls = self._reverse_urls(['info', 'progress'], course) - urls.extend([ - reverse('book', kwargs={'course_id': course.id, - 'book_index': index}) - for index, book in enumerate(course.textbooks) - ]) - return urls - - def light_student_urls(course): - """ - List of urls that students should be able to see before - launch. - """ - urls = self._reverse_urls(['about_course'], course) - urls.append(reverse('courses')) - - return urls - - def instructor_urls(course): - """ - List of urls that only instructors/staff should be able to see. - """ - urls = self._reverse_urls(['instructor_dashboard', - 'gradebook', 'grade_summary'], course) - return urls - - def check_non_staff_light(course): - """ - Check that non-staff have access to light urls. - """ - print '=== Checking non-staff access for {0}'.format(course.id) - - # Randomly sample a light url - url = random.choice(light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - check_for_get_code(self, 200, url) - - def check_non_staff_dark(course): - """ - Check that non-staff don't have access to dark urls. - """ - print '=== Checking non-staff access for {0}'.format(course.id) - - # Randomly sample a dark url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - self._reverse_urls(['courseware'], course)) - print 'checking for 404 on {0}'.format(url) - check_for_get_code(self, 404, url) - - def check_staff(course): - """ - Check that access is right for staff in course. - """ - print '=== Checking staff access for {0}'.format(course.id) - - # Randomly sample a url - url = random.choice(instructor_urls(course) + - dark_student_urls(course) + - light_student_urls(course)) - print 'checking for 200 on {0}'.format(url) - check_for_get_code(self, 200, url) - - # The student progress tab is not accessible to a student - # before launch, so the instructor view-as-student feature - # should return a 404 as well. - # TODO (vshnayder): If this is not the behavior we want, will need - # to make access checking smarter and understand both the effective - # user (the student), and the requesting user (the prof) - url = reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id}) - print 'checking for 404 on view-as-student: {0}'.format(url) - check_for_get_code(self, 404, url) - - # The courseware url should redirect, not 200 - url = self._reverse_urls(['courseware'], course)[0] - check_for_get_code(self, 302, url) - # First, try with an enrolled student print '=== Testing student access....' self.login(student_email, student_password) @@ -327,17 +337,27 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.enroll(self.full, True) # shouldn't be able to get to anything except the light pages - check_non_staff_light(self.course) - check_non_staff_dark(self.course) - check_non_staff_light(self.full) - check_non_staff_dark(self.full) + self._check_non_staff_light(self.course) + self._check_non_staff_dark(self.course) + self._check_non_staff_light(self.full) + self._check_non_staff_dark(self.full) + + def _do_test_dark_launch_instructor(self): + + instructor_email, instructor_password = self.ACCOUNT_INFO[1] + + now = datetime.datetime.now(pytz.UTC) + tomorrow = now + datetime.timedelta(days=1) + course_data = {'start': tomorrow} + full_data = {'start': tomorrow} + self.course = self.update_course(self.course, course_data) + self.full = self.update_course(self.full, full_data) print '=== Testing course instructor access....' # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(User.objects.get(email=instructor_email)) - self.logout() self.login(instructor_email, instructor_password) # Enroll in the classes---can't see courseware otherwise. @@ -345,9 +365,24 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.enroll(self.full, True) # should now be able to get to everything for self.course - check_non_staff_light(self.full) - check_non_staff_dark(self.full) - check_staff(self.course) + self._check_non_staff_light(self.full) + self._check_non_staff_dark(self.full) + self._check_staff(self.course) + + def _do_test_dark_launch_staff(self): + + instructor_email, instructor_password = self.ACCOUNT_INFO[1] + + now = datetime.datetime.now(pytz.UTC) + tomorrow = now + datetime.timedelta(days=1) + course_data = {'start': tomorrow} + full_data = {'start': tomorrow} + self.course = self.update_course(self.course, course_data) + self.full = self.update_course(self.full, full_data) + + self.login(instructor_email, instructor_password) + self.enroll(self.course, True) + self.enroll(self.full, True) print '=== Testing staff access....' # now also make the instructor staff @@ -356,8 +391,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): instructor.save() # and now should be able to load both - check_staff(self.course) - check_staff(self.full) + self._check_staff(self.course) + self._check_staff(self.full) def _do_test_enrollment_period(self): """ @@ -412,6 +447,9 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.unenroll(self.course) self.assertTrue(self.enroll(self.course)) + #from courseware.access import MITX_FEATURES + + #@patch.dict(MITX_FEATURES, {'DISABLE_START_DATES': True}) def _do_test_beta_period(self): """ Actually test beta periods, relying on settings to be right. From 986b63d85d9fda2135d239ad2caf84365b2f80d3 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 17:07:43 -0400 Subject: [PATCH 08/15] Removed run_wrapped() and replaced its functionality with patch.dict(): --- lms/djangoapps/courseware/tests/helpers.py | 4 ++ .../courseware/tests/test_navigation.py | 1 - .../tests/test_view_authentication.py | 64 ++++++++----------- 3 files changed, 29 insertions(+), 40 deletions(-) diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index ce0603990b..1ceeb14433 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -44,6 +44,10 @@ def check_for_post_code(self, code, url, data={}): class LoginEnrollmentTestCase(TestCase): + """ + Provides support for user creation, + activation, login, and course enrollment. + """ def setup_user(self): """ diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index f4662f2ef5..f1aa7f5b31 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -21,7 +21,6 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): """ def setUp(self): - xmodule.modulestore.django._MODULESTORES = {} self.course = CourseFactory.create() self.full = CourseFactory.create(display_name='Robot_Sub_Course') diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 8e03e2563b..1edeac58ed 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -10,7 +10,7 @@ from django.test.utils import override_settings # Need access to internal func to put users in the right group from courseware.access import (has_access, _course_staff_group_name, - course_beta_test_group_name) + course_beta_test_group_name, settings as access_settings) from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -20,6 +20,7 @@ from helpers import LoginEnrollmentTestCase, check_for_get_code from modulestore_config import TEST_DATA_MONGO_MODULESTORE +#@patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': True}) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ @@ -81,20 +82,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ List of urls that students should be able to see before launch. + `course` is an instance of CourseDescriptor. """ urls = self._reverse_urls(['about_course'], course) urls.append(reverse('courses')) return urls - def instructor_urls(self, course): - """ - List of urls that only instructors/staff should be able to see. - """ - urls = self._reverse_urls(['instructor_dashboard', - 'gradebook', 'grade_summary'], course) - return urls - def _check_non_staff_light(self, course): """ Check that non-staff have access to light urls. @@ -295,25 +289,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Make sure that before course start, students can't access course pages, but instructors can. """ - self.run_wrapped(self._do_test_dark_launch_enrolled_student) - self.run_wrapped(self._do_test_dark_launch_instructor) - self.run_wrapped(self._do_test_dark_launch_staff) - def test_enrollment_period(self): + @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) + def test_dark_launch_enrolled_student(self): """ - Check that enrollment periods work. - """ - self.run_wrapped(self._do_test_enrollment_period) - - # def test_beta_period(self): - # """ - # Check that beta-test access works. - # """ - # self.run_wrapped(self._do_test_beta_period) - - def _do_test_dark_launch_enrolled_student(self): - """ - Actually do the test, relying on settings to be right. + Make sure that before course start, students can't access course + pages. """ student_email, student_password = self.ACCOUNT_INFO[0] @@ -328,7 +309,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.assertFalse(self.course.has_started()) self.assertFalse(self.full.has_started()) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) # First, try with an enrolled student print '=== Testing student access....' @@ -342,7 +322,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self._check_non_staff_light(self.full) self._check_non_staff_dark(self.full) - def _do_test_dark_launch_instructor(self): + @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) + def test_dark_launch_instructor(self): + """ + Make sure that before course start instructors can access the + page for their course. + """ instructor_email, instructor_password = self.ACCOUNT_INFO[1] @@ -369,7 +354,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self._check_non_staff_dark(self.full) self._check_staff(self.course) - def _do_test_dark_launch_staff(self): + @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) + def test_dark_launch_staff(self): + """ + Make sure that before course start staff can access + course pages. + """ instructor_email, instructor_password = self.ACCOUNT_INFO[1] @@ -394,9 +384,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self._check_staff(self.course) self._check_staff(self.full) - def _do_test_enrollment_period(self): + @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) + def test_enrollment_period(self): """ - Actually do the test, relying on settings to be right. + Check that enrollment periods work. """ student_email, student_password = self.ACCOUNT_INFO[0] @@ -447,20 +438,15 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.unenroll(self.course) self.assertTrue(self.enroll(self.course)) - #from courseware.access import MITX_FEATURES - - #@patch.dict(MITX_FEATURES, {'DISABLE_START_DATES': True}) - def _do_test_beta_period(self): + @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) + def test_beta_period(self): """ - Actually test beta periods, relying on settings to be right. + Check that beta-test access works. """ student_email, student_password = self.ACCOUNT_INFO[0] instructor_email, instructor_password = self.ACCOUNT_INFO[1] - # trust, but verify :) - self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES']) - # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) From 7fd1190505fde49e24763927721701187bcceaf0 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 17:13:33 -0400 Subject: [PATCH 09/15] Updated a doc string. --- lms/djangoapps/courseware/tests/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index 1ceeb14433..a02a0dfe50 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -120,7 +120,7 @@ class LoginEnrollmentTestCase(TestCase): """ Try to enroll and return boolean indicating result. `course` is an instance of CourseDescriptor. - `verify` is an optional parameter specifying whether we + `verify` is an optional boolean parameter specifying whether we want to verify that the student was successfully enrolled in the course. """ From 1b344e4d4d14e7996ffff393a0d6d8d50f6aeae5 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 17:20:59 -0400 Subject: [PATCH 10/15] Removed some unused functions. --- .../tests/test_view_authentication.py | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 1edeac58ed..6a6c539b60 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -29,27 +29,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ACCOUNT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] - @classmethod - def _instructor_urls(self, course): - """ - `course` is an instance of CourseDescriptor whose section URLs are to be returned. - - Returns a list of URLs corresponding to sections in the passed in course. - """ - - urls = [reverse(name, kwargs={'course_id': course.id}) for name in ( - 'instructor_dashboard', - 'gradebook', - 'grade_summary',)] - - email, _ = self.ACCOUNT_INFO[0] - student_id = User.objects.get(email=email).id - - urls.append(reverse('student_progress', - kwargs={'course_id': course.id, - 'student_id': student_id})) - return urls - @staticmethod def _reverse_urls(names, course): """ @@ -65,30 +44,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): return [reverse(name, kwargs={'course_id': course.id}) for name in names] - def _dark_student_urls(self, course): - """ - List of urls that students should be able to see only - after launch, but staff should see before - """ - urls = self._reverse_urls(['info', 'progress'], course) - urls.extend([ - reverse('book', kwargs={'course_id': course.id, - 'book_index': index}) - for index, book in enumerate(course.textbooks) - ]) - return urls - - def _light_student_urls(self, course): - """ - List of urls that students should be able to see before - launch. - `course` is an instance of CourseDescriptor. - """ - urls = self._reverse_urls(['about_course'], course) - urls.append(reverse('courses')) - - return urls - def _check_non_staff_light(self, course): """ Check that non-staff have access to light urls. From c4c68f516b796c8f9ac837ff30fab9ac59126771 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Mon, 24 Jun 2013 17:31:49 -0400 Subject: [PATCH 11/15] Removed some unnecessary imports. --- .../courseware/tests/test_navigation.py | 2 -- .../courseware/tests/test_view_authentication.py | 15 --------------- 2 files changed, 17 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index f1aa7f5b31..eaeb062504 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -5,8 +5,6 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -import xmodule.modulestore.django - from helpers import LoginEnrollmentTestCase, check_for_get_code from modulestore_config import TEST_DATA_MONGO_MODULESTORE diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 6a6c539b60..b118f99ca2 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -224,21 +224,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) - def run_wrapped(self, test): - """ - test.py turns off start dates. Enable them. - Because settings is global, be careful not to mess it up for other tests - (Can't use override_settings because we're only changing part of the - MITX_FEATURES dict) - """ - oldDSD = settings.MITX_FEATURES['DISABLE_START_DATES'] - - try: - settings.MITX_FEATURES['DISABLE_START_DATES'] = False - test() - finally: - settings.MITX_FEATURES['DISABLE_START_DATES'] = oldDSD - def test_dark_launch(self): """ Make sure that before course start, students can't access course From 7f017d0ca98c8b1c1b40b380c0e97186e9bbf9bf Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Wed, 26 Jun 2013 14:50:16 -0400 Subject: [PATCH 12/15] Addressed pull request feedback --- common/djangoapps/student/views.py | 2 +- .../xmodule/modulestore/tests/django_utils.py | 46 ++++----- .../xmodule/modulestore/tests/factories.py | 8 +- lms/djangoapps/courseware/access.py | 1 - lms/djangoapps/courseware/tests/helpers.py | 27 ++---- .../courseware/tests/modulestore_config.py | 30 +++--- .../courseware/tests/test_navigation.py | 10 +- .../tests/test_view_authentication.py | 94 ++++++------------- 8 files changed, 78 insertions(+), 140 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 82c17c0e67..6b9c9104c5 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -357,7 +357,6 @@ def change_enrollment(request): return HttpResponseBadRequest("Course id not specified") if action == "enroll": - # Make sure the course exists # We don't do this check on unenroll, or a bad course id can't be unenrolled from try: @@ -366,6 +365,7 @@ def change_enrollment(request): log.warning("User {0} tried to enroll in non-existent course {1}" .format(user.username, course_id)) return HttpResponseBadRequest("Course id is invalid") + if not has_access(user, course, 'enroll'): return HttpResponseBadRequest("Enrollment is closed") diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index a2306a5c6b..27f0d9cf2a 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -17,30 +17,24 @@ class ModuleStoreTestCase(TestCase): @staticmethod def update_course(course, data): """ - Updates the version of course in the mongo modulestore - with the metadata in data and returns the updated version. + Updates the version of course in the modulestore + with the metadata in 'data' and returns the updated version. 'course' is an instance of CourseDescriptor for which we want to update metadata. 'data' is a dictionary with an entry for each CourseField we want to update. """ - store = xmodule.modulestore.django.modulestore() - - store.update_item(course.location, data) - store.update_metadata(course.location, data) - updated_course = store.get_instance(course.id, course.location) - return updated_course @staticmethod def flush_mongo_except_templates(): - ''' - Delete everything in the module store except templates - ''' + """ + Delete everything in the module store except templates. + """ modulestore = xmodule.modulestore.django.modulestore() # This query means: every item in the collection @@ -52,11 +46,11 @@ class ModuleStoreTestCase(TestCase): @staticmethod def load_templates_if_necessary(): - ''' + """ Load templates into the direct modulestore only if they do not already exist. We need the templates, because they are copied to create - XModules such as sections and problems - ''' + XModules such as sections and problems. + """ modulestore = xmodule.modulestore.django.modulestore('direct') # Count the number of templates @@ -68,9 +62,9 @@ class ModuleStoreTestCase(TestCase): @classmethod def setUpClass(cls): - ''' - Flush the mongo store and set up templates - ''' + """ + Flush the mongo store and set up templates. + """ # Use a uuid to differentiate # the mongo collections on jenkins. @@ -88,9 +82,9 @@ class ModuleStoreTestCase(TestCase): @classmethod def tearDownClass(cls): - ''' - Revert to the old modulestore settings - ''' + """ + Revert to the old modulestore settings. + """ # Clean up by dropping the collection modulestore = xmodule.modulestore.django.modulestore() @@ -102,9 +96,9 @@ class ModuleStoreTestCase(TestCase): settings.MODULESTORE = cls.orig_modulestore def _pre_setup(self): - ''' - Remove everything but the templates before each test - ''' + """ + Remove everything but the templates before each test. + """ # Flush anything that is not a template ModuleStoreTestCase.flush_mongo_except_templates() @@ -116,9 +110,9 @@ class ModuleStoreTestCase(TestCase): super(ModuleStoreTestCase, self)._pre_setup() def _post_teardown(self): - ''' - Flush everything we created except the templates - ''' + """ + Flush everything we created except the templates. + """ # Flush anything that is not a template ModuleStoreTestCase.flush_mongo_except_templates() diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index 82ff61204a..a7f0a71a59 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -60,8 +60,8 @@ class XModuleCourseFactory(Factory): if data is not None: store.update_item(new_course.location, data) - #update_item updates the the course as it exists in the modulestore, but doesn't - #update the instance we are working with, so have to refetch the course after updating it. + # update_item updates the the course as it exists in the modulestore, but doesn't + # update the instance we are working with, so have to refetch the course after updating it. new_course = store.get_instance(new_course.id, new_course.location) return new_course @@ -152,8 +152,8 @@ class XModuleItemFactory(Factory): if new_item.location.category not in DETACHED_CATEGORIES: store.update_children(parent_location, parent.children + [new_item.location.url()]) - #update_children updates the the item as it exists in the modulestore, but doesn't - #update the instance we are working with, so have to refetch the item after updating it. + # update_children updates the the item as it exists in the modulestore, but doesn't + # update the instance we are working with, so have to refetch the item after updating it. new_item = store.get_item(new_item.location) return new_item diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 8150d380c5..762020c421 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -526,7 +526,6 @@ def _adjust_start_date_for_beta_testers(user, descriptor): start_as_datetime = descriptor.lms.start delta = timedelta(descriptor.lms.days_early_for_beta) effective = start_as_datetime - delta - # ...and back to time_struct return effective diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index a02a0dfe50..6890a6df2a 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -48,7 +48,6 @@ class LoginEnrollmentTestCase(TestCase): Provides support for user creation, activation, login, and course enrollment. """ - def setup_user(self): """ Create a user account, activate, and log in. @@ -67,8 +66,8 @@ class LoginEnrollmentTestCase(TestCase): """ Login, check that the corresponding view's response has a 200 status code. """ - resp = resp = self.client.post(reverse('login'), - {'email': email, 'password': password}) + resp = self.client.post(reverse('login'), + {'email': email, 'password': password}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertTrue(data['success']) @@ -78,15 +77,14 @@ class LoginEnrollmentTestCase(TestCase): Logout; check that the HTTP response code indicates redirection as expected. """ - resp = self.client.get(reverse('logout'), {}) # should redirect - self.assertEqual(resp.status_code, 302) + check_for_get_code(self, 302, reverse('logout')) def create_account(self, username, email, password): """ Create the account and check that it worked. """ - resp = self.client.post(reverse('create_account'), { + resp = check_for_post_code(self, 200, reverse('create_account'), { 'username': username, 'email': email, 'password': password, @@ -94,10 +92,8 @@ class LoginEnrollmentTestCase(TestCase): 'terms_of_service': 'true', 'honor_code': 'true', }) - self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['success'], True) - # Check both that the user is created, and inactive self.assertFalse(User.objects.get(email=email).is_active) @@ -107,12 +103,8 @@ class LoginEnrollmentTestCase(TestCase): No error checking. """ activation_key = Registration.objects.get(user__email=email).activation_key - # and now we try to activate - url = reverse('activate', kwargs={'key': activation_key}) - - resp = self.client.get(url) - self.assertEqual(resp.status_code, 200) + check_for_get_code(self, 200, reverse('activate', kwargs={'key': activation_key})) # Now make sure that the user is now actually activated self.assertTrue(User.objects.get(email=email).is_active) @@ -128,8 +120,6 @@ class LoginEnrollmentTestCase(TestCase): 'enrollment_action': 'enroll', 'course_id': course.id, }) - print ('Enrollment in %s result status code: %s' - % (course.location.url(), str(resp.status_code))) result = resp.status_code == 200 if verify: self.assertTrue(result) @@ -140,8 +130,5 @@ class LoginEnrollmentTestCase(TestCase): Unenroll the currently logged-in user, and check that it worked. `course` is an instance of CourseDescriptor. """ - resp = self.client.post(reverse('change_enrollment'), { - 'enrollment_action': 'unenroll', - 'course_id': course.id, - }) - self.assertEqual(resp.status_code, 200) + check_for_post_code(self, 200, reverse('change_enrollment'), {'enrollment_action': 'unenroll', + 'course_id': course.id}) diff --git a/lms/djangoapps/courseware/tests/modulestore_config.py b/lms/djangoapps/courseware/tests/modulestore_config.py index c3c4ce4e5b..9515e449f9 100644 --- a/lms/djangoapps/courseware/tests/modulestore_config.py +++ b/lms/djangoapps/courseware/tests/modulestore_config.py @@ -16,7 +16,7 @@ def mongo_store_config(data_dir): 'default_class': 'xmodule.raw_module.RawDescriptor', 'host': 'localhost', 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, + 'collection': 'modulestore', 'fs_root': data_dir, 'render_template': 'mitxmako.shortcuts.render_to_string' } @@ -30,28 +30,24 @@ def draft_mongo_store_config(data_dir): """ Defines default module store using DraftMongoModuleStore. """ + + modulestore_options = { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'xmodule', + 'collection': 'modulestore_%s' % uuid4().hex, + 'fs_root': data_dir, + 'render_template': 'mitxmako.shortcuts.render_to_string' + } + return { 'default': { 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } + 'OPTIONS': modulestore_options }, 'direct': { 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'test_xmodule', - 'collection': 'modulestore_%s' % uuid4().hex, - 'fs_root': data_dir, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } + 'OPTIONS': modulestore_options } } diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index eaeb062504..282f6383fc 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -20,8 +20,8 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): def setUp(self): - self.course = CourseFactory.create() - self.full = CourseFactory.create(display_name='Robot_Sub_Course') + self.test_course = CourseFactory.create(display_name='Robot_Sub_Course') + self.course = CourseFactory.create(display_name='Robot_Super_Course') self.chapter0 = ItemFactory.create(parent_location=self.course.location, display_name='Overview') self.chapter9 = ItemFactory.create(parent_location=self.course.location, @@ -46,7 +46,7 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): email, password = self.STUDENT_INFO[0] self.login(email, password) self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) resp = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) @@ -64,7 +64,7 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): email, password = self.STUDENT_INFO[0] self.login(email, password) self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) self.client.get(reverse('courseware_section', kwargs={'course_id': self.course.id, 'chapter': 'Overview', @@ -84,7 +84,7 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): email, password = self.STUDENT_INFO[0] self.login(email, password) self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) # Now we directly navigate to a section in a chapter other than 'Overview'. check_for_get_code(self, 200, reverse('courseware_section', diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index b118f99ca2..055c860fcc 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -4,7 +4,6 @@ import pytz from mock import patch from django.contrib.auth.models import User, Group -from django.conf import settings from django.core.urlresolvers import reverse from django.test.utils import override_settings @@ -20,7 +19,6 @@ from helpers import LoginEnrollmentTestCase, check_for_get_code from modulestore_config import TEST_DATA_MONGO_MODULESTORE -#@patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': True}) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ @@ -50,17 +48,14 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): `course` is an instance of CourseDescriptor. """ - print '=== Checking non-staff access for {0}'.format(course.id) urls = [reverse('about_course', kwargs={'course_id': course.id}), reverse('courses')] for url in urls: - print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) def _check_non_staff_dark(self, course): """ Check that non-staff don't have access to dark urls. """ - print '=== Checking non-staff access for {0}'.format(course.id) names = ['courseware', 'instructor_dashboard', 'progress'] urls = self._reverse_urls(names, course) @@ -70,15 +65,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): for index, book in enumerate(course.textbooks) ]) for url in urls: - print 'checking for 404 on {0}'.format(url) check_for_get_code(self, 404, url) def _check_staff(self, course): """ Check that access is right for staff in course. """ - print '=== Checking staff access for {0}'.format(course.id) - names = ['about_course', 'instructor_dashboard', 'progress'] urls = self._reverse_urls(names, course) urls.extend([ @@ -87,7 +79,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): for index, book in enumerate(course.textbooks) ]) for url in urls: - print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) # The student progress tab is not accessible to a student @@ -99,7 +90,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): url = reverse('student_progress', kwargs={'course_id': course.id, 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id}) - print 'checking for 404 on view-as-student: {0}'.format(url) check_for_get_code(self, 404, url) # The courseware url should redirect, not 200 @@ -108,11 +98,12 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): def setUp(self): - self.full = CourseFactory.create(number='666', display_name='Robot_Sub_Course') - self.course = CourseFactory.create() + self.course = CourseFactory.create(number='999', display_name='Robot_Super_Course') self.overview_chapter = ItemFactory.create(display_name='Overview') self.courseware_chapter = ItemFactory.create(display_name='courseware') - self.sub_courseware_chapter = ItemFactory.create(parent_location=self.full.location, + + self.test_course = CourseFactory.create(number='666', display_name='Robot_Sub_Course') + self.sub_courseware_chapter = ItemFactory.create(parent_location=self.test_course.location, display_name='courseware') self.sub_overview_chapter = ItemFactory.create(parent_location=self.sub_courseware_chapter.location, display_name='Overview') @@ -130,7 +121,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify unenrolled student is redirected to the 'about' section of the chapter instead of the 'Welcome' section after clicking on the courseware tab. """ - email, password = self.ACCOUNT_INFO[0] self.login(email, password) response = self.client.get(reverse('courseware', @@ -144,7 +134,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify enrolled student is redirected to the 'Welcome' section of the chapter after clicking on the courseware tab. """ - email, password = self.ACCOUNT_INFO[0] self.login(email, password) self.enroll(self.course) @@ -163,19 +152,17 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify non-staff cannot load the instructor dashboard, the grade views, and student profile pages. """ - email, password = self.ACCOUNT_INFO[0] self.login(email, password) self.enroll(self.course) - self.enroll(self.full) + self.enroll(self.test_course) urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), - reverse('instructor_dashboard', kwargs={'course_id': self.full.id})] + reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] # Shouldn't be able to get to the instructor pages for url in urls: - print 'checking for 404 on {0}'.format(url) check_for_get_code(self, 404, url) def test_instructor_course_access(self): @@ -183,7 +170,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify instructor can load the instructor dashboard, the grade views, and student profile pages for their course. """ - email, password = self.ACCOUNT_INFO[1] # Make the instructor staff in self.course @@ -193,13 +179,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.login(email, password) - # Now should be able to get to self.course, but not self.full + # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) - print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) - url = reverse('instructor_dashboard', kwargs={'course_id': self.full.id}) - print 'checking for 404 on {0}'.format(url) + url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) check_for_get_code(self, 404, url) def test_instructor_as_staff_access(self): @@ -207,7 +191,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Verify the instructor can load staff pages if he is given staff permissions. """ - email, password = self.ACCOUNT_INFO[1] self.login(email, password) @@ -218,18 +201,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # and now should be able to load both urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), - reverse('instructor_dashboard', kwargs={'course_id': self.full.id})] + reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] for url in urls: - print 'checking for 200 on {0}'.format(url) check_for_get_code(self, 200, url) - def test_dark_launch(self): - """ - Make sure that before course start, students can't access course - pages, but instructors can. - """ - @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_dark_launch_enrolled_student(self): """ @@ -243,24 +219,23 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} - full_data = {'start': tomorrow} + test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) - self.full = self.update_course(self.full, full_data) + self.test_course = self.update_course(self.test_course, test_course_data) self.assertFalse(self.course.has_started()) - self.assertFalse(self.full.has_started()) + self.assertFalse(self.test_course.has_started()) # First, try with an enrolled student - print '=== Testing student access....' self.login(student_email, student_password) self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) # shouldn't be able to get to anything except the light pages self._check_non_staff_light(self.course) self._check_non_staff_dark(self.course) - self._check_non_staff_light(self.full) - self._check_non_staff_dark(self.full) + self._check_non_staff_light(self.test_course) + self._check_non_staff_dark(self.test_course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_dark_launch_instructor(self): @@ -268,17 +243,15 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Make sure that before course start instructors can access the page for their course. """ - instructor_email, instructor_password = self.ACCOUNT_INFO[1] now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} - full_data = {'start': tomorrow} + test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) - self.full = self.update_course(self.full, full_data) + self.test_course = self.update_course(self.test_course, test_course_data) - print '=== Testing course instructor access....' # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) @@ -287,11 +260,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): self.login(instructor_email, instructor_password) # Enroll in the classes---can't see courseware otherwise. self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) # should now be able to get to everything for self.course - self._check_non_staff_light(self.full) - self._check_non_staff_dark(self.full) + self._check_non_staff_light(self.test_course) + self._check_non_staff_dark(self.test_course) self._check_staff(self.course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) @@ -300,21 +273,19 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): Make sure that before course start staff can access course pages. """ - instructor_email, instructor_password = self.ACCOUNT_INFO[1] now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} - full_data = {'start': tomorrow} + test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) - self.full = self.update_course(self.full, full_data) + self.test_course = self.update_course(self.test_course, test_course_data) self.login(instructor_email, instructor_password) self.enroll(self.course, True) - self.enroll(self.full, True) + self.enroll(self.test_course, True) - print '=== Testing staff access....' # now also make the instructor staff instructor = User.objects.get(email=instructor_email) instructor.is_staff = True @@ -322,14 +293,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): # and now should be able to load both self._check_staff(self.course) - self._check_staff(self.full) + self._check_staff(self.test_course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_enrollment_period(self): """ Check that enrollment periods work. """ - student_email, student_password = self.ACCOUNT_INFO[0] instructor_email, instructor_password = self.ACCOUNT_INFO[1] @@ -340,34 +310,27 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): yesterday = now - datetime.timedelta(days=1) course_data = {'enrollment_start': tomorrow, 'enrollment_end': nextday} - full_data = {'enrollment_start': yesterday, 'enrollment_end': tomorrow} + test_course_data = {'enrollment_start': yesterday, 'enrollment_end': tomorrow} - print "changing" # self.course's enrollment period hasn't started self.course = self.update_course(self.course, course_data) - # full course's has - self.full = self.update_course(self.full, full_data) + # test_course course's has + self.test_course = self.update_course(self.test_course, test_course_data) - print "login" # First, try with an enrolled student - print '=== Testing student access....' self.login(student_email, student_password) self.assertFalse(self.enroll(self.course)) - self.assertTrue(self.enroll(self.full)) + self.assertTrue(self.enroll(self.test_course)) - print '=== Testing course instructor access....' # Make the instructor staff in the self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(User.objects.get(email=instructor_email)) - print "logout/login" self.logout() self.login(instructor_email, instructor_password) - print "Instructor should be able to enroll in self.course" self.assertTrue(self.enroll(self.course)) - print '=== Testing staff access....' # now make the instructor global staff, but not in the instructor group group.user_set.remove(User.objects.get(email=instructor_email)) instructor = User.objects.get(email=instructor_email) @@ -383,7 +346,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Check that beta-test access works. """ - student_email, student_password = self.ACCOUNT_INFO[0] instructor_email, instructor_password = self.ACCOUNT_INFO[1] From 3e0f933a73830e39f4499c25e184153150a18d72 Mon Sep 17 00:00:00 2001 From: Jean Manuel Nater Date: Thu, 27 Jun 2013 15:09:59 -0400 Subject: [PATCH 13/15] Formatted a docstring. --- lms/djangoapps/courseware/tests/test_navigation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py index 282f6383fc..dd1f00711c 100644 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ b/lms/djangoapps/courseware/tests/test_navigation.py @@ -11,13 +11,12 @@ from modulestore_config import TEST_DATA_MONGO_MODULESTORE @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): - - STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] - """ Check that navigation state is saved properly. """ + STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] + def setUp(self): self.test_course = CourseFactory.create(display_name='Robot_Sub_Course') From 7e6722c50ffa6fb3c7828f6643ff046942d80cfc Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 26 Jun 2013 17:14:12 -0400 Subject: [PATCH 14/15] Added clean reports dependency to rake JavaScript test tasks. This ensures that `rake test` will clean the report directories *before* running the JS tests. --- rakelib/jasmine.rake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rakelib/jasmine.rake b/rakelib/jasmine.rake index 0f532fdf6f..ff72161937 100644 --- a/rakelib/jasmine.rake +++ b/rakelib/jasmine.rake @@ -80,7 +80,7 @@ end namespace :jasmine do namespace system do desc "Open jasmine tests for #{system} in your default browser" - task :browser do + task :browser => [:clean_reports_dir] do Rake::Task[:assets].invoke(system, 'jasmine') django_for_jasmine(system, true) do |jasmine_url| jasmine_browser(jasmine_url) @@ -88,7 +88,7 @@ end end desc "Open jasmine tests for #{system} in your default browser, and dynamically recompile coffeescript" - task :'browser:watch' => :'assets:coffee:_watch' do + task :'browser:watch' => [:clean_reports_dir, :'assets:coffee:_watch'] do django_for_jasmine(system, true) do |jasmine_url| jasmine_browser(jasmine_url, jitter=0, wait=0) end @@ -97,7 +97,7 @@ end end desc "Use phantomjs to run jasmine tests for #{system} from the console" - task :phantomjs do + task :phantomjs => [:clean_reports_dir] do Rake::Task[:assets].invoke(system, 'jasmine') phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs' django_for_jasmine(system, false) do |jasmine_url| @@ -122,7 +122,7 @@ static_js_dirs.each do |dir| namespace :jasmine do namespace dir do desc "Open jasmine tests for #{dir} in your default browser" - task :browser do + task :browser => [:clean_reports_dir] do # We need to use either CMS or LMS to preprocess files. Use LMS by default Rake::Task['assets:coffee'].invoke('lms', 'jasmine') template_jasmine_runner(dir) do |f| @@ -131,7 +131,7 @@ static_js_dirs.each do |dir| end desc "Use phantomjs to run jasmine tests for #{dir} from the console" - task :phantomjs do + task :phantomjs => [:clean_reports_dir] do # We need to use either CMS or LMS to preprocess files. Use LMS by default Rake::Task[:assets].invoke('lms', 'jasmine') template_jasmine_runner(dir) do |f| From af05d05cbe289ba2e596901a2cd3bd8abe66fd64 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Thu, 27 Jun 2013 10:51:23 -0400 Subject: [PATCH 15/15] Used dependency to ensure that REPORT_DIR exists before cleaning. This allows the build to pass in Jenkins --- rakelib/tests.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rakelib/tests.rake b/rakelib/tests.rake index 0ca7c5c1e9..2bbe3a6ad8 100644 --- a/rakelib/tests.rake +++ b/rakelib/tests.rake @@ -53,7 +53,7 @@ task :clean_test_files do sh("git clean -fqdx test_root") end -task :clean_reports_dir do +task :clean_reports_dir => REPORT_DIR do desc "Clean coverage files, to ensure that we don't use stale data to generate reports." # We delete the files but preserve the directory structure