From 6039277a57d5839c900df8e5a3e87221d0cb6190 Mon Sep 17 00:00:00 2001 From: Akanshu Aich Date: Fri, 26 Sep 2025 12:49:08 +0000 Subject: [PATCH 01/27] refactor: moved remaining feature dicts settings into top-level settings. --- cms/djangoapps/contentstore/toggles.py | 18 +++++++++--------- common/djangoapps/util/milestones_helpers.py | 8 ++++---- lms/djangoapps/teams/toggles.py | 10 +++++----- openedx/core/djangoapps/credentials/helpers.py | 10 +++++----- .../waffle_utils/tests/test_views.py | 4 ++-- openedx/core/toggles.py | 10 +++++----- xmodule/course_block.py | 10 +++++----- xmodule/seq_block.py | 8 ++++---- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 9601d118fcd6..5462a28bae4b 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -1,13 +1,13 @@ """ CMS feature toggles. """ -from edx_toggles.toggles import SettingDictToggle, WaffleFlag +from edx_toggles.toggles import SettingToggle, WaffleFlag from openedx.core.djangoapps.content.search import api as search_api from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag -# .. toggle_name: FEATURES['ENABLE_EXPORT_GIT'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: ENABLE_EXPORT_GIT +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: When enabled, a "Export to Git" menu item is added to the course studio for courses that have a # valid "giturl" attribute. Exporting a course to git causes the course to be exported in the directory indicated by @@ -17,8 +17,8 @@ # existing directory. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2014-02-13 -EXPORT_GIT = SettingDictToggle( - "FEATURES", "ENABLE_EXPORT_GIT", default=False, module_name=__name__ +EXPORT_GIT = SettingToggle( + "ENABLE_EXPORT_GIT", default=False, module_name=__name__ ) # Namespace for studio dashboard waffle flags. @@ -406,8 +406,8 @@ def default_enable_flexible_peer_openassessments(course_key): return DEFAULT_ENABLE_FLEXIBLE_PEER_OPENASSESSMENTS.is_enabled(course_key) -# .. toggle_name: FEATURES['ENABLE_CONTENT_LIBRARIES'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: ENABLE_CONTENT_LIBRARIES +# .. toggle_implementation: SettingToggle # .. toggle_default: True # .. toggle_description: Enables use of the legacy and v2 libraries waffle flags. # Note that legacy content libraries are only supported in courses using split mongo. @@ -416,8 +416,8 @@ def default_enable_flexible_peer_openassessments(course_key): # .. toggle_target_removal_date: 2025-04-09 # .. toggle_warning: This flag is deprecated in Sumac, and will be removed in favor of the disable_legacy_libraries and # disable_new_libraries waffle flags. -ENABLE_CONTENT_LIBRARIES = SettingDictToggle( - "FEATURES", "ENABLE_CONTENT_LIBRARIES", default=True, module_name=__name__ +ENABLE_CONTENT_LIBRARIES = SettingToggle( + "ENABLE_CONTENT_LIBRARIES", default=True, module_name=__name__ ) # .. toggle_name: contentstore.new_studio_mfe.disable_legacy_libraries diff --git a/common/djangoapps/util/milestones_helpers.py b/common/djangoapps/util/milestones_helpers.py index f7909a08630e..94ec358f9286 100644 --- a/common/djangoapps/util/milestones_helpers.py +++ b/common/djangoapps/util/milestones_helpers.py @@ -3,7 +3,7 @@ """ from django.conf import settings from django.utils.translation import gettext as _ -from edx_toggles.toggles import SettingDictToggle +from edx_toggles.toggles import SettingToggle from milestones import api as milestones_api from milestones.exceptions import InvalidMilestoneRelationshipTypeException, InvalidUserException from milestones.models import MilestoneRelationshipType @@ -23,15 +23,15 @@ REQUEST_CACHE_NAME = "milestones" # TODO this should be moved to edx/edx-milestones -# .. toggle_name: FEATURES['MILESTONES_APP'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: MILESTONES_APP +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Enable the milestones application, which manages significant Course and/or Student events in # the Open edX platform. (see https://github.com/openedx/edx-milestones) Note that this feature is required to enable # course pre-requisites. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2014-11-21 -ENABLE_MILESTONES_APP = SettingDictToggle("FEATURES", "MILESTONES_APP", default=False, module_name=__name__) +ENABLE_MILESTONES_APP = SettingToggle("MILESTONES_APP", default=False, module_name=__name__) def get_namespace_choices(): diff --git a/lms/djangoapps/teams/toggles.py b/lms/djangoapps/teams/toggles.py index 5ce8fd08ce50..0f1c56c20fc7 100644 --- a/lms/djangoapps/teams/toggles.py +++ b/lms/djangoapps/teams/toggles.py @@ -1,7 +1,7 @@ """ Togglable settings for Teams behavior """ -from edx_toggles.toggles import SettingDictToggle +from edx_toggles.toggles import SettingToggle from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag @@ -9,8 +9,8 @@ WAFFLE_NAMESPACE = "openresponseassessment" TEAM_SUBMISSIONS_FLAG = "team_submissions" -# .. toggle_name: FEATURES['ENABLE_ORA_TEAM_SUBMISSIONS'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: ENABLE_ORA_TEAM_SUBMISSIONS +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Set to True to enable team-based ORA submissions. # .. toggle_use_cases: temporary @@ -20,8 +20,8 @@ # .. toggle_warning: This temporary feature toggle does not have a target removal date. This can be overridden by a # course waffle flags or a waffle switch with identical name. # TODO: this should be moved to edx/edx-ora2 -TEAM_SUBMISSIONS_FEATURE = SettingDictToggle( - "FEATURES", "ENABLE_ORA_TEAM_SUBMISSIONS", default=False, module_name=__name__ +TEAM_SUBMISSIONS_FEATURE = SettingToggle( + "ENABLE_ORA_TEAM_SUBMISSIONS", default=False, module_name=__name__ ) diff --git a/openedx/core/djangoapps/credentials/helpers.py b/openedx/core/djangoapps/credentials/helpers.py index b654a8cb921a..9dba4ac9c9b7 100644 --- a/openedx/core/djangoapps/credentials/helpers.py +++ b/openedx/core/djangoapps/credentials/helpers.py @@ -2,12 +2,12 @@ Helpers for the credentials service. """ -from edx_toggles.toggles import SettingDictToggle +from edx_toggles.toggles import SettingToggle from openedx.core.djangoapps.site_configuration import helpers as config_helpers -# .. toggle_name: FEATURES['ENABLE_LEARNER_RECORDS'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: ENABLE_LEARNER_RECORDS +# .. toggle_implementation: SettingToggle # .. toggle_default: True # .. toggle_description: Enable learner records for the whole platform. This setting may be overridden by site- and # org-specific site configurations with the same name. @@ -15,8 +15,8 @@ # setting. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2020-10-01 -ENABLE_LEARNER_RECORDS = SettingDictToggle( - "FEATURES", "ENABLE_LEARNER_RECORDS", default=True, module_name=__name__ +ENABLE_LEARNER_RECORDS = SettingToggle( + "ENABLE_LEARNER_RECORDS", default=True, module_name=__name__ ) diff --git a/openedx/core/djangoapps/waffle_utils/tests/test_views.py b/openedx/core/djangoapps/waffle_utils/tests/test_views.py index 496b7c6d0baf..c71c8f3f9bed 100644 --- a/openedx/core/djangoapps/waffle_utils/tests/test_views.py +++ b/openedx/core/djangoapps/waffle_utils/tests/test_views.py @@ -28,10 +28,10 @@ def test_failure_for_non_staff(self): def test_response_with_existing_setting_dict_toggle(self): response = get_toggle_state_response() assert { - "name": "FEATURES['MILESTONES_APP']", + "name": "MILESTONES_APP", "is_active": True, "module": "common.djangoapps.util.milestones_helpers", - "class": "SettingDictToggle", + "class": "SettingToggle", } in response.data["django_settings"] def test_response_with_course_override(self): diff --git a/openedx/core/toggles.py b/openedx/core/toggles.py index b3ef6e507e78..b4f704dca2e1 100644 --- a/openedx/core/toggles.py +++ b/openedx/core/toggles.py @@ -2,16 +2,16 @@ Feature toggles used across the platform. Toggles should only be added to this module if we don't have a better place for them. Generally speaking, they should be added to the most appropriate app or repo. """ -from edx_toggles.toggles import SettingDictToggle +from edx_toggles.toggles import SettingToggle -# .. toggle_name: FEATURES['ENTRANCE_EXAMS'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: ENTRANCE_EXAMS +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Enable entrance exams feature. When enabled, students see an exam xblock as the first unit # of the course. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2015-12-01 # .. toggle_tickets: https://openedx.atlassian.net/browse/SOL-40 -ENTRANCE_EXAMS = SettingDictToggle( - "FEATURES", "ENTRANCE_EXAMS", default=False, module_name=__name__ +ENTRANCE_EXAMS = SettingToggle( + "ENTRANCE_EXAMS", default=False, module_name=__name__ ) diff --git a/xmodule/course_block.py b/xmodule/course_block.py index 2a720baf41c7..ae241572b8df 100644 --- a/xmodule/course_block.py +++ b/xmodule/course_block.py @@ -11,7 +11,7 @@ import requests from django.conf import settings from django.core.validators import validate_email -from edx_toggles.toggles import SettingDictToggle +from edx_toggles.toggles import SettingToggle from lazy import lazy from lxml import etree from path import Path as path @@ -59,8 +59,8 @@ COURSE_VISIBILITY_PUBLIC_OUTLINE = 'public_outline' COURSE_VISIBILITY_PUBLIC = 'public' -# .. toggle_name: FEATURES['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: The default behavior, when this is disabled, is that a newly created course has no # enrollment_start date set. When the feature is enabled - the newly created courses will have the @@ -71,8 +71,8 @@ # the newly created (empty) course from appearing in the course listing. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2023-06-22 -CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE = SettingDictToggle( - "FEATURES", "CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE", default=False, module_name=__name__ +CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE = SettingToggle( + "CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE", default=False, module_name=__name__ ) diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index 6e19e04b2f1d..b78f5ac3c9a2 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -23,7 +23,7 @@ from xblock.fields import Boolean, Date, Integer, List, Scope, String from xblock.progress import Progress -from edx_toggles.toggles import WaffleFlag, SettingDictToggle +from edx_toggles.toggles import WaffleFlag, SettingToggle from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment from xmodule.x_module import ( ResourceTemplates, @@ -54,14 +54,14 @@ 'xmodule.rev_1377_rollout', __name__ ) -# .. toggle_name: FEATURES['SHOW_PROGRESS_BAR'] -# .. toggle_implementation: SettingDictToggle +# .. toggle_name: SHOW_PROGRESS_BAR +# .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: Set to True to show progress bar. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2022-02-09 # .. toggle_target_removal_date: None -SHOW_PROGRESS_BAR = SettingDictToggle("FEATURES", "SHOW_PROGRESS_BAR", default=False, module_name=__name__) +SHOW_PROGRESS_BAR = SettingToggle("SHOW_PROGRESS_BAR", default=False, module_name=__name__) class SequenceFields: # lint-amnesty, pylint: disable=missing-class-docstring From ba3629f8e3a3066adb6da903663ac693f97ce037 Mon Sep 17 00:00:00 2001 From: Akanshu Aich Date: Fri, 26 Sep 2025 12:27:21 +0000 Subject: [PATCH 02/27] refactor: moved remaining feature dicts settings into top-level settings. --- openedx/core/djangoapps/waffle_utils/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/waffle_utils/tests/test_views.py b/openedx/core/djangoapps/waffle_utils/tests/test_views.py index c71c8f3f9bed..126d65d30067 100644 --- a/openedx/core/djangoapps/waffle_utils/tests/test_views.py +++ b/openedx/core/djangoapps/waffle_utils/tests/test_views.py @@ -25,7 +25,7 @@ def test_failure_for_non_staff(self): response = get_toggle_state_response(is_staff=False) assert response.status_code == 403 - def test_response_with_existing_setting_dict_toggle(self): + def test_response_with_existing_setting_toggle(self): response = get_toggle_state_response() assert { "name": "MILESTONES_APP", From fe08b507b3021eb7339a863f4cff236c949b081f Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Thu, 9 Oct 2025 16:33:18 +0000 Subject: [PATCH 03/27] fix: fixed the test files --- .../contentstore/views/tests/test_entrance_exam.py | 5 +++-- .../djangoapps/util/tests/test_milestones_helpers.py | 10 +++++----- lms/djangoapps/courseware/tests/test_access.py | 3 ++- lms/djangoapps/courseware/tests/test_entrance_exam.py | 5 +++-- lms/djangoapps/courseware/tests/test_tabs.py | 5 +++-- lms/djangoapps/instructor/tests/test_api.py | 6 +++--- lms/djangoapps/mobile_api/tests/test_milestones.py | 10 +++++++--- lms/djangoapps/teams/tests/test_views.py | 5 +++-- .../core/djangoapps/credentials/tests/test_tasks.py | 2 +- .../djangoapps/models/tests/test_course_details.py | 5 +---- xmodule/tests/test_course_block.py | 4 +--- 11 files changed, 32 insertions(+), 28 deletions(-) diff --git a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py index a2cb570cf773..8db7f78ec1dc 100644 --- a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py +++ b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py @@ -7,6 +7,7 @@ from unittest.mock import patch from django.conf import settings +from django.test import override_settings from django.test.client import RequestFactory from milestones.tests.utils import MilestonesTestCaseMixin from opaque_keys.edx.keys import UsageKey @@ -30,7 +31,7 @@ from cms.djangoapps.contentstore.xblock_storage_handlers.create_xblock import create_xblock -@patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) +@override_settings(ENTRANCE_EXAMS=True) class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin): """ Base test class for create, save, and delete @@ -319,7 +320,7 @@ def test_entrance_exam_view_direct_missing_score_setting(self): resp = create_entrance_exam(request, self.course.id, None) self.assertEqual(resp.status_code, 201) - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False}) + @override_settings(ENTRANCE_EXAMS=False) def test_entrance_exam_feature_flag_gating(self): user = UserFactory() user.is_staff = True diff --git a/common/djangoapps/util/tests/test_milestones_helpers.py b/common/djangoapps/util/tests/test_milestones_helpers.py index 3ff758dd44ec..e70f9ac39070 100644 --- a/common/djangoapps/util/tests/test_milestones_helpers.py +++ b/common/djangoapps/util/tests/test_milestones_helpers.py @@ -7,6 +7,7 @@ import pytest from django.conf import settings from django.contrib.auth.models import AnonymousUser +from django.test import override_settings from milestones import api as milestones_api from milestones.exceptions import InvalidCourseKeyException, InvalidUserException from milestones.models import MilestoneRelationshipType @@ -16,7 +17,7 @@ from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order -@patch.dict(settings.FEATURES, {'MILESTONES_APP': False}) +@override_settings(MILESTONES_APP=False) @ddt.ddt class MilestonesHelpersTestCase(ModuleStoreTestCase): """ @@ -61,8 +62,7 @@ def test_pre_requisite_courses_enabled(self, feature_flags): with patch.dict("django.conf.settings.FEATURES", { 'ENABLE_PREREQUISITE_COURSES': feature_flags[0], - 'MILESTONES_APP': feature_flags[1] - }): + }), override_settings(MILESTONES_APP=feature_flags[1]): assert feature_flags[2] == milestones_helpers.is_prerequisite_courses_enabled() def test_add_milestone_returns_none_when_app_disabled(self): @@ -123,7 +123,7 @@ def test_get_service_returns_none_when_app_disabled(self): response = milestones_helpers.get_service() assert response is None - @patch.dict(settings.FEATURES, {'MILESTONES_APP': True}) + @override_settings(MILESTONES_APP=True) def test_any_unfulfilled_milestones(self): """ Tests any_unfulfilled_milestones for invalid arguments with the app enabled. @@ -137,7 +137,7 @@ def test_any_unfulfilled_milestones(self): with pytest.raises(InvalidUserException): milestones_helpers.any_unfulfilled_milestones(self.course.id, None) - @patch.dict(settings.FEATURES, {'MILESTONES_APP': True}) + @override_settings(MILESTONES_APP=True) def test_get_required_content_with_anonymous_user(self): course = CourseFactory() diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index c763a7577acb..4e97e09c83f1 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -681,7 +681,8 @@ def test__catalog_visibility(self): assert access._has_access_course(staff, 'see_in_catalog', course) assert access._has_access_course(staff, 'see_about_page', course) - @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) + @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True}) + @override_settings(MILESTONES_APP=True) def test_access_on_course_with_pre_requisites(self): """ Test course access when a course has pre-requisite course yet to be completed diff --git a/lms/djangoapps/courseware/tests/test_entrance_exam.py b/lms/djangoapps/courseware/tests/test_entrance_exam.py index 338a7bedfcf1..2a17a0f87bbf 100644 --- a/lms/djangoapps/courseware/tests/test_entrance_exam.py +++ b/lms/djangoapps/courseware/tests/test_entrance_exam.py @@ -5,6 +5,7 @@ from unittest.mock import patch from crum import set_current_request +from django.test import override_settings from django.urls import reverse from milestones.tests.utils import MilestonesTestCaseMixin from lms.djangoapps.courseware.entrance_exams import ( @@ -36,7 +37,7 @@ ) -@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True}) +@override_settings(ENTRANCE_EXAMS=True) class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin): """ Check that content is properly gated. @@ -44,7 +45,7 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, Milest Creates a test course from scratch. The tests below are designed to execute workflows regardless of the feature flag settings. """ - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True}) + @override_settings(ENTRANCE_EXAMS=True) def setUp(self): """ Test case scaffolding diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index 6ad7ef73de01..841b0ffe5362 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -8,6 +8,7 @@ from crum import set_current_request from django.contrib.auth.models import AnonymousUser from django.http import Http404 +from django.test import override_settings from django.urls import reverse from edx_toggles.toggles.testutils import override_waffle_flag from milestones.tests.utils import MilestonesTestCaseMixin @@ -333,12 +334,12 @@ def test_anonymous_user_xml(self): self.assertContains(resp, self.xml_data) -@patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True}) +@override_settings(ENTRANCE_EXAMS=True) class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin): """ Validate tab behavior when dealing with Entrance Exams """ - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True}) + @override_settings(ENTRANCE_EXAMS=True) def setUp(self): """ Test case scaffolding diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index d54da213531e..4d9f6f83b9e8 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -3368,7 +3368,7 @@ def test_rescore_problem_all(self, act): assert response.status_code == 200 assert act.called - @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) + @override_settings(ENTRANCE_EXAMS=True) def test_course_has_entrance_exam_in_student_attempts_reset(self): """ Test course has entrance exam id set while resetting attempts""" url = reverse('reset_student_attempts_for_entrance_exam', @@ -3379,7 +3379,7 @@ def test_course_has_entrance_exam_in_student_attempts_reset(self): }) assert response.status_code == 400 - @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) + @override_settings(ENTRANCE_EXAMS=True) def test_rescore_entrance_exam_with_invalid_exam(self): """ Test course has entrance exam id set while re-scoring. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': str(self.course.id)}) @@ -3389,7 +3389,7 @@ def test_rescore_entrance_exam_with_invalid_exam(self): assert response.status_code == 400 -@patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True}) +@override_settings(ENTRANCE_EXAMS=True) @ddt.ddt class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTestCase): """ diff --git a/lms/djangoapps/mobile_api/tests/test_milestones.py b/lms/djangoapps/mobile_api/tests/test_milestones.py index 12de519b04c4..2ecd7a0e3870 100644 --- a/lms/djangoapps/mobile_api/tests/test_milestones.py +++ b/lms/djangoapps/mobile_api/tests/test_milestones.py @@ -7,6 +7,7 @@ from crum import set_current_request from django.conf import settings +from django.test import override_settings from common.djangoapps.util.milestones_helpers import add_prerequisite_course, fulfill_course_milestone from lms.djangoapps.courseware.access_response import MilestoneAccessError @@ -56,7 +57,8 @@ def test_fulfilled_prerequisite_course(self): self.init_course_access() self.api_response() - @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True, 'ENABLE_MKTG_SITE': True}) + @override_settings(ENTRANCE_EXAMS=True) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_unpassed_entrance_exam(self): """ Tests the case where the user has not passed the entrance exam @@ -65,7 +67,8 @@ def test_unpassed_entrance_exam(self): self.init_course_access() self._verify_unfulfilled_milestone_response() - @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True, 'ENABLE_MKTG_SITE': True}) + @override_settings(ENTRANCE_EXAMS=True) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_unpassed_entrance_exam_for_staff(self): self._add_entrance_exam() self.user.is_staff = True @@ -73,7 +76,8 @@ def test_unpassed_entrance_exam_for_staff(self): self.init_course_access() self.api_response() - @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True, 'ENABLE_MKTG_SITE': True}) + @override_settings(ENTRANCE_EXAMS=True) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_passed_entrance_exam(self): """ Tests access when user has passed the entrance exam diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 6eaa1bdb680e..9c4ca5feab47 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -16,6 +16,7 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.core.files.uploadedfile import SimpleUploadedFile from django.db.models.signals import post_save +from django.test import override_settings from django.urls import reverse from django.utils import translation from elasticsearch.exceptions import ConnectionError # lint-amnesty, pylint: disable=redefined-builtin @@ -1673,7 +1674,7 @@ def test_teamset_type(self, requesting_user, expected_status): assert team['name'] == 'foo' -@patch.dict(settings.FEATURES, {'ENABLE_ORA_TEAM_SUBMISSIONS': True}) +@override_settings(ENABLE_ORA_TEAM_SUBMISSIONS=True) @ddt.ddt class TestTeamAssignmentsView(TeamAPITestCase): """ Tests for the TeamAssignmentsView """ @@ -1756,7 +1757,7 @@ def test_get_assignments_bad_team(self): expected_status = 404 self.get_team_assignments(team_id, expected_status, user=user) - @patch.dict(settings.FEATURES, {'ENABLE_ORA_TEAM_SUBMISSIONS': False}) + @override_settings(ENABLE_ORA_TEAM_SUBMISSIONS=False) def test_get_assignments_feature_not_enabled(self): # Given the team submissions feature is not enabled user = 'student_enrolled' diff --git a/openedx/core/djangoapps/credentials/tests/test_tasks.py b/openedx/core/djangoapps/credentials/tests/test_tasks.py index 68aea792be8d..6fb752302bd1 100644 --- a/openedx/core/djangoapps/credentials/tests/test_tasks.py +++ b/openedx/core/djangoapps/credentials/tests/test_tasks.py @@ -549,7 +549,7 @@ def test_send_grade_records_disabled_globally( _mock_is_learner_issuance_enabled ): assert is_learner_records_enabled() - with override_settings(FEATURES={"ENABLE_LEARNER_RECORDS": False}): + with override_settings(ENABLE_LEARNER_RECORDS=False): assert not is_learner_records_enabled() tasks.send_grade_if_interesting(self.user, self.key, 'verified', 'downloadable', None, None) assert not mock_send_grade_to_credentials.delay.called diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py index b23b56c88aa3..1a3ec3e8d05c 100644 --- a/openedx/core/djangoapps/models/tests/test_course_details.py +++ b/openedx/core/djangoapps/models/tests/test_course_details.py @@ -33,10 +33,7 @@ def setUp(self): @ddt.data(True, False) def test_virgin_fetch(self, should_have_default_enroll_start): - features = settings.FEATURES.copy() - features['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE'] = should_have_default_enroll_start - - with override_settings(FEATURES=features): + with override_settings(CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE=should_have_default_enroll_start): course = CourseFactory.create(default_enrollment_start=should_have_default_enroll_start) details = CourseDetails.fetch(course.id) wrong_enrollment_start_msg = ( diff --git a/xmodule/tests/test_course_block.py b/xmodule/tests/test_course_block.py index c88b519d708e..296045a62594 100644 --- a/xmodule/tests/test_course_block.py +++ b/xmodule/tests/test_course_block.py @@ -42,9 +42,7 @@ def test_default_start_date(self): @ddt.data(True, False) def test_default_enrollment_start_date(self, should_have_default_enroll_start): - features = settings.FEATURES.copy() - features['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE'] = should_have_default_enroll_start - with override_settings(FEATURES=features): + with override_settings(CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE=should_have_default_enroll_start): # reimport, so settings override could take effect del sys.modules['xmodule.course_block'] import xmodule.course_block # lint-amnesty, pylint: disable=redefined-outer-name, reimported From 98b079a35e188a6831969ab371b1b670af72da4b Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Thu, 9 Oct 2025 18:12:49 +0000 Subject: [PATCH 04/27] fix: fixed tehe pylint errors --- cms/djangoapps/contentstore/views/tests/test_entrance_exam.py | 1 - common/djangoapps/util/tests/test_milestones_helpers.py | 1 - openedx/core/djangoapps/models/tests/test_course_details.py | 1 - 3 files changed, 3 deletions(-) diff --git a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py index 8db7f78ec1dc..dea52cbe7b6f 100644 --- a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py +++ b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py @@ -4,7 +4,6 @@ import json -from unittest.mock import patch from django.conf import settings from django.test import override_settings diff --git a/common/djangoapps/util/tests/test_milestones_helpers.py b/common/djangoapps/util/tests/test_milestones_helpers.py index e70f9ac39070..efff367d75bb 100644 --- a/common/djangoapps/util/tests/test_milestones_helpers.py +++ b/common/djangoapps/util/tests/test_milestones_helpers.py @@ -5,7 +5,6 @@ import ddt import pytest -from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.test import override_settings from milestones import api as milestones_api diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py index 1a3ec3e8d05c..769499bf2353 100644 --- a/openedx/core/djangoapps/models/tests/test_course_details.py +++ b/openedx/core/djangoapps/models/tests/test_course_details.py @@ -9,7 +9,6 @@ import ddt from zoneinfo import ZoneInfo -from django.conf import settings from xmodule.modulestore import ModuleStoreEnum from xmodule.data import CertificatesDisplayBehaviors from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase From e589160a2137469fa224b19798dd26968b4f75b8 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Fri, 10 Oct 2025 15:51:33 +0000 Subject: [PATCH 05/27] fix: fixation of the cms ci failure --- .../tests/test_course_settings.py | 26 ++++++++++--------- .../contentstore/tests/test_utils.py | 4 +-- .../contentstore/views/tests/test_library.py | 5 ++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 7f2e8a151d04..b94bb087ffd3 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -388,6 +388,7 @@ def test_invalid_pre_requisite_course(self): (True, True, True), ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) + @patch.dict('django.conf.settings.FEATURES', {'MILESTONES_APP': False}) def test_visibility_of_entrance_exam_section(self, feature_flags): """ Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other @@ -405,6 +406,7 @@ def test_visibility_of_entrance_exam_section(self, feature_flags): ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) + @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False, 'MILESTONES_APP': False}) def test_marketing_site_fetch(self): settings_details_url = get_url(self.course.id) @@ -1128,16 +1130,16 @@ def test_fetch_initial_fields(self): self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ') - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': True}) - def test_fetch_giturl_present(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) + def test_fetch_giturl_present(self, mock_is_enabled): """ If feature flag ENABLE_EXPORT_GIT is on, show the setting as a non-deprecated Advanced Setting. """ test_model = CourseMetadata.fetch(self.fullcourse) self.assertIn('giturl', test_model) - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': False}) - def test_fetch_giturl_not_present(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) + def test_fetch_giturl_not_present(self, mock_is_enabled): """ If feature flag ENABLE_EXPORT_GIT is off, don't show the setting at all on the Advanced Settings page. """ @@ -1172,8 +1174,8 @@ def test_fetch_proctoring_escalation_email_not_present(self): test_model = CourseMetadata.fetch(self.fullcourse) self.assertNotIn('proctoring_escalation_email', test_model) - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': False}) - def test_validate_update_filtered_off(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) + def test_validate_update_filtered_off(self, mock_is_enabled): """ If feature flag is off, then giturl must be filtered. """ @@ -1187,8 +1189,8 @@ def test_validate_update_filtered_off(self): ) self.assertNotIn('giturl', test_model) - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': True}) - def test_validate_update_filtered_on(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) + def test_validate_update_filtered_on(self, mock_is_enabled): """ If feature flag is on, then giturl must not be filtered. """ @@ -1202,8 +1204,8 @@ def test_validate_update_filtered_on(self): ) self.assertIn('giturl', test_model) - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': True}) - def test_update_from_json_filtered_on(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) + def test_update_from_json_filtered_on(self, mock_is_enabled): """ If feature flag is on, then giturl must be updated. """ @@ -1216,8 +1218,8 @@ def test_update_from_json_filtered_on(self): ) self.assertIn('giturl', test_model) - @patch.dict(settings.FEATURES, {'ENABLE_EXPORT_GIT': False}) - def test_update_from_json_filtered_off(self): + @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) + def test_update_from_json_filtered_off(self, mock_is_enabled): """ If feature flag is on, then giturl must not be updated. """ diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index df55e2dde9fb..af0b303ae35e 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -913,7 +913,7 @@ def test_update_course_details_self_paced(self, mock_update): "self_paced": True, } - utils.update_course_details(mock_request, self.course.id, payload, None) + utils.update_course_details(mock_request, self.course.id, payload, self.course) mock_update.assert_called_once_with(self.course.id, expected_payload, mock_request.user) @patch.dict("django.conf.settings.FEATURES", { @@ -934,7 +934,7 @@ def test_update_course_details_instructor_paced(self, mock_update): "self_paced": False, } - utils.update_course_details(mock_request, self.course.id, payload, None) + utils.update_course_details(mock_request, self.course.id, payload, self.course) mock_update.assert_called_once_with(self.course.id, payload, mock_request.user) diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py index b6c5765c5108..6d275886c128 100644 --- a/cms/djangoapps/contentstore/views/tests/test_library.py +++ b/cms/djangoapps/contentstore/views/tests/test_library.py @@ -16,6 +16,7 @@ from organizations.api import get_organization_by_short_name from organizations.exceptions import InvalidOrganizationException +from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_library_url from cms.djangoapps.course_creators.views import add_user_with_status_granted as grant_course_creator_status @@ -161,8 +162,8 @@ def test_library_creator_status_with_no_course_creator_role_and_disabled_nonstaf self.assertEqual(get_response.status_code, 200) self.assertEqual(post_response.status_code, 403) - @mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_CONTENT_LIBRARIES': False}) - def test_with_libraries_disabled(self): + @patch.object(toggles.ENABLE_CONTENT_LIBRARIES, 'is_enabled', return_value=False) + def test_with_libraries_disabled(self, mock_is_enabled): """ The library URLs should return 404 if libraries are disabled. """ From 2cd066de693ff1478a6d6fa9d26d3b461d8e6168 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Fri, 10 Oct 2025 16:31:30 +0000 Subject: [PATCH 06/27] fix: fixed remaining feature settings for cms --- .../contentstore/tests/test_course_settings.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index b94bb087ffd3..cb8b498935ba 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -44,6 +44,7 @@ from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.util import milestones_helpers from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag +from openedx.core import toggles as core_toggles from openedx.core.djangoapps.discussions.config.waffle import ( ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND, OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG @@ -388,16 +389,15 @@ def test_invalid_pre_requisite_course(self): (True, True, True), ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @patch.dict('django.conf.settings.FEATURES', {'MILESTONES_APP': False}) - def test_visibility_of_entrance_exam_section(self, feature_flags): + @patch.object(milestones_helpers.ENABLE_MILESTONES_APP, 'is_enabled', return_value=False) + def test_visibility_of_entrance_exam_section(self, feature_flags, mock_milestones): """ Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other feature is enabled or disabled i.e ENABLE_PUBLISHER. """ with patch.dict("django.conf.settings.FEATURES", { - 'ENTRANCE_EXAMS': feature_flags[0], 'ENABLE_PUBLISHER': feature_flags[1] - }): + }), patch.object(core_toggles.ENTRANCE_EXAMS, 'is_enabled', return_value=feature_flags[0]): course_details_url = get_url(self.course.id) resp = self.client.get_html(course_details_url) self.assertEqual( @@ -406,8 +406,8 @@ def test_visibility_of_entrance_exam_section(self, feature_flags): ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False, 'MILESTONES_APP': False}) - def test_marketing_site_fetch(self): + @patch.object(milestones_helpers.ENABLE_MILESTONES_APP, 'is_enabled', return_value=False) + def test_marketing_site_fetch(self, mock_milestones): settings_details_url = get_url(self.course.id) with mock.patch.dict('django.conf.settings.FEATURES', { From a71a041664d99f4c99061634631a5c72d0b3d7e5 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Fri, 10 Oct 2025 18:39:33 +0000 Subject: [PATCH 07/27] fix: added fix for requirements --- cms/djangoapps/contentstore/tests/test_course_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index cb8b498935ba..4ec8fc4a7c8a 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -407,14 +407,14 @@ def test_visibility_of_entrance_exam_section(self, feature_flags, mock_milestone @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) @patch.object(milestones_helpers.ENABLE_MILESTONES_APP, 'is_enabled', return_value=False) - def test_marketing_site_fetch(self, mock_milestones): + @patch.object(core_toggles.ENTRANCE_EXAMS, 'is_enabled', return_value=False) + def test_marketing_site_fetch(self, mock_milestones, mock_entrance_exams): settings_details_url = get_url(self.course.id) with mock.patch.dict('django.conf.settings.FEATURES', { 'ENABLE_PUBLISHER': True, 'ENABLE_MKTG_SITE': True, - 'ENTRANCE_EXAMS': False, - 'ENABLE_PREREQUISITE_COURSES': False + 'ENABLE_PREREQUISITE_COURSES': False, }): response = self.client.get_html(settings_details_url) self.assertNotContains(response, "Course Summary Page") From 3d68ad8daf649840e134765f936c3055fae50571 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Fri, 10 Oct 2025 19:34:57 +0000 Subject: [PATCH 08/27] fix: added fix for lms tests --- .../mfe_config_api/tests/test_views.py | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index fcf1f1ad29e8..ba08a7181b84 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -67,14 +67,23 @@ def side_effect(key, default=None): return {"EXAMPLE_VAR": "value", "OTHER": "other"} if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) + configuration_helpers_mock.get_value.assert_has_calls([ + call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES) + ], any_order=True) self.assertEqual( response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} ) @@ -143,36 +152,53 @@ def side_effect(key, default=None): return mfe_config if key == "MFE_CONFIG_OVERRIDES": return mfe_config_overrides + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) + configuration_helpers_mock.get_value.assert_has_calls([ + call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES) + ], any_order=True) self.assertEqual(response.json(), expected_response) - def test_get_mfe_config_from_django_settings(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_from_django_settings(self, configuration_helpers_mock): """Test that when there is no site configuration, the API takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG in lms/envs/test.py""" + + configuration_helpers_mock.get_value.side_effect = lambda key, default: default + response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), default_legacy_config | settings.MFE_CONFIG) + expected_response = {**default_legacy_config, **settings.MFE_CONFIG} + self.assertEqual(response.json(), expected_response) - def test_get_mfe_config_with_queryparam_from_django_settings(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration_helpers_mock): """Test that when there is no site configuration, the API with queryparam takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_OVERRIDES['mymfe'] """ + configuration_helpers_mock.get_value.side_effect = lambda key, default: default + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] + expected = {**default_legacy_config, **settings.MFE_CONFIG, **settings.MFE_CONFIG_OVERRIDES["mymfe"]} self.assertEqual(response.json(), expected) @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @@ -196,25 +222,19 @@ def test_get_mfe_config_for_catalog(self, configuration_helpers_mock): - The configuration_helpers get_value is called for each catalog-specific configuration. - The catalog-specific values are included in the response. """ - mfe_config = {"BASE_URL": "https://catalog.example.com", "COURSE_ABOUT_TWITTER_ACCOUNT": "@TestAccount"} - mfe_config_overrides = { - "catalog": { - "SOME_SETTING": "catalog_value", - "NON_BROWSABLE_COURSES": True, - } - } - def side_effect(key, default=None): if key == "MFE_CONFIG": - return mfe_config + return {"BASE_URL": "https://catalog.example.com"} if key == "MFE_CONFIG_OVERRIDES": - return mfe_config_overrides + return {"catalog": {"SOME_SETTING": "catalog_value", "NON_BROWSABLE_COURSES": True}} if key == "ENABLE_COURSE_SORTING_BY_START_DATE": return True if key == "homepage_promo_video_youtube_id": return None if key == "HOMEPAGE_COURSE_MAX": return 8 + if key == "course_about_twitter_account": + return "@TestAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect From 5adf2b9ae7ec071885ca2d583d53d36c000a70b6 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Fri, 10 Oct 2025 20:23:26 +0000 Subject: [PATCH 09/27] fix: resolved the test views issue --- .../mfe_config_api/tests/test_views.py | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index ba08a7181b84..d798a5e1ed9f 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -45,6 +45,10 @@ def test_get_mfe_config(self, configuration_helpers_mock): def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} + # Handle legacy config calls + if key in ["ENABLE_COURSE_SORTING_BY_START_DATE", "homepage_promo_video_youtube_id", + "HOMEPAGE_COURSE_MAX", "course_about_twitter_account"]: + return default return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -67,23 +71,18 @@ def side_effect(key, default=None): return {"EXAMPLE_VAR": "value", "OTHER": "other"} if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" + # Handle legacy config calls + if key in ["ENABLE_COURSE_SORTING_BY_START_DATE", "homepage_promo_video_youtube_id", + "HOMEPAGE_COURSE_MAX", "course_about_twitter_account"]: + return default return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - configuration_helpers_mock.get_value.assert_has_calls([ - call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES) - ], any_order=True) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) self.assertEqual( response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} ) @@ -153,52 +152,43 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": return mfe_config_overrides if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True + return True # matches default_legacy_config if key == "homepage_promo_video_youtube_id": return None if key == "HOMEPAGE_COURSE_MAX": - return None + return None # matches default_legacy_config if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" + return "@YourPlatformTwitterAccount" # matches default_legacy_config return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - configuration_helpers_mock.get_value.assert_has_calls([ - call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES) - ], any_order=True) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) self.assertEqual(response.json(), expected_response) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_from_django_settings(self, configuration_helpers_mock): + def test_get_mfe_config_from_django_settings(self): """Test that when there is no site configuration, the API takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG in lms/envs/test.py""" - - configuration_helpers_mock.get_value.side_effect = lambda key, default: default - response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - expected_response = {**default_legacy_config, **settings.MFE_CONFIG} - self.assertEqual(response.json(), expected_response) + self.assertEqual(response.json(), default_legacy_config | settings.MFE_CONFIG) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration_helpers_mock): + def test_get_mfe_config_with_queryparam_from_django_settings(self): """Test that when there is no site configuration, the API with queryparam takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_OVERRIDES['mymfe'] """ - configuration_helpers_mock.get_value.side_effect = lambda key, default: default - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - expected = {**default_legacy_config, **settings.MFE_CONFIG, **settings.MFE_CONFIG_OVERRIDES["mymfe"]} + expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] self.assertEqual(response.json(), expected) @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @@ -222,19 +212,25 @@ def test_get_mfe_config_for_catalog(self, configuration_helpers_mock): - The configuration_helpers get_value is called for each catalog-specific configuration. - The catalog-specific values are included in the response. """ + mfe_config = {"BASE_URL": "https://catalog.example.com", "COURSE_ABOUT_TWITTER_ACCOUNT": "@TestAccount"} + mfe_config_overrides = { + "catalog": { + "SOME_SETTING": "catalog_value", + "NON_BROWSABLE_COURSES": True, + } + } + def side_effect(key, default=None): if key == "MFE_CONFIG": - return {"BASE_URL": "https://catalog.example.com"} + return mfe_config if key == "MFE_CONFIG_OVERRIDES": - return {"catalog": {"SOME_SETTING": "catalog_value", "NON_BROWSABLE_COURSES": True}} + return mfe_config_overrides if key == "ENABLE_COURSE_SORTING_BY_START_DATE": return True if key == "homepage_promo_video_youtube_id": return None if key == "HOMEPAGE_COURSE_MAX": return 8 - if key == "course_about_twitter_account": - return "@TestAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -285,6 +281,10 @@ def side_effect(key, default=None): return 5 # Plain site configuration if key == "homepage_promo_video_youtube_id": return "site-conf-youtube-id" + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return False + if key == "course_about_twitter_account": + return "" return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -311,4 +311,4 @@ def side_effect(key, default=None): self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") # Value in original MFE_CONFIG not overridden by catalog config should be preserved - self.assertEqual(data["PRESERVED_SETTING"], "preserved") + self.assertEqual(data["PRESERVED_SETTING"], "preserved") \ No newline at end of file From 04392928ac7cffd2bb5bdd74f6fdf87fbc3da603 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sat, 11 Oct 2025 08:58:06 +0000 Subject: [PATCH 10/27] fix: configured views file and test_views --- .../mfe_config_api/tests/test_views.py | 24 ++----------------- lms/djangoapps/mfe_config_api/views.py | 10 ++++---- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index d798a5e1ed9f..6032d5c399cd 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -45,10 +45,6 @@ def test_get_mfe_config(self, configuration_helpers_mock): def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} - # Handle legacy config calls - if key in ["ENABLE_COURSE_SORTING_BY_START_DATE", "homepage_promo_video_youtube_id", - "HOMEPAGE_COURSE_MAX", "course_about_twitter_account"]: - return default return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -71,10 +67,6 @@ def side_effect(key, default=None): return {"EXAMPLE_VAR": "value", "OTHER": "other"} if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} - # Handle legacy config calls - if key in ["ENABLE_COURSE_SORTING_BY_START_DATE", "homepage_promo_video_youtube_id", - "HOMEPAGE_COURSE_MAX", "course_about_twitter_account"]: - return default return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -82,7 +74,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) + configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual( response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} ) @@ -151,14 +143,6 @@ def side_effect(key, default=None): return mfe_config if key == "MFE_CONFIG_OVERRIDES": return mfe_config_overrides - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True # matches default_legacy_config - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None # matches default_legacy_config - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" # matches default_legacy_config return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -166,7 +150,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) + configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), expected_response) def test_get_mfe_config_from_django_settings(self): @@ -281,10 +265,6 @@ def side_effect(key, default=None): return 5 # Plain site configuration if key == "homepage_promo_video_youtube_id": return "site-conf-youtube-id" - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return False - if key == "course_about_twitter_account": - return "" return default configuration_helpers_mock.get_value.side_effect = side_effect diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 0ab71b151b88..ec965a4e4134 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -101,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] + getattr(settings, 'ENABLE_COURSE_SORTING_BY_START_DATE', True) ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -109,12 +109,12 @@ def _get_legacy_config() -> dict: ), "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( "HOMEPAGE_COURSE_MAX", - settings.HOMEPAGE_COURSE_MAX + getattr(settings, 'HOMEPAGE_COURSE_MAX', None) ), "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( "course_about_twitter_account", - settings.PLATFORM_TWITTER_ACCOUNT + getattr(settings, 'PLATFORM_TWITTER_ACCOUNT', "@YourPlatformTwitterAccount") ), - "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), - "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], + "NON_BROWSABLE_COURSES": not getattr(settings, "COURSES_ARE_BROWSABLE", True), + "ENABLE_COURSE_DISCOVERY": getattr(settings, "ENABLE_COURSE_DISCOVERY", False), } From 372b014097307b96e084f05f7a6b567a621910e0 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sat, 11 Oct 2025 10:55:31 +0000 Subject: [PATCH 11/27] fix: fixed lint errors and assertion issues --- lms/djangoapps/mfe_config_api/tests/test_views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 6032d5c399cd..bc846908b7c5 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -74,7 +74,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) + configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) self.assertEqual( response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} ) @@ -150,7 +150,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) + configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) self.assertEqual(response.json(), expected_response) def test_get_mfe_config_from_django_settings(self): @@ -291,4 +291,4 @@ def side_effect(key, default=None): self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") # Value in original MFE_CONFIG not overridden by catalog config should be preserved - self.assertEqual(data["PRESERVED_SETTING"], "preserved") \ No newline at end of file + self.assertEqual(data["PRESERVED_SETTING"], "preserved") From 61cc4a128508f75ae0b8fadfb7af0a2710d744ab Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sat, 11 Oct 2025 11:40:11 +0000 Subject: [PATCH 12/27] fix: added fix for base url issue in test view --- .../mfe_config_api/tests/test_views.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index bc846908b7c5..6c54acb87e0e 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -153,23 +153,36 @@ def side_effect(key, default=None): configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) self.assertEqual(response.json(), expected_response) - def test_get_mfe_config_from_django_settings(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_from_django_settings(self, configuration_helpers_mock): """Test that when there is no site configuration, the API takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG in lms/envs/test.py""" + + def side_effect(key, default=None): + # Return None to force using Django settings defaults + return default + configuration_helpers_mock.get_value.side_effect = side_effect + response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), default_legacy_config | settings.MFE_CONFIG) - def test_get_mfe_config_with_queryparam_from_django_settings(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration_helpers_mock): """Test that when there is no site configuration, the API with queryparam takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_OVERRIDES['mymfe'] """ + def side_effect(key, default=None): + # Return None to force using Django settings defaults + return default + configuration_helpers_mock.get_value.side_effect = side_effect + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] @@ -215,6 +228,8 @@ def side_effect(key, default=None): return None if key == "HOMEPAGE_COURSE_MAX": return 8 + if key == "course_about_twitter_account": + return "@TestAccount" # This should match what's in mfe_config return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -265,16 +280,18 @@ def side_effect(key, default=None): return 5 # Plain site configuration if key == "homepage_promo_video_youtube_id": return "site-conf-youtube-id" + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True # Plain site configuration (to be overridden by mfe_config) + if key == "course_about_twitter_account": + return "@SiteConfigTwitter" return default configuration_helpers_mock.get_value.side_effect = side_effect with override_settings( HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) - FEATURES={ # Settings FEATURES - "ENABLE_COURSE_SORTING_BY_START_DATE": True, - "ENABLE_COURSE_DISCOVERY": True, - } + ENABLE_COURSE_SORTING_BY_START_DATE=True, # Individual setting instead of FEATURES + ENABLE_COURSE_DISCOVERY=True, # Individual setting instead of FEATURES ): response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") From 7a4525b419c993000895ba08dee4fedbb5f2962a Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sat, 11 Oct 2025 18:39:06 +0000 Subject: [PATCH 13/27] fix: added fix for base_url and assertion issue --- .../mfe_config_api/tests/test_views.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 6c54acb87e0e..bcdd2a466320 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -45,11 +45,17 @@ def test_get_mfe_config(self, configuration_helpers_mock): def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} + # Return default for all legacy config keys to use Django settings return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Verify the key calls were made + expected_calls = [call("MFE_CONFIG", settings.MFE_CONFIG)] + configuration_helpers_mock.get_value.assert_has_calls(expected_calls, any_order=True) + self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @@ -162,13 +168,22 @@ def test_get_mfe_config_from_django_settings(self, configuration_helpers_mock): - The json response is equal to MFE_CONFIG in lms/envs/test.py""" def side_effect(key, default=None): - # Return None to force using Django settings defaults + if key == "MFE_CONFIG": + # Return the Django settings value explicitly + return settings.MFE_CONFIG + if key == "MFE_CONFIG_OVERRIDES": + # Return the Django settings value explicitly + return settings.MFE_CONFIG_OVERRIDES + # For legacy config keys, return default to use Django settings fallbacks return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), default_legacy_config | settings.MFE_CONFIG) + + # The expected result should be legacy config merged with Django MFE_CONFIG + expected = default_legacy_config | settings.MFE_CONFIG + self.assertEqual(response.json(), expected) @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration_helpers_mock): @@ -179,7 +194,13 @@ def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_OVERRIDES['mymfe'] """ def side_effect(key, default=None): - # Return None to force using Django settings defaults + if key == "MFE_CONFIG": + # Return the Django settings value explicitly + return settings.MFE_CONFIG + if key == "MFE_CONFIG_OVERRIDES": + # Return the Django settings value explicitly + return settings.MFE_CONFIG_OVERRIDES + # For legacy config keys, return default to use Django settings fallbacks return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -229,7 +250,7 @@ def side_effect(key, default=None): if key == "HOMEPAGE_COURSE_MAX": return 8 if key == "course_about_twitter_account": - return "@TestAccount" # This should match what's in mfe_config + return "@TestAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect From 9bed0f2e3bf04a184ce18415b15b4f5b661667e7 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sat, 11 Oct 2025 19:17:06 +0000 Subject: [PATCH 14/27] fix: added configurations for base utl fix --- .../mfe_config_api/tests/test_views.py | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index bcdd2a466320..2707216f9c39 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -45,7 +45,16 @@ def test_get_mfe_config(self, configuration_helpers_mock): def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} - # Return default for all legacy config keys to use Django settings + # Explicitly return values that match default_legacy_config + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" + # Return default for all other keys return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -73,6 +82,16 @@ def side_effect(key, default=None): return {"EXAMPLE_VAR": "value", "OTHER": "other"} if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} + # Explicitly return values that match default_legacy_config + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" + # Return default for all other keys return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -149,6 +168,15 @@ def side_effect(key, default=None): return mfe_config if key == "MFE_CONFIG_OVERRIDES": return mfe_config_overrides + # Explicitly return values that match default_legacy_config + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -174,7 +202,16 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": # Return the Django settings value explicitly return settings.MFE_CONFIG_OVERRIDES - # For legacy config keys, return default to use Django settings fallbacks + # For legacy config keys, return specific values to ensure consistency + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" + # For any other keys, return default return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -200,7 +237,16 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": # Return the Django settings value explicitly return settings.MFE_CONFIG_OVERRIDES - # For legacy config keys, return default to use Django settings fallbacks + # For legacy config keys, return specific values to ensure consistency + if key == "ENABLE_COURSE_SORTING_BY_START_DATE": + return True + if key == "homepage_promo_video_youtube_id": + return None + if key == "HOMEPAGE_COURSE_MAX": + return None + if key == "course_about_twitter_account": + return "@YourPlatformTwitterAccount" + # For any other keys, return default return default configuration_helpers_mock.get_value.side_effect = side_effect From 73ef6b05592f48cba4241146d244e631500550d7 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sun, 12 Oct 2025 05:53:09 +0000 Subject: [PATCH 15/27] fix: handled none issue for mfe config --- .../mfe_config_api/tests/test_views.py | 31 ++++++++++++++++--- lms/djangoapps/mfe_config_api/views.py | 10 ++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 2707216f9c39..1cc65dc9fed5 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -109,17 +109,36 @@ def side_effect(key, default=None): dict( mfe_config={}, mfe_config_overrides={}, - expected_response={**default_legacy_config}, + expected_response={ + **default_legacy_config, + # When both mfe_config and mfe_config_overrides are empty, they fall back to settings + "BASE_URL": "https://name_of_mfe.example.com", + "LANGUAGE_PREFERENCE_COOKIE_NAME": "mymfe-language-preference", + "LOGO_URL": "https://courses.example.com/mymfe-logo.png", + }, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, mfe_config_overrides={}, - expected_response={**default_legacy_config, "EXAMPLE_VAR": "value"}, + expected_response={ + **default_legacy_config, + "EXAMPLE_VAR": "value", + # When mfe_config_overrides is empty, it falls back to settings.MFE_CONFIG_OVERRIDES + "LANGUAGE_PREFERENCE_COOKIE_NAME": "mymfe-language-preference", + "LOGO_URL": "https://courses.example.com/mymfe-logo.png", + }, ), dict( mfe_config={}, mfe_config_overrides={"mymfe": {"EXAMPLE_VAR": "mymfe_value"}}, - expected_response={**default_legacy_config, "EXAMPLE_VAR": "mymfe_value"}, + expected_response={ + **default_legacy_config, + "EXAMPLE_VAR": "mymfe_value", + # When mfe_config is empty, it falls back to settings.MFE_CONFIG + "BASE_URL": "https://name_of_mfe.example.com", + "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", + "LOGO_URL": "https://courses.example.com/logo.png", + }, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, @@ -134,7 +153,11 @@ def side_effect(key, default=None): dict( mfe_config={"EXAMPLE_VAR": "value"}, mfe_config_overrides={"yourmfe": {"EXAMPLE_VAR": "yourmfe_value"}}, - expected_response={**default_legacy_config, "EXAMPLE_VAR": "value"}, + expected_response={ + **default_legacy_config, + "EXAMPLE_VAR": "value", + # yourmfe doesn't have mymfe overrides, so no MFE-specific values + }, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index ec965a4e4134..a97d7ff33df2 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -77,6 +77,9 @@ def get(self, request): # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) + # Ensure mfe_config is never None or empty + if not mfe_config: + mfe_config = settings.MFE_CONFIG # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) mfe_config_overrides = {} @@ -86,11 +89,18 @@ def get(self, request): "MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES, ) + # Ensure app_config is never None + if not app_config: + app_config = settings.MFE_CONFIG_OVERRIDES mfe_config_overrides = app_config.get(mfe, {}) # Merge the three configs in the order of precedence merged_config = legacy_config | mfe_config | mfe_config_overrides + # Ensure merged_config is never empty and always has required structure + if not merged_config: + merged_config = legacy_config | settings.MFE_CONFIG + return JsonResponse(merged_config, status=status.HTTP_200_OK) @staticmethod From 5ff3d4fe5eedf9ba8773f2a4b095b7f4501d7834 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sun, 12 Oct 2025 06:35:45 +0000 Subject: [PATCH 16/27] fix: corrected override settings in test views --- .../mfe_config_api/tests/test_views.py | 131 ++---------------- lms/djangoapps/mfe_config_api/views.py | 20 +-- 2 files changed, 16 insertions(+), 135 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 1cc65dc9fed5..15d8cfa6709b 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -45,26 +45,11 @@ def test_get_mfe_config(self, configuration_helpers_mock): def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} - # Explicitly return values that match default_legacy_config - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" - # Return default for all other keys return default configuration_helpers_mock.get_value.side_effect = side_effect response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Verify the key calls were made - expected_calls = [call("MFE_CONFIG", settings.MFE_CONFIG)] - configuration_helpers_mock.get_value.assert_has_calls(expected_calls, any_order=True) - self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @@ -82,16 +67,6 @@ def side_effect(key, default=None): return {"EXAMPLE_VAR": "value", "OTHER": "other"} if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} - # Explicitly return values that match default_legacy_config - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" - # Return default for all other keys return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -99,7 +74,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) + configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual( response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} ) @@ -109,36 +84,17 @@ def side_effect(key, default=None): dict( mfe_config={}, mfe_config_overrides={}, - expected_response={ - **default_legacy_config, - # When both mfe_config and mfe_config_overrides are empty, they fall back to settings - "BASE_URL": "https://name_of_mfe.example.com", - "LANGUAGE_PREFERENCE_COOKIE_NAME": "mymfe-language-preference", - "LOGO_URL": "https://courses.example.com/mymfe-logo.png", - }, + expected_response={**default_legacy_config}, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, mfe_config_overrides={}, - expected_response={ - **default_legacy_config, - "EXAMPLE_VAR": "value", - # When mfe_config_overrides is empty, it falls back to settings.MFE_CONFIG_OVERRIDES - "LANGUAGE_PREFERENCE_COOKIE_NAME": "mymfe-language-preference", - "LOGO_URL": "https://courses.example.com/mymfe-logo.png", - }, + expected_response={**default_legacy_config, "EXAMPLE_VAR": "value"}, ), dict( mfe_config={}, mfe_config_overrides={"mymfe": {"EXAMPLE_VAR": "mymfe_value"}}, - expected_response={ - **default_legacy_config, - "EXAMPLE_VAR": "mymfe_value", - # When mfe_config is empty, it falls back to settings.MFE_CONFIG - "BASE_URL": "https://name_of_mfe.example.com", - "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", - "LOGO_URL": "https://courses.example.com/logo.png", - }, + expected_response={**default_legacy_config, "EXAMPLE_VAR": "mymfe_value"}, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, @@ -153,11 +109,7 @@ def side_effect(key, default=None): dict( mfe_config={"EXAMPLE_VAR": "value"}, mfe_config_overrides={"yourmfe": {"EXAMPLE_VAR": "yourmfe_value"}}, - expected_response={ - **default_legacy_config, - "EXAMPLE_VAR": "value", - # yourmfe doesn't have mymfe overrides, so no MFE-specific values - }, + expected_response={**default_legacy_config, "EXAMPLE_VAR": "value"}, ), dict( mfe_config={"EXAMPLE_VAR": "value"}, @@ -191,15 +143,6 @@ def side_effect(key, default=None): return mfe_config if key == "MFE_CONFIG_OVERRIDES": return mfe_config_overrides - # Explicitly return values that match default_legacy_config - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -207,72 +150,26 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", settings.MFE_CONFIG), call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls, any_order=True) + configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), expected_response) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_from_django_settings(self, configuration_helpers_mock): + def test_get_mfe_config_from_django_settings(self): """Test that when there is no site configuration, the API takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG in lms/envs/test.py""" - - def side_effect(key, default=None): - if key == "MFE_CONFIG": - # Return the Django settings value explicitly - return settings.MFE_CONFIG - if key == "MFE_CONFIG_OVERRIDES": - # Return the Django settings value explicitly - return settings.MFE_CONFIG_OVERRIDES - # For legacy config keys, return specific values to ensure consistency - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" - # For any other keys, return default - return default - configuration_helpers_mock.get_value.side_effect = side_effect - response = self.client.get(self.mfe_config_api_url) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), default_legacy_config | settings.MFE_CONFIG) - # The expected result should be legacy config merged with Django MFE_CONFIG - expected = default_legacy_config | settings.MFE_CONFIG - self.assertEqual(response.json(), expected) - - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_with_queryparam_from_django_settings(self, configuration_helpers_mock): + def test_get_mfe_config_with_queryparam_from_django_settings(self): """Test that when there is no site configuration, the API with queryparam takes the django settings. Expected result: - The status of the response of the request is a HTTP_200_OK. - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_OVERRIDES['mymfe'] """ - def side_effect(key, default=None): - if key == "MFE_CONFIG": - # Return the Django settings value explicitly - return settings.MFE_CONFIG - if key == "MFE_CONFIG_OVERRIDES": - # Return the Django settings value explicitly - return settings.MFE_CONFIG_OVERRIDES - # For legacy config keys, return specific values to ensure consistency - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True - if key == "homepage_promo_video_youtube_id": - return None - if key == "HOMEPAGE_COURSE_MAX": - return None - if key == "course_about_twitter_account": - return "@YourPlatformTwitterAccount" - # For any other keys, return default - return default - configuration_helpers_mock.get_value.side_effect = side_effect - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] @@ -318,8 +215,6 @@ def side_effect(key, default=None): return None if key == "HOMEPAGE_COURSE_MAX": return 8 - if key == "course_about_twitter_account": - return "@TestAccount" return default configuration_helpers_mock.get_value.side_effect = side_effect @@ -370,18 +265,14 @@ def side_effect(key, default=None): return 5 # Plain site configuration if key == "homepage_promo_video_youtube_id": return "site-conf-youtube-id" - if key == "ENABLE_COURSE_SORTING_BY_START_DATE": - return True # Plain site configuration (to be overridden by mfe_config) - if key == "course_about_twitter_account": - return "@SiteConfigTwitter" return default configuration_helpers_mock.get_value.side_effect = side_effect with override_settings( HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) - ENABLE_COURSE_SORTING_BY_START_DATE=True, # Individual setting instead of FEATURES - ENABLE_COURSE_DISCOVERY=True, # Individual setting instead of FEATURES + ENABLE_COURSE_SORTING_BY_START_DATE=True, + ENABLE_COURSE_DISCOVERY=True, ): response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index a97d7ff33df2..b40b727abeff 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -77,9 +77,6 @@ def get(self, request): # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) - # Ensure mfe_config is never None or empty - if not mfe_config: - mfe_config = settings.MFE_CONFIG # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) mfe_config_overrides = {} @@ -89,18 +86,11 @@ def get(self, request): "MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES, ) - # Ensure app_config is never None - if not app_config: - app_config = settings.MFE_CONFIG_OVERRIDES mfe_config_overrides = app_config.get(mfe, {}) # Merge the three configs in the order of precedence merged_config = legacy_config | mfe_config | mfe_config_overrides - # Ensure merged_config is never empty and always has required structure - if not merged_config: - merged_config = legacy_config | settings.MFE_CONFIG - return JsonResponse(merged_config, status=status.HTTP_200_OK) @staticmethod @@ -111,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - getattr(settings, 'ENABLE_COURSE_SORTING_BY_START_DATE', True) + settings.ENABLE_COURSE_SORTING_BY_START_DATE ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -119,12 +109,12 @@ def _get_legacy_config() -> dict: ), "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( "HOMEPAGE_COURSE_MAX", - getattr(settings, 'HOMEPAGE_COURSE_MAX', None) + settings.HOMEPAGE_COURSE_MAX ), "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( "course_about_twitter_account", - getattr(settings, 'PLATFORM_TWITTER_ACCOUNT', "@YourPlatformTwitterAccount") + settings.PLATFORM_TWITTER_ACCOUNT ), - "NON_BROWSABLE_COURSES": not getattr(settings, "COURSES_ARE_BROWSABLE", True), - "ENABLE_COURSE_DISCOVERY": getattr(settings, "ENABLE_COURSE_DISCOVERY", False), + "NON_BROWSABLE_COURSES": not settings.COURSES_ARE_BROWSABLE, + "ENABLE_COURSE_DISCOVERY": settings.ENABLE_COURSE_DISCOVERY, } From 2466cad0c8b24732a8a976e942231244d474553b Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Sun, 12 Oct 2025 07:14:40 +0000 Subject: [PATCH 17/27] fix: added getattr defensive technique for view settings --- lms/djangoapps/mfe_config_api/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index b40b727abeff..7b39a831d301 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -76,7 +76,7 @@ def get(self, request): legacy_config = self._get_legacy_config() # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) - mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) + mfe_config = configuration_helpers.get_value("MFE_CONFIG", getattr(settings, 'MFE_CONFIG', {})) # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) mfe_config_overrides = {} @@ -84,7 +84,7 @@ def get(self, request): mfe = str(request.query_params.get("mfe")) app_config = configuration_helpers.get_value( "MFE_CONFIG_OVERRIDES", - settings.MFE_CONFIG_OVERRIDES, + getattr(settings, 'MFE_CONFIG_OVERRIDES', {}), ) mfe_config_overrides = app_config.get(mfe, {}) @@ -101,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.ENABLE_COURSE_SORTING_BY_START_DATE + getattr(settings, 'ENABLE_COURSE_SORTING_BY_START_DATE', True) ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -109,12 +109,12 @@ def _get_legacy_config() -> dict: ), "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( "HOMEPAGE_COURSE_MAX", - settings.HOMEPAGE_COURSE_MAX + getattr(settings, 'HOMEPAGE_COURSE_MAX', None) ), "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( "course_about_twitter_account", - settings.PLATFORM_TWITTER_ACCOUNT + getattr(settings, 'PLATFORM_TWITTER_ACCOUNT', "@YourPlatformTwitterAccount") ), - "NON_BROWSABLE_COURSES": not settings.COURSES_ARE_BROWSABLE, - "ENABLE_COURSE_DISCOVERY": settings.ENABLE_COURSE_DISCOVERY, + "NON_BROWSABLE_COURSES": not getattr(settings, 'COURSES_ARE_BROWSABLE', True), + "ENABLE_COURSE_DISCOVERY": getattr(settings, 'ENABLE_COURSE_DISCOVERY', False), } From 772dc249608063a9a2b02c65ba29216776dd4e16 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Mon, 13 Oct 2025 12:27:42 +0000 Subject: [PATCH 18/27] fix: reverted views and test_views file --- lms/djangoapps/mfe_config_api/tests/test_views.py | 6 ++++-- lms/djangoapps/mfe_config_api/views.py | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 15d8cfa6709b..fcf1f1ad29e8 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -271,8 +271,10 @@ def side_effect(key, default=None): with override_settings( HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) - ENABLE_COURSE_SORTING_BY_START_DATE=True, - ENABLE_COURSE_DISCOVERY=True, + FEATURES={ # Settings FEATURES + "ENABLE_COURSE_SORTING_BY_START_DATE": True, + "ENABLE_COURSE_DISCOVERY": True, + } ): response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 7b39a831d301..0ab71b151b88 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -76,7 +76,7 @@ def get(self, request): legacy_config = self._get_legacy_config() # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) - mfe_config = configuration_helpers.get_value("MFE_CONFIG", getattr(settings, 'MFE_CONFIG', {})) + mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) mfe_config_overrides = {} @@ -84,7 +84,7 @@ def get(self, request): mfe = str(request.query_params.get("mfe")) app_config = configuration_helpers.get_value( "MFE_CONFIG_OVERRIDES", - getattr(settings, 'MFE_CONFIG_OVERRIDES', {}), + settings.MFE_CONFIG_OVERRIDES, ) mfe_config_overrides = app_config.get(mfe, {}) @@ -101,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - getattr(settings, 'ENABLE_COURSE_SORTING_BY_START_DATE', True) + settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -109,12 +109,12 @@ def _get_legacy_config() -> dict: ), "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( "HOMEPAGE_COURSE_MAX", - getattr(settings, 'HOMEPAGE_COURSE_MAX', None) + settings.HOMEPAGE_COURSE_MAX ), "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( "course_about_twitter_account", - getattr(settings, 'PLATFORM_TWITTER_ACCOUNT', "@YourPlatformTwitterAccount") + settings.PLATFORM_TWITTER_ACCOUNT ), - "NON_BROWSABLE_COURSES": not getattr(settings, 'COURSES_ARE_BROWSABLE', True), - "ENABLE_COURSE_DISCOVERY": getattr(settings, 'ENABLE_COURSE_DISCOVERY', False), + "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), + "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], } From a5710f04f72a8c870695cf86c9f3accf7f71b6fd Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Mon, 13 Oct 2025 13:14:51 +0000 Subject: [PATCH 19/27] fix: added settings in views file --- lms/djangoapps/mfe_config_api/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 0ab71b151b88..b40b727abeff 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -101,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] + settings.ENABLE_COURSE_SORTING_BY_START_DATE ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -115,6 +115,6 @@ def _get_legacy_config() -> dict: "course_about_twitter_account", settings.PLATFORM_TWITTER_ACCOUNT ), - "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), - "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], + "NON_BROWSABLE_COURSES": not settings.COURSES_ARE_BROWSABLE, + "ENABLE_COURSE_DISCOVERY": settings.ENABLE_COURSE_DISCOVERY, } From a1e6d1f35293a47c35915071371a527cab99a7aa Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 14 Oct 2025 09:20:13 +0000 Subject: [PATCH 20/27] fix: added with patch within functions in test view --- .../mfe_config_api/tests/test_views.py | 203 +++++++++--------- 1 file changed, 102 insertions(+), 101 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index fcf1f1ad29e8..f2f043a83aeb 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -32,8 +32,7 @@ def setUp(self): self.mfe_config_api_url = reverse("mfe_config_api:config") return super().setUp() - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config(self, configuration_helpers_mock): + def test_get_mfe_config(self): """Test the get mfe config from site configuration with the mfe api. Expected result: @@ -46,14 +45,15 @@ def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} return default - configuration_helpers_mock.get_value.side_effect = side_effect + + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + configuration_helpers_mock.get_value.side_effect = side_effect - response = self.client.get(self.mfe_config_api_url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) + response = self.client.get(self.mfe_config_api_url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): + def test_get_mfe_config_with_queryparam(self): """Test the get mfe config with a query param from site configuration. Expected result: @@ -68,16 +68,18 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} return default - configuration_helpers_mock.get_value.side_effect = side_effect - - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") - self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual( - response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} - ) + + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + configuration_helpers_mock.get_value.side_effect = side_effect + + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual( + response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} + ) @ddt.unpack @ddt.data( @@ -120,10 +122,8 @@ def side_effect(key, default=None): expected_response={**default_legacy_config, "EXAMPLE_VAR": "mymfe_value"}, ), ) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") def test_get_mfe_config_with_queryparam_multiple_configs( self, - configuration_helpers_mock, mfe_config, mfe_config_overrides, expected_response, @@ -138,20 +138,21 @@ def test_get_mfe_config_with_queryparam_multiple_configs( and once with the parameters ("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES). - The json of the response is the expected_response passed by ddt.data. """ - def side_effect(key, default=None): - if key == "MFE_CONFIG": - return mfe_config - if key == "MFE_CONFIG_OVERRIDES": - return mfe_config_overrides - return default - configuration_helpers_mock.get_value.side_effect = side_effect - - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") - self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual(response.json(), expected_response) + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + def side_effect(key, default=None): + if key == "MFE_CONFIG": + return mfe_config + if key == "MFE_CONFIG_OVERRIDES": + return mfe_config_overrides + return default + configuration_helpers_mock.get_value.side_effect = side_effect + + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual(response.json(), expected_response) def test_get_mfe_config_from_django_settings(self): """Test that when there is no site configuration, the API takes the django settings. @@ -175,21 +176,20 @@ def test_get_mfe_config_with_queryparam_from_django_settings(self): expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] self.assertEqual(response.json(), expected) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @override_settings(ENABLE_MFE_CONFIG_API=False) - def test_404_get_mfe_config(self, configuration_helpers_mock): + def test_404_get_mfe_config(self): """Test the 404 not found response from get mfe config. Expected result: - The get_value method of configuration_helpers is not called. - The status of the response of the request is a HTTP_404_NOT_FOUND. """ - response = self.client.get(self.mfe_config_api_url) - configuration_helpers_mock.get_value.assert_not_called() - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + response = self.client.get(self.mfe_config_api_url) + configuration_helpers_mock.get_value.assert_not_called() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_get_mfe_config_for_catalog(self, configuration_helpers_mock): + def test_get_mfe_config_for_catalog(self): """Test the mfe config by explicitly using catalog mfe as an example. Expected result: @@ -217,23 +217,23 @@ def side_effect(key, default=None): return 8 return default - configuration_helpers_mock.get_value.side_effect = side_effect + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + configuration_helpers_mock.get_value.side_effect = side_effect - response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(data["BASE_URL"], "https://catalog.example.com") - self.assertEqual(data["SOME_SETTING"], "catalog_value") - self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], True) - self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], None) - self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 8) - self.assertEqual(data["COURSE_ABOUT_TWITTER_ACCOUNT"], "@TestAccount") - self.assertEqual(data["NON_BROWSABLE_COURSES"], True) - self.assertEqual(data["ENABLE_COURSE_DISCOVERY"], False) - - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") - def test_config_order_of_precedence(self, configuration_helpers_mock): + response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + self.assertEqual(data["BASE_URL"], "https://catalog.example.com") + self.assertEqual(data["SOME_SETTING"], "catalog_value") + self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], True) + self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], None) + self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 8) + self.assertEqual(data["COURSE_ABOUT_TWITTER_ACCOUNT"], "@TestAccount") + self.assertEqual(data["NON_BROWSABLE_COURSES"], True) + self.assertEqual(data["ENABLE_COURSE_DISCOVERY"], False) + + def test_config_order_of_precedence(self): """Test the precedence of configuration values by explicitly using catalog MFE as an example. Expected result: @@ -245,50 +245,51 @@ def test_config_order_of_precedence(self, configuration_helpers_mock): 5. Plain site configuration 6. Plain settings """ - mfe_config = { - "HOMEPAGE_COURSE_MAX": 10, - "ENABLE_COURSE_SORTING_BY_START_DATE": False, - "PRESERVED_SETTING": "preserved" - } - mfe_config_overrides = { - "catalog": { - "HOMEPAGE_COURSE_MAX": 15, + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: + mfe_config = { + "HOMEPAGE_COURSE_MAX": 10, + "ENABLE_COURSE_SORTING_BY_START_DATE": False, + "PRESERVED_SETTING": "preserved" } - } - - def side_effect(key, default=None): - if key == "MFE_CONFIG": - return mfe_config - if key == "MFE_CONFIG_OVERRIDES": - return mfe_config_overrides - if key == "HOMEPAGE_COURSE_MAX": - return 5 # Plain site configuration - if key == "homepage_promo_video_youtube_id": - return "site-conf-youtube-id" - return default - - configuration_helpers_mock.get_value.side_effect = side_effect - - with override_settings( - HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) - FEATURES={ # Settings FEATURES - "ENABLE_COURSE_SORTING_BY_START_DATE": True, - "ENABLE_COURSE_DISCOVERY": True, + mfe_config_overrides = { + "catalog": { + "HOMEPAGE_COURSE_MAX": 15, + } } - ): - response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") - - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - - # MFE_CONFIG_OVERRIDES from site conf (highest precedence) - self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 15) - - # MFE_CONFIG from site conf takes precedence over plain site configuration and settings - self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], False) - - # Plain site configuration takes precedence over plain settings - self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") - # Value in original MFE_CONFIG not overridden by catalog config should be preserved - self.assertEqual(data["PRESERVED_SETTING"], "preserved") + def side_effect(key, default=None): + if key == "MFE_CONFIG": + return mfe_config + if key == "MFE_CONFIG_OVERRIDES": + return mfe_config_overrides + if key == "HOMEPAGE_COURSE_MAX": + return 5 # Plain site configuration + if key == "homepage_promo_video_youtube_id": + return "site-conf-youtube-id" + return default + + configuration_helpers_mock.get_value.side_effect = side_effect + + with override_settings( + HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) + FEATURES={ # Settings FEATURES + "ENABLE_COURSE_SORTING_BY_START_DATE": True, + "ENABLE_COURSE_DISCOVERY": True, + } + ): + response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() + + # MFE_CONFIG_OVERRIDES from site conf (highest precedence) + self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 15) + + # MFE_CONFIG from site conf takes precedence over plain site configuration and settings + self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], False) + + # Plain site configuration takes precedence over plain settings + self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") + + # Value in original MFE_CONFIG not overridden by catalog config should be preserved + self.assertEqual(data["PRESERVED_SETTING"], "preserved") From c2a3b0fb8dd908bd5fb0f4720f6b95a00d37b515 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 14 Oct 2025 13:35:12 +0000 Subject: [PATCH 21/27] fix: rearranged the features in default_legacy_config --- lms/djangoapps/mfe_config_api/tests/test_views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index f2f043a83aeb..5344c77f90c1 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -13,11 +13,11 @@ # Default legacy configuration values, used in tests to build a correct expected response default_legacy_config = { - "COURSE_ABOUT_TWITTER_ACCOUNT": "@YourPlatformTwitterAccount", - "NON_BROWSABLE_COURSES": False, "ENABLE_COURSE_SORTING_BY_START_DATE": True, - "HOMEPAGE_COURSE_MAX": None, "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": None, + "HOMEPAGE_COURSE_MAX": None, + "COURSE_ABOUT_TWITTER_ACCOUNT": "@YourPlatformTwitterAccount", + "NON_BROWSABLE_COURSES": False, "ENABLE_COURSE_DISCOVERY": False, } @@ -45,7 +45,7 @@ def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} return default - + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: configuration_helpers_mock.get_value.side_effect = side_effect @@ -68,7 +68,7 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} return default - + with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: configuration_helpers_mock.get_value.side_effect = side_effect From 650da17d2053ce1fbcad4e93294e138850750f9b Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 14 Oct 2025 14:14:36 +0000 Subject: [PATCH 22/27] fix: fixing the tests with clearing cache --- lms/djangoapps/mfe_config_api/tests/test_views.py | 2 ++ lms/djangoapps/mfe_config_api/views.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 5344c77f90c1..2de879f3cf13 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -5,6 +5,7 @@ from unittest.mock import call, patch import ddt +from django.core.cache import cache from django.conf import settings from django.test import override_settings from django.urls import reverse @@ -30,6 +31,7 @@ class MFEConfigTestCase(APITestCase): def setUp(self): self.mfe_config_api_url = reverse("mfe_config_api:config") + cache.clear() return super().setUp() def test_get_mfe_config(self): diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index b40b727abeff..0ab71b151b88 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -101,7 +101,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.ENABLE_COURSE_SORTING_BY_START_DATE + settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -115,6 +115,6 @@ def _get_legacy_config() -> dict: "course_about_twitter_account", settings.PLATFORM_TWITTER_ACCOUNT ), - "NON_BROWSABLE_COURSES": not settings.COURSES_ARE_BROWSABLE, - "ENABLE_COURSE_DISCOVERY": settings.ENABLE_COURSE_DISCOVERY, + "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), + "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], } From 10c429b6d9e311a29c93cd368e2b93685070f31c Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 14 Oct 2025 14:54:14 +0000 Subject: [PATCH 23/27] fix: reverted test views to verify the CI check --- .../mfe_config_api/tests/test_views.py | 209 +++++++++--------- 1 file changed, 103 insertions(+), 106 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 2de879f3cf13..fcf1f1ad29e8 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -5,7 +5,6 @@ from unittest.mock import call, patch import ddt -from django.core.cache import cache from django.conf import settings from django.test import override_settings from django.urls import reverse @@ -14,11 +13,11 @@ # Default legacy configuration values, used in tests to build a correct expected response default_legacy_config = { - "ENABLE_COURSE_SORTING_BY_START_DATE": True, - "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": None, - "HOMEPAGE_COURSE_MAX": None, "COURSE_ABOUT_TWITTER_ACCOUNT": "@YourPlatformTwitterAccount", "NON_BROWSABLE_COURSES": False, + "ENABLE_COURSE_SORTING_BY_START_DATE": True, + "HOMEPAGE_COURSE_MAX": None, + "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": None, "ENABLE_COURSE_DISCOVERY": False, } @@ -31,10 +30,10 @@ class MFEConfigTestCase(APITestCase): def setUp(self): self.mfe_config_api_url = reverse("mfe_config_api:config") - cache.clear() return super().setUp() - def test_get_mfe_config(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config(self, configuration_helpers_mock): """Test the get mfe config from site configuration with the mfe api. Expected result: @@ -47,15 +46,14 @@ def side_effect(key, default=None): if key == "MFE_CONFIG": return {"EXAMPLE_VAR": "value"} return default + configuration_helpers_mock.get_value.side_effect = side_effect - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - configuration_helpers_mock.get_value.side_effect = side_effect - - response = self.client.get(self.mfe_config_api_url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) + response = self.client.get(self.mfe_config_api_url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) - def test_get_mfe_config_with_queryparam(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): """Test the get mfe config with a query param from site configuration. Expected result: @@ -70,18 +68,16 @@ def side_effect(key, default=None): if key == "MFE_CONFIG_OVERRIDES": return {"mymfe": {"EXAMPLE_VAR": "mymfe_value"}} return default + configuration_helpers_mock.get_value.side_effect = side_effect - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - configuration_helpers_mock.get_value.side_effect = side_effect - - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") - self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual( - response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} - ) + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual( + response.json(), {**default_legacy_config, "EXAMPLE_VAR": "mymfe_value", "OTHER": "other"} + ) @ddt.unpack @ddt.data( @@ -124,8 +120,10 @@ def side_effect(key, default=None): expected_response={**default_legacy_config, "EXAMPLE_VAR": "mymfe_value"}, ), ) + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") def test_get_mfe_config_with_queryparam_multiple_configs( self, + configuration_helpers_mock, mfe_config, mfe_config_overrides, expected_response, @@ -140,21 +138,20 @@ def test_get_mfe_config_with_queryparam_multiple_configs( and once with the parameters ("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES). - The json of the response is the expected_response passed by ddt.data. """ - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - def side_effect(key, default=None): - if key == "MFE_CONFIG": - return mfe_config - if key == "MFE_CONFIG_OVERRIDES": - return mfe_config_overrides - return default - configuration_helpers_mock.get_value.side_effect = side_effect - - response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") - self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", settings.MFE_CONFIG), - call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] - configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual(response.json(), expected_response) + def side_effect(key, default=None): + if key == "MFE_CONFIG": + return mfe_config + if key == "MFE_CONFIG_OVERRIDES": + return mfe_config_overrides + return default + configuration_helpers_mock.get_value.side_effect = side_effect + + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", settings.MFE_CONFIG), + call("MFE_CONFIG_OVERRIDES", settings.MFE_CONFIG_OVERRIDES)] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual(response.json(), expected_response) def test_get_mfe_config_from_django_settings(self): """Test that when there is no site configuration, the API takes the django settings. @@ -178,20 +175,21 @@ def test_get_mfe_config_with_queryparam_from_django_settings(self): expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] self.assertEqual(response.json(), expected) + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @override_settings(ENABLE_MFE_CONFIG_API=False) - def test_404_get_mfe_config(self): + def test_404_get_mfe_config(self, configuration_helpers_mock): """Test the 404 not found response from get mfe config. Expected result: - The get_value method of configuration_helpers is not called. - The status of the response of the request is a HTTP_404_NOT_FOUND. """ - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - response = self.client.get(self.mfe_config_api_url) - configuration_helpers_mock.get_value.assert_not_called() - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + response = self.client.get(self.mfe_config_api_url) + configuration_helpers_mock.get_value.assert_not_called() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_get_mfe_config_for_catalog(self): + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_get_mfe_config_for_catalog(self, configuration_helpers_mock): """Test the mfe config by explicitly using catalog mfe as an example. Expected result: @@ -219,23 +217,23 @@ def side_effect(key, default=None): return 8 return default - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - configuration_helpers_mock.get_value.side_effect = side_effect + configuration_helpers_mock.get_value.side_effect = side_effect - response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(data["BASE_URL"], "https://catalog.example.com") - self.assertEqual(data["SOME_SETTING"], "catalog_value") - self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], True) - self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], None) - self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 8) - self.assertEqual(data["COURSE_ABOUT_TWITTER_ACCOUNT"], "@TestAccount") - self.assertEqual(data["NON_BROWSABLE_COURSES"], True) - self.assertEqual(data["ENABLE_COURSE_DISCOVERY"], False) - - def test_config_order_of_precedence(self): + response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + self.assertEqual(data["BASE_URL"], "https://catalog.example.com") + self.assertEqual(data["SOME_SETTING"], "catalog_value") + self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], True) + self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], None) + self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 8) + self.assertEqual(data["COURSE_ABOUT_TWITTER_ACCOUNT"], "@TestAccount") + self.assertEqual(data["NON_BROWSABLE_COURSES"], True) + self.assertEqual(data["ENABLE_COURSE_DISCOVERY"], False) + + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + def test_config_order_of_precedence(self, configuration_helpers_mock): """Test the precedence of configuration values by explicitly using catalog MFE as an example. Expected result: @@ -247,51 +245,50 @@ def test_config_order_of_precedence(self): 5. Plain site configuration 6. Plain settings """ - with patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") as configuration_helpers_mock: - mfe_config = { - "HOMEPAGE_COURSE_MAX": 10, - "ENABLE_COURSE_SORTING_BY_START_DATE": False, - "PRESERVED_SETTING": "preserved" + mfe_config = { + "HOMEPAGE_COURSE_MAX": 10, + "ENABLE_COURSE_SORTING_BY_START_DATE": False, + "PRESERVED_SETTING": "preserved" + } + mfe_config_overrides = { + "catalog": { + "HOMEPAGE_COURSE_MAX": 15, } - mfe_config_overrides = { - "catalog": { - "HOMEPAGE_COURSE_MAX": 15, - } + } + + def side_effect(key, default=None): + if key == "MFE_CONFIG": + return mfe_config + if key == "MFE_CONFIG_OVERRIDES": + return mfe_config_overrides + if key == "HOMEPAGE_COURSE_MAX": + return 5 # Plain site configuration + if key == "homepage_promo_video_youtube_id": + return "site-conf-youtube-id" + return default + + configuration_helpers_mock.get_value.side_effect = side_effect + + with override_settings( + HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) + FEATURES={ # Settings FEATURES + "ENABLE_COURSE_SORTING_BY_START_DATE": True, + "ENABLE_COURSE_DISCOVERY": True, } + ): + response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = response.json() + + # MFE_CONFIG_OVERRIDES from site conf (highest precedence) + self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 15) + + # MFE_CONFIG from site conf takes precedence over plain site configuration and settings + self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], False) + + # Plain site configuration takes precedence over plain settings + self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") - def side_effect(key, default=None): - if key == "MFE_CONFIG": - return mfe_config - if key == "MFE_CONFIG_OVERRIDES": - return mfe_config_overrides - if key == "HOMEPAGE_COURSE_MAX": - return 5 # Plain site configuration - if key == "homepage_promo_video_youtube_id": - return "site-conf-youtube-id" - return default - - configuration_helpers_mock.get_value.side_effect = side_effect - - with override_settings( - HOMEPAGE_COURSE_MAX=3, # Plain settings (lowest precedence) - FEATURES={ # Settings FEATURES - "ENABLE_COURSE_SORTING_BY_START_DATE": True, - "ENABLE_COURSE_DISCOVERY": True, - } - ): - response = self.client.get(f"{self.mfe_config_api_url}?mfe=catalog") - - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - - # MFE_CONFIG_OVERRIDES from site conf (highest precedence) - self.assertEqual(data["HOMEPAGE_COURSE_MAX"], 15) - - # MFE_CONFIG from site conf takes precedence over plain site configuration and settings - self.assertEqual(data["ENABLE_COURSE_SORTING_BY_START_DATE"], False) - - # Plain site configuration takes precedence over plain settings - self.assertEqual(data["HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID"], "site-conf-youtube-id") - - # Value in original MFE_CONFIG not overridden by catalog config should be preserved - self.assertEqual(data["PRESERVED_SETTING"], "preserved") + # Value in original MFE_CONFIG not overridden by catalog config should be preserved + self.assertEqual(data["PRESERVED_SETTING"], "preserved") From a3cad1fe622b7cd5e6006ce924e2e1da85d2b625 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 14 Oct 2025 15:14:51 +0000 Subject: [PATCH 24/27] fix: added cache clear in mfe config test --- lms/djangoapps/mfe_config_api/tests/test_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index fcf1f1ad29e8..0dfc63e82790 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -5,6 +5,7 @@ from unittest.mock import call, patch import ddt +from django.core.cache import cache from django.conf import settings from django.test import override_settings from django.urls import reverse @@ -30,6 +31,7 @@ class MFEConfigTestCase(APITestCase): def setUp(self): self.mfe_config_api_url = reverse("mfe_config_api:config") + cache.clear() return super().setUp() @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") From 9aee2e227f6daf52c735a00da597886c5413ceff Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Mon, 19 Jan 2026 08:49:11 +0000 Subject: [PATCH 25/27] fix: fixed the patch toggles to override settings --- .../contentstore/tests/test_course_settings.py | 13 ++++++------- cms/djangoapps/contentstore/tests/test_utils.py | 8 ++++---- .../contentstore/views/tests/test_library.py | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 4ec8fc4a7c8a..8a2a22a535e0 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -44,7 +44,6 @@ from common.djangoapps.student.tests.factories import UserFactory from common.djangoapps.util import milestones_helpers from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag -from openedx.core import toggles as core_toggles from openedx.core.djangoapps.discussions.config.waffle import ( ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND, OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG @@ -389,15 +388,15 @@ def test_invalid_pre_requisite_course(self): (True, True, True), ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @patch.object(milestones_helpers.ENABLE_MILESTONES_APP, 'is_enabled', return_value=False) - def test_visibility_of_entrance_exam_section(self, feature_flags, mock_milestones): + @override_settings(MILESTONES_APP=False) + def test_visibility_of_entrance_exam_section(self, feature_flags): """ Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other feature is enabled or disabled i.e ENABLE_PUBLISHER. """ with patch.dict("django.conf.settings.FEATURES", { 'ENABLE_PUBLISHER': feature_flags[1] - }), patch.object(core_toggles.ENTRANCE_EXAMS, 'is_enabled', return_value=feature_flags[0]): + }), override_settings(ENTRANCE_EXAMS=feature_flags[0]): course_details_url = get_url(self.course.id) resp = self.client.get_html(course_details_url) self.assertEqual( @@ -406,9 +405,9 @@ def test_visibility_of_entrance_exam_section(self, feature_flags, mock_milestone ) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) - @patch.object(milestones_helpers.ENABLE_MILESTONES_APP, 'is_enabled', return_value=False) - @patch.object(core_toggles.ENTRANCE_EXAMS, 'is_enabled', return_value=False) - def test_marketing_site_fetch(self, mock_milestones, mock_entrance_exams): + @override_settings(MILESTONES_APP=False) + @override_settings(ENTRANCE_EXAMS=False) + def test_marketing_site_fetch(self): settings_details_url = get_url(self.course.id) with mock.patch.dict('django.conf.settings.FEATURES', { diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index af0b303ae35e..c529410cd110 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -892,8 +892,8 @@ def setUp(self): @patch.dict("django.conf.settings.FEATURES", { "ENABLE_PREREQUISITE_COURSES": False, - "ENTRANCE_EXAMS": False, }) + @override_settings(ENTRANCE_EXAMS=False) @patch("cms.djangoapps.contentstore.utils.CourseDetails.update_from_json") def test_update_course_details_self_paced(self, mock_update): """ @@ -913,13 +913,13 @@ def test_update_course_details_self_paced(self, mock_update): "self_paced": True, } - utils.update_course_details(mock_request, self.course.id, payload, self.course) + utils.update_course_details(mock_request, self.course.id, payload, None) mock_update.assert_called_once_with(self.course.id, expected_payload, mock_request.user) @patch.dict("django.conf.settings.FEATURES", { "ENABLE_PREREQUISITE_COURSES": False, - "ENTRANCE_EXAMS": False, }) + @override_settings(ENTRANCE_EXAMS=False) @patch("cms.djangoapps.contentstore.utils.CourseDetails.update_from_json") def test_update_course_details_instructor_paced(self, mock_update): """ @@ -934,7 +934,7 @@ def test_update_course_details_instructor_paced(self, mock_update): "self_paced": False, } - utils.update_course_details(mock_request, self.course.id, payload, self.course) + utils.update_course_details(mock_request, self.course.id, payload, None) mock_update.assert_called_once_with(self.course.id, payload, mock_request.user) diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py index 6d275886c128..bf0c04699029 100644 --- a/cms/djangoapps/contentstore/views/tests/test_library.py +++ b/cms/djangoapps/contentstore/views/tests/test_library.py @@ -162,8 +162,8 @@ def test_library_creator_status_with_no_course_creator_role_and_disabled_nonstaf self.assertEqual(get_response.status_code, 200) self.assertEqual(post_response.status_code, 403) - @patch.object(toggles.ENABLE_CONTENT_LIBRARIES, 'is_enabled', return_value=False) - def test_with_libraries_disabled(self, mock_is_enabled): + @override_settings(ENABLE_CONTENT_LIBRARIES=False) + def test_with_libraries_disabled(self): """ The library URLs should return 404 if libraries are disabled. """ From 55a26cd21c793f33403a2e200526f147e6f1047b Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Mon, 19 Jan 2026 09:23:32 +0000 Subject: [PATCH 26/27] fix: fixed the lint errors --- cms/djangoapps/contentstore/views/tests/test_library.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py index bf0c04699029..fd62faa56db1 100644 --- a/cms/djangoapps/contentstore/views/tests/test_library.py +++ b/cms/djangoapps/contentstore/views/tests/test_library.py @@ -16,7 +16,6 @@ from organizations.api import get_organization_by_short_name from organizations.exceptions import InvalidOrganizationException -from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_library_url from cms.djangoapps.course_creators.views import add_user_with_status_granted as grant_course_creator_status From bb6ad96fa31936a8e7cf589f95ec5479a186d8b8 Mon Sep 17 00:00:00 2001 From: Akanshu-2u Date: Tue, 20 Jan 2026 15:08:55 +0000 Subject: [PATCH 27/27] fix: changed patch toggle to override settings --- .../tests/test_course_settings.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 8a2a22a535e0..876bb37ee783 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -1129,16 +1129,16 @@ def test_fetch_initial_fields(self): self.assertIn('showanswer', test_model, 'showanswer field ') self.assertIn('xqa_key', test_model, 'xqa_key field ') - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) - def test_fetch_giturl_present(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=True) + def test_fetch_giturl_present(self): """ If feature flag ENABLE_EXPORT_GIT is on, show the setting as a non-deprecated Advanced Setting. """ test_model = CourseMetadata.fetch(self.fullcourse) self.assertIn('giturl', test_model) - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) - def test_fetch_giturl_not_present(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=False) + def test_fetch_giturl_not_present(self): """ If feature flag ENABLE_EXPORT_GIT is off, don't show the setting at all on the Advanced Settings page. """ @@ -1173,8 +1173,8 @@ def test_fetch_proctoring_escalation_email_not_present(self): test_model = CourseMetadata.fetch(self.fullcourse) self.assertNotIn('proctoring_escalation_email', test_model) - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) - def test_validate_update_filtered_off(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=False) + def test_validate_update_filtered_off(self): """ If feature flag is off, then giturl must be filtered. """ @@ -1188,8 +1188,8 @@ def test_validate_update_filtered_off(self, mock_is_enabled): ) self.assertNotIn('giturl', test_model) - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) - def test_validate_update_filtered_on(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=True) + def test_validate_update_filtered_on(self): """ If feature flag is on, then giturl must not be filtered. """ @@ -1203,8 +1203,8 @@ def test_validate_update_filtered_on(self, mock_is_enabled): ) self.assertIn('giturl', test_model) - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=True) - def test_update_from_json_filtered_on(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=True) + def test_update_from_json_filtered_on(self): """ If feature flag is on, then giturl must be updated. """ @@ -1217,8 +1217,8 @@ def test_update_from_json_filtered_on(self, mock_is_enabled): ) self.assertIn('giturl', test_model) - @patch.object(toggles.EXPORT_GIT, 'is_enabled', return_value=False) - def test_update_from_json_filtered_off(self, mock_is_enabled): + @override_settings(ENABLE_EXPORT_GIT=False) + def test_update_from_json_filtered_off(self): """ If feature flag is on, then giturl must not be updated. """