Skip to content

Commit 63d9a9f

Browse files
committed
chore(idempotency): add tests
1 parent cf3040e commit 63d9a9f

File tree

3 files changed

+72
-52
lines changed

3 files changed

+72
-52
lines changed

tests/functional/idempotency/conftest.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ def expected_params_update_item(serialized_lambda_response, hashed_idempotency_k
9898

9999
@pytest.fixture
100100
def expected_params_update_item_with_validation(
101-
serialized_lambda_response, hashed_idempotency_key, hashed_validation_key
101+
serialized_lambda_response, hashed_idempotency_key, hashed_validation_key, idempotency_config
102102
):
103-
return {
103+
params = {
104104
"ExpressionAttributeNames": {
105105
"#expiry": "expiration",
106106
"#response_data": "data",
@@ -122,41 +122,48 @@ def expected_params_update_item_with_validation(
122122
),
123123
}
124124

125+
if idempotency_config.expires_in_progress:
126+
params["ExpressionAttributeNames"]["#in_progress_expiry"] = "in_progress_expiration"
127+
params["ExpressionAttributeValues"][":in_progress_expiry"] = stub.ANY
128+
params["UpdateExpression"] += ", #in_progress_expiry = :in_progress_expiry"
129+
130+
return params
131+
125132

126133
@pytest.fixture
127-
def expected_params_put_item(hashed_idempotency_key):
128-
return {
129-
"ConditionExpression": (
130-
"attribute_not_exists(#id) OR #now < :now OR "
131-
"(attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now AND #status = :inprogress)"
132-
),
134+
def expected_params_put_item(hashed_idempotency_key, idempotency_config):
135+
params = {
136+
"ConditionExpression": "attribute_not_exists(#id) OR #now < :now",
133137
"ExpressionAttributeNames": {
134138
"#id": "id",
135139
"#now": "expiration",
136-
"#in_progress_expiry": "in_progress_expiration",
137140
"#status": "status",
138141
},
139-
"ExpressionAttributeValues": {":now": stub.ANY, ":inprogress": "INPROGRESS"},
142+
"ExpressionAttributeValues": {":now": stub.ANY},
140143
"Item": {"expiration": stub.ANY, "id": hashed_idempotency_key, "status": "INPROGRESS"},
141144
"TableName": "TEST_TABLE",
142145
}
143146

147+
if idempotency_config.expires_in_progress:
148+
params[
149+
"ConditionExpression"
150+
] += " OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now AND #status = :inprogress)"
151+
params["ExpressionAttributeNames"]["#in_progress_expiry"] = "in_progress_expiration"
152+
params["ExpressionAttributeValues"][":inprogress"] = "INPROGRESS"
153+
154+
return params
155+
144156

145157
@pytest.fixture
146-
def expected_params_put_item_with_validation(hashed_idempotency_key, hashed_validation_key):
147-
return {
148-
"ConditionExpression": (
149-
"attribute_not_exists(#id) OR #now < :now OR "
150-
"(attribute_exists(#in_progress_expiry) AND "
151-
"#in_progress_expiry < :now AND #status = :inprogress)"
152-
),
158+
def expected_params_put_item_with_validation(hashed_idempotency_key, hashed_validation_key, idempotency_config):
159+
params = {
160+
"ConditionExpression": "attribute_not_exists(#id) OR #now < :now",
153161
"ExpressionAttributeNames": {
154162
"#id": "id",
155-
"#in_progress_expiry": stub.ANY,
156-
"#now": stub.ANY,
163+
"#now": "expiration",
157164
"#status": "status",
158165
},
159-
"ExpressionAttributeValues": {":now": stub.ANY, ":inprogress": "INPROGRESS"},
166+
"ExpressionAttributeValues": {":now": stub.ANY},
160167
"Item": {
161168
"expiration": stub.ANY,
162169
"id": hashed_idempotency_key,
@@ -166,6 +173,15 @@ def expected_params_put_item_with_validation(hashed_idempotency_key, hashed_vali
166173
"TableName": "TEST_TABLE",
167174
}
168175

176+
if idempotency_config.expires_in_progress:
177+
params[
178+
"ConditionExpression"
179+
] += " OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now AND #status = :inprogress)"
180+
params["ExpressionAttributeNames"]["#in_progress_expiry"] = "in_progress_expiration"
181+
params["ExpressionAttributeValues"][":inprogress"] = "INPROGRESS"
182+
183+
return params
184+
169185

170186
@pytest.fixture
171187
def hashed_idempotency_key(lambda_apigw_event, default_jmespath, lambda_context):
@@ -203,6 +219,7 @@ def idempotency_config(config, request, default_jmespath):
203219
event_key_jmespath=request.param.get("event_key_jmespath") or default_jmespath,
204220
use_local_cache=request.param["use_local_cache"],
205221
expires_in_progress=request.param.get("expires_in_progress") or False,
222+
payload_validation_jmespath=request.param.get("payload_validation_jmespath") or "",
206223
)
207224

208225

@@ -211,15 +228,6 @@ def config_without_jmespath(config, request):
211228
return IdempotencyConfig(use_local_cache=request.param["use_local_cache"])
212229

213230

214-
@pytest.fixture
215-
def config_with_validation(config, request, default_jmespath):
216-
return IdempotencyConfig(
217-
event_key_jmespath=default_jmespath,
218-
use_local_cache=request.param,
219-
payload_validation_jmespath="requestContext",
220-
)
221-
222-
223231
@pytest.fixture
224232
def config_with_expires_in_progress(config, request, default_jmespath):
225233
return IdempotencyConfig(

tests/functional/idempotency/test_idempotency.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,11 @@ def test_idempotent_lambda_first_execution_event_mutation(
345345
event = copy.deepcopy(lambda_apigw_event)
346346
stubber = stub.Stubber(persistence_store.table.meta.client)
347347
ddb_response = {}
348-
stubber.add_response("put_item", ddb_response, build_idempotency_put_item_stub(data=event["body"]))
348+
stubber.add_response(
349+
"put_item",
350+
ddb_response,
351+
build_idempotency_put_item_stub(data=event["body"], config=idempotency_config),
352+
)
349353
stubber.add_response(
350354
"update_item",
351355
ddb_response,
@@ -459,17 +463,17 @@ def lambda_handler(event, context):
459463

460464

461465
@pytest.mark.parametrize(
462-
"config_with_validation",
466+
"idempotency_config",
463467
[
464-
{"use_local_cache": False, "expires_in_progress": True},
465-
{"use_local_cache": False, "expires_in_progress": False},
466-
{"use_local_cache": True, "expires_in_progress": True},
467-
{"use_local_cache": True, "expires_in_progress": False},
468+
{"use_local_cache": False, "payload_validation_jmespath": "requestContext", "expires_in_progress": True},
469+
{"use_local_cache": False, "payload_validation_jmespath": "requestContext", "expires_in_progress": False},
470+
{"use_local_cache": True, "payload_validation_jmespath": "requestContext", "expires_in_progress": True},
471+
{"use_local_cache": True, "payload_validation_jmespath": "requestContext", "expires_in_progress": False},
468472
],
469473
indirect=True,
470474
)
471475
def test_idempotent_lambda_already_completed_with_validation_bad_payload(
472-
config_with_validation: IdempotencyConfig,
476+
idempotency_config: IdempotencyConfig,
473477
persistence_store: DynamoDBPersistenceLayer,
474478
lambda_apigw_event,
475479
timestamp_future,
@@ -499,7 +503,7 @@ def test_idempotent_lambda_already_completed_with_validation_bad_payload(
499503
stubber.add_response("get_item", ddb_response, expected_params)
500504
stubber.activate()
501505

502-
@idempotent(config=config_with_validation, persistence_store=persistence_store)
506+
@idempotent(config=idempotency_config, persistence_store=persistence_store)
503507
def lambda_handler(event, context):
504508
return lambda_response
505509

@@ -707,17 +711,17 @@ def lambda_handler(event, context):
707711

708712

709713
@pytest.mark.parametrize(
710-
"config_with_validation",
714+
"idempotency_config",
711715
[
712-
{"use_local_cache": False, "expires_in_progress": True},
713-
{"use_local_cache": False, "expires_in_progress": False},
714-
{"use_local_cache": True, "expires_in_progress": True},
715-
{"use_local_cache": True, "expires_in_progress": False},
716+
{"use_local_cache": False, "payload_validation_jmespath": "requestContext", "expires_in_progress": True},
717+
{"use_local_cache": False, "payload_validation_jmespath": "requestContext", "expires_in_progress": False},
718+
{"use_local_cache": True, "payload_validation_jmespath": "requestContext", "expires_in_progress": True},
719+
{"use_local_cache": True, "payload_validation_jmespath": "requestContext", "expires_in_progress": False},
716720
],
717721
indirect=True,
718722
)
719723
def test_idempotent_lambda_first_execution_with_validation(
720-
config_with_validation: IdempotencyConfig,
724+
idempotency_config: IdempotencyConfig,
721725
persistence_store: DynamoDBPersistenceLayer,
722726
lambda_apigw_event,
723727
expected_params_update_item_with_validation,
@@ -737,7 +741,7 @@ def test_idempotent_lambda_first_execution_with_validation(
737741
stubber.add_response("update_item", ddb_response, expected_params_update_item_with_validation)
738742
stubber.activate()
739743

740-
@idempotent(config=config_with_validation, persistence_store=persistence_store)
744+
@idempotent(config=idempotency_config, persistence_store=persistence_store)
741745
def lambda_handler(event, context):
742746
return lambda_response
743747

tests/functional/idempotency/utils.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,33 @@ def hash_idempotency_key(data: Any):
1313

1414

1515
def build_idempotency_put_item_stub(
16-
data: Dict, function_name: str = "test-func", handler_name: str = "lambda_handler"
16+
data: Dict,
17+
config: IdempotencyConfig,
18+
function_name: str = "test-func",
19+
handler_name: str = "lambda_handler",
1720
) -> Dict:
1821
idempotency_key_hash = f"{function_name}.{handler_name}#{hash_idempotency_key(data)}"
19-
return {
20-
"ConditionExpression": (
21-
"attribute_not_exists(#id) OR #now < :now OR "
22-
"(attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now AND #status = :inprogress)"
23-
),
22+
params = {
23+
"ConditionExpression": ("attribute_not_exists(#id) OR #now < :now"),
2424
"ExpressionAttributeNames": {
2525
"#id": "id",
2626
"#now": "expiration",
27-
"#in_progress_expiry": "in_progress_expiration",
2827
"#status": "status",
2928
},
30-
"ExpressionAttributeValues": {":now": stub.ANY, ":inprogress": "INPROGRESS"},
29+
"ExpressionAttributeValues": {":now": stub.ANY},
3130
"Item": {"expiration": stub.ANY, "id": idempotency_key_hash, "status": "INPROGRESS"},
3231
"TableName": "TEST_TABLE",
3332
}
3433

34+
if config.expires_in_progress:
35+
params[
36+
"ConditionExpression"
37+
] += " OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now AND #status = :inprogress)"
38+
params["ExpressionAttributeNames"]["#in_progress_expiry"] = "in_progress_expiration"
39+
params["ExpressionAttributeValues"][":inprogress"] = "INPROGRESS"
40+
41+
return params
42+
3543

3644
def build_idempotency_update_item_stub(
3745
data: Dict,

0 commit comments

Comments
 (0)