diff --git a/lms/djangoapps/learner_home/test_views.py b/lms/djangoapps/learner_home/test_views.py index 2328e9e92342..d5271af0f693 100644 --- a/lms/djangoapps/learner_home/test_views.py +++ b/lms/djangoapps/learner_home/test_views.py @@ -23,6 +23,7 @@ from lms.djangoapps.bulk_email.models import Optout from lms.djangoapps.learner_home.test_utils import create_test_enrollment, random_string, random_url from lms.djangoapps.learner_home.views import ( + EnterpriseCustomerData, get_course_overviews_for_pseudo_sessions, get_course_programs, get_course_share_urls, @@ -412,26 +413,49 @@ def test_no_suggested_courses(self): self.assertDictEqual(return_data, self.EMPTY_SUGGESTED_COURSES) # noqa: PT009 +FAKE_ENTERPRISE_CUSTOMER_DATA: EnterpriseCustomerData = { + "name": "Fake Enterprise", + "uuid": str(uuid4()), + "slug": "fake-enterprise", + "auth_org_id": None, + "enable_learner_portal": True, +} + + +def _fake_enterprise_customer_override(prev_fn, user, request, is_masquerading): + """Test double target for OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER""" + _fake_enterprise_customer_override.last_call = (prev_fn, user, request, is_masquerading) + return FAKE_ENTERPRISE_CUSTOMER_DATA + + @ddt.ddt class TestGetEnterpriseCustomer(TestCase): """Test for get_enterprise_customer""" @ddt.data(True, False) - @patch("lms.djangoapps.learner_home.views.get_enterprise_learner_data_from_db") - @patch( - "lms.djangoapps.learner_home.views.enterprise_customer_from_session_or_learner_data" - ) - def test_get_enterprise_customer( - self, is_masquerading, mock_get_from_session, mock_get_from_db - ): - """Don't load the user from session if we're masquerading, load directly from db""" + def test_get_enterprise_customer_default(self, is_masquerading): + """With no plugin override configured, the base implementation returns None""" user, request = Mock(), Mock() result = get_enterprise_customer(user, request, is_masquerading) - if is_masquerading: - assert not mock_get_from_session.called - assert result is mock_get_from_db.return_value[0]["enterprise_customer"] - else: - assert result is mock_get_from_session.return_value + assert result is None + + @override_settings( + OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER=( + f"{__name__}._fake_enterprise_customer_override" + ) + ) + def test_get_enterprise_customer_uses_plugin_override(self): + """When OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER is configured, it is used instead""" + user, request = Mock(), Mock() + result = get_enterprise_customer(user, request, is_masquerading=True) + assert result == FAKE_ENTERPRISE_CUSTOMER_DATA + + _, received_user, received_request, received_is_masquerading = ( + _fake_enterprise_customer_override.last_call + ) + assert received_user is user + assert received_request is request + assert received_is_masquerading is True class TestGetSocialShareSettings(TestCase): diff --git a/lms/djangoapps/learner_home/views.py b/lms/djangoapps/learner_home/views.py index 19151bef46d5..927965d9ca21 100644 --- a/lms/djangoapps/learner_home/views.py +++ b/lms/djangoapps/learner_home/views.py @@ -2,8 +2,11 @@ Views for Learner Home """ +from __future__ import annotations + import logging from collections import OrderedDict +from typing import TYPE_CHECKING, TypedDict from completion.exceptions import UnavailableCompletionData from completion.utilities import get_key_to_last_completed_block @@ -11,11 +14,13 @@ from django.urls import reverse from edx_django_utils import monitoring as monitoring_utils from edx_django_utils.monitoring import function_trace +from edx_django_utils.plugins import pluggable_override from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser from edx_rest_framework_extensions.permissions import NotJwtRestrictedApplication from opaque_keys.edx.keys import CourseKey from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView @@ -44,10 +49,9 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser from openedx.features.course_duration_limits.access import get_user_course_expiration_date -from openedx.features.enterprise_support.api import ( - enterprise_customer_from_session_or_learner_data, - get_enterprise_learner_data_from_db, -) + +if TYPE_CHECKING: + from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user logger = logging.getLogger(__name__) @@ -192,17 +196,29 @@ def get_email_settings_info(user, course_enrollments): return show_email_settings_for, course_optouts +class EnterpriseCustomerData(TypedDict): + """Contract for the OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER return value.""" + name: str + uuid: str + slug: str + auth_org_id: str | None + enable_learner_portal: bool + + @function_trace("get_enterprise_customer") -def get_enterprise_customer(user, request, is_masquerading): +@pluggable_override('OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER') +def get_enterprise_customer( + user: User, + request: Request, + is_masquerading: bool, +) -> EnterpriseCustomerData | None: """ - If we are not masquerading, try to load the enterprise learner from session data, falling back to the db. - If we are masquerading, don't read or write to/from session data, go directly to db. + Return the enterprise customer dict for the given user, or None. + + This function can be overridden by an installed plugin via the + OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER setting. """ - if is_masquerading: - learner_data = get_enterprise_learner_data_from_db(user) - return learner_data[0]["enterprise_customer"] if learner_data else None - else: - return enterprise_customer_from_session_or_learner_data(request) + return None @function_trace("get_ecommerce_payment_page")