From 1d9b3842eff22a7e19f3f65a6bedc653e4d731ec Mon Sep 17 00:00:00 2001 From: "Albert St. Aubin" <astaubin@edx.org> Date: Tue, 27 Mar 2018 08:20:43 -0400 Subject: [PATCH] Corrects issue with Entitlement Auto upgrade and Soft Upgrade deadline issues [LEARNER-4493] --- .../entitlements/api/v1/tests/test_views.py | 61 +++++++++++++++++-- .../djangoapps/entitlements/api/v1/views.py | 51 +++++++++------- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/common/djangoapps/entitlements/api/v1/tests/test_views.py b/common/djangoapps/entitlements/api/v1/tests/test_views.py index b48e76aef1c..09d8b9ca2a6 100644 --- a/common/djangoapps/entitlements/api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/api/v1/tests/test_views.py @@ -4,19 +4,23 @@ import unittest import uuid from datetime import datetime, timedelta +from courseware.models import ( + DynamicUpgradeDeadlineConfiguration +) from django.conf import settings from django.core.urlresolvers import reverse from django.utils.timezone import now from mock import patch from opaque_keys.edx.locator import CourseKey +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory +from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from student.models import CourseEnrollment -from student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory +from student.tests.factories import (TEST_PASSWORD, CourseEnrollmentFactory, UserFactory) log = logging.getLogger(__name__) @@ -52,7 +56,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): """ return { "user": user.username, - "mode": "verified", + "mode": CourseMode.VERIFIED, "course_uuid": course_uuid, "order_number": "EDX-1001", } @@ -362,6 +366,55 @@ class EntitlementViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run == enrollment assert results == CourseEntitlementSerializer(course_entitlement).data + @patch("entitlements.api.v1.views.get_course_runs_for_course") + def test_add_entitlement_and_upgrade_audit_enrollment_with_dynamic_deadline(self, mock_get_course_runs): + """ + Verify that if an entitlement is added for a user, if the user has one upgradeable enrollment + that enrollment is upgraded to the mode of the entitlement and linked to the entitlement regardless of + dynamic upgrade deadline being set. + """ + DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) + course = CourseFactory.create(self_paced=True) + course_uuid = uuid.uuid4() + course_mode = CourseModeFactory( + course_id=course.id, + mode_slug=CourseMode.VERIFIED, + # This must be in the future to ensure it is returned by downstream code. + expiration_datetime=now() + timedelta(days=1) + ) + + # Set up Entitlement + entitlement_data = self._get_data_set(self.user, str(course_uuid)) + mock_get_course_runs.return_value = [{'key': str(course.id)}] + + # Add an audit course enrollment for user. + enrollment = CourseEnrollment.enroll(self.user, course.id, mode=CourseMode.AUDIT) + + # Set an upgrade schedule so that dynamic upgrade deadlines are used + ScheduleFactory.create( + enrollment=enrollment, + upgrade_deadline=course_mode.expiration_datetime + timedelta(days=-3) + ) + + # The upgrade should complete and ignore the deadline + response = self.client.post( + self.entitlements_list_url, + data=json.dumps(entitlement_data), + content_type='application/json', + ) + assert response.status_code == 201 + results = response.data + + course_entitlement = CourseEntitlement.objects.get( + user=self.user, + course_uuid=course_uuid + ) + # Assert that enrollment mode is now verified + enrollment_mode = CourseEnrollment.enrollment_mode_for_user(self.user, course.id)[0] + assert enrollment_mode == course_entitlement.mode + assert course_entitlement.enrollment_course_run == enrollment + assert results == CourseEntitlementSerializer(course_entitlement).data + @patch("entitlements.api.v1.views.get_course_runs_for_course") def test_add_entitlement_inactive_audit_enrollment(self, mock_get_course_runs): """ diff --git a/common/djangoapps/entitlements/api/v1/views.py b/common/djangoapps/entitlements/api/v1/views.py index 15c16a31c64..a2f56dfeb26 100644 --- a/common/djangoapps/entitlements/api/v1/views.py +++ b/common/djangoapps/entitlements/api/v1/views.py @@ -141,6 +141,32 @@ class EntitlementViewSet(viewsets.ModelViewSet): # to Admin users return CourseEntitlement.objects.all().select_related('user').select_related('enrollment_course_run') + def get_upgradeable_enrollments_for_entitlement(self, entitlement): + """ + Retrieve all the CourseEnrollments that are upgradeable for a given CourseEntitlement + + Arguments: + entitlement: CourseEntitlement that we are requesting the CourseEnrollments for. + + Returns: + list: List of upgradeable CourseEnrollments + """ + # find all course_runs within the course + course_runs = get_course_runs_for_course(entitlement.course_uuid) + + # check if the user has enrollments for any of the course_runs + upgradeable_enrollments = [] + for course_run in course_runs: + course_run_id = CourseKey.from_string(course_run.get('key')) + enrollment = CourseEnrollment.get_enrollment(entitlement.user, course_run_id) + + if (enrollment and + enrollment.is_active and + is_course_run_entitlement_fulfillable(course_run_id, entitlement)): + upgradeable_enrollments.append(enrollment) + + return upgradeable_enrollments + def create(self, request, *args, **kwargs): support_details = request.data.pop('support_details', []) serializer = self.get_serializer(data=request.data) @@ -157,29 +183,12 @@ class EntitlementViewSet(viewsets.ModelViewSet): CourseEntitlementSupportDetail.objects.create(**support_detail) else: user = entitlement.user + upgradeable_enrollments = self.get_upgradeable_enrollments_for_entitlement(entitlement) - # find all course_runs within the course - course_runs = get_course_runs_for_course(entitlement.course_uuid) - - # check if the user has enrollments for any of the course_runs - user_run_enrollments = [ - CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key'))) - for course_run - in course_runs - if CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key'))) - ] - - # filter to just enrollments that can be upgraded. - upgradeable_enrollments = [ - enrollment - for enrollment - in user_run_enrollments - if enrollment.is_active and enrollment.upgrade_deadline and enrollment.upgrade_deadline > timezone.now() - ] - - # if there is only one upgradeable enrollment, convert it from audit to the entitlement.mode + # if there is only one upgradeable enrollment, update the mode to the paid entitlement.mode # if there is any ambiguity about which enrollment to upgrade - # (i.e. multiple upgradeable enrollments or no available upgradeable enrollment), dont enroll + # (i.e. multiple upgradeable enrollments or no available upgradeable enrollment), don't alter + # the enrollment if len(upgradeable_enrollments) == 1: enrollment = upgradeable_enrollments[0] log.info( -- GitLab