diff --git a/aws_lambda_powertools/shared/constants.py b/aws_lambda_powertools/shared/constants.py index 2ec120e4d4a..0cde7582976 100644 --- a/aws_lambda_powertools/shared/constants.py +++ b/aws_lambda_powertools/shared/constants.py @@ -22,6 +22,9 @@ IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED" +PARAMETERS_SSM_DECRYPT_ENV: str = "POWERTOOLS_PARAMETERS_SSM_DECRYPT" +PARAMETERS_MAX_AGE_ENV: str = "POWERTOOLS_PARAMETERS_MAX_AGE" + LOGGER_LAMBDA_CONTEXT_KEYS = [ "function_arn", "function_memory_size", diff --git a/aws_lambda_powertools/shared/functions.py b/aws_lambda_powertools/shared/functions.py index 86ba74d5e78..20a4994e9be 100644 --- a/aws_lambda_powertools/shared/functions.py +++ b/aws_lambda_powertools/shared/functions.py @@ -51,6 +51,11 @@ def resolve_truthy_env_var_choice(env: str, choice: Optional[bool] = None) -> bo return choice if choice is not None else strtobool(env) +def resolve_max_age(env: str, choice: Optional[int]) -> int: + """Resolve max age value""" + return choice if choice is not None else int(env) + + @overload def resolve_env_var_choice(env: Optional[str], choice: float) -> float: ... diff --git a/aws_lambda_powertools/utilities/parameters/appconfig.py b/aws_lambda_powertools/utilities/parameters/appconfig.py index 7884728024e..297762628e1 100644 --- a/aws_lambda_powertools/utilities/parameters/appconfig.py +++ b/aws_lambda_powertools/utilities/parameters/appconfig.py @@ -14,8 +14,12 @@ if TYPE_CHECKING: from mypy_boto3_appconfigdata import AppConfigDataClient -from ...shared import constants -from ...shared.functions import resolve_env_var_choice +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import ( + resolve_env_var_choice, + resolve_max_age, +) + from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider @@ -136,7 +140,7 @@ def get_app_config( application: Optional[str] = None, transform: TransformOptions = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, **sdk_options ) -> Union[str, list, dict, bytes]: """ @@ -154,7 +158,7 @@ def get_app_config( Transforms the content from a JSON object ('json') or base64 binary string ('binary') force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional SDK options to propagate to `start_configuration_session` API call @@ -187,6 +191,8 @@ def get_app_config( >>> print(value) My configuration's JSON value """ + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) # Only create the provider if this function is called at least once if "appconfig" not in DEFAULT_PROVIDERS: diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index 8587d3b5f3f..8ec1052ae37 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -5,6 +5,7 @@ import base64 import json +import os from abc import ABC, abstractmethod from datetime import datetime, timedelta from typing import ( @@ -24,6 +25,8 @@ import boto3 from botocore.config import Config +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import resolve_max_age from aws_lambda_powertools.utilities.parameters.types import TransformOptions from .exceptions import GetParameterError, TransformParameterError @@ -35,7 +38,8 @@ from mypy_boto3_ssm import SSMClient -DEFAULT_MAX_AGE_SECS = 5 +DEFAULT_MAX_AGE_SECS = "5" + # These providers will be dynamically initialized on first use of the helper functions DEFAULT_PROVIDERS: Dict[str, Any] = {} TRANSFORM_METHOD_JSON = "json" @@ -77,7 +81,7 @@ def has_not_expired_in_cache(self, key: Tuple[str, TransformOptions]) -> bool: def get( self, name: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, force_fetch: bool = False, **sdk_options, @@ -121,6 +125,9 @@ def get( value: Optional[Union[str, bytes, dict]] = None key = (name, transform) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value @@ -149,7 +156,7 @@ def _get(self, name: str, **sdk_options) -> Union[str, bytes]: def get_multiple( self, path: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, raise_on_transform_error: bool = False, force_fetch: bool = False, @@ -186,6 +193,9 @@ def get_multiple( """ key = (path, transform) + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + if not force_fetch and self.has_not_expired_in_cache(key): return self.store[key].value # type: ignore # need to revisit entire typing here diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index 4a616fcf9f9..b11cb472012 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -3,6 +3,7 @@ """ +import os from typing import TYPE_CHECKING, Any, Dict, Optional, Union import boto3 @@ -11,6 +12,9 @@ if TYPE_CHECKING: from mypy_boto3_secretsmanager import SecretsManagerClient +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import resolve_max_age + from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider @@ -111,11 +115,7 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]: def get_secret( - name: str, - transform: Optional[str] = None, - force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, - **sdk_options + name: str, transform: Optional[str] = None, force_fetch: bool = False, max_age: Optional[int] = None, **sdk_options ) -> Union[str, dict, bytes]: """ Retrieve a parameter value from AWS Secrets Manager @@ -128,7 +128,7 @@ def get_secret( Transforms the content from a JSON object ('json') or base64 binary string ('binary') force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional Dictionary of options that will be passed to the get_secret_value call @@ -162,6 +162,9 @@ def get_secret( >>> get_secret("my-secret", VersionId="f658cac0-98a5-41d9-b993-8a76a7799194") """ + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + # Only create the provider if this function is called at least once if "secrets" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["secrets"] = SecretsProvider() diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index ae4a76dac4a..26f225cfb28 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -3,13 +3,19 @@ """ from __future__ import annotations +import os from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, overload import boto3 from botocore.config import Config from typing_extensions import Literal -from aws_lambda_powertools.shared.functions import slice_dictionary +from aws_lambda_powertools.shared import constants +from aws_lambda_powertools.shared.functions import ( + resolve_max_age, + resolve_truthy_env_var_choice, + slice_dictionary, +) from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider, transform_value from .exceptions import GetParameterError @@ -110,9 +116,9 @@ def __init__( def get( # type: ignore[override] self, name: str, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, transform: TransformOptions = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, **sdk_options, ) -> Optional[Union[str, dict, bytes]]: @@ -123,7 +129,7 @@ def get( # type: ignore[override] ---------- name: str Parameter name - max_age: int + max_age: int, optional Maximum age of the cached value transform: str Optional transformation of the parameter value. Supported values @@ -145,6 +151,14 @@ def get( # type: ignore[override] When the parameter provider fails to transform a parameter value. """ + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # If decrypt is not set, resolve it from the environment variable, defaulting to False + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt + ) + # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt @@ -212,8 +226,8 @@ def get_parameters_by_name( self, parameters: Dict[str, Dict], transform: TransformOptions = None, - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, str] | Dict[str, bytes] | Dict[str, dict]: """ @@ -247,7 +261,7 @@ def get_parameters_by_name( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_error: bool Whether to fail-fast or fail gracefully by including "_errors" key in the response, by default True @@ -259,6 +273,15 @@ def get_parameters_by_name( When "_errors" reserved key is in parameters to be fetched from SSM. """ + + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # If decrypt is not set, resolve it from the environment variable, defaulting to False + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt + ) + # Init potential batch/decrypt batch responses and errors batch_ret: Dict[str, Any] = {} decrypt_ret: Dict[str, Any] = {} @@ -487,9 +510,9 @@ def _raise_if_errors_key_is_present(parameters: Dict, reserved_parameter: str, r def get_parameter( name: str, transform: Optional[str] = None, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, **sdk_options, ) -> Union[str, dict, bytes]: """ @@ -505,7 +528,7 @@ def get_parameter( If the parameter values should be decrypted force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -543,6 +566,14 @@ def get_parameter( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # If decrypt is not set, resolve it from the environment variable, defaulting to False + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt + ) + # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt @@ -555,9 +586,9 @@ def get_parameters( path: str, transform: Optional[str] = None, recursive: bool = True, - decrypt: bool = False, + decrypt: Optional[bool] = None, force_fetch: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + max_age: Optional[int] = None, raise_on_transform_error: bool = False, **sdk_options, ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: @@ -576,7 +607,7 @@ def get_parameters( If the parameter values should be decrypted force_fetch: bool, optional Force update even before a cached item has expired, defaults to False - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_transform_error: bool, optional Raises an exception if any transform fails, otherwise this will @@ -617,6 +648,14 @@ def get_parameters( if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # If decrypt is not set, resolve it from the environment variable, defaulting to False + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt + ) + sdk_options["recursive"] = recursive sdk_options["decrypt"] = decrypt @@ -634,8 +673,8 @@ def get_parameters( def get_parameters_by_name( parameters: Dict[str, Dict], transform: None = None, - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, str]: ... @@ -645,8 +684,8 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["binary"], - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, bytes]: ... @@ -656,8 +695,8 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["json"], - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Dict[str, Dict[str, Any]]: ... @@ -667,8 +706,8 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Dict], transform: Literal["auto"], - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, dict]]: ... @@ -677,8 +716,8 @@ def get_parameters_by_name( def get_parameters_by_name( parameters: Dict[str, Any], transform: TransformOptions = None, - decrypt: bool = False, - max_age: int = DEFAULT_MAX_AGE_SECS, + decrypt: Optional[bool] = None, + max_age: Optional[int] = None, raise_on_error: bool = True, ) -> Union[Dict[str, str], Dict[str, bytes], Dict[str, dict]]: """ @@ -692,7 +731,7 @@ def get_parameters_by_name( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted - max_age: int + max_age: int, optional Maximum age of the cached value raise_on_error: bool, optional Whether to fail-fast or fail gracefully by including "_errors" key in the response, by default True @@ -732,6 +771,14 @@ def get_parameters_by_name( # NOTE: Decided against using multi-thread due to single-thread outperforming in 128M and 1G + timeout risk # see: https://github.com/awslabs/aws-lambda-powertools-python/issues/1040#issuecomment-1299954613 + # If max_age is not set, resolve it from the environment variable, defaulting to DEFAULT_MAX_AGE_SECS + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=max_age) + + # If decrypt is not set, resolve it from the environment variable, defaulting to False + decrypt = resolve_truthy_env_var_choice( + env=os.getenv(constants.PARAMETERS_SSM_DECRYPT_ENV, "false"), choice=decrypt + ) + # Only create the provider if this function is called at least once if "ssm" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["ssm"] = SSMProvider() diff --git a/docs/index.md b/docs/index.md index 55d44ced65d..074d5e40809 100644 --- a/docs/index.md +++ b/docs/index.md @@ -706,6 +706,8 @@ Core utilities such as Tracing, Logging, Metrics, and Event Handler will be avai | **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` | | **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` | | **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` | +| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](./utilities/parameters/#adjusting-cache-ttl) | `5` | +| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS SSM Parameters Store | [Parameters](./utilities/parameters/#ssmprovider) | `false` | | **POWERTOOLS_DEV** | Increases verbosity across utilities | Multiple; see [POWERTOOLS_DEV effect below](#increasing-verbosity-across-utilities) | `false` | | **LOG_LEVEL** | Sets logging level | [Logging](./core/logger) | `INFO` | diff --git a/docs/utilities/parameters.md b/docs/utilities/parameters.md index e00851fbb8b..3e2fd37c8aa 100644 --- a/docs/utilities/parameters.md +++ b/docs/utilities/parameters.md @@ -103,7 +103,7 @@ The following will retrieve the latest version and store it in the cache. ???+ tip `max_age` parameter is also available in underlying provider functions like `get()`, `get_multiple()`, etc. -By default, we cache parameters retrieved in-memory for 5 seconds. +By default, we cache parameters retrieved in-memory for 5 seconds. If you want to change this default value and set the same TTL for all parameters, you can set the `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable. **You can still set `max_age` for individual parameters**. You can adjust how long we should keep values in cache by using the param `max_age`, when using `get_parameter()`, `get_parameters()` and `get_secret()` methods across all providers. @@ -179,6 +179,9 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen You can create `SecureString` parameters, which are parameters that have a plaintext parameter name and an encrypted parameter value. If you don't use the `decrypt` argument, you will get an encrypted value. Read [here](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) about best practices using KMS to secure your parameters. +???+ tip + If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but you can still set the `decrypt` option for individual parameters**. + === "builtin_provider_ssm_with_decrypt.py" ```python hl_lines="6 10 16" --8<-- "examples/parameters/src/builtin_provider_ssm_with_decrypt.py" diff --git a/tests/functional/test_utilities_parameters.py b/tests/functional/test_utilities_parameters.py index f3d326fcd58..83b6440a50c 100644 --- a/tests/functional/test_utilities_parameters.py +++ b/tests/functional/test_utilities_parameters.py @@ -547,6 +547,44 @@ def test_ssm_provider_get_with_custom_client(mock_name, mock_value, mock_version stubber.deactivate() +def test_ssm_provider_get_with_decrypt_environment_variable(monkeypatch, mock_name, mock_value, mock_version, config): + """ + Test SSMProvider.get() with decrypt value replaced by environment variable + """ + + # Setting environment variable to override the default value + monkeypatch.setenv("POWERTOOLS_PARAMETERS_SSM_DECRYPT", "true") + + # Create a new provider + provider = parameters.SSMProvider(config=config) + + # Stub the boto3 client + stubber = stub.Stubber(provider.client) + response = { + "Parameter": { + "Name": mock_name, + "Type": "String", + "Value": mock_value, + "Version": mock_version, + "Selector": f"{mock_name}:{mock_version}", + "SourceResult": "string", + "LastModifiedDate": datetime(2015, 1, 1), + "ARN": f"arn:aws:ssm:us-east-2:111122223333:parameter/{mock_name}", + } + } + expected_params = {"Name": mock_name, "WithDecryption": True} + stubber.add_response("get_parameter", response, expected_params) + stubber.activate() + + try: + value = provider.get(mock_name) + + assert value == mock_value + stubber.assert_no_pending_responses() + finally: + stubber.deactivate() + + def test_ssm_provider_get_default_config(monkeypatch, mock_name, mock_value, mock_version): """ Test SSMProvider.get() without specifying the config diff --git a/tests/unit/test_shared_functions.py b/tests/unit/test_shared_functions.py index 00abe2c6e08..9232b72527b 100644 --- a/tests/unit/test_shared_functions.py +++ b/tests/unit/test_shared_functions.py @@ -1,3 +1,4 @@ +import os import warnings from dataclasses import dataclass @@ -10,10 +11,12 @@ powertools_debug_is_set, powertools_dev_is_set, resolve_env_var_choice, + resolve_max_age, resolve_truthy_env_var_choice, strtobool, ) from aws_lambda_powertools.utilities.data_classes.common import DictWrapper +from aws_lambda_powertools.utilities.parameters.base import DEFAULT_MAX_AGE_SECS def test_resolve_env_var_choice_explicit_wins_over_env_var(): @@ -103,3 +106,35 @@ class DummyDataclass: @pytest.mark.parametrize("data", [False, True, "", 10, [], {}, object]) def test_extract_event_any(data): assert extract_event_from_common_models(data) == data + + +def test_resolve_max_age_explicit_wins_over_env_var(monkeypatch: pytest.MonkeyPatch): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE_ENV, "20") + + # WHEN the choice is set explicitly + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=10) + + # THEN the result must be the choice + assert max_age == 10 + + +def test_resolve_max_age_with_default_value(): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE is not set + + # WHEN the choice is set to None + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=None) + + # THEN the result must be the default value (DEFAULT_MAX_AGE_SECS) + assert max_age == int(DEFAULT_MAX_AGE_SECS) + + +def test_resolve_max_age_env_var_wins_over_default_value(monkeypatch: pytest.MonkeyPatch): + # GIVEN POWERTOOLS_PARAMETERS_MAX_AGE environment variable is set + monkeypatch.setenv(constants.PARAMETERS_MAX_AGE_ENV, "20") + + # WHEN the choice is set to None + max_age = resolve_max_age(env=os.getenv(constants.PARAMETERS_MAX_AGE_ENV, DEFAULT_MAX_AGE_SECS), choice=None) + + # THEN the result must be the environment variable value + assert max_age == 20