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