Skip to content

Fix running tests when google-cloud-secret-manager is not installed #602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 21, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 127 additions & 125 deletions tests/test_source_gcp_secret_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@

@pytest.fixture
def mock_secret_client(mocker: MockerFixture):
if not gcp_secret_manager:
pytest.skip('pydantic-settings[gcp-secret-manager] is not installed')

client = mocker.Mock(spec=SecretManagerServiceClient)

# Mock common_project_path
Expand Down Expand Up @@ -79,125 +76,130 @@ def mock_google_auth(mocker: MockerFixture):
)


def test_secret_manager_mapping_init(secret_manager_mapping):
assert secret_manager_mapping._project_id == 'test-project'
assert len(secret_manager_mapping._loaded_secrets) == 0


def test_secret_manager_mapping_gcp_project_path(secret_manager_mapping, mock_secret_client):
secret_manager_mapping._gcp_project_path
mock_secret_client.common_project_path.assert_called_once_with('test-project')


def test_secret_manager_mapping_secret_names(secret_manager_mapping):
names = secret_manager_mapping._secret_names
assert names == ['test-secret']


def test_secret_manager_mapping_getitem_success(secret_manager_mapping):
value = secret_manager_mapping['test-secret']
assert value == 'test-value'


def test_secret_manager_mapping_getitem_nonexistent_key(secret_manager_mapping):
with pytest.raises(KeyError):
_ = secret_manager_mapping['nonexistent-secret']


def test_secret_manager_mapping_getitem_access_error(secret_manager_mapping, mocker):
secret_manager_mapping._secret_client.access_secret_version = mocker.Mock(side_effect=Exception('Access denied'))

with pytest.raises(KeyError):
_ = secret_manager_mapping['test-secret']


def test_secret_manager_mapping_iter(secret_manager_mapping):
assert list(secret_manager_mapping) == ['test-secret']


def test_settings_source_init_with_defaults(mock_google_auth, test_settings):
source = GoogleSecretManagerSettingsSource(test_settings)
assert source._project_id == 'default-project'


def test_settings_source_init_with_custom_values(mocker, test_settings):
credentials = mocker.Mock()
source = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials, project_id='custom-project')
assert source._project_id == 'custom-project'
assert source._credentials == credentials


def test_settings_source_init_with_custom_values_no_project_raises_error(mocker, test_settings):
credentials = mocker.Mock()
mocker.patch('pydantic_settings.sources.providers.gcp.google_auth_default', return_value=(mocker.Mock(), None))

with pytest.raises(AttributeError):
_ = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials)


def test_settings_source_load_env_vars(mock_secret_client, mocker, test_settings):
credentials = mocker.Mock()
source = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials, project_id='test-project')
source._secret_client = mock_secret_client

env_vars = source._load_env_vars()
assert isinstance(env_vars, GoogleSecretManagerMapping)
assert env_vars.get('test-secret') == 'test-value'
assert env_vars.get('another_secret') is None


def test_settings_source_repr(test_settings):
source = GoogleSecretManagerSettingsSource(test_settings, project_id='test-project')
assert 'test-project' in repr(source)
assert 'GoogleSecretManagerSettingsSource' in repr(source)


def test_pydantic_base_settings(mock_secret_client, monkeypatch, mocker):
monkeypatch.setenv('ANOTHER_SECRET', 'yep_this_one')

class Settings(BaseSettings, case_sensitive=False):
test_secret: str = Field(..., alias='test-secret')
another_secret: str = Field(..., alias='ANOTHER_SECRET')

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
google_secret_manager_settings = GoogleSecretManagerSettingsSource(
settings_cls, secret_client=mock_secret_client
)
return (init_settings, env_settings, dotenv_settings, file_secret_settings, google_secret_manager_settings)

settings = Settings() # type: ignore
assert settings.another_secret == 'yep_this_one'
assert settings.test_secret == 'test-value'


def test_pydantic_base_settings_with_unknown_attribute(mock_secret_client, monkeypatch, mocker):
from pydantic_core._pydantic_core import ValidationError

class Settings(BaseSettings, case_sensitive=False):
test_secret: str = Field(..., alias='test-secret')
another_secret: str = Field(..., alias='ANOTHER_SECRET')

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
google_secret_manager_settings = GoogleSecretManagerSettingsSource(
settings_cls, secret_client=mock_secret_client
)
return (init_settings, env_settings, dotenv_settings, file_secret_settings, google_secret_manager_settings)

with pytest.raises(ValidationError):
_ = Settings()
@pytest.mark.skipif(not gcp_secret_manager, reason='pydantic-settings[gcp-secret-manager] is not installed')
class TestGoogleSecretManagerSettingsSource:
"""Test GoogleSecretManagerSettingsSource."""

def test_secret_manager_mapping_init(self, secret_manager_mapping):
assert secret_manager_mapping._project_id == 'test-project'
assert len(secret_manager_mapping._loaded_secrets) == 0

def test_secret_manager_mapping_gcp_project_path(self, secret_manager_mapping, mock_secret_client):
secret_manager_mapping._gcp_project_path
mock_secret_client.common_project_path.assert_called_once_with('test-project')

def test_secret_manager_mapping_secret_names(self, secret_manager_mapping):
names = secret_manager_mapping._secret_names
assert names == ['test-secret']

def test_secret_manager_mapping_getitem_success(self, secret_manager_mapping):
value = secret_manager_mapping['test-secret']
assert value == 'test-value'

def test_secret_manager_mapping_getitem_nonexistent_key(self, secret_manager_mapping):
with pytest.raises(KeyError):
_ = secret_manager_mapping['nonexistent-secret']

def test_secret_manager_mapping_getitem_access_error(self, secret_manager_mapping, mocker):
secret_manager_mapping._secret_client.access_secret_version = mocker.Mock(
side_effect=Exception('Access denied')
)

with pytest.raises(KeyError):
_ = secret_manager_mapping['test-secret']

def test_secret_manager_mapping_iter(self, secret_manager_mapping):
assert list(secret_manager_mapping) == ['test-secret']

def test_settings_source_init_with_defaults(self, mock_google_auth, test_settings):
source = GoogleSecretManagerSettingsSource(test_settings)
assert source._project_id == 'default-project'

def test_settings_source_init_with_custom_values(self, mocker, test_settings):
credentials = mocker.Mock()
source = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials, project_id='custom-project')
assert source._project_id == 'custom-project'
assert source._credentials == credentials

def test_settings_source_init_with_custom_values_no_project_raises_error(self, mocker, test_settings):
credentials = mocker.Mock()
mocker.patch('pydantic_settings.sources.providers.gcp.google_auth_default', return_value=(mocker.Mock(), None))

with pytest.raises(AttributeError):
_ = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials)

def test_settings_source_load_env_vars(self, mock_secret_client, mocker, test_settings):
credentials = mocker.Mock()
source = GoogleSecretManagerSettingsSource(test_settings, credentials=credentials, project_id='test-project')
source._secret_client = mock_secret_client

env_vars = source._load_env_vars()
assert isinstance(env_vars, GoogleSecretManagerMapping)
assert env_vars.get('test-secret') == 'test-value'
assert env_vars.get('another_secret') is None

def test_settings_source_repr(self, test_settings):
source = GoogleSecretManagerSettingsSource(test_settings, project_id='test-project')
assert 'test-project' in repr(source)
assert 'GoogleSecretManagerSettingsSource' in repr(source)

def test_pydantic_base_settings(self, mock_secret_client, monkeypatch, mocker):
monkeypatch.setenv('ANOTHER_SECRET', 'yep_this_one')

class Settings(BaseSettings, case_sensitive=False):
test_secret: str = Field(..., alias='test-secret')
another_secret: str = Field(..., alias='ANOTHER_SECRET')

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
google_secret_manager_settings = GoogleSecretManagerSettingsSource(
settings_cls, secret_client=mock_secret_client
)
return (
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
google_secret_manager_settings,
)

settings = Settings() # type: ignore
assert settings.another_secret == 'yep_this_one'
assert settings.test_secret == 'test-value'

def test_pydantic_base_settings_with_unknown_attribute(self, mock_secret_client, monkeypatch, mocker):
from pydantic_core._pydantic_core import ValidationError

class Settings(BaseSettings, case_sensitive=False):
test_secret: str = Field(..., alias='test-secret')
another_secret: str = Field(..., alias='ANOTHER_SECRET')

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
google_secret_manager_settings = GoogleSecretManagerSettingsSource(
settings_cls, secret_client=mock_secret_client
)
return (
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
google_secret_manager_settings,
)

with pytest.raises(ValidationError):
_ = Settings()