test_admin.py 7.94 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
"""
Tests course_creators.admin.py.
"""

from django.test import TestCase
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.http import HttpRequest
import mock

from course_creators.admin import CourseCreatorAdmin
from course_creators.models import CourseCreator
13
from django.core import mail
14 15
from student.roles import CourseCreatorRole
from student import auth
16 17


18 19 20 21 22
def mock_render_to_string(template_name, context):
    """Return a string that encodes template_name and context"""
    return str((template_name, context))


23 24 25 26 27 28 29 30
class CourseCreatorAdminTest(TestCase):
    """
    Tests for course creator admin.
    """

    def setUp(self):
        """ Test case setup """
        self.user = User.objects.create_user('test_user', 'test_user+courses@edx.org', 'foo')
31
        self.table_entry = CourseCreator(user=self.user)
32 33 34 35 36 37 38 39 40 41
        self.table_entry.save()

        self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
        self.admin.is_staff = True

        self.request = HttpRequest()
        self.request.user = self.admin

        self.creator_admin = CourseCreatorAdmin(self.table_entry, AdminSite())

42 43 44 45 46 47
        self.studio_request_email = 'mark@marky.mark'
        self.enable_creator_group_patch = {
            "ENABLE_CREATOR_GROUP": True,
            "STUDIO_REQUEST_EMAIL": self.studio_request_email
        }

48 49 50
    @mock.patch('course_creators.admin.render_to_string', mock.Mock(side_effect=mock_render_to_string, autospec=True))
    @mock.patch('django.contrib.auth.models.User.email_user')
    def test_change_status(self, email_user):
51
        """
52
        Tests that updates to state impact the creator group maintained in authz.py and that e-mails are sent.
53
        """
Diana Huang committed
54

55 56 57
        def change_state_and_verify_email(state, is_creator):
            """ Changes user state, verifies creator status, and verifies e-mail is sent based on transition """
            self._change_state(state)
58
            self.assertEqual(is_creator, auth.has_access(self.user, CourseCreatorRole()))
Diana Huang committed
59

60
            context = {'studio_request_email': self.studio_request_email}
61 62 63 64 65 66 67 68 69
            if state == CourseCreator.GRANTED:
                template = 'emails/course_creator_granted.txt'
            elif state == CourseCreator.DENIED:
                template = 'emails/course_creator_denied.txt'
            else:
                template = 'emails/course_creator_revoked.txt'
            email_user.assert_called_with(
                mock_render_to_string('emails/course_creator_subject.txt', context),
                mock_render_to_string(template, context),
70
                self.studio_request_email
71 72
            )

73
        with mock.patch.dict('django.conf.settings.FEATURES', self.enable_creator_group_patch):
74 75

            # User is initially unrequested.
76
            self.assertFalse(auth.has_access(self.user, CourseCreatorRole()))
77

78 79 80 81 82 83 84
            change_state_and_verify_email(CourseCreator.GRANTED, True)

            change_state_and_verify_email(CourseCreator.DENIED, False)

            change_state_and_verify_email(CourseCreator.GRANTED, True)

            change_state_and_verify_email(CourseCreator.PENDING, False)
85

86
            change_state_and_verify_email(CourseCreator.GRANTED, True)
87

88
            change_state_and_verify_email(CourseCreator.UNREQUESTED, False)
89

90
            change_state_and_verify_email(CourseCreator.DENIED, False)
91

92 93 94 95 96
    @mock.patch('course_creators.admin.render_to_string', mock.Mock(side_effect=mock_render_to_string, autospec=True))
    def test_mail_admin_on_pending(self):
        """
        Tests that the admin account is notified when a user is in the 'pending' state.
        """
97

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
        def check_admin_message_state(state, expect_sent_to_admin, expect_sent_to_user):
            """ Changes user state and verifies e-mail sent to admin address only when pending. """
            mail.outbox = []
            self._change_state(state)

            # If a message is sent to the user about course creator status change, it will be the first
            # message sent. Admin message will follow.
            base_num_emails = 1 if expect_sent_to_user else 0
            if expect_sent_to_admin:
                context = {'user_name': "test_user", 'user_email': 'test_user+courses@edx.org'}
                self.assertEquals(base_num_emails + 1, len(mail.outbox), 'Expected admin message to be sent')
                sent_mail = mail.outbox[base_num_emails]
                self.assertEquals(
                    mock_render_to_string('emails/course_creator_admin_subject.txt', context),
                    sent_mail.subject
                )
                self.assertEquals(
                    mock_render_to_string('emails/course_creator_admin_user_pending.txt', context),
                    sent_mail.body
                )
                self.assertEquals(self.studio_request_email, sent_mail.from_email)
                self.assertEqual([self.studio_request_email], sent_mail.to)
            else:
                self.assertEquals(base_num_emails, len(mail.outbox))

123
        with mock.patch.dict('django.conf.settings.FEATURES', self.enable_creator_group_patch):
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
            # E-mail message should be sent to admin only when new state is PENDING, regardless of what
            # previous state was (unless previous state was already PENDING).
            # E-mail message sent to user only on transition into and out of GRANTED state.
            check_admin_message_state(CourseCreator.UNREQUESTED, expect_sent_to_admin=False, expect_sent_to_user=False)
            check_admin_message_state(CourseCreator.PENDING, expect_sent_to_admin=True, expect_sent_to_user=False)
            check_admin_message_state(CourseCreator.GRANTED, expect_sent_to_admin=False, expect_sent_to_user=True)
            check_admin_message_state(CourseCreator.DENIED, expect_sent_to_admin=False, expect_sent_to_user=True)
            check_admin_message_state(CourseCreator.GRANTED, expect_sent_to_admin=False, expect_sent_to_user=True)
            check_admin_message_state(CourseCreator.PENDING, expect_sent_to_admin=True, expect_sent_to_user=True)
            check_admin_message_state(CourseCreator.PENDING, expect_sent_to_admin=False, expect_sent_to_user=False)
            check_admin_message_state(CourseCreator.DENIED, expect_sent_to_admin=False, expect_sent_to_user=True)

    def _change_state(self, state):
        """ Helper method for changing state """
        self.table_entry.state = state
        self.creator_admin.save_model(self.request, self.table_entry, None, True)
140

cahrens committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
    def test_add_permission(self):
        """
        Tests that staff cannot add entries
        """
        self.assertFalse(self.creator_admin.has_add_permission(self.request))

    def test_delete_permission(self):
        """
        Tests that staff cannot delete entries
        """
        self.assertFalse(self.creator_admin.has_delete_permission(self.request))

    def test_change_permission(self):
        """
        Tests that only staff can change entries
        """
        self.assertTrue(self.creator_admin.has_change_permission(self.request))

        self.request.user = self.user
        self.assertFalse(self.creator_admin.has_change_permission(self.request))
Diana Huang committed
161 162

    def test_rate_limit_login(self):
163
        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
Diana Huang committed
164 165 166 167 168 169 170 171 172 173 174 175
            post_params = {'username': self.user.username, 'password': 'wrong_password'}
            # 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 _ in xrange(30):
                response = self.client.post('/admin/', post_params)
                self.assertEquals(response.status_code, 200)

            response = self.client.post('/admin/', post_params)
            # Since we are using the default rate limit behavior, we are
            # expecting this to return a 403 error to indicate that there have
            # been too many attempts
            self.assertEquals(response.status_code, 403)