Skip to content

Commit ca65b25

Browse files
committed
add high level functional tests to cover UnprocessedItems in batch writes
1 parent ec4eca1 commit ca65b25

File tree

4 files changed

+159
-5
lines changed

4 files changed

+159
-5
lines changed

test/functional/encrypted/test_client.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from ..functional_test_utils import example_table # noqa pylint: disable=unused-import
1818
from ..functional_test_utils import (
1919
TEST_TABLE_NAME,
20+
build_static_jce_cmp,
21+
client_batch_items_unprocessed_check,
2022
client_cycle_batch_items_check,
2123
client_cycle_batch_items_check_paginators,
2224
client_cycle_single_item_check,
@@ -53,6 +55,12 @@ def _client_cycle_batch_items_check_paginators(materials_provider, initial_actio
5355
)
5456

5557

58+
def _client_batch_items_unprocessed_check(materials_provider, initial_actions, initial_item):
59+
client_batch_items_unprocessed_check(
60+
materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2"
61+
)
62+
63+
5664
def test_ephemeral_item_cycle(example_table, some_cmps, parametrized_actions, parametrized_item):
5765
"""Test a small number of curated CMPs against a small number of curated items."""
5866
_client_cycle_single_item_check(some_cmps, parametrized_actions, parametrized_item)
@@ -68,6 +76,12 @@ def test_ephemeral_batch_item_cycle_paginators(example_table, some_cmps, paramet
6876
_client_cycle_batch_items_check_paginators(some_cmps, parametrized_actions, parametrized_item)
6977

7078

79+
def test_batch_item_unprocessed(example_table, parametrized_actions, parametrized_item):
80+
"""Test Unprocessed Items handling with a single ephemeral static CMP against a small number of curated items."""
81+
cmp = build_static_jce_cmp("AES", 256, "HmacSHA256", 256)
82+
_client_batch_items_unprocessed_check(cmp, parametrized_actions, parametrized_item)
83+
84+
7185
@pytest.mark.slow
7286
def test_ephemeral_item_cycle_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item):
7387
"""Test ALL THE CMPS against a small number of curated items."""

test/functional/encrypted/test_resource.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
"""Functional tests for ``dynamodb_encryption_sdk.encrypted.resource``."""
1414
import pytest
1515

16-
from ..functional_test_utils import example_table # noqa pylint: disable=unused-import
16+
from ..functional_test_utils import example_table # noqa pylint: disable=unused-import
1717
from ..functional_test_utils import (
1818
TEST_TABLE_NAME,
19+
build_static_jce_cmp,
20+
resource_batch_items_unprocessed_check,
1921
resource_cycle_batch_items_check,
2022
set_parametrized_actions,
2123
set_parametrized_cmp,
@@ -35,11 +37,24 @@ def _resource_cycle_batch_items_check(materials_provider, initial_actions, initi
3537
resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2")
3638

3739

40+
def _resource_batch_items_unprocessed_check(materials_provider, initial_actions, initial_item):
41+
resource_batch_items_unprocessed_check(
42+
materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2"
43+
)
44+
45+
3846
def test_ephemeral_batch_item_cycle(example_table, some_cmps, parametrized_actions, parametrized_item):
3947
"""Test a small number of curated CMPs against a small number of curated items."""
4048
_resource_cycle_batch_items_check(some_cmps, parametrized_actions, parametrized_item)
4149

4250

51+
def test_batch_item_unprocessed(example_table, parametrized_actions, parametrized_item):
52+
"""Test Unprocessed Items handling with a single ephemeral static CMP against a small number of curated items."""
53+
_resource_batch_items_unprocessed_check(
54+
build_static_jce_cmp("AES", 256, "HmacSHA256", 256), parametrized_actions, parametrized_item
55+
)
56+
57+
4358
@pytest.mark.travis_isolation
4459
@pytest.mark.slow
4560
def test_ephemeral_batch_item_cycle_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item):

test/functional/encrypted/test_table.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
import hypothesis
1515
import pytest
1616

17-
from ..functional_test_utils import example_table # noqa pylint: disable=unused-import
17+
from ..functional_test_utils import example_table # noqa pylint: disable=unused-import
1818
from ..functional_test_utils import (
1919
TEST_TABLE_NAME,
20+
build_static_jce_cmp,
2021
set_parametrized_actions,
2122
set_parametrized_cmp,
2223
set_parametrized_item,
24+
table_batch_writer_unprocessed_items_check,
2325
table_cycle_batch_writer_check,
2426
table_cycle_check,
2527
)
@@ -48,6 +50,14 @@ def test_ephemeral_item_cycle_batch_writer(example_table, some_cmps, parametrize
4850
table_cycle_batch_writer_check(some_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2")
4951

5052

53+
def test_batch_writer_unprocessed(example_table, parametrized_actions, parametrized_item):
54+
"""Test Unprocessed Items handling with a single ephemeral static CMP against a small number of curated items."""
55+
cmp = build_static_jce_cmp("AES", 256, "HmacSHA256", 256)
56+
table_batch_writer_unprocessed_items_check(
57+
cmp, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2"
58+
)
59+
60+
5161
@pytest.mark.slow
5262
def test_ephemeral_item_cycle_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item):
5363
"""Test ALL THE CMPS against a small number of curated items."""

test/functional/functional_test_utils.py

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import pytest
2525
from boto3.dynamodb.types import Binary
2626
from botocore.exceptions import NoRegionError
27+
from mock import patch
2728
from moto import mock_dynamodb2
2829

2930
from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey
@@ -336,6 +337,12 @@ def diverse_item():
336337
_reserved_attributes = set([attr.value for attr in ReservedAttributes])
337338

338339

340+
def return_requestitems_as_unprocessed(*args, **kwargs):
341+
return {
342+
"UnprocessedItems": kwargs['RequestItems']
343+
}
344+
345+
339346
def check_encrypted_item(plaintext_item, ciphertext_item, attribute_actions):
340347
# Verify that all expected attributes are present
341348
ciphertext_attributes = set(ciphertext_item.keys())
@@ -374,12 +381,20 @@ def _nop_transformer(item):
374381
return item
375382

376383

384+
def assert_items_exist_in_list(source, expected, transformer):
385+
for actual_item in source:
386+
expected_item = _matching_key(actual_item, expected)
387+
assert transformer(actual_item) == transformer(expected_item)
388+
389+
377390
def assert_equal_lists_of_items(actual, expected, transformer=_nop_transformer):
378391
assert len(actual) == len(expected)
392+
assert_items_exist_in_list(actual, expected, transformer)
379393

380-
for actual_item in actual:
381-
expected_item = _matching_key(actual_item, expected)
382-
assert transformer(actual_item) == transformer(expected_item)
394+
395+
def assert_list_of_items_contains(full, subset, transformer=_nop_transformer):
396+
assert len(full) >= len(subset)
397+
assert_items_exist_in_list(subset, full, transformer)
383398

384399

385400
def check_many_encrypted_items(actual, expected, attribute_actions, transformer=_nop_transformer):
@@ -479,6 +494,28 @@ def cycle_batch_writer_check(raw_table, encrypted_table, initial_actions, initia
479494
del items
480495

481496

497+
def batch_write_item_unprocessed_check(
498+
encrypted,
499+
initial_item,
500+
write_transformer=_nop_transformer,
501+
table_name=TEST_TABLE_NAME,
502+
):
503+
"""Check that unprocessed items in a batch result are unencrypted."""
504+
items = _generate_items(initial_item, write_transformer)
505+
506+
request_items = {table_name: [{"PutRequest": {"Item": _item}} for _item in items]}
507+
_put_result = encrypted.batch_write_item(RequestItems=request_items)
508+
509+
# we expect results to include Unprocessed items, or the test case is invalid!
510+
unprocessed_items = _put_result["UnprocessedItems"]
511+
assert unprocessed_items != {}
512+
513+
unprocessed = [operation["PutRequest"]["Item"] for operation in _put_result["UnprocessedItems"][TEST_TABLE_NAME]]
514+
assert_list_of_items_contains(items, unprocessed, transformer=_nop_transformer)
515+
516+
del items
517+
518+
482519
def cycle_item_check(plaintext_item, crypto_config):
483520
"""Check that cycling (plaintext->encrypted->decrypted) an item has the expected results."""
484521
ciphertext_item = encrypt_python_item(plaintext_item, crypto_config)
@@ -527,6 +564,34 @@ def table_cycle_batch_writer_check(materials_provider, initial_actions, initial_
527564
cycle_batch_writer_check(table, e_table, initial_actions, initial_item)
528565

529566

567+
def table_batch_writer_unprocessed_items_check(
568+
materials_provider,
569+
initial_actions,
570+
initial_item,
571+
table_name,
572+
region_name=None
573+
):
574+
kwargs = {}
575+
if region_name is not None:
576+
kwargs["region_name"] = region_name
577+
resource = boto3.resource("dynamodb", **kwargs)
578+
table = resource.Table(table_name)
579+
580+
items = _generate_items(initial_item, _nop_transformer)
581+
request_items = {table_name: [{"PutRequest": {"Item": _item}} for _item in items]}
582+
583+
with patch.object(table.meta.client, "batch_write_item") as batch_write_mock:
584+
# Check that unprocessed items returned to a BatchWriter are successfully retried
585+
batch_write_mock.side_effect = [{"UnprocessedItems": request_items}, {'UnprocessedItems': {}}]
586+
e_table = EncryptedTable(table=table, materials_provider=materials_provider, attribute_actions=initial_actions)
587+
588+
with e_table.batch_writer() as writer:
589+
for item in items:
590+
writer.put_item(item)
591+
592+
del items
593+
594+
530595
def resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item, table_name, region_name=None):
531596
kwargs = {}
532597
if region_name is not None:
@@ -550,6 +615,31 @@ def resource_cycle_batch_items_check(materials_provider, initial_actions, initia
550615
assert not e_scan_result["Items"]
551616

552617

618+
def resource_batch_items_unprocessed_check(
619+
materials_provider,
620+
initial_actions,
621+
initial_item,
622+
table_name,
623+
region_name=None
624+
):
625+
kwargs = {}
626+
if region_name is not None:
627+
kwargs["region_name"] = region_name
628+
resource = boto3.resource('dynamodb', **kwargs)
629+
630+
with patch.object(resource, "batch_write_item", return_requestitems_as_unprocessed):
631+
e_resource = EncryptedResource(
632+
resource=resource, materials_provider=materials_provider, attribute_actions=initial_actions
633+
)
634+
635+
batch_write_item_unprocessed_check(
636+
encrypted=e_resource,
637+
initial_item=initial_item,
638+
write_transformer=dict_to_ddb,
639+
table_name=table_name,
640+
)
641+
642+
553643
def client_cycle_single_item_check(materials_provider, initial_actions, initial_item, table_name, region_name=None):
554644
check_attribute_actions = initial_actions.copy()
555645
check_attribute_actions.set_index_keys(*list(TEST_KEY.keys()))
@@ -600,6 +690,31 @@ def client_cycle_batch_items_check(materials_provider, initial_actions, initial_
600690
assert not e_scan_result["Items"]
601691

602692

693+
def client_batch_items_unprocessed_check(
694+
materials_provider,
695+
initial_actions,
696+
initial_item,
697+
table_name,
698+
region_name=None
699+
):
700+
kwargs = {}
701+
if region_name is not None:
702+
kwargs["region_name"] = region_name
703+
client = boto3.client('dynamodb', **kwargs)
704+
705+
with patch.object(client, "batch_write_item", return_requestitems_as_unprocessed):
706+
e_client = EncryptedClient(
707+
client=client, materials_provider=materials_provider, attribute_actions=initial_actions
708+
)
709+
710+
batch_write_item_unprocessed_check(
711+
encrypted=e_client,
712+
initial_item=initial_item,
713+
write_transformer=dict_to_ddb,
714+
table_name=table_name,
715+
)
716+
717+
603718
def client_cycle_batch_items_check_paginators(
604719
materials_provider, initial_actions, initial_item, table_name, region_name=None
605720
):

0 commit comments

Comments
 (0)