Commit 97275a32 by Jean Manuel Nater

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
parent 78128e76
from factory import Factory, lazy_attribute_sequence, lazy_attribute from factory import Factory, lazy_attribute_sequence, lazy_attribute
from uuid import uuid4 from uuid import uuid4
import datetime
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from xblock.runtime import InvalidScopeError from xblock.runtime import InvalidScopeError
import datetime
from pytz import UTC from pytz import UTC
...@@ -149,6 +150,8 @@ class XModuleItemFactory(Factory): ...@@ -149,6 +150,8 @@ class XModuleItemFactory(Factory):
if new_item.location.category not in DETACHED_CATEGORIES: if new_item.location.category not in DETACHED_CATEGORIES:
store.update_children(parent_location, parent.children + [new_item.location.url()]) store.update_children(parent_location, parent.children + [new_item.location.url()])
new_item = store.get_item(new_item.location)
return new_item return new_item
......
...@@ -245,7 +245,8 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): ...@@ -245,7 +245,8 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
if descriptor.lms.start is not None: if descriptor.lms.start is not None:
now = datetime.now(UTC()) now = datetime.now(UTC())
effective_start = _adjust_start_date_for_beta_testers(user, descriptor) 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 # after start date, everyone can see it
debug("Allow: now > effective start date") debug("Allow: now > effective start date")
return True return True
...@@ -508,6 +509,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor): ...@@ -508,6 +509,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
start_as_datetime = descriptor.lms.start start_as_datetime = descriptor.lms.start
delta = timedelta(descriptor.lms.days_early_for_beta) delta = timedelta(descriptor.lms.days_early_for_beta)
effective = start_as_datetime - delta effective = start_as_datetime - delta
# ...and back to time_struct # ...and back to time_struct
return effective return effective
...@@ -570,7 +572,6 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -570,7 +572,6 @@ def _has_access_to_location(user, location, access_level, course_context):
debug("Deny: user not in groups %s", instructor_groups) debug("Deny: user not in groups %s", instructor_groups)
else: else:
log.debug("Error in access._has_access_to_location access_level=%s unknown" % access_level) log.debug("Error in access._has_access_to_location access_level=%s unknown" % access_level)
return False return False
......
...@@ -80,7 +80,6 @@ def xml_store_config(data_dir): ...@@ -80,7 +80,6 @@ def xml_store_config(data_dir):
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR) TEST_DATA_XML_MODULESTORE = xml_store_config(TEST_DATA_DIR)
TEST_DATA_MONGO_MODULESTORE = mongo_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): class LoginEnrollmentTestCase(TestCase):
......
import logging import logging
import time
import datetime import datetime
import pytz import pytz
import random import random
...@@ -80,10 +79,14 @@ class TestViewAuth(MongoLoginHelpers): ...@@ -80,10 +79,14 @@ class TestViewAuth(MongoLoginHelpers):
def setUp(self): def setUp(self):
xmodule.modulestore.django._MODULESTORES = {} 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.course = CourseFactory.create()
self.overview_chapter = ItemFactory.create(display_name='Overview') 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, self.progress_chapter = ItemFactory.create(parent_location=self.course.location,
display_name='progress') display_name='progress')
self.info_chapter = ItemFactory.create(parent_location=self.course.location, self.info_chapter = ItemFactory.create(parent_location=self.course.location,
...@@ -121,6 +124,7 @@ class TestViewAuth(MongoLoginHelpers): ...@@ -121,6 +124,7 @@ class TestViewAuth(MongoLoginHelpers):
# should work now -- redirect to first page # should work now -- redirect to first page
response = self.client.get(reverse('courseware', response = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id}))
self.assertRedirectsNoFollow(response, self.assertRedirectsNoFollow(response,
reverse('courseware_section', reverse('courseware_section',
kwargs={'course_id': self.course.id, kwargs={'course_id': self.course.id,
...@@ -210,8 +214,8 @@ class TestViewAuth(MongoLoginHelpers): ...@@ -210,8 +214,8 @@ class TestViewAuth(MongoLoginHelpers):
# Make courses start in the future # Make courses start in the future
now = datetime.datetime.now(pytz.UTC) now = datetime.datetime.now(pytz.UTC)
tomorrow = now + datetime.timedelta(days=1) tomorrow = now + datetime.timedelta(days=1)
self.course.start = tomorrow self.course.lms.start = tomorrow
self.full.start = tomorrow self.full.lms.start = tomorrow
self.assertFalse(self.course.has_started()) self.assertFalse(self.course.has_started())
self.assertFalse(self.full.has_started()) self.assertFalse(self.full.has_started())
...@@ -344,7 +348,6 @@ class TestViewAuth(MongoLoginHelpers): ...@@ -344,7 +348,6 @@ class TestViewAuth(MongoLoginHelpers):
print "changing" print "changing"
# self.course's enrollment period hasn't started # self.course's enrollment period hasn't started
print self.course.enrollment_start
self.course = update_course(self.course, course_data) self.course = update_course(self.course, course_data)
# full course's has # full course's has
self.full = update_course(self.full, full_data) self.full = update_course(self.full, full_data)
...@@ -391,7 +394,7 @@ class TestViewAuth(MongoLoginHelpers): ...@@ -391,7 +394,7 @@ class TestViewAuth(MongoLoginHelpers):
# nextday = tomorrow + 24 * 3600 # nextday = tomorrow + 24 * 3600
# yesterday = time.time() - 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.course.lms.start = tomorrow
self.assertFalse(self.course.has_started()) self.assertFalse(self.course.has_started())
......
...@@ -272,144 +272,6 @@ class LoginEnrollmentTestCase(TestCase): ...@@ -272,144 +272,6 @@ class LoginEnrollmentTestCase(TestCase):
return resp 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) @override_settings(MODULESTORE=TEST_DATA_DRAFT_MONGO_MODULESTORE)
class TestDraftModuleStore(TestCase): class TestDraftModuleStore(TestCase):
def test_get_items_with_course_items(self): def test_get_items_with_course_items(self):
...@@ -425,327 +287,6 @@ class TestDraftModuleStore(TestCase): ...@@ -425,327 +287,6 @@ class TestDraftModuleStore(TestCase):
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) @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): class TestSubmittingProblems(LoginEnrollmentTestCase):
"""Check that a course gets graded properly""" """Check that a course gets graded properly"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment