test_credit.py 9.5 KB
Newer Older
1 2 3 4
"""
Tests for credit courses on the student dashboard.
"""
import datetime
5
import unittest
6

7
import ddt
8 9 10 11
import pytz
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
12
from mock import patch
13

14 15 16 17
from openedx.core.djangoapps.credit import api as credit_api
from openedx.core.djangoapps.credit.models import CreditCourse, CreditEligibility, CreditProvider
from student.models import CourseEnrollmentAttribute
from student.tests.factories import CourseEnrollmentFactory, UserFactory
18 19 20 21 22 23 24 25 26 27
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"


@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
    "hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY,
})
28
@patch.dict(settings.FEATURES, {"ENABLE_CREDIT_ELIGIBILITY": True})
29
@ddt.ddt
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
class CreditCourseDashboardTest(ModuleStoreTestCase):
    """
    Tests for credit courses on the student dashboard.
    """

    USERNAME = "ron"
    PASSWORD = "mobiliarbus"

    PROVIDER_ID = "hogwarts"
    PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
    PROVIDER_STATUS_URL = "http://credit.example.com/status"

    def setUp(self):
        """Create a course and an enrollment. """
        super(CreditCourseDashboardTest, self).setUp()

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
        result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Create a course and configure it as a credit course
        self.course = CourseFactory()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)  # pylint: disable=no-member

        # Configure a credit provider
        CreditProvider.objects.create(
            provider_id=self.PROVIDER_ID,
            display_name=self.PROVIDER_NAME,
            provider_status_url=self.PROVIDER_STATUS_URL,
            enable_integration=True,
        )

        # Configure a single credit requirement (minimum passing grade)
        credit_api.set_credit_requirements(
            self.course.id,  # pylint: disable=no-member
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Final Grade",
                    "criteria": {
                        "min_grade": 0.8
                    }
                }
            ]
        )

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,  # pylint: disable=no-member
            mode="verified"
        )

    def test_not_eligible_for_credit(self):
        # The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
        response = self._load_dashboard()
88 89
        self.assertNotContains(response, "credit-eligibility-msg")
        self.assertNotContains(response, "purchase-credit-btn")
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

    def test_eligible_for_credit(self):
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        response = self._load_dashboard()
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")

        # Move the eligibility deadline so it's within 30 days
        eligibility = CreditEligibility.objects.get(username=self.USERNAME)
        eligibility.deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=29)
        eligibility.save()

        # The user should still have the option to purchase credit,
        # but there should also be a message urging the user to purchase soon.
108

109
        response = self._load_dashboard()
110

111 112
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
113
        self.assertContains(response, "You have completed this course and are eligible")
114 115 116 117 118 119 120 121

    def test_purchased_credit(self):
        # Simulate that the user has purchased credit, but has not
        # yet initiated a request to the credit provider
        self._make_eligible()
        self._purchase_credit()

        response = self._load_dashboard()
122
        self.assertContains(response, "credit-request-not-started-msg")
123 124 125 126 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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

    def test_purchased_credit_and_request_pending(self):
        # Simulate that the user has purchased credit and initiated a request,
        # but we haven't yet heard back from the credit provider.
        self._make_eligible()
        self._purchase_credit()
        self._initiate_request()

        # Expect that the user's status is "pending"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-pending-msg")

    def test_purchased_credit_and_request_approved(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request approved by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "approved")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-approved-msg")

    def test_purchased_credit_and_request_rejected(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request rejected by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "rejected")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-rejected-msg")

    def test_credit_status_error(self):
        # Simulate an error condition: the user has a credit enrollment
        # but no enrollment attribute indicating which provider the user
        # purchased credit from.
        self._make_eligible()
        self._purchase_credit()
        CourseEnrollmentAttribute.objects.all().delete()

        # Expect an error message
        response = self._load_dashboard()
        self.assertContains(response, "credit-error-msg")

    def _load_dashboard(self):
        """Load the student dashboard and return the HttpResponse. """
        return self.client.get(reverse("dashboard"))

    def _make_eligible(self):
        """Make the user eligible for credit in the course. """
        credit_api.set_credit_requirement_status(
178
            self.user,
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
            self.course.id,  # pylint: disable=no-member
            "grade", "grade",
            status="satisfied",
            reason={
                "final_grade": 0.95
            }
        )

    def _purchase_credit(self):
        """Purchase credit from a provider in the course. """
        self.enrollment.mode = "credit"
        self.enrollment.save()  # pylint: disable=no-member

        CourseEnrollmentAttribute.objects.create(
            enrollment=self.enrollment,
            namespace="credit",
            name="provider_id",
            value=self.PROVIDER_ID,
        )

    def _initiate_request(self):
        """Initiate a request for credit from a provider. """
        request = credit_api.create_credit_request(
            self.course.id,  # pylint: disable=no-member
            self.PROVIDER_ID,
            self.USERNAME
        )
        return request["parameters"]["request_uuid"]

    def _set_request_status(self, uuid, status):
        """Set the status of a request for credit, simulating the notification from the provider. """
        credit_api.update_credit_request_status(uuid, self.PROVIDER_ID, status)
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

    @ddt.data(
        (
            [u'Arizona State University'],
            'You are now eligible for credit from Arizona State University'),
        (
            [u'Arizona State University', u'Hogwarts School of Witchcraft'],
            'You are now eligible for credit from Arizona State University and Hogwarts School of Witchcraft'
        ),
        (
            [u'Arizona State University', u'Hogwarts School of Witchcraft and Wizardry', u'Charter Oak'],
            'You are now eligible for credit from Arizona State University, Hogwarts School'
            ' of Witchcraft and Wizardry, and Charter Oak'
        ),
        ([], 'You have completed this course and are eligible'),
        (None, 'You have completed this course and are eligible')
    )
    @ddt.unpack
    def test_eligible_for_credit_with_providers_names(self, providers_list, credit_string):
        """Verify the message on dashboard with different number of providers."""
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        with patch('student.views.get_credit_provider_display_names') as mock_method:
            mock_method.return_value = providers_list
            response = self._load_dashboard()

        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response, credit_string)