From 34d4d162f74f38f347d7f9536c0332ff3a429bd3 Mon Sep 17 00:00:00 2001
From: Simon Chen <schen@edx.org>
Date: Wed, 29 Apr 2020 09:18:17 -0400
Subject: [PATCH] MST-108 Add masquerading fix to the upgrade message display

---
 lms/djangoapps/courseware/tests/test_views.py |   2 +-
 lms/djangoapps/courseware/testutils.py        |   4 +-
 .../tests/views/test_masquerade.py            | 127 ++++++++++++++++++
 openedx/features/course_experience/utils.py   |  26 ++++
 .../course_experience/views/course_home.py    |   8 +-
 .../course_experience/views/course_sock.py    |   7 +-
 6 files changed, 166 insertions(+), 8 deletions(-)
 create mode 100644 openedx/features/course_experience/tests/views/test_masquerade.py

diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 10101001c2c..ef67dee7af4 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -273,7 +273,7 @@ class IndexQueryTestCase(ModuleStoreTestCase):
     NUM_PROBLEMS = 20
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 10, 170),
+        (ModuleStoreEnum.Type.mongo, 11, 170),
         (ModuleStoreEnum.Type.split, 4, 168),
     )
     @ddt.unpack
diff --git a/lms/djangoapps/courseware/testutils.py b/lms/djangoapps/courseware/testutils.py
index a7613585eb2..df4bab33059 100644
--- a/lms/djangoapps/courseware/testutils.py
+++ b/lms/djangoapps/courseware/testutils.py
@@ -155,9 +155,9 @@ class RenderXBlockTestMixin(six.with_metaclass(ABCMeta, object)):
         return response
 
     @ddt.data(
-        ('vertical_block', ModuleStoreEnum.Type.mongo, 13),
+        ('vertical_block', ModuleStoreEnum.Type.mongo, 14),
         ('vertical_block', ModuleStoreEnum.Type.split, 6),
-        ('html_block', ModuleStoreEnum.Type.mongo, 14),
+        ('html_block', ModuleStoreEnum.Type.mongo, 15),
         ('html_block', ModuleStoreEnum.Type.split, 6),
     )
     @ddt.unpack
diff --git a/openedx/features/course_experience/tests/views/test_masquerade.py b/openedx/features/course_experience/tests/views/test_masquerade.py
new file mode 100644
index 00000000000..57be7fe5f4c
--- /dev/null
+++ b/openedx/features/course_experience/tests/views/test_masquerade.py
@@ -0,0 +1,127 @@
+"""
+Tests for masquerading functionality on course_experience
+"""
+
+
+import json
+
+import six
+from django.urls import reverse
+
+from lms.djangoapps.commerce.models import CommerceConfiguration
+from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
+from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG, SHOW_UPGRADE_MSG_ON_COURSE_HOME
+from student.roles import CourseStaffRole
+from student.tests.factories import CourseEnrollmentFactory, UserFactory
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
+from xmodule.partitions.partitions_service import PartitionService
+
+from .helpers import add_course_mode
+from .test_course_home import TEST_UPDATE_MESSAGE, course_home_url
+from .test_course_sock import TEST_VERIFICATION_SOCK_LOCATOR
+
+TEST_PASSWORD = 'test'
+UPGRADE_MESSAGE_CONTAINER = 'section-upgrade'
+
+
+class MasqueradeTestBase(SharedModuleStoreTestCase):
+    """
+    Base test class for masquerading functionality on course_experience
+    """
+    @classmethod
+    def setUpClass(cls):
+        super(MasqueradeTestBase, cls).setUpClass()
+
+        # Create two courses
+        cls.verified_course = CourseFactory.create()
+        cls.masters_course = CourseFactory.create()
+        # Create a verifiable course mode with an upgrade deadline in each course
+        add_course_mode(cls.verified_course, upgrade_deadline_expired=False)
+        add_course_mode(cls.masters_course, upgrade_deadline_expired=False)
+        add_course_mode(cls.masters_course, mode_slug='masters', mode_display_name='Masters')
+
+    def setUp(self):
+        super(MasqueradeTestBase, self).setUp()
+        self.course_staff = UserFactory.create()
+        CourseStaffRole(self.verified_course.id).add_users(self.course_staff)
+        CourseStaffRole(self.masters_course.id).add_users(self.course_staff)
+
+        # Enroll the user in the two courses
+        CourseEnrollmentFactory.create(user=self.course_staff, course_id=self.verified_course.id)
+        CourseEnrollmentFactory.create(user=self.course_staff, course_id=self.masters_course.id)
+
+        # Log the staff user in
+        self.client.login(username=self.course_staff.username, password=TEST_PASSWORD)
+
+    def get_group_id_by_course_mode_name(self, course_id, mode_name):
+        """
+        Get the needed group_id from the Enrollment_Track partition for the specific masquerading track.
+        """
+        partition_service = PartitionService(course_id)
+        enrollment_track_user_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
+        for group in enrollment_track_user_partition.groups:
+            if group.name == mode_name:
+                return group.id
+        return None
+
+    def update_masquerade(self, role, course, username=None, group_id=None):
+        """
+        Toggle masquerade state.
+        """
+        masquerade_url = reverse(
+            'masquerade_update',
+            kwargs={
+                'course_key_string': six.text_type(course.id),
+            }
+        )
+        response = self.client.post(
+            masquerade_url,
+            json.dumps({
+                "role": role,
+                "group_id": group_id,
+                "user_name": username,
+                "user_partition_id": ENROLLMENT_TRACK_PARTITION_ID
+            }),
+            "application/json"
+        )
+        self.assertEqual(response.status_code, 200)
+        return response
+
+
+class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase):
+    """
+    Tests for the course verification upgrade messages while the user is being masqueraded.
+    """
+
+    @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
+    @override_waffle_flag(SHOW_UPGRADE_MSG_ON_COURSE_HOME, active=True)
+    def test_masquerade_as_student(self):
+        # Elevate the staff user to be student
+        self.update_masquerade(role='student', course=self.verified_course)
+        response = self.client.get(course_home_url(self.verified_course))
+        self.assertContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
+        self.assertContains(response, UPGRADE_MESSAGE_CONTAINER, html=False)
+
+    @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
+    def test_masquerade_as_verified_student(self):
+        user_group_id = self.get_group_id_by_course_mode_name(
+            self.verified_course.id,
+            'Verified Certificate'
+        )
+        self.update_masquerade(role='student', course=self.verified_course, group_id=user_group_id)
+        response = self.client.get(course_home_url(self.verified_course))
+        self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
+        self.assertNotContains(response, UPGRADE_MESSAGE_CONTAINER, html=False)
+
+    @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
+    def test_masquerade_as_masters_student(self):
+        user_group_id = self.get_group_id_by_course_mode_name(
+            self.masters_course.id,
+            'Masters'
+        )
+        self.update_masquerade(role='student', course=self.masters_course, group_id=user_group_id)
+        response = self.client.get(course_home_url(self.masters_course))
+        self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
+        self.assertNotContains(response, UPGRADE_MESSAGE_CONTAINER, html=False)
diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py
index d8c0364f129..d6b90880e46 100644
--- a/openedx/features/course_experience/utils.py
+++ b/openedx/features/course_experience/utils.py
@@ -7,6 +7,7 @@ import logging
 from datetime import timedelta
 
 from completion.models import BlockCompletion
+from django.conf import settings
 from django.utils import timezone
 from opaque_keys.edx.keys import CourseKey
 from six.moves import range
@@ -15,10 +16,13 @@ from course_modes.models import CourseMode
 from lms.djangoapps.course_api.blocks.api import get_blocks
 from lms.djangoapps.course_blocks.utils import get_student_module_as_dict
 from lms.djangoapps.courseware.access import has_access
+from lms.djangoapps.courseware.utils import verified_upgrade_link_is_valid
 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
 from openedx.core.lib.cache_utils import request_cached
 from student.models import CourseEnrollment
 from xmodule.modulestore.django import modulestore
+from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
+from xmodule.partitions.partitions_service import PartitionService
 
 log = logging.getLogger(__name__)
 
@@ -291,3 +295,25 @@ def reset_deadlines_banner_should_display(course_key, request):
                         display_reset_dates_banner = True
                         break
     return display_reset_dates_banner
+
+
+def can_show_verified_upgrade(user, course_id, enrollment):
+    """
+    Check if we are able to show verified upgrade message based
+    on the enrollment and current user partition
+    """
+    if not enrollment:
+        return False
+    partition_service = PartitionService(course_id)
+    enrollment_track_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
+    group = partition_service.get_group(user, enrollment_track_partition)
+    current_mode = None
+    if group:
+        try:
+            current_mode = [
+                mode.get('slug') for mode in settings.COURSE_ENROLLMENT_MODES.values() if mode['id'] == group.id
+            ].pop()
+        except IndexError:
+            pass
+    upgradable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES
+    return upgradable_mode and verified_upgrade_link_is_valid(enrollment)
diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py
index 38eb8bdb823..31c7f3c54cd 100644
--- a/openedx/features/course_experience/views/course_home.py
+++ b/openedx/features/course_experience/views/course_home.py
@@ -42,7 +42,7 @@ from .. import (
     SHOW_UPGRADE_MSG_ON_COURSE_HOME,
     USE_BOOTSTRAP_FLAG
 )
-from ..utils import get_course_outline_block_tree, get_resume_block
+from ..utils import can_show_verified_upgrade, get_course_outline_block_tree, get_resume_block
 from .course_dates import CourseDatesFragmentView
 from .course_home_messages import CourseHomeMessageFragmentView
 from .course_outline import CourseOutlineFragmentView
@@ -222,7 +222,11 @@ class CourseHomeFragmentView(EdxFragmentView):
         has_discount = False
 
         # TODO Add switch to control deployment
-        if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(course_key) and enrollment and enrollment.upgrade_deadline:
+        if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(course_key) and can_show_verified_upgrade(
+            request.user,
+            course.id,
+            enrollment
+        ):
             upgrade_url = EcommerceService().upgrade_url(request.user, course_key)
             upgrade_price, has_discount = format_strikeout_price(request.user, course_overview)
 
diff --git a/openedx/features/course_experience/views/course_sock.py b/openedx/features/course_experience/views/course_sock.py
index a41d9b8f819..98646d9e3aa 100644
--- a/openedx/features/course_experience/views/course_sock.py
+++ b/openedx/features/course_experience/views/course_sock.py
@@ -2,15 +2,16 @@
 Fragment for rendering the course's sock and associated toggle button.
 """
 
-
 from django.template.loader import render_to_string
 from web_fragments.fragment import Fragment
 
-from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
 from openedx.features.discounts.utils import format_strikeout_price
 from student.models import CourseEnrollment
 
+from ..utils import can_show_verified_upgrade
+
 
 class CourseSockFragmentView(EdxFragmentView):
     """
@@ -27,7 +28,7 @@ class CourseSockFragmentView(EdxFragmentView):
     @staticmethod
     def get_verification_context(request, course):
         enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
-        show_course_sock = verified_upgrade_link_is_valid(enrollment)
+        show_course_sock = can_show_verified_upgrade(request.user, course.id, enrollment)
         if show_course_sock:
             upgrade_url = verified_upgrade_deadline_link(request.user, course=course)
             course_price, _ = format_strikeout_price(request.user, course)
-- 
GitLab