Skip to content

Commit 381b4f5

Browse files
author
Lucas McDonald
committed
m
1 parent a1e8b23 commit 381b4f5

File tree

5 files changed

+175
-49
lines changed

5 files changed

+175
-49
lines changed

.github/workflows/ci_test_python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ jobs:
2525
fail-fast: false
2626
matrix:
2727
library: [DynamoDbEncryption]
28-
python-version: ["3.11"]
28+
python-version: ["3.11", "3.12", "3.13"]
2929
os: [
30-
# macos-13,
30+
macos-13,
3131
ubuntu-22.04,
3232
# Dafny-transpiled Python tests use a PYTHONPATH hack that doesn't work on Windows.
3333
# Windows is tested with non-Dafny-transpiled Python tests.

DynamoDbEncryption/runtimes/python/test/integ/encrypted/test_client.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import (
99
DynamoDbEncryptionTransformsException,
1010
)
11+
import uuid
12+
from copy import deepcopy
1113

1214
from ...constants import (
1315
INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME,
@@ -110,45 +112,80 @@ def client(encrypted, expect_standard_dictionaries):
110112
def use_complex_item(request):
111113
return request.param
112114

115+
# Append a suffix to the partition key to avoid collisions between test runs.
116+
@pytest.fixture(scope="module")
117+
def test_run_suffix():
118+
return str(uuid.uuid4())
113119

114120
@pytest.fixture
115-
def test_item(expect_standard_dictionaries, use_complex_item):
121+
def test_item(expect_standard_dictionaries, use_complex_item, test_run_suffix):
116122
"""Get a single test item in the appropriate format for the client."""
117123
if expect_standard_dictionaries:
118124
if use_complex_item:
119-
return complex_item_dict
120-
return simple_item_dict
121-
if use_complex_item:
122-
return complex_item_ddb
123-
return simple_item_ddb
124-
125+
item = deepcopy(complex_item_dict)
126+
else:
127+
item = deepcopy(simple_item_dict)
128+
else:
129+
if use_complex_item:
130+
item = deepcopy(complex_item_ddb)
131+
else:
132+
item = deepcopy(simple_item_ddb)
133+
# Add a suffix to the partition key to avoid collisions between test runs.
134+
if isinstance(item["partition_key"], dict):
135+
item["partition_key"]["S"] += test_run_suffix
136+
else:
137+
item["partition_key"] += test_run_suffix
138+
return item
125139

126140
@pytest.fixture
127-
def test_key(expect_standard_dictionaries, use_complex_item):
141+
def test_key(expect_standard_dictionaries, use_complex_item, test_run_suffix):
128142
"""Get a single test item in the appropriate format for the client."""
129143
if expect_standard_dictionaries:
130144
if use_complex_item:
131-
return complex_key_dict
132-
return simple_key_dict
133-
if use_complex_item:
134-
return complex_key_ddb
135-
return simple_key_ddb
145+
key = deepcopy(complex_key_dict)
146+
else:
147+
key = deepcopy(simple_key_dict)
148+
else:
149+
if use_complex_item:
150+
key = deepcopy(complex_key_ddb)
151+
else:
152+
key = deepcopy(simple_key_ddb)
153+
# Add a suffix to the partition key to avoid collisions between test runs.
154+
if isinstance(key["partition_key"], dict):
155+
key["partition_key"]["S"] += test_run_suffix
156+
else:
157+
key["partition_key"] += test_run_suffix
158+
return key
136159

137160

138161
@pytest.fixture
139-
def multiple_test_items(expect_standard_dictionaries):
162+
def multiple_test_items(expect_standard_dictionaries, test_run_suffix):
140163
"""Get two test items in the appropriate format for the client."""
141164
if expect_standard_dictionaries:
142-
return [simple_item_dict, complex_item_dict]
143-
return [simple_item_ddb, complex_item_ddb]
165+
items = [deepcopy(simple_item_dict), deepcopy(complex_item_dict)]
166+
else:
167+
items = [deepcopy(simple_item_ddb), deepcopy(complex_item_ddb)]
168+
for item in items:
169+
if isinstance(item["partition_key"], dict):
170+
item["partition_key"]["S"] += test_run_suffix
171+
else:
172+
item["partition_key"] += test_run_suffix
173+
return items
144174

145175

146176
@pytest.fixture
147-
def multiple_test_keys(expect_standard_dictionaries):
177+
def multiple_test_keys(expect_standard_dictionaries, test_run_suffix):
148178
"""Get two test keys in the appropriate format for the client."""
149179
if expect_standard_dictionaries:
150-
return [simple_key_dict, complex_key_dict]
151-
return [simple_key_ddb, complex_key_ddb]
180+
keys = [deepcopy(simple_key_dict), deepcopy(complex_key_dict)]
181+
else:
182+
keys = [deepcopy(simple_key_ddb), deepcopy(complex_key_ddb)]
183+
for key in keys:
184+
if isinstance(key["partition_key"], dict):
185+
key["partition_key"]["S"] += test_run_suffix
186+
else:
187+
key["partition_key"] += test_run_suffix
188+
return keys
152189

153190

154191
@pytest.fixture
@@ -303,10 +340,18 @@ def test_GIVEN_valid_put_and_query_requests_WHEN_put_and_query_THEN_round_trip_p
303340

304341

305342
@pytest.fixture
306-
def scan_request(expect_standard_dictionaries, test_item):
343+
def scan_request(expect_standard_dictionaries, test_item, encrypted):
307344
if expect_standard_dictionaries:
308-
return {**basic_scan_request_dict(test_item), "TableName": INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME}
309-
return basic_scan_request_ddb(test_item)
345+
request = {**basic_scan_request_dict(test_item), "TableName": INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME}
346+
else:
347+
request = basic_scan_request_ddb(test_item)
348+
if encrypted:
349+
# If the encrypted scan encounters a plaintext item, the scan will fail.
350+
# To avoid this, encrypted scans add a filter expression that matches only encrypted items.
351+
request["FilterExpression"] = request["FilterExpression"] + " AND attribute_exists (#sig)"
352+
request["ExpressionAttributeNames"] = {}
353+
request["ExpressionAttributeNames"]["#sig"] = "amzn-ddb-map-sig"
354+
return request
310355

311356

312357
def test_GIVEN_valid_put_and_scan_requests_WHEN_put_and_scan_THEN_round_trip_passes(
@@ -323,7 +368,6 @@ def test_GIVEN_valid_put_and_scan_requests_WHEN_put_and_scan_THEN_round_trip_pas
323368
scan_response = client.scan(**scan_request)
324369
# Then: scan succeeds
325370
assert scan_response["ResponseMetadata"]["HTTPStatusCode"] == 200
326-
assert len(scan_response["Items"]) >= 1
327371
# Can't assert anything about the scan;
328372
# there are too many items.
329373
# The critical assertion is that the scan succeeds.
@@ -590,3 +634,13 @@ def test_WHEN_call_passthrough_method_THEN_correct_response_is_returned():
590634
response = encrypted_client(expect_standard_dictionaries=False).list_backups()
591635
# Then: Correct response is returned, i.e. EncryptedClient forwards the call to the underlying boto3 client
592636
assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
637+
638+
# Delete the items in the table after the module runs
639+
@pytest.fixture(scope="module", autouse=True)
640+
def cleanup_after_module(test_run_suffix):
641+
yield
642+
table = boto3.client("dynamodb")
643+
items = [deepcopy(simple_item_ddb), deepcopy(complex_item_ddb)]
644+
for item in items:
645+
item["partition_key"]["S"] += test_run_suffix
646+
table.delete_item(**basic_delete_item_request_ddb(item))

DynamoDbEncryption/runtimes/python/test/integ/encrypted/test_paginator.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import boto3
22
import pytest
3+
import uuid
4+
from copy import deepcopy
35

46
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
57

@@ -86,37 +88,65 @@ def scan_paginator(client):
8688
def use_complex_item(request):
8789
return request.param
8890

91+
# Append a suffix to the partition key to avoid collisions between test runs.
92+
@pytest.fixture(scope="module")
93+
def test_run_suffix():
94+
return str(uuid.uuid4())
8995

9096
@pytest.fixture
91-
def test_key(expect_standard_dictionaries, use_complex_item):
97+
def test_key(expect_standard_dictionaries, use_complex_item, test_run_suffix):
9298
"""Get a single test item in the appropriate format for the client."""
9399
if expect_standard_dictionaries:
94100
if use_complex_item:
95-
return complex_key_dict
96-
return simple_key_dict
97-
if use_complex_item:
98-
return complex_key_ddb
99-
return simple_key_ddb
100-
101+
key = deepcopy(complex_key_dict)
102+
else:
103+
key = deepcopy(simple_key_dict)
104+
else:
105+
if use_complex_item:
106+
key = deepcopy(complex_key_ddb)
107+
else:
108+
key = deepcopy(simple_key_ddb)
109+
# Add a suffix to the partition key to avoid collisions between test runs.
110+
if isinstance(key["partition_key"], dict):
111+
key["partition_key"]["S"] += test_run_suffix
112+
else:
113+
key["partition_key"] += test_run_suffix
114+
return key
101115

102116
@pytest.fixture
103-
def multiple_test_keys(expect_standard_dictionaries):
117+
def multiple_test_keys(expect_standard_dictionaries, test_run_suffix):
104118
"""Get two test keys in the appropriate format for the client."""
105119
if expect_standard_dictionaries:
106-
return [simple_key_dict, complex_key_dict]
107-
return [simple_key_ddb, complex_key_ddb]
108-
120+
keys = [deepcopy(simple_key_dict), deepcopy(complex_key_dict)]
121+
else:
122+
keys = [deepcopy(simple_key_ddb), deepcopy(complex_key_ddb)]
123+
# Add a suffix to the partition key to avoid collisions between test runs.
124+
for key in keys:
125+
if isinstance(key["partition_key"], dict):
126+
key["partition_key"]["S"] += test_run_suffix
127+
else:
128+
key["partition_key"] += test_run_suffix
129+
return keys
109130

110131
@pytest.fixture
111-
def test_item(expect_standard_dictionaries, use_complex_item):
132+
def test_item(expect_standard_dictionaries, use_complex_item, test_run_suffix):
112133
"""Get a single test item in the appropriate format for the client."""
113134
if expect_standard_dictionaries:
114135
if use_complex_item:
115-
return complex_item_dict
116-
return simple_item_dict
117-
if use_complex_item:
118-
return complex_item_ddb
119-
return simple_item_ddb
136+
item = deepcopy(complex_item_dict)
137+
else:
138+
item = deepcopy(simple_item_dict)
139+
else:
140+
if use_complex_item:
141+
item = deepcopy(complex_item_ddb)
142+
else:
143+
item = deepcopy(simple_item_ddb)
144+
# Add a suffix to the partition key to avoid collisions between test runs.
145+
if isinstance(item["partition_key"], dict):
146+
item["partition_key"]["S"] += test_run_suffix
147+
else:
148+
item["partition_key"] += test_run_suffix
149+
return item
120150

121151

122152
@pytest.fixture

DynamoDbEncryption/runtimes/python/test/integ/encrypted/test_resource.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import boto3
22
import pytest
3+
import uuid
4+
from copy import deepcopy
35

46
from aws_dbesdk_dynamodb.encrypted.resource import EncryptedResource, EncryptedTablesCollectionManager
57
from aws_dbesdk_dynamodb.encrypted.table import EncryptedTable
@@ -41,23 +43,43 @@ def resource(encrypted):
4143
def tables(resource):
4244
return resource.tables
4345

46+
@pytest.fixture(scope="module")
47+
def test_run_suffix():
48+
return str(uuid.uuid4())
49+
50+
@pytest.fixture
51+
def test_items(test_run_suffix):
52+
items = [deepcopy(complex_item_dict), deepcopy(simple_item_dict)]
53+
for item in items:
54+
item["partition_key"] += test_run_suffix
55+
return items
56+
57+
@pytest.fixture
58+
def test_keys(test_run_suffix):
59+
keys = [deepcopy(complex_key_dict), deepcopy(simple_key_dict)]
60+
for key in keys:
61+
key["partition_key"] += test_run_suffix
62+
return keys
63+
4464

4565
def test_GIVEN_items_WHEN_batch_write_and_get_THEN_round_trip_passes(
4666
resource,
67+
test_items,
68+
test_keys,
4769
):
48-
batch_write_item_put_request = basic_batch_write_item_put_request_dict([simple_item_dict, complex_item_dict])
70+
batch_write_item_put_request = basic_batch_write_item_put_request_dict(test_items)
4971
batch_write_response = resource.batch_write_item(**batch_write_item_put_request)
5072
assert batch_write_response["ResponseMetadata"]["HTTPStatusCode"] == 200
5173

52-
batch_get_item_request = basic_batch_get_item_request_dict([simple_key_dict, complex_key_dict])
74+
batch_get_item_request = basic_batch_get_item_request_dict(test_keys)
5375
batch_get_response = resource.batch_get_item(**batch_get_item_request)
5476
assert batch_get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
5577
responses = batch_get_response["Responses"][INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME]
5678
assert len(responses) == 2
5779
for response in responses:
58-
assert response in [simple_item_dict, complex_item_dict]
80+
assert response in test_items
5981

60-
batch_write_item_delete_request = basic_batch_write_item_delete_request_dict([simple_key_dict, complex_key_dict])
82+
batch_write_item_delete_request = basic_batch_write_item_delete_request_dict(test_keys)
6183
batch_write_response = resource.batch_write_item(**batch_write_item_delete_request)
6284
assert batch_write_response["ResponseMetadata"]["HTTPStatusCode"] == 200
6385

DynamoDbEncryption/runtimes/python/test/integ/encrypted/test_table.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import boto3
22
import pytest
3+
import uuid
4+
from copy import deepcopy
35

46
from aws_dbesdk_dynamodb.encrypted.table import EncryptedTable
57
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb_transforms.errors import (
@@ -57,14 +59,19 @@ def table(encrypted):
5759
else:
5860
return plaintext_table()
5961

62+
@pytest.fixture(scope="module")
63+
def test_run_suffix():
64+
return str(uuid.uuid4())
6065

6166
# Creates a matrix of tests for each value in param,
6267
# with a user-friendly string for test output:
6368
# use_complex_item = True -> "complex_item"
6469
# use_complex_item = False -> "simple_item"
6570
@pytest.fixture(params=[simple_item_dict, complex_item_dict], ids=["simple_item", "complex_item"])
66-
def test_item(request):
67-
return request.param
71+
def test_item(request, test_run_suffix):
72+
item = deepcopy(request.param)
73+
item["partition_key"] += test_run_suffix
74+
return item
6875

6976

7077
def test_GIVEN_item_WHEN_basic_put_AND_basic_get_AND_basic_delete_THEN_round_trip_passes(table, test_item):
@@ -183,7 +190,10 @@ def test_GIVEN_valid_put_and_scan_requests_WHEN_put_and_scan_THEN_round_trip_pas
183190
# When: Scanning items
184191
scan_request_dict = scan_request
185192
scan_response = table.scan(**scan_request_dict)
186-
# Then: Scan returns both test items
193+
# Then: Scan succeeds
194+
# Can't assert anything about the scan;
195+
# there are too many items.
196+
# The critical assertion is that the scan succeeds.
187197
assert scan_response["ResponseMetadata"]["HTTPStatusCode"] == 200
188198

189199

@@ -228,3 +238,13 @@ def test_WHEN_call_passthrough_method_THEN_correct_response_is_returned():
228238
response = encrypted_table().table_name
229239
# Then: Correct response is returned, i.e. EncryptedTable forwards the call to the underlying boto3 table
230240
assert response == INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME
241+
242+
# Delete the items in the table after the module runs
243+
@pytest.fixture(scope="module", autouse=True)
244+
def cleanup_after_module(test_run_suffix):
245+
yield
246+
table = boto3.resource("dynamodb").Table(INTEG_TEST_DEFAULT_DYNAMODB_TABLE_NAME)
247+
items = [deepcopy(simple_item_dict), deepcopy(complex_item_dict)]
248+
for item in items:
249+
item["partition_key"] = item["partition_key"] + test_run_suffix
250+
table.delete_item(**basic_delete_item_request_dict(item))

0 commit comments

Comments
 (0)