Files
edx-platform/common/djangoapps/student/tests/test_views.py
Clinton Blackburn 3f19cc0265 Updated logout view
In addition to logging the user out of LMS, the logout view also logs users out of the IDAs to which they previously authenticated.

ECOM-4610
2016-06-15 11:11:49 -04:00

198 lines
8.0 KiB
Python

"""
Test the student dashboard view.
"""
import unittest
import ddt
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from edx_oauth2_provider.constants import AUTHORIZED_CLIENTS_SESSION_KEY
from edx_oauth2_provider.tests.factories import ClientFactory, TrustedClientFactory
from mock import patch
from pyquery import PyQuery as pq
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.helpers import DISABLE_UNENROLL_CERT_STATES
from student.models import CourseEnrollment, LogoutViewConfiguration
from student.tests.factories import UserFactory, CourseEnrollmentFactory
PASSWORD = 'test'
@ddt.ddt
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestStudentDashboardUnenrollments(SharedModuleStoreTestCase):
"""
Test to ensure that the student dashboard does not show the unenroll button for users with certificates.
"""
UNENROLL_ELEMENT_ID = "#actions-item-unenroll-0"
@classmethod
def setUpClass(cls):
super(TestStudentDashboardUnenrollments, cls).setUpClass()
cls.course = CourseFactory.create()
def setUp(self):
""" Create a course and user, then log in. """
super(TestStudentDashboardUnenrollments, self).setUp()
self.user = UserFactory()
CourseEnrollmentFactory(course_id=self.course.id, user=self.user)
self.cert_status = None
self.client.login(username=self.user.username, password=PASSWORD)
def mock_cert(self, _user, _course_overview, _course_mode):
""" Return a preset certificate status. """
if self.cert_status is not None:
return {
'status': self.cert_status,
'can_unenroll': self.cert_status not in DISABLE_UNENROLL_CERT_STATES
}
else:
return {}
@ddt.data(
('notpassing', 1),
('restricted', 1),
('processing', 1),
(None, 1),
('generating', 0),
('ready', 0),
)
@ddt.unpack
def test_unenroll_available(self, cert_status, unenroll_action_count):
""" Assert that the unenroll action is shown or not based on the cert status."""
self.cert_status = cert_status
with patch('student.views.cert_info', side_effect=self.mock_cert):
response = self.client.get(reverse('dashboard'))
self.assertEqual(pq(response.content)(self.UNENROLL_ELEMENT_ID).length, unenroll_action_count)
@ddt.data(
('notpassing', 200),
('restricted', 200),
('processing', 200),
(None, 200),
('generating', 400),
('ready', 400),
)
@ddt.unpack
@patch.object(CourseEnrollment, 'unenroll')
def test_unenroll_request(self, cert_status, status_code, course_enrollment):
""" Assert that the unenroll method is called or not based on the cert status"""
self.cert_status = cert_status
with patch('student.views.cert_info', side_effect=self.mock_cert):
response = self.client.post(
reverse('change_enrollment'),
{'enrollment_action': 'unenroll', 'course_id': self.course.id}
)
self.assertEqual(response.status_code, status_code)
if status_code == 200:
course_enrollment.assert_called_with(self.user, self.course.id)
else:
course_enrollment.assert_not_called()
def test_no_cert_status(self):
""" Assert that the dashboard loads when cert_status is None."""
with patch('student.views.cert_info', return_value=None):
response = self.client.get(reverse('dashboard'))
self.assertEqual(response.status_code, 200)
def test_cant_unenroll_status(self):
""" Assert that the dashboard loads when cert_status does not allow for unenrollment"""
with patch('certificates.models.certificate_status_for_student', return_value={'status': 'ready'}):
response = self.client.get(reverse('dashboard'))
self.assertEqual(response.status_code, 200)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class LogoutTests(TestCase):
""" Tests for the logout functionality. """
def setUp(self):
""" Create a course and user, then log in. """
super(LogoutTests, self).setUp()
self.user = UserFactory()
self.client.login(username=self.user.username, password=PASSWORD)
LogoutViewConfiguration.objects.create(enabled=True)
def create_oauth_client(self):
""" Creates a trusted OAuth client. """
client = ClientFactory(logout_uri='https://www.example.com/logout/')
TrustedClientFactory(client=client)
return client
def assert_session_logged_out(self, oauth_client, **logout_headers):
""" Authenticates a user via OAuth 2.0, logs out, and verifies the session is logged out. """
self.authenticate_with_oauth(oauth_client)
# Logging out should remove the session variables, and send a list of logout URLs to the template.
# The template will handle loading those URLs and redirecting the user. That functionality is not tested here.
response = self.client.get(reverse('logout'), **logout_headers)
self.assertEqual(response.status_code, 200)
self.assertNotIn(AUTHORIZED_CLIENTS_SESSION_KEY, self.client.session)
return response
def authenticate_with_oauth(self, oauth_client):
""" Perform an OAuth authentication using the current web client.
This should add an AUTHORIZED_CLIENTS_SESSION_KEY entry to the current session.
"""
data = {
'client_id': oauth_client.client_id,
'client_secret': oauth_client.client_secret,
'response_type': 'code'
}
# Authenticate with OAuth to set the appropriate session values
self.client.post(reverse('oauth2:capture'), data, follow=True)
self.assertListEqual(self.client.session[AUTHORIZED_CLIENTS_SESSION_KEY], [oauth_client.client_id])
def assert_logout_redirects(self):
""" Verify logging out redirects the user to the homepage. """
response = self.client.get(reverse('logout'))
self.assertRedirects(response, '/', fetch_redirect_response=False)
def test_switch(self):
""" Verify the IDA logout functionality is disabled if the associated switch is disabled. """
LogoutViewConfiguration.objects.create(enabled=False)
oauth_client = self.create_oauth_client()
self.authenticate_with_oauth(oauth_client)
self.assert_logout_redirects()
def test_without_session_value(self):
""" Verify logout works even if the session does not contain an entry with
the authenticated OpenID Connect clients."""
self.assert_logout_redirects()
def test_client_logout(self):
""" Verify the context includes a list of the logout URIs of the authenticated OpenID Connect clients.
The list should only include URIs of the clients for which the user has been authenticated.
"""
client = self.create_oauth_client()
response = self.assert_session_logged_out(client)
expected = {
'logout_uris': [client.logout_uri + '?no_redirect=1'], # pylint: disable=no-member
'target': '/',
}
self.assertDictContainsSubset(expected, response.context_data) # pylint: disable=no-member
def test_filter_referring_service(self):
""" Verify that, if the user is directed to the logout page from a service, that service's logout URL
is not included in the context sent to the template.
"""
client = self.create_oauth_client()
response = self.assert_session_logged_out(client, HTTP_REFERER=client.logout_uri) # pylint: disable=no-member
expected = {
'logout_uris': [],
'target': '/',
}
self.assertDictContainsSubset(expected, response.context_data) # pylint: disable=no-member