""" Tests for credit courses on the student dashboard. """ import datetime import unittest from unittest.mock import patch import ddt import pytz from django.conf import settings from django.test.utils import override_settings from django.urls import reverse from common.djangoapps.student.models import CourseEnrollmentAttribute from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from openedx.core.djangoapps.credit import api as credit_api from openedx.core.djangoapps.credit.models import CreditCourse, CreditEligibility, CreditProvider 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, }) @patch.dict(settings.FEATURES, {"ENABLE_CREDIT_ELIGIBILITY": True}) @ddt.ddt 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().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) assert result, '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() self.assertNotContains(response, "credit-eligibility-msg") self.assertNotContains(response, "purchase-credit-btn") 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. response = self._load_dashboard() self.assertContains(response, "credit-eligibility-msg") self.assertContains(response, "purchase-credit-btn") self.assertContains(response, "You have completed this course and are eligible") 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() self.assertContains(response, "credit-request-not-started-msg") 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( self.user, 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() 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) @ddt.data( ( ['Arizona State University'], 'You are now eligible for credit from Arizona State University'), ( ['Arizona State University', 'Hogwarts School of Witchcraft'], 'You are now eligible for credit from Arizona State University and Hogwarts School of Witchcraft' ), ( ['Arizona State University', 'Hogwarts School of Witchcraft and Wizardry', '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('common.djangoapps.student.views.dashboard.get_credit_provider_attribute_values') 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)