From 9a4d4ece7a7a093248266d07bc1c678bd2fbf296 Mon Sep 17 00:00:00 2001 From: Rafael Marques Date: Sun, 14 Jul 2024 14:51:45 -0300 Subject: [PATCH] tests: adding coverage --- pydantic_settings_aws/aws.py | 6 +-- tests/aws_mocks.py | 3 ++ tests/aws_test.py | 57 ++++++++++++++++++++++++ tests/boto3_mocks.py | 24 +++++----- tests/settings_mocks.py | 4 +- tests/{main_test.py => settings_test.py} | 0 6 files changed, 78 insertions(+), 16 deletions(-) rename tests/{main_test.py => settings_test.py} (100%) diff --git a/pydantic_settings_aws/aws.py b/pydantic_settings_aws/aws.py index 5751e4b..b8d84e5 100644 --- a/pydantic_settings_aws/aws.py +++ b/pydantic_settings_aws/aws.py @@ -114,9 +114,9 @@ def _get_secrets_content(secret: dict[str, Any]) -> Optional[str]: if secret_binary: try: secrets_content = secret_binary.decode("utf-8") - except ValueError as val_err: - logger.error(f"Error decoding secrets content: {val_err}") + except (AttributeError, ValueError) as err: + logger.error(f"Error decoding secrets content: {err}") - raise val_err + raise err return secrets_content diff --git a/tests/aws_mocks.py b/tests/aws_mocks.py index 9924584..0cc7cdf 100644 --- a/tests/aws_mocks.py +++ b/tests/aws_mocks.py @@ -11,6 +11,9 @@ TARGET_SECRET_CONTENT = "pydantic_settings_aws.aws._get_secrets_content" +def mock_secrets_content_invalid_json(*args): + return ClientMock(secret_string="invalid-json") + def mock_secrets_content_empty(*args): return ClientMock(secret_string=None) diff --git a/tests/aws_test.py b/tests/aws_test.py index 1e9a9f5..c0c1aba 100644 --- a/tests/aws_test.py +++ b/tests/aws_test.py @@ -1,7 +1,9 @@ +import json from unittest import mock import pytest +from pydantic import ValidationError from pydantic_settings_aws import aws from .aws_mocks import ( @@ -12,6 +14,7 @@ BaseSettingsMock, mock_create_client, mock_secrets_content_empty, + mock_secrets_content_invalid_json, ) from .boto3_mocks import SessionMock @@ -48,3 +51,57 @@ def test_get_secrets_content_must_raise_value_error_if_secrets_content_is_none( with pytest.raises(ValueError): aws.get_secrets_content(settings) + + +@mock.patch(TARGET_BOTO3_CLIENT, mock_secrets_content_invalid_json) +def test_should_not_obfuscate_json_error_in_case_of_invalid_secrets(*args): + settings = BaseSettingsMock() + settings.model_config = { + "secrets_name": "secrets/name", + "aws_region": "region", + "aws_profile": "profile", + } + + with pytest.raises(json.decoder.JSONDecodeError): + aws.get_secrets_content(settings) + + +def test_get_secrets_content_must_get_binary_content_if_string_is_not_set(*args): + content = { + "SecretBinary": json.dumps({"username": "admin"}).encode("utf-8") + } + secret_content = aws._get_secrets_content(content) + + assert isinstance(secret_content, str) + + +def test_get_secrets_content_must_not_hide_decode_error_if_not_binary_in_secret_binary(*args): + content = { + "SecretBinary": json.dumps({"username": "admin"}) + } + + with pytest.raises(AttributeError): + aws._get_secrets_content(content) + + +def test_get_secrets_content_must_return_none_if_neither_string_nor_binary_are_present(*args): + secret_content = aws._get_secrets_content({}) + + assert secret_content is None + + +def test_get_secrets_content_must_return_none_if_binary_is_present_but_none(*args): + content = { + "SecretBinary": None + } + secret_content = aws._get_secrets_content(content) + + assert secret_content is None + + +def test_get_secrets_args_must_not_shadow_pydantic_validation_if_required_args_are_not_present(*args): + settings = BaseSettingsMock() + settings.model_config = {} + + with pytest.raises(ValidationError): + aws._get_secrets_args(settings) diff --git a/tests/boto3_mocks.py b/tests/boto3_mocks.py index 98807e1..7612a71 100644 --- a/tests/boto3_mocks.py +++ b/tests/boto3_mocks.py @@ -2,7 +2,6 @@ class SessionMock: - def __init__(self, *args, **kwargs) -> None: pass @@ -10,25 +9,26 @@ def client(self, name: str): return self - class ClientMock: - - def __init__(self, secret_string: str = None, raise_client_err: bool = False) -> None: + def __init__( + self, + secret_string: str = None, + secret_bytes: bytes = None + ) -> None: self.secret_string = secret_string - self.raise_client_err = raise_client_err - - def get_secret_value(self, SecretId=None, VersionId=None, VersionStage=None): - if self.raise_client_err: - raise + self.secret_bytes = secret_bytes + def get_secret_value( + self, SecretId=None, VersionId=None, VersionStage=None + ): return { "ARN": "string", "Name": "string", "VersionId": "string", - "SecretBinary": b"bytes", - "SecretString": str(self.secret_string), + "SecretBinary": self.secret_bytes, + "SecretString": self.secret_string, "VersionStages": [ "string", ], - "CreatedDate": datetime.datetime.utcnow() + "CreatedDate": datetime.datetime.utcnow(), } diff --git a/tests/settings_mocks.py b/tests/settings_mocks.py index f67ce27..4ac7862 100644 --- a/tests/settings_mocks.py +++ b/tests/settings_mocks.py @@ -1,4 +1,5 @@ import json +from typing import Optional from pydantic import BaseModel from pydantic_settings import SettingsConfigDict @@ -8,7 +9,7 @@ from .boto3_mocks import ClientMock secrets_with_username_and_password = json.dumps( - {"username": "myusername", "password": "password1234"} + {"username": "myusername", "password": "password1234", "name": None} ) mock_secrets_with_username_and_pwd = ClientMock( @@ -24,6 +25,7 @@ class MySecretsWithClientConfig(SecretsManagerBaseSettings): username: str password: str + name: Optional[str] = None secrets_with_nested_content = json.dumps( diff --git a/tests/main_test.py b/tests/settings_test.py similarity index 100% rename from tests/main_test.py rename to tests/settings_test.py