@@ -65,6 +65,27 @@ def test_parse_certificate(self, mock_load_cert):
6565 mock_load_cert .assert_called_once_with (b"cert_bytes" )
6666 assert result == mock_load_cert .return_value
6767
68+ @mock .patch ("google.auth._agent_identity_utils.os.stat" )
69+ def test_is_certificate_file_ready_permission_error (self , mock_stat ):
70+ mock_stat .side_effect = PermissionError ("Permission denied" )
71+ with pytest .raises (PermissionError ):
72+ _agent_identity_utils ._is_certificate_file_ready ("/path/to/cert" )
73+
74+ @mock .patch ("google.auth._agent_identity_utils.os.stat" )
75+ def test_is_certificate_file_ready_os_error (self , mock_stat ):
76+ mock_stat .side_effect = OSError ("Not found" )
77+ # Should swallow the OSError and return False
78+ result = _agent_identity_utils ._is_certificate_file_ready ("/path/to/cert" )
79+ assert result is False
80+
81+ @mock .patch ("google.auth._agent_identity_utils.os.stat" )
82+ def test_is_certificate_file_ready_not_a_file (self , mock_stat ):
83+ import stat
84+
85+ mock_stat .return_value = mock .MagicMock (st_mode = stat .S_IFDIR , st_size = 4096 )
86+ result = _agent_identity_utils ._is_certificate_file_ready ("/path/to/cert" )
87+ assert result is False
88+
6889 def test__is_agent_identity_certificate_invalid (self ):
6990 cert = _agent_identity_utils .parse_certificate (NON_AGENT_IDENTITY_CERT_BYTES )
7091 assert not _agent_identity_utils ._is_agent_identity_certificate (cert )
@@ -268,7 +289,7 @@ def test_get_agent_identity_certificate_path_workstation_fail_fast(
268289 assert result is None
269290
270291 @mock .patch ("time.sleep" )
271- @mock .patch ("os.path.exists" )
292+ @mock .patch ("google.auth._agent_identity_utils. os.path.exists" )
272293 def test_get_agent_identity_certificate_path_cert_not_found (
273294 self , mock_exists , mock_sleep , tmpdir , monkeypatch
274295 ):
@@ -358,7 +379,7 @@ def test_get_agent_identity_certificate_path_workload_config_missing_cert_path(
358379 mock_sleep .assert_not_called ()
359380
360381 @mock .patch ("time.sleep" )
361- @mock .patch ("os.path.exists" )
382+ @mock .patch ("google.auth._agent_identity_utils. os.path.exists" )
362383 @mock .patch ("google.auth._agent_identity_utils._is_certificate_file_ready" )
363384 def test_get_agent_identity_certificate_path_no_config_but_has_well_known_dir (
364385 self , mock_is_ready , mock_exists , mock_sleep , monkeypatch
@@ -378,7 +399,7 @@ def test_get_agent_identity_certificate_path_no_config_but_has_well_known_dir(
378399 mock_sleep .assert_not_called ()
379400
380401 @mock .patch ("time.sleep" )
381- @mock .patch ("os.path.exists" )
402+ @mock .patch ("google.auth._agent_identity_utils. os.path.exists" )
382403 def test_get_agent_identity_certificate_path_no_config_no_well_known_dir (
383404 self , mock_exists , mock_sleep , monkeypatch
384405 ):
@@ -396,7 +417,7 @@ def test_get_agent_identity_certificate_path_no_config_no_well_known_dir(
396417 mock_sleep .assert_not_called ()
397418
398419 @mock .patch ("time.sleep" )
399- @mock .patch ("os.path.exists" )
420+ @mock .patch ("google.auth._agent_identity_utils. os.path.exists" )
400421 @mock .patch ("google.auth._agent_identity_utils._is_certificate_file_ready" )
401422 def test_get_agent_identity_certificate_path_no_config_well_known_polling_success (
402423 self , mock_is_ready , mock_exists , mock_sleep , monkeypatch
@@ -415,7 +436,7 @@ def test_get_agent_identity_certificate_path_no_config_well_known_polling_succes
415436 assert mock_sleep .call_count == 1
416437
417438 @mock .patch ("time.sleep" )
418- @mock .patch ("os.path.exists" )
439+ @mock .patch ("google.auth._agent_identity_utils. os.path.exists" )
419440 @mock .patch ("google.auth._agent_identity_utils._is_certificate_file_ready" )
420441 def test_get_agent_identity_certificate_path_no_config_well_known_polling_timeout (
421442 self , mock_is_ready , mock_exists , mock_sleep , monkeypatch
@@ -433,6 +454,45 @@ def test_get_agent_identity_certificate_path_no_config_well_known_polling_timeou
433454
434455 assert mock_sleep .call_count == len (_agent_identity_utils ._POLLING_INTERVALS )
435456
457+ @mock .patch ("time.sleep" )
458+ @mock .patch ("google.auth._agent_identity_utils._is_certificate_file_ready" )
459+ @mock .patch ("google.auth._agent_identity_utils.os.path.exists" )
460+ def test_get_agent_identity_certificate_path_permission_error_well_known (
461+ self , mock_exists , mock_is_ready , mock_sleep , monkeypatch
462+ ):
463+ monkeypatch .delenv (
464+ environment_vars .GOOGLE_API_CERTIFICATE_CONFIG , raising = False
465+ )
466+ mock_exists .return_value = True
467+ mock_is_ready .side_effect = PermissionError ("Permission denied" )
468+
469+ # It should fail-fast and return None immediately
470+ result = _agent_identity_utils .get_agent_identity_certificate_path ()
471+ assert result is None
472+ mock_sleep .assert_not_called ()
473+
474+ @mock .patch ("time.sleep" )
475+ @mock .patch ("google.auth._agent_identity_utils.os.path.exists" )
476+ def test_get_agent_identity_certificate_path_permission_error_config (
477+ self , mock_exists , mock_sleep , tmpdir , monkeypatch
478+ ):
479+ config_path = tmpdir .join ("config.json" )
480+ monkeypatch .setenv (
481+ environment_vars .GOOGLE_API_CERTIFICATE_CONFIG , str (config_path )
482+ )
483+ # Mock os.path.exists so ECP workstation fail-fast is not triggered
484+ mock_exists .return_value = True
485+
486+ # Mocking open to raise PermissionError
487+ mock_open = mock .mock_open ()
488+ mock_open .side_effect = PermissionError ("Permission denied" )
489+
490+ with mock .patch ("builtins.open" , mock_open ):
491+ result = _agent_identity_utils .get_agent_identity_certificate_path ()
492+
493+ assert result is None
494+ mock_sleep .assert_not_called ()
495+
436496 @mock .patch ("google.auth._agent_identity_utils.get_agent_identity_certificate_path" )
437497 def test_get_and_parse_agent_identity_certificate_opted_out (
438498 self , mock_get_path , monkeypatch
@@ -501,6 +561,23 @@ def test_get_and_parse_agent_identity_certificate_use_client_cert_invalid(
501561 assert result is None
502562 mock_get_path .assert_not_called ()
503563
564+ @mock .patch ("google.auth._agent_identity_utils.get_agent_identity_certificate_path" )
565+ def test_get_and_parse_agent_identity_certificate_file_read_error (
566+ self , mock_get_path , monkeypatch
567+ ):
568+ monkeypatch .setenv (
569+ environment_vars .GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES ,
570+ "true" ,
571+ )
572+ mock_get_path .return_value = "/fake/cert.pem"
573+ mock_open = mock .mock_open ()
574+ mock_open .side_effect = PermissionError ("Permission denied" )
575+
576+ with mock .patch ("builtins.open" , mock_open ):
577+ result = _agent_identity_utils .get_and_parse_agent_identity_certificate ()
578+
579+ assert result is None
580+
504581 def test_get_cached_cert_fingerprint_no_cert (self ):
505582 with pytest .raises (ValueError , match = "mTLS connection is not configured." ):
506583 _agent_identity_utils .get_cached_cert_fingerprint (None )
0 commit comments