Skip to content

Commit 0c8a2d4

Browse files
committed
chore: move serialization/hashing to test util
1 parent 91726c1 commit 0c8a2d4

File tree

3 files changed

+40
-52
lines changed

3 files changed

+40
-52
lines changed

tests/functional/idempotency/conftest.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import datetime
2-
import hashlib
32
import json
43
from collections import namedtuple
54
from decimal import Decimal
@@ -11,20 +10,15 @@
1110
from botocore.config import Config
1211
from jmespath import functions
1312

14-
from aws_lambda_powertools.shared.json_encoder import Encoder
1513
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer
1614
from aws_lambda_powertools.utilities.idempotency.idempotency import IdempotencyConfig
1715
from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope
1816
from aws_lambda_powertools.utilities.validation import envelopes
19-
from tests.functional.utils import load_event
17+
from tests.functional.utils import hash_idempotency_key, json_serialize, load_event
2018

2119
TABLE_NAME = "TEST_TABLE"
2220

2321

24-
def serialize(data):
25-
return json.dumps(data, sort_keys=True, cls=Encoder)
26-
27-
2822
@pytest.fixture(scope="module")
2923
def config() -> Config:
3024
return Config(region_name="us-east-1")
@@ -66,12 +60,12 @@ def lambda_response():
6660

6761
@pytest.fixture(scope="module")
6862
def serialized_lambda_response(lambda_response):
69-
return serialize(lambda_response)
63+
return json_serialize(lambda_response)
7064

7165

7266
@pytest.fixture(scope="module")
7367
def deserialized_lambda_response(lambda_response):
74-
return json.loads(serialize(lambda_response))
68+
return json.loads(json_serialize(lambda_response))
7569

7670

7771
@pytest.fixture
@@ -150,20 +144,20 @@ def expected_params_put_item_with_validation(hashed_idempotency_key, hashed_vali
150144
def hashed_idempotency_key(lambda_apigw_event, default_jmespath, lambda_context):
151145
compiled_jmespath = jmespath.compile(default_jmespath)
152146
data = compiled_jmespath.search(lambda_apigw_event)
153-
return "test-func.lambda_handler#" + hashlib.md5(serialize(data).encode()).hexdigest()
147+
return "test-func.lambda_handler#" + hash_idempotency_key(data)
154148

155149

156150
@pytest.fixture
157151
def hashed_idempotency_key_with_envelope(lambda_apigw_event):
158152
event = extract_data_from_envelope(
159153
data=lambda_apigw_event, envelope=envelopes.API_GATEWAY_HTTP, jmespath_options={}
160154
)
161-
return "test-func.lambda_handler#" + hashlib.md5(serialize(event).encode()).hexdigest()
155+
return "test-func.lambda_handler#" + hash_idempotency_key(event)
162156

163157

164158
@pytest.fixture
165159
def hashed_validation_key(lambda_apigw_event):
166-
return hashlib.md5(serialize(lambda_apigw_event["requestContext"]).encode()).hexdigest()
160+
return hash_idempotency_key(lambda_apigw_event["requestContext"])
167161

168162

169163
@pytest.fixture

tests/functional/idempotency/test_idempotency.py

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import copy
2-
import hashlib
3-
import json
42
import sys
53
from hashlib import md5
64
from unittest.mock import MagicMock
@@ -24,8 +22,7 @@
2422
from aws_lambda_powertools.utilities.idempotency.idempotency import idempotent, idempotent_function
2523
from aws_lambda_powertools.utilities.idempotency.persistence.base import BasePersistenceLayer, DataRecord
2624
from aws_lambda_powertools.utilities.validation import envelopes, validator
27-
from tests.functional.idempotency.conftest import serialize
28-
from tests.functional.utils import load_event
25+
from tests.functional.utils import hash_idempotency_key, json_serialize, load_event
2926

3027
TABLE_NAME = "TEST_TABLE"
3128

@@ -753,7 +750,7 @@ def test_default_no_raise_on_missing_idempotency_key(
753750
hashed_key = persistence_store._get_hashed_idempotency_key({})
754751

755752
# THEN return the hash of None
756-
expected_value = f"test-func.{function_name}#" + md5(serialize(None).encode()).hexdigest()
753+
expected_value = f"test-func.{function_name}#" + md5(json_serialize(None).encode()).hexdigest()
757754
assert expected_value == hashed_key
758755

759756

@@ -797,7 +794,7 @@ def test_jmespath_with_powertools_json(
797794
expected_value = [sub_attr_value, static_pk_value]
798795
api_gateway_proxy_event = {
799796
"requestContext": {"authorizer": {"claims": {"sub": sub_attr_value}}},
800-
"body": serialize({"id": static_pk_value}),
797+
"body": json_serialize({"id": static_pk_value}),
801798
}
802799

803800
# WHEN calling _get_hashed_idempotency_key
@@ -881,9 +878,7 @@ def _delete_record(self, data_record: DataRecord) -> None:
881878
def test_idempotent_lambda_event_source(lambda_context):
882879
# Scenario to validate that we can use the event_source decorator before or after the idempotent decorator
883880
mock_event = load_event("apiGatewayProxyV2Event.json")
884-
persistence_layer = MockPersistenceLayer(
885-
"test-func.lambda_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
886-
)
881+
persistence_layer = MockPersistenceLayer("test-func.lambda_handler#" + hash_idempotency_key(mock_event))
887882
expected_result = {"message": "Foo"}
888883

889884
# GIVEN an event_source decorator
@@ -903,9 +898,8 @@ def lambda_handler(event, _):
903898
def test_idempotent_function():
904899
# Scenario to validate we can use idempotent_function with any function
905900
mock_event = {"data": "value"}
906-
persistence_layer = MockPersistenceLayer(
907-
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
908-
)
901+
idempotency_key = "test-func.record_handler#" + hash_idempotency_key(mock_event)
902+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
909903
expected_result = {"message": "Foo"}
910904

911905
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
@@ -922,9 +916,8 @@ def test_idempotent_function_arbitrary_args_kwargs():
922916
# Scenario to validate we can use idempotent_function with a function
923917
# with an arbitrary number of args and kwargs
924918
mock_event = {"data": "value"}
925-
persistence_layer = MockPersistenceLayer(
926-
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
927-
)
919+
idempotency_key = "test-func.record_handler#" + hash_idempotency_key(mock_event)
920+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
928921
expected_result = {"message": "Foo"}
929922

930923
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
@@ -939,9 +932,8 @@ def record_handler(arg_one, arg_two, record, is_record):
939932

940933
def test_idempotent_function_invalid_data_kwarg():
941934
mock_event = {"data": "value"}
942-
persistence_layer = MockPersistenceLayer(
943-
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
944-
)
935+
idempotency_key = "test-func.record_handler#" + hash_idempotency_key(mock_event)
936+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
945937
expected_result = {"message": "Foo"}
946938
keyword_argument = "payload"
947939

@@ -958,9 +950,8 @@ def record_handler(record):
958950

959951
def test_idempotent_function_arg_instead_of_kwarg():
960952
mock_event = {"data": "value"}
961-
persistence_layer = MockPersistenceLayer(
962-
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
963-
)
953+
idempotency_key = "test-func.record_handler#" + hash_idempotency_key(mock_event)
954+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
964955
expected_result = {"message": "Foo"}
965956
keyword_argument = "record"
966957

@@ -978,18 +969,15 @@ def record_handler(record):
978969
def test_idempotent_function_and_lambda_handler(lambda_context):
979970
# Scenario to validate we can use both idempotent_function and idempotent decorators
980971
mock_event = {"data": "value"}
981-
persistence_layer = MockPersistenceLayer(
982-
"test-func.record_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
983-
)
972+
idempotency_key = "test-func.record_handler#" + hash_idempotency_key(mock_event)
973+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
984974
expected_result = {"message": "Foo"}
985975

986976
@idempotent_function(persistence_store=persistence_layer, data_keyword_argument="record")
987977
def record_handler(record):
988978
return expected_result
989979

990-
persistence_layer = MockPersistenceLayer(
991-
"test-func.lambda_handler#" + hashlib.md5(serialize(mock_event).encode()).hexdigest()
992-
)
980+
persistence_layer = MockPersistenceLayer("test-func.lambda_handler#" + hash_idempotency_key(mock_event))
993981

994982
@idempotent(persistence_store=persistence_layer)
995983
def lambda_handler(event, _):
@@ -1010,18 +998,16 @@ def test_idempotent_data_sorting():
1010998
# Scenario to validate same data in different order hashes to the same idempotency key
1011999
data_one = {"data": "test message 1", "more_data": "more data 1"}
10121000
data_two = {"more_data": "more data 1", "data": "test message 1"}
1013-
1001+
idempotency_key = "test-func.dummy#" + hash_idempotency_key(data_one)
10141002
# Assertion will happen in MockPersistenceLayer
1015-
persistence_layer = MockPersistenceLayer(
1016-
"test-func.dummy#" + hashlib.md5(json.dumps(data_one).encode()).hexdigest()
1017-
)
1003+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
10181004

10191005
# GIVEN
10201006
@idempotent_function(data_keyword_argument="payload", persistence_store=persistence_layer)
10211007
def dummy(payload):
10221008
return {"message": "hello"}
10231009

1024-
# WHEN
1010+
# WHEN/THEN assertion will happen at MockPersistenceLayer
10251011
dummy(payload=data_two)
10261012

10271013

@@ -1120,10 +1106,8 @@ def test_idempotent_function_dataclass_with_jmespath():
11201106
dataclasses = get_dataclasses_lib()
11211107
config = IdempotencyConfig(event_key_jmespath="transaction_id", use_local_cache=True)
11221108
mock_event = {"customer_id": "fake", "transaction_id": "fake-id"}
1123-
persistence_layer = MockPersistenceLayer(
1124-
expected_idempotency_key="test-func.collect_payment#"
1125-
+ hashlib.md5(serialize(mock_event["transaction_id"]).encode()).hexdigest()
1126-
)
1109+
idempotency_key = "test-func.collect_payment#" + hash_idempotency_key(mock_event["transaction_id"])
1110+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
11271111

11281112
@dataclasses.dataclass
11291113
class Payment:
@@ -1147,10 +1131,8 @@ def test_idempotent_function_pydantic_with_jmespath():
11471131
# GIVEN
11481132
config = IdempotencyConfig(event_key_jmespath="transaction_id", use_local_cache=True)
11491133
mock_event = {"customer_id": "fake", "transaction_id": "fake-id"}
1150-
persistence_layer = MockPersistenceLayer(
1151-
expected_idempotency_key="test-func.collect_payment#"
1152-
+ hashlib.md5(serialize(mock_event["transaction_id"]).encode()).hexdigest()
1153-
)
1134+
idempotency_key = "test-func.collect_payment#" + hash_idempotency_key(mock_event["transaction_id"])
1135+
persistence_layer = MockPersistenceLayer(expected_idempotency_key=idempotency_key)
11541136

11551137
class Payment(BaseModel):
11561138
customer_id: str

tests/functional/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import base64
2+
import hashlib
23
import json
34
from pathlib import Path
45
from typing import Any
56

7+
from aws_lambda_powertools.shared.json_encoder import Encoder
8+
69

710
def load_event(file_name: str) -> Any:
811
path = Path(str(Path(__file__).parent.parent) + "/events/" + file_name)
@@ -15,3 +18,12 @@ def str_to_b64(data: str) -> str:
1518

1619
def b64_to_str(data: str) -> str:
1720
return base64.b64decode(data.encode()).decode("utf-8")
21+
22+
23+
def json_serialize(data):
24+
return json.dumps(data, sort_keys=True, cls=Encoder)
25+
26+
27+
def hash_idempotency_key(data: Any):
28+
"""Serialize data to JSON, encode, and hash it for idempotency key"""
29+
return hashlib.md5(json_serialize(data).encode()).hexdigest()

0 commit comments

Comments
 (0)