|
2 | 2 |
|
3 | 3 | from unittest.mock import patch |
4 | 4 |
|
5 | | -import pytest |
6 | 5 | from django.contrib.auth import get_user_model |
7 | 6 | from django.contrib.auth.models import Group |
8 | 7 | from django.core.management import CommandError, call_command |
@@ -274,6 +273,46 @@ def setUp(self): |
274 | 273 | role="invalid-legacy-role", |
275 | 274 | ) |
276 | 275 |
|
| 276 | + class MockPermission: |
| 277 | + """Mock class to simulate CourseAccessRole entries for testing the rollback migration.""" |
| 278 | + def __init__(self, user, role, course_id, id_in): |
| 279 | + self.user = user |
| 280 | + self.role = role |
| 281 | + self.course_id = course_id |
| 282 | + self.id = id_in |
| 283 | + |
| 284 | + class MockUser: |
| 285 | + """Mock class to simulate User objects for testing the rollback migration.""" |
| 286 | + def __init__(self, username): |
| 287 | + self.username = username |
| 288 | + |
| 289 | + class MockQuerySet: |
| 290 | + """Mock class to simulate QuerySet behavior for testing the rollback migration.""" |
| 291 | + def __init__(self, permissions): |
| 292 | + self.permissions = permissions |
| 293 | + |
| 294 | + def filter(self, **kwargs): |
| 295 | + return self |
| 296 | + |
| 297 | + def select_related(self, *args, **kwargs): |
| 298 | + return self |
| 299 | + |
| 300 | + def all(self): |
| 301 | + return self.permissions |
| 302 | + |
| 303 | + def get_or_create(self): |
| 304 | + raise Exception("Unexpected error mock") |
| 305 | + |
| 306 | + class MockCourseAccessRole: |
| 307 | + """Mock class to simulate CourseAccessRole manager for testing the rollback migration.""" |
| 308 | + objects = MockQuerySet( |
| 309 | + [ |
| 310 | + MockPermission(MockUser("testuser"), "instructor", "course-v1:test", 1), |
| 311 | + ] |
| 312 | + ) |
| 313 | + |
| 314 | + self.mock_course_access_role = MockCourseAccessRole |
| 315 | + |
277 | 316 | def tearDown(self): |
278 | 317 | """ |
279 | 318 | Clean up test data created for the migration test. |
@@ -927,69 +966,91 @@ def test_authz_migrate_course_authoring_command_with_org_and_courses(self): |
927 | 966 | with self.assertRaises(CommandError): |
928 | 967 | call_command("authz_migrate_course_authoring", "--course-id-list", self.course_id, "--org-id", self.org) |
929 | 968 |
|
| 969 | + @patch("openedx_authz.engine.utils.assign_role_to_user_in_scope", return_value=False) |
| 970 | + @patch("openedx_authz.engine.utils.LEGACY_COURSE_ROLE_EQUIVALENCES", {"instructor": "instructor-role"}) |
| 971 | + def test_migrate_legacy_course_roles_to_authz_user_not_added( |
| 972 | + self, |
| 973 | + _, # comes from patch |
| 974 | + ): |
| 975 | + errors, successes = migrate_legacy_course_roles_to_authz( |
| 976 | + self.mock_course_access_role, |
| 977 | + course_id_list=["course-v1:test"], |
| 978 | + org_id=None, |
| 979 | + delete_after_migration=False, |
| 980 | + ) |
930 | 981 |
|
931 | | -@pytest.fixture |
932 | | -def mock_course_access_role(): |
933 | | - """Fixture to mock the CourseAccessRole model and its queryset |
934 | | - for testing the migration functions without relying on the actual database model.""" |
935 | | - |
936 | | - class MockPermission: |
937 | | - """A simple class to represent a permission with user, role, course_id and id attributes.""" |
938 | | - |
939 | | - def __init__(self, user, role, course_id, id_in): |
940 | | - self.user = user |
941 | | - self.role = role |
942 | | - self.course_id = course_id |
943 | | - self.id = id_in |
944 | | - |
945 | | - class MockUser: |
946 | | - """A simple class to represent a user with a username attribute.""" |
| 982 | + self.assertEqual(len(errors), 1) |
| 983 | + self.assertEqual(len(successes), 0) |
| 984 | + self.assertEqual(errors[0].user.username, "testuser") |
947 | 985 |
|
948 | | - def __init__(self, username): |
949 | | - self.username = username |
| 986 | + @patch("openedx_authz.api.data.CourseOverview", CourseOverview) |
| 987 | + def test_migrate_authz_to_legacy_course_roles_user_not_added(self): |
| 988 | + permissions_with_errors, permissions_with_no_errors = migrate_legacy_course_roles_to_authz( |
| 989 | + CourseAccessRole, course_id_list=[self.course_id], org_id=None, delete_after_migration=False |
| 990 | + ) |
| 991 | + self.assertEqual(len(permissions_with_errors), 1) |
| 992 | + self.assertEqual(len(permissions_with_no_errors), 12) |
| 993 | + errors, successes = migrate_authz_to_legacy_course_roles( |
| 994 | + self.mock_course_access_role, |
| 995 | + UserSubject, |
| 996 | + course_id_list=[self.course_id], |
| 997 | + org_id=None, |
| 998 | + delete_after_migration=False, |
| 999 | + ) |
950 | 1000 |
|
951 | | - class MockQuerySet: |
952 | | - """A simple class to represent a queryset of permissions.""" |
| 1001 | + # 3 users for each of the 4 roles = 12 total entries that will |
| 1002 | + # fail to migrate back to legacy roles due to our mock |
| 1003 | + self.assertEqual(len(errors), 12) |
| 1004 | + self.assertEqual(len(successes), 0) |
953 | 1005 |
|
954 | | - def __init__(self, permissions): |
955 | | - self.permissions = permissions |
| 1006 | + def create_library_env(self): |
| 1007 | + """Helper method to create a ContentLibrary environment for testing the migration of legacy permissions |
| 1008 | + related to ContentLibraryPermission to the new Casbin-based model. |
| 1009 | + """ |
956 | 1010 |
|
957 | | - def filter(self, **kwargs): |
958 | | - return self |
| 1011 | + # Create ContentLibrary |
| 1012 | + org = Organization.objects.create(name=org_name, short_name=org_short_name) |
| 1013 | + library = ContentLibrary.objects.create(org=org, slug=lib_name) |
959 | 1014 |
|
960 | | - def select_related(self, *args, **kwargs): |
961 | | - return self |
| 1015 | + # Create Users and Groups |
| 1016 | + users = [ |
| 1017 | + User.objects.create_user(username=user_name, email=f"lib_{user_name}@example.com") |
| 1018 | + for user_name in user_names |
| 1019 | + ] |
962 | 1020 |
|
963 | | - def all(self): |
964 | | - return self.permissions |
| 1021 | + group_users = [ |
| 1022 | + User.objects.create_user(username=user_name, email=f"lib_{user_name}@example.com") |
| 1023 | + for user_name in group_user_names |
| 1024 | + ] |
| 1025 | + group = Group.objects.create(name=group_name) |
| 1026 | + group.user_set.set(group_users) |
965 | 1027 |
|
966 | | - class MockCourseAccessRole: |
967 | | - """A simple class to represent the CourseAccessRole model.""" |
| 1028 | + # Assign legacy permissions for users and group |
| 1029 | + for user in users: |
| 1030 | + ContentLibraryPermission.objects.create( |
| 1031 | + user=user, |
| 1032 | + library=library, |
| 1033 | + access_level=ContentLibraryPermission.ADMIN_LEVEL, |
| 1034 | + ) |
968 | 1035 |
|
969 | | - objects = MockQuerySet( |
970 | | - [ |
971 | | - MockPermission(MockUser("testuser"), "instructor", "course-v1:test", 1), |
972 | | - ] |
| 1036 | + ContentLibraryPermission.objects.create( |
| 1037 | + group=group, |
| 1038 | + library=library, |
| 1039 | + access_level=ContentLibraryPermission.READ_LEVEL, |
973 | 1040 | ) |
974 | 1041 |
|
975 | | - def __init__(self): |
976 | | - pass |
977 | | - |
978 | | - return MockCourseAccessRole |
979 | | - |
| 1042 | + @patch("openedx_authz.api.data.CourseOverview", CourseOverview) |
| 1043 | + def test_migrate_authz_to_legacy_course_roles_with_no_course_scopes(self): |
| 1044 | + self.create_library_env() |
| 1045 | + migrate_legacy_permissions(ContentLibraryPermission) |
| 1046 | + permissions_with_errors, permissions_with_no_errors = migrate_legacy_course_roles_to_authz( |
| 1047 | + CourseAccessRole, course_id_list=[self.course_id], org_id=None, delete_after_migration=False |
| 1048 | + ) |
| 1049 | + self.assertEqual(len(permissions_with_errors), 1) |
| 1050 | + self.assertEqual(len(permissions_with_no_errors), 12) |
| 1051 | + errors, successes = migrate_authz_to_legacy_course_roles( |
| 1052 | + CourseAccessRole, UserSubject, course_id_list=[self.course_id], org_id=None, delete_after_migration=False |
| 1053 | + ) |
980 | 1054 |
|
981 | | -# pylint: disable=redefined-outer-name |
982 | | -def test_migrate_legacy_course_roles_to_authz_user_not_added(monkeypatch, mock_course_access_role): |
983 | | - # Patch assign_role_to_user_in_scope to always return False |
984 | | - monkeypatch.setattr( |
985 | | - "openedx_authz.engine.utils.assign_role_to_user_in_scope", |
986 | | - lambda user_external_key, role_external_key, scope_external_key: False, |
987 | | - ) |
988 | | - # Patch LEGACY_COURSE_ROLE_EQUIVALENCES |
989 | | - monkeypatch.setattr("openedx_authz.engine.utils.LEGACY_COURSE_ROLE_EQUIVALENCES", {"instructor": "instructor-role"}) |
990 | | - errors, successes = migrate_legacy_course_roles_to_authz( |
991 | | - mock_course_access_role, course_id_list=["course-v1:test"], org_id=None, delete_after_migration=False |
992 | | - ) |
993 | | - assert len(errors) == 1 |
994 | | - assert len(successes) == 0 |
995 | | - assert errors[0].user.username == "testuser" |
| 1055 | + self.assertEqual(len(errors), 0) |
| 1056 | + self.assertEqual(len(successes), 12) |
0 commit comments