tests.py 13.5 KB
Newer Older
1 2 3 4
"""
This test file will test registration, login, activation, and session activity timeouts
"""
import time
5
import mock
6
import unittest
7
from ddt import ddt, data, unpack
8

9
from django.test import TestCase
10
from django.test.utils import override_settings
Diana Huang committed
11
from django.core.cache import cache
12
from django.conf import settings
13
from django.contrib.auth.models import User
14
from django.core.urlresolvers import reverse
15

16
from contentstore.models import PushNotificationConfig
17
from contentstore.tests.utils import parse_json, user, registration, AjaxEnabledTestClient
18
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
19 20 21 22
from contentstore.tests.test_course_settings import CourseTestCase
from xmodule.modulestore.tests.factories import CourseFactory
import datetime
from pytz import UTC
23

24
from freezegun import freeze_time
Calen Pennington committed
25

26

27
class ContentStoreTestCase(ModuleStoreTestCase):
David Baumgold committed
28 29 30 31 32
    def _login(self, email, password):
        """
        Login.  View should always return 200.  The success/fail is in the
        returned json
        """
David Baumgold committed
33 34 35 36
        resp = self.client.post(
            reverse('login_post'),
            {'email': email, 'password': password}
        )
37
        self.assertEqual(resp.status_code, 200)
38
        return resp
39

David Baumgold committed
40
    def login(self, email, password):
41
        """Login, check that it worked."""
David Baumgold committed
42
        resp = self._login(email, password)
43
        data = parse_json(resp)
44 45
        self.assertTrue(data['success'])
        return resp
46

David Baumgold committed
47
    def _create_account(self, username, email, password):
48
        """Try to create an account.  No error checking"""
49
        resp = self.client.post('/create_account', {
50 51
            'username': username,
            'email': email,
David Baumgold committed
52
            'password': password,
53 54 55 56 57
            'location': 'home',
            'language': 'Franglish',
            'name': 'Fred Weasley',
            'terms_of_service': 'true',
            'honor_code': 'true',
Calen Pennington committed
58
        })
59 60
        return resp

David Baumgold committed
61
    def create_account(self, username, email, password):
62
        """Create the account and check that it worked"""
David Baumgold committed
63
        resp = self._create_account(username, email, password)
64 65 66
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertEqual(data['success'], True)
67 68

        # Check both that the user is created, and inactive
69
        self.assertFalse(user(email).is_active)
70 71 72 73

        return resp

    def _activate_user(self, email):
74 75
        """Look up the activation key for the user, then hit the activate view.
        No error checking"""
76 77 78 79 80 81 82 83 84 85
        activation_key = registration(email).activation_key

        # and now we try to activate
        resp = self.client.get(reverse('activate', kwargs={'key': activation_key}))
        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
86
        self.assertTrue(user(email).is_active)
87

88

89 90 91 92
class AuthTestCase(ContentStoreTestCase):
    """Check that various permissions-related things work"""

    def setUp(self):
93 94
        super(AuthTestCase, self).setUp(create_user=False)

95 96 97
        self.email = 'a@b.com'
        self.pw = 'xyz'
        self.username = 'testuser'
98
        self.client = AjaxEnabledTestClient()
Diana Huang committed
99 100
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()
101 102

    def check_page_get(self, url, expected):
103
        resp = self.client.get_html(url)
104
        self.assertEqual(resp.status_code, expected)
105
        return resp
106

107 108 109
    def test_public_pages_load(self):
        """Make sure pages that don't require login load without error."""
        pages = (
110 111 112
            reverse('login'),
            reverse('signup'),
        )
113
        for page in pages:
114
            print "Checking '{0}'".format(page)
115
            self.check_page_get(page, 200)
116

117 118 119
    def test_create_account_errors(self):
        # No post data -- should fail
        resp = self.client.post('/create_account', {})
120
        self.assertEqual(resp.status_code, 400)
121
        data = parse_json(resp)
122 123 124 125 126
        self.assertEqual(data['success'], False)

    def test_create_account(self):
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)
127

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    def test_create_account_username_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account(self.username, "abc@def.com", "password")
        # we have a constraint on unique usernames, so this should fail
        self.assertEqual(resp.status_code, 400)

    def test_create_account_pw_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", "abc@def.com", self.pw)
        # we can have two users with the same password, so this should succeed
        self.assertEqual(resp.status_code, 200)

    @unittest.skipUnless(settings.SOUTH_TESTS_MIGRATE, "South migrations required")
    def test_create_account_email_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", self.email, "password")
        # This is tricky. Django's user model doesn't have a constraint on
        # unique email addresses, but we *add* that constraint during the
        # migration process:
        # see common/djangoapps/student/migrations/0004_add_email_index.py
        #
        # The behavior we *want* is for this account creation request
        # to fail, due to this uniqueness constraint, but the request will
        # succeed if the migrations have not run.
        self.assertEqual(resp.status_code, 400)

154 155 156 157 158 159 160
    def test_login(self):
        self.create_account(self.username, self.email, self.pw)

        # Not activated yet.  Login should fail.
        resp = self._login(self.email, self.pw)
        data = parse_json(resp)
        self.assertFalse(data['success'])
Calen Pennington committed
161

162 163 164 165 166
        self.activate_user(self.email)

        # Now login should work
        self.login(self.email, self.pw)

Diana Huang committed
167 168 169 170 171 172 173 174 175 176 177 178
    def test_login_ratelimited(self):
        # try logging in 30 times, the default limit in the number of failed
        # login attempts in one 5 minute period before the rate gets limited
        for i in xrange(30):
            resp = self._login(self.email, 'wrong_password{0}'.format(i))
            self.assertEqual(resp.status_code, 200)
        resp = self._login(self.email, 'wrong_password')
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertFalse(data['success'])
        self.assertIn('Too many failed login attempts.', data['value'])

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3)
    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2)
    def test_excessive_login_failures(self):
        # try logging in 3 times, the account should get locked for 3 seconds
        # note we want to keep the lockout time short, so we don't slow down the tests

        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}):
            self.create_account(self.username, self.email, self.pw)
            self.activate_user(self.email)

            for i in xrange(3):
                resp = self._login(self.email, 'wrong_password{0}'.format(i))
                self.assertEqual(resp.status_code, 200)
                data = parse_json(resp)
                self.assertFalse(data['success'])
                self.assertIn(
                    'Email or password is incorrect.',
                    data['value']
                )

            # now the account should be locked

            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])
            self.assertIn(
                'This account has been temporarily locked due to excessive login failures. Try again later.',
                data['value']
            )

            with freeze_time('2100-01-01'):
                self.login(self.email, self.pw)

            # make sure the failed attempt counter gets reset on successful login
            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])

            # account should not be locked out after just one attempt
            self.login(self.email, self.pw)

            # do one more login when there is no bad login counter row at all in the database to
            # test the "ObjectNotFound" case
            self.login(self.email, self.pw)

226 227 228 229 230 231 232 233 234 235 236 237
    def test_login_link_on_activation_age(self):
        self.create_account(self.username, self.email, self.pw)
        # we want to test the rendering of the activation page when the user isn't logged in
        self.client.logout()
        resp = self._activate_user(self.email)
        self.assertEqual(resp.status_code, 200)

        # check the the HTML has links to the right login page. Note that this is merely a content
        # check and thus could be fragile should the wording change on this page
        expected = 'You can now <a href="' + reverse('login') + '">login</a>.'
        self.assertIn(expected, resp.content)

238 239 240
    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = (
241
            '/home/',
242
        )
243 244 245 246

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = (
247
            '/home/',
248
        )
249 250 251 252

        # need an activated user
        self.test_create_account()

Calen Pennington committed
253
        # Create a new session
254
        self.client = AjaxEnabledTestClient()
Calen Pennington committed
255

256
        # Not logged in.  Should redirect to login.
257
        print 'Not logged in'
258
        for page in auth_pages:
259
            print "Checking '{0}'".format(page)
260 261 262 263 264
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

265
        print 'Logged in'
266
        for page in simple_auth_pages:
267
            print "Checking '{0}'".format(page)
268 269 270 271 272
            self.check_page_get(page, expected=200)

    def test_index_auth(self):

        # not logged in.  Should return a redirect.
273
        resp = self.client.get_html('/home/')
274 275 276
        self.assertEqual(resp.status_code, 302)

        # Logged in should work.
277

278 279 280 281 282 283 284 285 286 287 288 289
    @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
        """
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

        self.login(self.email, self.pw)

        # make sure we can access courseware immediately
290
        course_url = '/home/'
291
        resp = self.client.get_html(course_url)
292 293 294 295 296
        self.assertEquals(resp.status_code, 200)

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

297
        resp = self.client.get_html(course_url)
298 299

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

302 303 304 305 306 307 308 309 310

class ForumTestCase(CourseTestCase):
    def setUp(self):
        """ Creates the test course. """
        super(ForumTestCase, self).setUp()
        self.course = CourseFactory.create(org='testX', number='727', display_name='Forum Course')

    def test_blackouts(self):
        now = datetime.datetime.now(UTC)
David Baumgold committed
311 312 313 314 315
        times1 = [
            (now - datetime.timedelta(days=14), now - datetime.timedelta(days=11)),
            (now + datetime.timedelta(days=24), now + datetime.timedelta(days=30))
        ]
        self.course.discussion_blackouts = [(t.isoformat(), t2.isoformat()) for t, t2 in times1]
316
        self.assertTrue(self.course.forum_posts_allowed)
David Baumgold committed
317 318 319 320 321
        times2 = [
            (now - datetime.timedelta(days=14), now + datetime.timedelta(days=2)),
            (now + datetime.timedelta(days=24), now + datetime.timedelta(days=30))
        ]
        self.course.discussion_blackouts = [(t.isoformat(), t2.isoformat()) for t, t2 in times2]
322
        self.assertFalse(self.course.forum_posts_allowed)
323

324 325 326 327
        # test if user gives empty blackout date it should return true for forum_posts_allowed
        self.course.discussion_blackouts = [[]]
        self.assertTrue(self.course.forum_posts_allowed)

328 329 330 331 332 333 334 335 336 337

@ddt
class CourseKeyVerificationTestCase(CourseTestCase):
    def setUp(self):
        """
        Create test course.
        """
        super(CourseKeyVerificationTestCase, self).setUp()
        self.course = CourseFactory.create(org='edX', number='test_course_key', display_name='Test Course')

338
    @data(('edX/test_course_key/Test_Course', 200), ('garbage:edX+test_course_key+Test_Course', 404))
339 340 341 342 343 344 345 346 347
    @unpack
    def test_course_key_decorator(self, course_key, status_code):
        """
        Tests for the ensure_valid_course_key decorator.
        """
        url = '/import/{course_key}'.format(course_key=course_key)
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, status_code)

348
        url = '/import_status/{course_key}/{filename}'.format(
349 350 351 352 353
            course_key=course_key,
            filename='xyz.tar.gz'
        )
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, status_code)
354 355 356 357 358 359 360 361 362 363 364 365


class PushNotificationConfigTestCase(TestCase):
    """
    Tests PushNotificationConfig.
    """
    def test_notifications_defaults(self):
        self.assertFalse(PushNotificationConfig.is_enabled())

    def test_notifications_enabled(self):
        PushNotificationConfig(enabled=True).save()
        self.assertTrue(PushNotificationConfig.is_enabled())