Skip to content

Commit f290079

Browse files
committed
fixup! feat: add course authoring migration and rollback scripts
1 parent 82aed9e commit f290079

1 file changed

Lines changed: 116 additions & 55 deletions

File tree

openedx_authz/tests/test_migrations.py

Lines changed: 116 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from unittest.mock import patch
44

5-
import pytest
65
from django.contrib.auth import get_user_model
76
from django.contrib.auth.models import Group
87
from django.core.management import CommandError, call_command
@@ -274,6 +273,46 @@ def setUp(self):
274273
role="invalid-legacy-role",
275274
)
276275

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+
277316
def tearDown(self):
278317
"""
279318
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):
927966
with self.assertRaises(CommandError):
928967
call_command("authz_migrate_course_authoring", "--course-id-list", self.course_id, "--org-id", self.org)
929968

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+
)
930981

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")
947985

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+
)
9501000

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)
9531005

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+
"""
9561010

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)
9591014

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+
]
9621020

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)
9651027

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+
)
9681035

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,
9731040
)
9741041

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+
)
9801054

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

Comments
 (0)