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