@@ -565,7 +565,7 @@ def test_anon_drop_requires_confirmation_for_dangerous_ops():
565565
566566@pytest .mark .django_db
567567@patch ("builtins.input" , return_value = "yes" )
568- def test_anon_drop_interactive_confirmation_accepted (mock_input ):
568+ def test_anon_drop_interactive_confirmation_accepted (_mock_input ):
569569 """Test drop command with interactive confirmation accepted"""
570570 baker .make (MaskingRule , table_name = "auth_user" , column_name = "email" )
571571
@@ -789,5 +789,135 @@ def test_anon_fix_permissions_command():
789789 output = str (mock_stdout .write .call_args_list )
790790 assert "Failed" in output or "failed" in output .lower ()
791791
792+ # Test with database error in permission fix
793+ from django .db import DatabaseError
794+
795+ with patch ("django_postgres_anon.management.commands.anon_fix_permissions.create_masked_role" ) as mock_create :
796+ mock_create .side_effect = DatabaseError ("permission denied" )
797+ with patch ("sys.stdout" , new_callable = MagicMock ) as mock_stdout :
798+ call_command ("anon_fix_permissions" , "--role" , "error_role" )
799+ output = str (mock_stdout .write .call_args_list )
800+ assert "Error fixing permissions" in output or "permission denied" in output
801+
792802 # Clean up
793803 MaskedRole .objects .all ().delete ()
804+
805+
806+ @pytest .mark .django_db
807+ def test_create_masked_role_permission_failures ():
808+ """Test permission failure scenarios in create_masked_role to improve coverage"""
809+ from unittest .mock import MagicMock , patch
810+
811+ from django .db import DatabaseError , OperationalError
812+
813+ from django_postgres_anon .utils import create_masked_role
814+
815+ # Test write permission failure
816+ with patch ("django.db.connection.cursor" ) as mock_cursor_ctx :
817+ mock_cursor = MagicMock ()
818+ mock_cursor_ctx .return_value .__enter__ .return_value = mock_cursor
819+
820+ # Mock table exists check to return True, but write permissions fail
821+ def mock_execute_side_effect (sql , params = None ):
822+ if "INSERT, UPDATE ON TABLE" in sql :
823+ raise DatabaseError ("permission denied for table test_table" )
824+ elif "SELECT table_name FROM information_schema.tables" in sql :
825+ return None # Query for tables
826+ # Let other queries pass through (CREATE ROLE, etc.)
827+
828+ mock_cursor .execute .side_effect = mock_execute_side_effect
829+ mock_cursor .fetchall .return_value = [("auth_user" ,), ("django_content_type" ,)] # Mock tables
830+
831+ result = create_masked_role ("test_role" )
832+ assert result is True # Should still succeed despite write permission failure
833+
834+ # Test table permission failure
835+ with patch ("django.db.connection.cursor" ) as mock_cursor_ctx :
836+ mock_cursor = MagicMock ()
837+ mock_cursor_ctx .return_value .__enter__ .return_value = mock_cursor
838+
839+ def mock_execute_side_effect (sql , params = None ):
840+ if "GRANT SELECT ON" in sql and "TO" in sql :
841+ raise OperationalError ("permission denied for table test_table" )
842+
843+ mock_cursor .execute .side_effect = mock_execute_side_effect
844+ mock_cursor .fetchall .return_value = [("auth_user" ,), ("django_content_type" ,)]
845+
846+ result = create_masked_role ("test_role" )
847+ assert result is True # Should still succeed despite table permission failure
848+
849+ # Test database CONNECT permission failure
850+ with patch ("django.db.connection.cursor" ) as mock_cursor_ctx :
851+ mock_cursor = MagicMock ()
852+ mock_cursor_ctx .return_value .__enter__ .return_value = mock_cursor
853+
854+ def mock_execute_side_effect (sql , params = None ):
855+ if "GRANT CONNECT ON DATABASE" in sql :
856+ raise DatabaseError ("permission denied for database" )
857+
858+ mock_cursor .execute .side_effect = mock_execute_side_effect
859+ mock_cursor .fetchall .return_value = [] # No tables
860+
861+ result = create_masked_role ("test_role" )
862+ assert result is True # Should still succeed despite CONNECT failure
863+
864+ # Test schema USAGE permission failure
865+ with patch ("django.db.connection.cursor" ) as mock_cursor_ctx :
866+ mock_cursor = MagicMock ()
867+ mock_cursor_ctx .return_value .__enter__ .return_value = mock_cursor
868+
869+ def mock_execute_side_effect (sql , params = None ):
870+ if "GRANT USAGE ON SCHEMA" in sql :
871+ raise OperationalError ("permission denied for schema public" )
872+
873+ mock_cursor .execute .side_effect = mock_execute_side_effect
874+ mock_cursor .fetchall .return_value = [] # No tables
875+
876+ result = create_masked_role ("test_role" )
877+ assert result is True # Should still succeed despite schema failure
878+
879+
880+ @pytest .mark .django_db
881+ def test_database_role_permission_failures ():
882+ """Test database role switching permission failures"""
883+ from unittest .mock import MagicMock , patch
884+
885+ from django .db import OperationalError
886+
887+ from django_postgres_anon .context_managers import database_role
888+
889+ # Test role switching failure - database_role doesn't have auto_create
890+ with patch ("django_postgres_anon.utils.switch_to_role" ) as mock_switch :
891+ mock_switch .return_value = False # Role switch fails
892+
893+ try :
894+ with database_role ("nonexistent_role" ):
895+ # Should handle the role switch failure gracefully
896+ pass
897+ except RuntimeError as e :
898+ assert "does not exist" in str (e )
899+
900+ # Test role switching permission failure in switch_to_role itself
901+ with patch ("django.db.connection.cursor" ) as mock_cursor_ctx :
902+ mock_cursor = MagicMock ()
903+ mock_cursor_ctx .return_value .__enter__ .return_value = mock_cursor
904+
905+ def mock_execute_side_effect (sql , params = None ):
906+ if "SET ROLE" in sql :
907+ raise OperationalError ("permission denied to set role" )
908+
909+ mock_cursor .execute .side_effect = mock_execute_side_effect
910+
911+ from django_postgres_anon .utils import switch_to_role
912+
913+ # Test with auto_create=True
914+ with patch ("django_postgres_anon.utils.create_masked_role" ) as mock_create :
915+ mock_create .return_value = True
916+
917+ result = switch_to_role ("test_role" , auto_create = True )
918+ mock_create .assert_called_once_with ("test_role" )
919+ assert result is True
920+
921+ # Test with auto_create=False
922+ result = switch_to_role ("test_role" , auto_create = False )
923+ assert result is False
0 commit comments