Skip to content
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ def _get_hashed_payload(self, data: dict[str, Any]) -> str:
"""
if not self.payload_validation_enabled:
return ""
data = self.validation_key_jmespath.search(data)
data = self.validation_key_jmespath.search(
data,
options=jmespath.Options(**(self.jmespath_options or {})),
)
return self._generate_hash(data=data)

def _generate_hash(self, data: Any) -> str:
Expand Down
71 changes: 71 additions & 0 deletions tests/functional/idempotency/_boto3/test_idempotency.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest
from botocore import stub
from botocore.config import Config
from jmespath import functions
from pytest import FixtureRequest
from pytest_mock import MockerFixture

Expand Down Expand Up @@ -1177,6 +1178,28 @@
persistence_store._get_hashed_idempotency_key({})


def test_payload_validation_jmespath_with_custom_jmespath_options(persistence_store: DynamoDBPersistenceLayer):
# GIVEN custom jmespath_options
# AND a payload_validation_jmespath using one of its custom functions
class CustomFunctions(functions.Functions):
@functions.signature({"types": ["string"]})
def _func_to_upper(self, value):
return value.upper()

idempotency_config = IdempotencyConfig(
event_key_jmespath="order_id",
payload_validation_jmespath="to_upper(currency)",
jmespath_options={"custom_functions": CustomFunctions()},
)
persistence_store.configure(idempotency_config)

# WHEN calling _get_hashed_payload
result = persistence_store._get_hashed_payload({"order_id": "1", "currency": "eur"})

# THEN the hashed payload should match the custom function result generated hash
assert result == persistence_store._generate_hash("EUR")


def test_idempotent_lambda_save_inprogress_error(persistence_store: DynamoDBPersistenceLayer, lambda_context):
# GIVEN a miss configured persistence layer
# like no table was created for the idempotency persistence layer
Expand Down Expand Up @@ -1933,6 +1956,54 @@
assert cache_spy.call_count == 0


def test_idempotency_payload_validation_with_powertools_json(
persistence_store: DynamoDBPersistenceLayer,
timestamp_future,
lambda_context,
request: FixtureRequest,
):
# GIVEN an idempotency config where both the idempotency key and the payload validation key
# read a JSON string body with the powertools_json built-in function
idempotency_config = IdempotencyConfig(
event_key_jmespath="powertools_json(body).order_id",
payload_validation_jmespath="powertools_json(body).amount",
use_local_cache=False,
)

# AND a previous order already processed in the persistent store
order = {"order_id": "ffd11882-d476-4598-bbf1-643f2be5addf", "amount": 100}

stubber = stub.Stubber(persistence_store.client)
ddb_response = build_idempotency_put_item_response_stub(
data=order,
expiration=timestamp_future,
status="COMPLETED",
request=request,
validation_data=order["amount"],
)

stubber.add_client_error("put_item", "ConditionalCheckFailedException", modeled_fields=ddb_response)
stubber.add_client_error("put_item", "ConditionalCheckFailedException", modeled_fields=ddb_response)
stubber.activate()

@idempotent(config=idempotency_config, persistence_store=persistence_store)
def lambda_handler(event, context):
return event

# WHEN the same order is sent again
# THEN we should return the stored response
assert lambda_handler({"body": json_serialize(order)}, lambda_context) == order

# WHEN the same order is sent again with a tampered amount
# THEN we should raise
tampered_order = {**order, "amount": 1}
with pytest.raises(IdempotencyValidationError):

Check warning on line 2000 in tests/functional/idempotency/_boto3/test_idempotency.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this exception test to have only one invocation possibly throwing an exception.

See more on https://sonarcloud.io/project/issues?id=aws-powertools_powertools-lambda-python&issues=AaC2RaDebBkFSgCXuwxC&open=AaC2RaDebBkFSgCXuwxC&pullRequest=8474
lambda_handler({"body": json_serialize(tampered_order)}, lambda_context)

stubber.assert_no_pending_responses()
stubber.deactivate()


@pytest.mark.parametrize("idempotency_config", [{"use_local_cache": False}, {"use_local_cache": True}], indirect=True)
def test_responsehook_lambda_first_execution(
idempotency_config: IdempotencyConfig,
Expand Down