test_navigation.py 12.5 KB
Newer Older
1 2 3 4 5
"""
This test file will run through some LMS test scenarios regarding access and navigation of the LMS
"""
import time

6 7
from courseware.tests.factories import GlobalStaffFactory
from courseware.tests.helpers import LoginEnrollmentTestCase
8
from django.conf import settings
9 10
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
11 12
from mock import patch
from nose.plugins.attrib import attr
13 14
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from openedx.features.course_experience import COURSE_OUTLINE_PAGE_FLAG
15
from student.tests.factories import UserFactory
16
from xmodule.modulestore.django import modulestore
17
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
18
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
19 20


21
@attr(shard=1)
22
class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
23 24 25
    """
    Check that navigation state is saved properly.
    """
26

27 28
    STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')]

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
    @classmethod
    def setUpClass(cls):
        #  pylint: disable=super-method-not-called
        with super(TestNavigation, cls).setUpClassAndTestData():
            cls.test_course = CourseFactory.create()
            cls.test_course_proctored = CourseFactory.create()
            cls.course = CourseFactory.create()

    @classmethod
    def setUpTestData(cls):
        cls.chapter0 = ItemFactory.create(parent=cls.course,
                                          display_name='Overview')
        cls.chapter9 = ItemFactory.create(parent=cls.course,
                                          display_name='factory_chapter')
        cls.section0 = ItemFactory.create(parent=cls.chapter0,
                                          display_name='Welcome')
        cls.section9 = ItemFactory.create(parent=cls.chapter9,
                                          display_name='factory_section')
        cls.unit0 = ItemFactory.create(parent=cls.section0,
48
                                       display_name='New Unit 0')
49 50 51 52 53 54 55 56 57 58 59 60 61 62

        cls.chapterchrome = ItemFactory.create(parent=cls.course,
                                               display_name='Chrome')
        cls.chromelesssection = ItemFactory.create(parent=cls.chapterchrome,
                                                   display_name='chromeless',
                                                   chrome='none')
        cls.accordionsection = ItemFactory.create(parent=cls.chapterchrome,
                                                  display_name='accordion',
                                                  chrome='accordion')
        cls.tabssection = ItemFactory.create(parent=cls.chapterchrome,
                                             display_name='tabs',
                                             chrome='tabs')
        cls.defaultchromesection = ItemFactory.create(
            parent=cls.chapterchrome,
63 64
            display_name='defaultchrome',
        )
65 66 67 68 69 70 71 72 73 74 75 76
        cls.fullchromesection = ItemFactory.create(parent=cls.chapterchrome,
                                                   display_name='fullchrome',
                                                   chrome='accordion,tabs')
        cls.tabtest = ItemFactory.create(parent=cls.chapterchrome,
                                         display_name='progress_tab',
                                         default_tab='progress')

        cls.staff_user = GlobalStaffFactory()
        cls.user = UserFactory()

    def setUp(self):
        super(TestNavigation, self).setUp()
77

78 79
        # Create student accounts and activate them.
        for i in range(len(self.STUDENT_INFO)):
80 81 82 83
            email, password = self.STUDENT_INFO[i]
            username = 'u{0}'.format(i)
            self.create_account(username, email, password)
            self.activate_user(email)
84

Piotr Mitros committed
85
    def assertTabActive(self, tabname, response):
86
        ''' Check if the progress tab is active in the tab set '''
Piotr Mitros committed
87 88 89
        for line in response.content.split('\n'):
            if tabname in line and 'active' in line:
                return
Don Mitchell committed
90
        raise AssertionError("assertTabActive failed: {} not active".format(tabname))
Piotr Mitros committed
91 92

    def assertTabInactive(self, tabname, response):
93
        ''' Check if the progress tab is active in the tab set '''
Piotr Mitros committed
94 95
        for line in response.content.split('\n'):
            if tabname in line and 'active' in line:
96
                raise AssertionError("assertTabInactive failed: " + tabname + " active")
Piotr Mitros committed
97 98
        return

99 100
    # TODO: LEARNER-71: Do we need to adjust or remove this test?
    @override_waffle_flag(COURSE_OUTLINE_PAGE_FLAG, active=False)
101 102
    def test_chrome_settings(self):
        '''
103
        Test settings for disabling and modifying navigation chrome in the courseware:
104 105 106 107 108 109 110 111 112 113 114 115
        - Accordion enabled, or disabled
        - Navigation tabs enabled, disabled, or redirected
        '''
        email, password = self.STUDENT_INFO[0]
        self.login(email, password)
        self.enroll(self.course, True)

        test_data = (
            ('tabs', False, True),
            ('none', False, False),
            ('fullchrome', True, True),
            ('accordion', True, False),
Piotr Mitros committed
116
            ('fullchrome', True, True)
Don Mitchell committed
117
        )
118 119
        for (displayname, accordion, tabs) in test_data:
            response = self.client.get(reverse('courseware_section', kwargs={
Piotr Mitros committed
120 121 122 123
                'course_id': self.course.id.to_deprecated_string(),
                'chapter': 'Chrome',
                'section': displayname,
            }))
124
            self.assertEquals('course-tabs' in response.content, tabs)
125
            self.assertEquals('course-navigation' in response.content, accordion)
126

Piotr Mitros committed
127 128
        self.assertTabInactive('progress', response)
        self.assertTabActive('courseware', response)
129 130

        response = self.client.get(reverse('courseware_section', kwargs={
Piotr Mitros committed
131 132 133 134
            'course_id': self.course.id.to_deprecated_string(),
            'chapter': 'Chrome',
            'section': 'progress_tab',
        }))
135

Piotr Mitros committed
136 137
        self.assertTabActive('progress', response)
        self.assertTabInactive('courseware', response)
138

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
    def test_inactive_session_timeout(self):
        """
        Verify that an inactive session times out and redirects to the
        login page
        """
        email, password = self.STUDENT_INFO[0]
        self.login(email, password)

        # make sure we can access courseware immediately
        resp = self.client.get(reverse('dashboard'))
        self.assertEquals(resp.status_code, 200)

        # then wait a bit and see if we get timed out
        time.sleep(2)

        resp = self.client.get(reverse('dashboard'))

        # re-request, and we should get a redirect to login page
        self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=' + reverse('dashboard'))

160 161 162 163 164
    def test_redirects_first_time(self):
        """
        Verify that the first time we click on the courseware tab we are
        redirected to the 'Welcome' section.
        """
165 166
        email, password = self.STUDENT_INFO[0]
        self.login(email, password)
167
        self.enroll(self.course, True)
168
        self.enroll(self.test_course, True)
169 170

        resp = self.client.get(reverse('courseware',
171
                               kwargs={'course_id': self.course.id.to_deprecated_string()}))
172
        self.assertRedirects(resp, reverse(
173
            'courseware_section', kwargs={'course_id': self.course.id.to_deprecated_string(),
174 175 176
                                          'chapter': 'Overview',
                                          'section': 'Welcome'}))

177 178 179
    def test_redirects_second_time(self):
        """
        Verify the accordion remembers we've already visited the Welcome section
180
        and redirects correspondingly.
181
        """
182 183
        email, password = self.STUDENT_INFO[0]
        self.login(email, password)
184
        self.enroll(self.course, True)
185
        self.enroll(self.test_course, True)
186

187 188
        section_url = reverse(
            'courseware_section',
189 190
            kwargs={
                'course_id': self.course.id.to_deprecated_string(),
191 192 193
                'chapter': 'Overview',
                'section': 'Welcome',
            },
194
        )
195 196 197 198 199
        self.client.get(section_url)
        resp = self.client.get(
            reverse('courseware', kwargs={'course_id': self.course.id.to_deprecated_string()}),
        )
        self.assertRedirects(resp, section_url)
200 201 202 203 204

    def test_accordion_state(self):
        """
        Verify the accordion remembers which chapter you were last viewing.
        """
205 206
        email, password = self.STUDENT_INFO[0]
        self.login(email, password)
207
        self.enroll(self.course, True)
208
        self.enroll(self.test_course, True)
209

210
        # Now we directly navigate to a section in a chapter other than 'Overview'.
211
        section_url = reverse(
212 213 214 215
            'courseware_section',
            kwargs={
                'course_id': self.course.id.to_deprecated_string(),
                'chapter': 'factory_chapter',
216
                'section': 'factory_section',
217
            }
218
        )
219
        self.assert_request_status_code(200, section_url)
220

221
        # And now hitting the courseware tab should redirect to 'factory_chapter'
222 223 224 225 226
        url = reverse(
            'courseware',
            kwargs={'course_id': self.course.id.to_deprecated_string()}
        )
        resp = self.client.get(url)
227
        self.assertRedirects(resp, section_url)
228

229 230
    # TODO: LEARNER-71: Do we need to adjust or remove this test?
    @override_waffle_flag(COURSE_OUTLINE_PAGE_FLAG, active=False)
231 232 233 234 235 236 237 238
    def test_incomplete_course(self):
        email = self.staff_user.email
        password = "test"
        self.login(email, password)
        self.enroll(self.test_course, True)

        test_course_id = self.test_course.id.to_deprecated_string()

239 240 241
        url = reverse(
            'courseware',
            kwargs={'course_id': test_course_id}
242
        )
243 244
        response = self.assert_request_status_code(200, url)
        self.assertIn("No content has been added to this course", response.content)
245 246 247 248 249

        section = ItemFactory.create(
            parent_location=self.test_course.location,
            display_name='New Section'
        )
250 251 252
        url = reverse(
            'courseware',
            kwargs={'course_id': test_course_id}
253
        )
254 255 256
        response = self.assert_request_status_code(200, url)
        self.assertNotIn("No content has been added to this course", response.content)
        self.assertIn("New Section", response.content)
257 258 259

        subsection = ItemFactory.create(
            parent_location=section.location,
260
            display_name='New Subsection',
261
        )
262 263 264
        url = reverse(
            'courseware',
            kwargs={'course_id': test_course_id}
265
        )
266 267 268
        response = self.assert_request_status_code(200, url)
        self.assertIn("New Subsection", response.content)
        self.assertNotIn("sequence-nav", response.content)
269 270 271

        ItemFactory.create(
            parent_location=subsection.location,
272
            display_name='New Unit',
273
        )
274 275 276
        url = reverse(
            'courseware',
            kwargs={'course_id': test_course_id}
277
        )
278
        self.assert_request_status_code(302, url)
279 280 281 282 283 284 285 286 287 288

    def test_proctoring_js_includes(self):
        """
        Make sure that proctoring JS does not get included on
        courseware pages if either the FEATURE flag is turned off
        or the course is not proctored enabled
        """

        email, password = self.STUDENT_INFO[0]
        self.login(email, password)
289
        self.enroll(self.test_course_proctored, True)
290

291
        test_course_id = self.test_course_proctored.id.to_deprecated_string()
292

293
        with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': False}):
294 295 296 297 298 299 300 301
            url = reverse(
                'courseware',
                kwargs={'course_id': test_course_id}
            )
            resp = self.client.get(url)

            self.assertNotContains(resp, '/static/js/lms-proctoring.js')

302
        with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': True}):
303 304 305 306 307 308 309 310 311 312
            url = reverse(
                'courseware',
                kwargs={'course_id': test_course_id}
            )
            resp = self.client.get(url)

            self.assertNotContains(resp, '/static/js/lms-proctoring.js')

            # now set up a course which is proctored enabled

313 314
            self.test_course_proctored.enable_proctored_exams = True
            self.test_course_proctored.save()
315

316
            modulestore().update_item(self.test_course_proctored, self.user.id)
317 318 319 320

            resp = self.client.get(url)

            self.assertContains(resp, '/static/js/lms-proctoring.js')