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):