blob: 25230571ac9e47a805d01ed38ba9ef7ebde3cf31 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for gcloud_creds.py."""
# Add our active parent directory to sys.path to enable flat, direct imports of sister modules
import pathlib
import sys
_SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(_SCRIPT_DIR))
import json
import os
import shutil
import subprocess
import tempfile
import unittest
from typing import Any
from unittest import mock
import apt
import gcloud
import gcloud_creds
class IsolateGcloudCredsTestBase(unittest.TestCase):
"""Shared base class for isolating credentials tests.
De-duplicates temporary directories, mock ADC file creation, and CLI runner helpers.
"""
def setUp(self) -> None:
self.temp_dir_obj = tempfile.TemporaryDirectory()
self.temp_dir = pathlib.Path(self.temp_dir_obj.name)
self.out_dir = self.temp_dir / "out"
self.out_dir.mkdir()
def tearDown(self) -> None:
self.temp_dir_obj.cleanup()
def _create_mock_adc(
self,
filename: str = "global_adc.json",
content_dict: dict[str, Any] | None = None,
) -> pathlib.Path:
"""Helper to create a mock ADC file with given JSON contents."""
path = self.temp_dir / filename
content = {
"fake_id": "fake-value",
"type": "authorized_user",
"quota_project_id": "some-other-project",
}
if content_dict:
content.update(content_dict)
path.write_text(json.dumps(content))
return path
def _run_main_cli(
self, args: list[str], adc_path: pathlib.Path | None = None
) -> tuple[int, str, str]:
"""Helper to execute main() as a CLI, capturing exit code, stdout, and stderr."""
import contextlib
import io
environ = {}
if adc_path:
environ["GOOGLE_APPLICATION_CREDENTIALS"] = str(adc_path)
with mock.patch.dict(os.environ, environ):
stdout = io.StringIO()
stderr = io.StringIO()
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(
stderr
):
exit_code = gcloud_creds.main(args)
return exit_code, stdout.getvalue(), stderr.getvalue()
class IsolateCredentialsTest(IsolateGcloudCredsTestBase):
"""Tests the primary credentials isolation function 'isolate_credentials()'."""
def test_isolate_credentials_authorized_user(self) -> None:
# Create a mock standard user default credentials file
adc_src = self._create_mock_adc(
content_dict={"fake_extra_field": "fake-extra-value"}
)
# Run isolation
local_adc = gcloud_creds.isolate_credentials(
out_dir=self.out_dir,
quota_project="fake-project",
source_credentials_path=adc_src,
)
self.assertIsNotNone(local_adc)
assert local_adc is not None
self.assertTrue(local_adc.is_file())
self.assertEqual(
local_adc, self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME
)
# Read the isolated copy back and verify changes
isolated_content = json.loads(local_adc.read_text())
self.assertEqual(isolated_content["fake_id"], "fake-value")
self.assertEqual(
isolated_content["fake_extra_field"], "fake-extra-value"
)
self.assertEqual(isolated_content["type"], "authorized_user")
self.assertEqual(
isolated_content["quota_project_id"],
"fake-project",
)
def test_isolate_credentials_with_custom_quota_project(self) -> None:
adc_src = self._create_mock_adc()
# Run isolation with a custom overridden quota project
local_adc = gcloud_creds.isolate_credentials(
out_dir=self.out_dir,
quota_project="custom-override-project",
source_credentials_path=adc_src,
)
self.assertIsNotNone(local_adc)
assert local_adc is not None
isolated_content = json.loads(local_adc.read_text())
self.assertEqual(isolated_content["fake_id"], "fake-value")
self.assertEqual(isolated_content["type"], "authorized_user")
self.assertEqual(
isolated_content["quota_project_id"], "custom-override-project"
)
def test_isolate_credentials_ignores_service_account(self) -> None:
# Create a mock service account key file
adc_src = self._create_mock_adc(
filename="service_account.json",
content_dict={
"client_email": "mock-service-account@google.com",
"type": "service_account",
},
)
# Run isolation
local_adc = gcloud_creds.isolate_credentials(
out_dir=self.out_dir,
quota_project="fake-project",
source_credentials_path=adc_src,
)
# Should not create any isolated copy or modify anything
self.assertIsNone(local_adc)
self.assertFalse(
(self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME).exists()
)
def test_isolate_credentials_missing_source_raises_gcloud_creds_error(
self,
) -> None:
# Resolve to non-existent file
adc_src = self.temp_dir / "does_not_exist.json"
# Verify that it raises GcloudCredsError
with self.assertRaises(gcloud_creds.GcloudCredsError):
gcloud_creds.isolate_credentials(
out_dir=self.out_dir,
quota_project="fake-project",
source_credentials_path=adc_src,
)
class IsolateCredentialsSafeTest(IsolateGcloudCredsTestBase):
"""Tests the exception-safe version 'isolate_credentials_safe()'. Packs direct exceptions."""
def test_isolate_credentials_safe_success(self) -> None:
adc_src = self._create_mock_adc()
local_adc = gcloud_creds.isolate_credentials_safe(
out_dir=self.out_dir,
quota_project="fake-project",
source_credentials_path=adc_src,
)
self.assertIsNotNone(local_adc)
assert local_adc is not None
self.assertTrue(local_adc.is_file())
self.assertEqual(
local_adc, self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME
)
def test_isolate_credentials_missing_source_safe_returns_none(self) -> None:
# Resolve to non-existent file
adc_src = self.temp_dir / "does_not_exist.json"
local_adc = gcloud_creds.isolate_credentials_safe(
out_dir=self.out_dir,
quota_project="fake-project",
source_credentials_path=adc_src,
)
self.assertIsNone(local_adc)
class MainCliTest(IsolateGcloudCredsTestBase):
"""Tests the CLI entrypoint 'main()' function directly."""
def test_main_cli_no_args_displays_help(self) -> None:
exit_code, stdout, _ = self._run_main_cli([])
self.assertEqual(exit_code, 0)
self.assertIn("usage:", stdout)
self.assertIn("{isolate,login,ensure}", stdout)
def test_main_cli_isolate_subcommand_success(self) -> None:
adc_src = self._create_mock_adc()
exit_code, stdout, _ = self._run_main_cli(
[
"isolate",
"--out-dir",
str(self.out_dir),
"--quota-project",
"cli-project-sub",
],
adc_src,
)
self.assertEqual(exit_code, 0)
local_adc_path = self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME
self.assertEqual(stdout.strip(), str(local_adc_path))
self.assertTrue(local_adc_path.is_file())
isolated_content = json.loads(local_adc_path.read_text())
self.assertEqual(
isolated_content["quota_project_id"], "cli-project-sub"
)
@mock.patch.object(shutil, "which", return_value="/bin/gcloud")
@mock.patch.object(subprocess, "run")
def test_run_gcloud_login_when_gcloud_exists(
self, mock_run: mock.Mock, mock_which: mock.Mock
) -> None:
gcloud_creds.run_gcloud_login()
mock_run.assert_called_once_with(
["/bin/gcloud", "auth", "application-default", "login"],
stdin=mock.ANY,
stdout=mock.ANY,
stderr=mock.ANY,
check=True,
)
@mock.patch.object(gcloud, "path")
@mock.patch.object(apt, "exists", return_value=True)
@mock.patch.object(apt, "install")
def test_run_gcloud_login_missing_apt_install_accepts(
self,
mock_install: mock.Mock,
mock_exists: mock.Mock,
mock_path: mock.Mock,
) -> None:
mock_path.side_effect = [None, pathlib.Path("/usr/bin/gcloud")]
gcloud_creds.ensure_gcloud_installed()
mock_install.assert_called_once_with(
gcloud_creds.GOOGLE_CLOUD_CLI_PACKAGE, interactive=True
)
@mock.patch.object(gcloud, "path", return_value=None)
@mock.patch.object(apt, "exists", return_value=True)
@mock.patch.object(apt, "install")
def test_run_gcloud_login_missing_apt_install_declines(
self,
mock_install: mock.Mock,
mock_exists: mock.Mock,
mock_path: mock.Mock,
) -> None:
mock_install.side_effect = RuntimeError("Installation declined.")
with self.assertRaises(gcloud_creds.GcloudCredsError) as ctx:
gcloud_creds.ensure_gcloud_installed()
self.assertIn("Installation declined.", str(ctx.exception))
@mock.patch.object(gcloud, "path", return_value=None)
@mock.patch.object(apt, "exists", return_value=False)
def test_run_gcloud_login_missing_no_apt_falls_back_to_documentation(
self, mock_exists: mock.Mock, mock_path: mock.Mock
) -> None:
with self.assertRaises(gcloud_creds.GcloudCredsError) as ctx:
gcloud_creds.ensure_gcloud_installed()
self.assertIn(
"https://cloud.google.com/sdk/docs/install", str(ctx.exception)
)
@mock.patch.object(shutil, "which", return_value="/bin/gcloud")
@mock.patch.object(subprocess, "run")
def test_main_cli_login_success(
self, mock_run: mock.Mock, mock_which: mock.Mock
) -> None:
exit_code, _, _ = self._run_main_cli(["login"])
self.assertEqual(exit_code, 0)
mock_run.assert_called_once_with(
["/bin/gcloud", "auth", "application-default", "login"],
stdin=mock.ANY,
stdout=mock.ANY,
stderr=mock.ANY,
check=True,
)
def test_main_cli_ensure_when_already_exists(self) -> None:
adc_src = self._create_mock_adc()
exit_code, stdout, _ = self._run_main_cli(
[
"ensure",
"--out-dir",
str(self.out_dir),
"--quota-project",
"ensure-project",
],
adc_src,
)
self.assertEqual(exit_code, 0)
local_adc_path = self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME
self.assertEqual(stdout.strip(), str(local_adc_path))
@mock.patch.object(gcloud_creds, "run_gcloud_login")
def test_main_cli_ensure_when_missing_with_interactive(
self, mock_login: mock.Mock
) -> None:
# Simulate missing global credentials file during resolve
with mock.patch.object(
gcloud, "resolve_global_adc_path"
) as mock_resolve:
mock_resolve.return_value = self.temp_dir / "missing.json"
# Create the file after login is mock-called
def side_effect() -> None:
self._create_mock_adc(filename="missing.json")
mock_login.side_effect = side_effect
exit_code, stdout, _ = self._run_main_cli(
[
"ensure",
"--out-dir",
str(self.out_dir),
"--quota-project",
"ensure-project-interactive",
"--interactive",
]
)
self.assertEqual(exit_code, 0)
mock_login.assert_called_once()
local_adc_path = self.out_dir / gcloud_creds.ISOLATED_ADC_FILENAME
self.assertEqual(stdout.strip(), str(local_adc_path))
def test_main_cli_ensure_when_missing_non_interactive_returns_one(
self,
) -> None:
with mock.patch.object(
gcloud, "resolve_global_adc_path"
) as mock_resolve:
mock_resolve.return_value = self.temp_dir / "missing.json"
exit_code, _, stderr = self._run_main_cli(
[
"ensure",
"--out-dir",
str(self.out_dir),
"--quota-project",
"ensure-project",
]
)
self.assertEqual(exit_code, 1)
self.assertIn(
"No credentials found and --interactive is not set.", stderr
)
if __name__ == "__main__":
unittest.main()