@@ -441,6 +441,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_no_deletion(self):
441441
442442 # Now let's rollback
443443
444+ # Capture the state of permissions before rollback to verify that rollback restores the original state
445+ original_state_user_subjects = list (
446+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
447+ .distinct ()
448+ .order_by ("id" )
449+ .values ("id" , "user_id" )
450+ )
451+ original_state_access_roles = list (
452+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
453+ )
454+
444455 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
445456 CourseAccessRole , UserSubject , course_id_list = course_id_list , org_id = None , delete_after_migration = False
446457 )
@@ -475,6 +486,34 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_no_deletion(self):
475486 self .assertEqual (len (permissions_with_errors ), 0 )
476487 self .assertEqual (len (permissions_with_no_errors ), 12 ) # 3 users for each of the 4 roles = 12 total entries
477488
489+ state_after_migration_user_subjects = list (
490+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
491+ .distinct ()
492+ .order_by ("id" )
493+ .values ("id" , "user_id" )
494+ )
495+ after_migrate_state_access_roles = list (
496+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
497+ )
498+
499+ # The number of CourseAccessRole entries should be the same as the original state
500+ # since we are not deleting any entries in this test.
501+ self .assertEqual (len (original_state_access_roles ), 13 )
502+
503+ # All original entries should still be there since we are not deleting any entries
504+ # and when creating new entries for the users that were migrated back to legacy roles,
505+ # we are creating them with get_or_create which will not create duplicates if an entry
506+ # with the same user, org, course_id and role already exists.
507+ self .assertEqual (len (after_migrate_state_access_roles ), 13 )
508+
509+ # Sanity check to ensure we have the expected number of UserSubjects related to
510+ # the course permissions before migration (3 users * 4 roles = 12)
511+ self .assertEqual (len (original_state_user_subjects ), 12 )
512+
513+ # After rollback, we should have the same 12 UserSubjects related to the course permissions
514+ # since we are not deleting any entries in this test,
515+ self .assertEqual (len (state_after_migration_user_subjects ), 12 )
516+
478517 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
479518 def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion (self ):
480519 """Test the migration of legacy permissions from CourseAccessRole to
@@ -556,6 +595,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion(self):
556595
557596 # Now let's rollback
558597
598+ # Capture the state of permissions before rollback to verify that rollback restores the original state
599+ original_state_user_subjects = list (
600+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
601+ .distinct ()
602+ .order_by ("id" )
603+ .values ("id" , "user_id" )
604+ )
605+ original_state_access_roles = list (
606+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
607+ )
608+
559609 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
560610 CourseAccessRole , UserSubject , course_id_list = course_id_list , org_id = None , delete_after_migration = True
561611 )
@@ -586,13 +636,30 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion(self):
586636 self .assertEqual (len (permissions_with_errors ), 0 )
587637 self .assertEqual (len (permissions_with_no_errors ), 12 )
588638
639+ state_after_migration_user_subjects = list (
640+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
641+ .distinct ()
642+ .order_by ("id" )
643+ .values ("id" , "user_id" )
644+ )
589645 after_migrate_state_access_roles = list (
590646 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
591647 )
592648
649+ # Before the rollback, we should only have the 1 invalid role entry
650+ # since we set delete_after_migration to True in the migration.
651+ self .assertEqual (len (original_state_access_roles ), 1 )
652+
593653 # All original entries + 3 users * 4 roles = 12
594654 # plus the original invalid entry = 1 + 12 = 13 total entries
595- self .assertEqual (len (after_migrate_state_access_roles ), 13 )
655+ self .assertEqual (len (after_migrate_state_access_roles ), 1 + 12 )
656+
657+ # Sanity check to ensure we have the expected number of UserSubjects related to
658+ # the course permissions before migration (3 users * 4 roles = 12)
659+ self .assertEqual (len (original_state_user_subjects ), 12 )
660+
661+ # After rollback, we should have 0 UserSubjects related to the course permissions
662+ self .assertEqual (len (state_after_migration_user_subjects ), 0 )
596663
597664 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
598665 def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equivalent (self ):
@@ -609,6 +676,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
609676
610677 # Now let's rollback
611678
679+ # Capture the state of permissions before rollback to verify that rollback restores the original state
680+ original_state_user_subjects = list (
681+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
682+ .distinct ()
683+ .order_by ("id" )
684+ .values ("id" , "user_id" )
685+ )
686+ original_state_access_roles = list (
687+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
688+ )
689+
612690 # Mock the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping
613691 # for COURSE_ADMIN to simulate the scenario where the staff, limited_staff
614692 # and data_researcher roles do not have a legacy role equivalent and
@@ -653,13 +731,32 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
653731 # 3 staff + 3 limited_staff + 3 data_researcher = 9 entries with no legacy role equivalent
654732 self .assertEqual (len (permissions_with_errors ), 9 )
655733
734+ state_after_migration_user_subjects = list (
735+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
736+ .distinct ()
737+ .order_by ("id" )
738+ .values ("id" , "user_id" )
739+ )
656740 after_migrate_state_access_roles = list (
657741 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
658742 )
659743
744+ # Before the rollback, we should only have the 1 invalid role entry
745+ # since we set delete_after_migration to True in the migration.
746+ self .assertEqual (len (original_state_access_roles ), 1 )
747+
660748 # All original entries (1) + 3 users * 1 roles = 4
661749 self .assertEqual (len (after_migrate_state_access_roles ), 1 + 3 )
662750
751+ # Before the rollback, we should have the 12 UserSubjects related to the course permissions
752+ # since we had 3 users with 4 roles each in the original state.
753+ self .assertEqual (len (original_state_user_subjects ), 12 )
754+
755+ # After rollback, we should have 9 UserSubjects related to the course permissions
756+ # since the users with staff, limited_staff and data_researcher roles will not be
757+ # migrated back to legacy roles due to our mocked COURSE_ROLE_EQUIVALENCES mapping.
758+ self .assertEqual (len (state_after_migration_user_subjects ), 9 )
759+
663760 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
664761 def test_migrate_legacy_course_roles_to_authz_using_org_id (self ):
665762 """Test the migration of legacy course roles to the new Casbin-based model
@@ -713,6 +810,17 @@ def test_migrate_legacy_course_roles_to_authz_using_org_id(self):
713810 # to True and we are deleting all
714811 # Now let's rollback
715812
813+ # Capture the state of permissions before rollback to verify that rollback restores the original state
814+ original_state_user_subjects = list (
815+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
816+ .distinct ()
817+ .order_by ("id" )
818+ .values ("id" , "user_id" )
819+ )
820+ original_state_access_roles = list (
821+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
822+ )
823+
716824 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
717825 CourseAccessRole , UserSubject , course_id_list = None , org_id = self .org , delete_after_migration = True
718826 )
@@ -743,14 +851,31 @@ def test_migrate_legacy_course_roles_to_authz_using_org_id(self):
743851 self .assertEqual (len (permissions_with_errors ), 0 )
744852 self .assertEqual (len (permissions_with_no_errors ), 12 )
745853
854+ state_after_migration_user_subjects = list (
855+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
856+ .distinct ()
857+ .order_by ("id" )
858+ .values ("id" , "user_id" )
859+ )
746860 after_migrate_state_access_roles = list (
747861 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
748862 )
749863
864+ # Before the rollback, we should only have the 1 invalid role entry
865+ # since we set delete_after_migration to True in the migration.
866+ self .assertEqual (len (original_state_access_roles ), 1 )
867+
750868 # All original entries + 3 users * 4 roles = 12
751869 # plus the original invalid entry = 1 + 12 = 13 total entries
752870 self .assertEqual (len (after_migrate_state_access_roles ), 1 + 12 )
753871
872+ # Sanity check to ensure we have the expected number of UserSubjects related to
873+ # the course permissions before migration (3 users * 4 roles = 12)
874+ self .assertEqual (len (original_state_user_subjects ), 12 )
875+
876+ # After rollback, we should have 0 UserSubjects related to the course permissions
877+ self .assertEqual (len (state_after_migration_user_subjects ), 0 )
878+
754879 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
755880 def test_migrate_authz_to_legacy_course_roles_with_no_org_and_courses (self ):
756881 # Migrate from legacy CourseAccessRole to new Casbin-based model
0 commit comments