@@ -440,6 +440,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_no_deletion(self):
440440
441441 # Now let's rollback
442442
443+ # Capture the state of permissions before rollback to verify that rollback restores the original state
444+ original_state_user_subjects = list (
445+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
446+ .distinct ()
447+ .order_by ("id" )
448+ .values ("id" , "user_id" )
449+ )
450+ original_state_access_roles = list (
451+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
452+ )
453+
443454 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
444455 CourseAccessRole , UserSubject , course_id_list = course_id_list , org_id = None , delete_after_migration = False
445456 )
@@ -474,6 +485,34 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_no_deletion(self):
474485 self .assertEqual (len (permissions_with_errors ), 0 )
475486 self .assertEqual (len (permissions_with_no_errors ), 12 ) # 3 users for each of the 4 roles = 12 total entries
476487
488+ state_after_migration_user_subjects = list (
489+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
490+ .distinct ()
491+ .order_by ("id" )
492+ .values ("id" , "user_id" )
493+ )
494+ after_migrate_state_access_roles = list (
495+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
496+ )
497+
498+ # The number of CourseAccessRole entries should be the same as the original state
499+ # since we are not deleting any entries in this test.
500+ self .assertEqual (len (original_state_access_roles ), 13 )
501+
502+ # All original entries should still be there since we are not deleting any entries
503+ # and when creating new entries for the users that were migrated back to legacy roles,
504+ # we are creating them with get_or_create which will not create duplicates if an entry
505+ # with the same user, org, course_id and role already exists.
506+ self .assertEqual (len (after_migrate_state_access_roles ), 13 )
507+
508+ # Sanity check to ensure we have the expected number of UserSubjects related to
509+ # the course permissions before migration (3 users * 4 roles = 12)
510+ self .assertEqual (len (original_state_user_subjects ), 12 )
511+
512+ # After rollback, we should have the same 12 UserSubjects related to the course permissions
513+ # since we are not deleting any entries in this test,
514+ self .assertEqual (len (state_after_migration_user_subjects ), 12 )
515+
477516 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
478517 def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion (self ):
479518 """Test the migration of legacy permissions from CourseAccessRole to
@@ -555,6 +594,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion(self):
555594
556595 # Now let's rollback
557596
597+ # Capture the state of permissions before rollback to verify that rollback restores the original state
598+ original_state_user_subjects = list (
599+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
600+ .distinct ()
601+ .order_by ("id" )
602+ .values ("id" , "user_id" )
603+ )
604+ original_state_access_roles = list (
605+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
606+ )
607+
558608 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
559609 CourseAccessRole , UserSubject , course_id_list = course_id_list , org_id = None , delete_after_migration = True
560610 )
@@ -585,13 +635,30 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion(self):
585635 self .assertEqual (len (permissions_with_errors ), 0 )
586636 self .assertEqual (len (permissions_with_no_errors ), 12 )
587637
638+ state_after_migration_user_subjects = list (
639+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
640+ .distinct ()
641+ .order_by ("id" )
642+ .values ("id" , "user_id" )
643+ )
588644 after_migrate_state_access_roles = list (
589645 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
590646 )
591647
648+ # Before the rollback, we should only have the 1 invalid role entry
649+ # since we set delete_after_migration to True in the migration.
650+ self .assertEqual (len (original_state_access_roles ), 1 )
651+
592652 # All original entries + 3 users * 4 roles = 12
593653 # plus the original invalid entry = 1 + 12 = 13 total entries
594- self .assertEqual (len (after_migrate_state_access_roles ), 13 )
654+ self .assertEqual (len (after_migrate_state_access_roles ), 1 + 12 )
655+
656+ # Sanity check to ensure we have the expected number of UserSubjects related to
657+ # the course permissions before migration (3 users * 4 roles = 12)
658+ self .assertEqual (len (original_state_user_subjects ), 12 )
659+
660+ # After rollback, we should have 0 UserSubjects related to the course permissions
661+ self .assertEqual (len (state_after_migration_user_subjects ), 0 )
595662
596663 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
597664 def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equivalent (self ):
@@ -608,6 +675,17 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
608675
609676 # Now let's rollback
610677
678+ # Capture the state of permissions before rollback to verify that rollback restores the original state
679+ original_state_user_subjects = list (
680+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
681+ .distinct ()
682+ .order_by ("id" )
683+ .values ("id" , "user_id" )
684+ )
685+ original_state_access_roles = list (
686+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
687+ )
688+
611689 # Mock the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping
612690 # for COURSE_ADMIN to simulate the scenario where the staff, limited_staff
613691 # and data_researcher roles do not have a legacy role equivalent and
@@ -652,13 +730,32 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
652730 # 3 staff + 3 limited_staff + 3 data_researcher = 9 entries with no legacy role equivalent
653731 self .assertEqual (len (permissions_with_errors ), 9 )
654732
733+ state_after_migration_user_subjects = list (
734+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
735+ .distinct ()
736+ .order_by ("id" )
737+ .values ("id" , "user_id" )
738+ )
655739 after_migrate_state_access_roles = list (
656740 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
657741 )
658742
743+ # Before the rollback, we should only have the 1 invalid role entry
744+ # since we set delete_after_migration to True in the migration.
745+ self .assertEqual (len (original_state_access_roles ), 1 )
746+
659747 # All original entries (1) + 3 users * 1 roles = 4
660748 self .assertEqual (len (after_migrate_state_access_roles ), 1 + 3 )
661749
750+ # Before the rollback, we should have the 12 UserSubjects related to the course permissions
751+ # since we had 3 users with 4 roles each in the original state.
752+ self .assertEqual (len (original_state_user_subjects ), 12 )
753+
754+ # After rollback, we should have 9 UserSubjects related to the course permissions
755+ # since the users with staff, limited_staff and data_researcher roles will not be
756+ # migrated back to legacy roles due to our mocked COURSE_ROLE_EQUIVALENCES mapping.
757+ self .assertEqual (len (state_after_migration_user_subjects ), 9 )
758+
662759 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
663760 def test_migrate_legacy_course_roles_to_authz_using_org_id (self ):
664761 """Test the migration of legacy course roles to the new Casbin-based model
@@ -712,6 +809,17 @@ def test_migrate_legacy_course_roles_to_authz_using_org_id(self):
712809 # to True and we are deleting all
713810 # Now let's rollback
714811
812+ # Capture the state of permissions before rollback to verify that rollback restores the original state
813+ original_state_user_subjects = list (
814+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
815+ .distinct ()
816+ .order_by ("id" )
817+ .values ("id" , "user_id" )
818+ )
819+ original_state_access_roles = list (
820+ CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
821+ )
822+
715823 permissions_with_errors , permissions_with_no_errors = migrate_authz_to_legacy_course_roles (
716824 CourseAccessRole , UserSubject , course_id_list = None , org_id = self .org , delete_after_migration = True
717825 )
@@ -742,14 +850,31 @@ def test_migrate_legacy_course_roles_to_authz_using_org_id(self):
742850 self .assertEqual (len (permissions_with_errors ), 0 )
743851 self .assertEqual (len (permissions_with_no_errors ), 12 )
744852
853+ state_after_migration_user_subjects = list (
854+ UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
855+ .distinct ()
856+ .order_by ("id" )
857+ .values ("id" , "user_id" )
858+ )
745859 after_migrate_state_access_roles = list (
746860 CourseAccessRole .objects .all ().order_by ("id" ).values ("id" , "user_id" , "org" , "course_id" , "role" )
747861 )
748862
863+ # Before the rollback, we should only have the 1 invalid role entry
864+ # since we set delete_after_migration to True in the migration.
865+ self .assertEqual (len (original_state_access_roles ), 1 )
866+
749867 # All original entries + 3 users * 4 roles = 12
750868 # plus the original invalid entry = 1 + 12 = 13 total entries
751869 self .assertEqual (len (after_migrate_state_access_roles ), 1 + 12 )
752870
871+ # Sanity check to ensure we have the expected number of UserSubjects related to
872+ # the course permissions before migration (3 users * 4 roles = 12)
873+ self .assertEqual (len (original_state_user_subjects ), 12 )
874+
875+ # After rollback, we should have 0 UserSubjects related to the course permissions
876+ self .assertEqual (len (state_after_migration_user_subjects ), 0 )
877+
753878 @patch ("openedx_authz.api.data.CourseOverview" , CourseOverview )
754879 def test_migrate_authz_to_legacy_course_roles_with_no_org_and_courses (self ):
755880 # Migrate from legacy CourseAccessRole to new Casbin-based model
0 commit comments