@@ -668,6 +668,79 @@ def test_validate_gcs_path(self):
668668 with pytest .raises (ValueError , match = err_msg ):
669669 gcs_utils .validate_gcs_path (test_invalid_path )
670670
671+ @patch .object (
672+ gcs_utils .resource_manager_utils ,
673+ "get_project_number" ,
674+ return_value = 12345 ,
675+ )
676+ @patch .object (storage .Bucket , "reload" )
677+ def test_verify_bucket_ownership_matching_project (
678+ self , mock_reload , mock_get_project_number
679+ ):
680+ mock_client = mock .MagicMock (spec = storage .Client )
681+ mock_bucket = mock .MagicMock (spec = storage .Bucket )
682+ mock_bucket .project_number = 12345
683+ assert gcs_utils ._verify_bucket_ownership (
684+ mock_bucket , "test-project" , mock_client
685+ )
686+
687+ @patch .object (
688+ gcs_utils .resource_manager_utils ,
689+ "get_project_number" ,
690+ return_value = 12345 ,
691+ )
692+ @patch .object (storage .Bucket , "reload" )
693+ def test_verify_bucket_ownership_different_project (
694+ self , mock_reload , mock_get_project_number
695+ ):
696+ mock_client = mock .MagicMock (spec = storage .Client )
697+ mock_bucket = mock .MagicMock (spec = storage .Bucket )
698+ mock_bucket .project_number = 99999
699+ assert not gcs_utils ._verify_bucket_ownership (
700+ mock_bucket , "test-project" , mock_client
701+ )
702+
703+ @patch .object (storage .Bucket , "exists" , return_value = True )
704+ @patch .object (storage , "Client" )
705+ @patch .object (gcs_utils , "_verify_bucket_ownership" , return_value = False )
706+ def test_stage_local_data_in_gcs_rejects_squatted_bucket (
707+ self , mock_verify , mock_storage_client , mock_bucket_exists , json_file
708+ ):
709+ mock_config = mock .MagicMock ()
710+ mock_config .project = "victim-project"
711+ mock_config .location = "us-central1"
712+ mock_config .staging_bucket = None
713+ mock_config .credentials = None
714+ with patch .object (gcs_utils .initializer , "global_config" , mock_config ):
715+ with pytest .raises (
716+ ValueError ,
717+ match = "bucket squatting" ,
718+ ):
719+ gcs_utils .stage_local_data_in_gcs (json_file )
720+
721+ @patch .object (storage .Bucket , "exists" , return_value = True )
722+ @patch .object (storage , "Client" )
723+ @patch .object (gcs_utils , "_verify_bucket_ownership" , return_value = True )
724+ @patch ("google.cloud.storage.Blob.upload_from_filename" )
725+ def test_stage_local_data_in_gcs_accepts_owned_bucket (
726+ self ,
727+ mock_upload ,
728+ mock_verify ,
729+ mock_storage_client ,
730+ mock_bucket_exists ,
731+ json_file ,
732+ mock_datetime ,
733+ ):
734+ mock_config = mock .MagicMock ()
735+ mock_config .project = "my-project"
736+ mock_config .location = "us-central1"
737+ mock_config .staging_bucket = None
738+ mock_config .credentials = None
739+ with patch .object (gcs_utils .initializer , "global_config" , mock_config ):
740+ result = gcs_utils .stage_local_data_in_gcs (json_file )
741+ assert result .startswith ("gs://" )
742+ mock_verify .assert_called_once ()
743+
671744
672745class TestPipelineUtils :
673746 SAMPLE_JOB_SPEC = {
0 commit comments