|
10 | 10 | from ccx_keys.locator import CCXLocator |
11 | 11 | from django.test import RequestFactory |
12 | 12 | from opaque_keys.edx.locations import CourseLocator |
13 | | -from openedx_authz.api.data import OrgCourseOverviewGlobData |
| 13 | +from openedx_authz.api.data import OrgCourseOverviewGlobData, PlatformCourseOverviewGlobData |
14 | 14 | from openedx_authz.api.users import assign_role_to_user_in_scope |
15 | 15 | from openedx_authz.constants.roles import COURSE_DATA_RESEARCHER, COURSE_EDITOR, COURSE_STAFF |
16 | 16 |
|
|
21 | 21 | _accessible_courses_iter_for_tests, |
22 | 22 | _accessible_courses_list_from_groups, |
23 | 23 | _accessible_courses_summary_iter, |
| 24 | + _get_course_keys_from_scopes, |
24 | 25 | get_courses_accessible_to_user, |
25 | 26 | ) |
26 | 27 | from common.djangoapps.course_action_state.models import CourseRerunState |
@@ -832,3 +833,94 @@ def test_course_listing_with_org_scope_fetched_once(self): |
832 | 833 | courses, _ = get_courses_accessible_to_user(request) |
833 | 834 |
|
834 | 835 | mock_get_all_courses.assert_called_once_with(orgs={"Org1", "Org2"}) |
| 836 | + |
| 837 | + def test_course_listing_with_platform_scope(self): |
| 838 | + """ |
| 839 | + Verify that a platform-wide scope (`course-v1:*`) grants access to all |
| 840 | + courses across orgs when the AuthZ course authoring toggle is enabled. |
| 841 | + """ |
| 842 | + _, _, authz_courses, _ = self._create_courses() |
| 843 | + org2_course_key = CourseLocator("Org2", "Course1", "AuthzRun") |
| 844 | + org2_course = self._create_course(org2_course_key) |
| 845 | + assign_role_to_user_in_scope( |
| 846 | + self.authorized_user.username, |
| 847 | + COURSE_STAFF.external_key, |
| 848 | + PlatformCourseOverviewGlobData.build_external_key(), |
| 849 | + ) |
| 850 | + |
| 851 | + request = self._make_request(self.authorized_user) |
| 852 | + |
| 853 | + with self._authz_waffle_context(active=True): |
| 854 | + courses, _ = get_courses_accessible_to_user(request) |
| 855 | + |
| 856 | + result_ids = {c.id for c in courses} |
| 857 | + expected_ids = {*(c.id for c in authz_courses), org2_course.id} |
| 858 | + |
| 859 | + self.assertEqual(result_ids, expected_ids) # noqa: PT009 |
| 860 | + |
| 861 | + def test_course_listing_with_platform_scope_with_toggle(self): |
| 862 | + """ |
| 863 | + If the authz toggle is enabled only for a subset of courses, only those |
| 864 | + course keys should appear when resolving a platform-wide scope. |
| 865 | + """ |
| 866 | + authz_keys, _, _, _ = self._create_courses() |
| 867 | + org2_course_key = CourseLocator("Org2", "Course1", "AuthzRun") |
| 868 | + self._create_course(org2_course_key) |
| 869 | + enabled_keys = {str(authz_keys[0]), str(authz_keys[2])} |
| 870 | + assign_role_to_user_in_scope( |
| 871 | + self.authorized_user.username, |
| 872 | + COURSE_STAFF.external_key, |
| 873 | + PlatformCourseOverviewGlobData.build_external_key(), |
| 874 | + ) |
| 875 | + |
| 876 | + request = self._make_request(self.authorized_user) |
| 877 | + |
| 878 | + with patch.object( |
| 879 | + core_toggles.AUTHZ_COURSE_AUTHORING_FLAG, |
| 880 | + "is_enabled", |
| 881 | + side_effect=self._mock_authz_toggle(enabled_keys), |
| 882 | + ): |
| 883 | + courses, _ = get_courses_accessible_to_user(request) |
| 884 | + |
| 885 | + result_ids = {c.id for c in courses} |
| 886 | + expected = {authz_keys[0], authz_keys[2]} |
| 887 | + self.assertEqual(result_ids, expected) # noqa: PT009 |
| 888 | + |
| 889 | + def test_get_course_keys_from_scopes_with_platform_scope(self): |
| 890 | + """ |
| 891 | + Platform-wide scopes should resolve to all courses with AuthZ enabled. |
| 892 | + """ |
| 893 | + authz_keys, _, _, _ = self._create_courses() |
| 894 | + org2_course_key = CourseLocator("Org2", "Course1", "AuthzRun") |
| 895 | + self._create_course(org2_course_key) |
| 896 | + enabled_keys = {str(key) for key in authz_keys} |
| 897 | + |
| 898 | + with patch.object( |
| 899 | + core_toggles.AUTHZ_COURSE_AUTHORING_FLAG, |
| 900 | + "is_enabled", |
| 901 | + side_effect=self._mock_authz_toggle(enabled_keys), |
| 902 | + ): |
| 903 | + course_keys = _get_course_keys_from_scopes([PlatformCourseOverviewGlobData.build_external_key()]) |
| 904 | + |
| 905 | + self.assertEqual(course_keys, set(authz_keys)) # noqa: PT009 |
| 906 | + |
| 907 | + def test_get_course_keys_from_scopes_platform_scope_short_circuits(self): |
| 908 | + """ |
| 909 | + When a platform-wide scope is present, org and course scopes should be ignored. |
| 910 | + """ |
| 911 | + authz_keys, _, _, _ = self._create_courses() |
| 912 | + enabled_keys = {str(authz_keys[0])} |
| 913 | + |
| 914 | + with patch.object( |
| 915 | + core_toggles.AUTHZ_COURSE_AUTHORING_FLAG, |
| 916 | + "is_enabled", |
| 917 | + side_effect=self._mock_authz_toggle(enabled_keys), |
| 918 | + ): |
| 919 | + course_keys = _get_course_keys_from_scopes( |
| 920 | + [ |
| 921 | + OrgCourseOverviewGlobData.build_external_key("Org1"), |
| 922 | + PlatformCourseOverviewGlobData.build_external_key(), |
| 923 | + ] |
| 924 | + ) |
| 925 | + |
| 926 | + self.assertEqual(course_keys, {authz_keys[0]}) # noqa: PT009 |
0 commit comments