24
24
import pytest
25
25
from boto3 .dynamodb .types import Binary
26
26
from botocore .exceptions import NoRegionError
27
+ from mock import patch
27
28
from moto import mock_dynamodb2
28
29
29
30
from dynamodb_encryption_sdk .delegated_keys .jce import JceNameLocalDelegatedKey
@@ -336,6 +337,12 @@ def diverse_item():
336
337
_reserved_attributes = set ([attr .value for attr in ReservedAttributes ])
337
338
338
339
340
+ def return_requestitems_as_unprocessed (* args , ** kwargs ):
341
+ return {
342
+ "UnprocessedItems" : kwargs ['RequestItems' ]
343
+ }
344
+
345
+
339
346
def check_encrypted_item (plaintext_item , ciphertext_item , attribute_actions ):
340
347
# Verify that all expected attributes are present
341
348
ciphertext_attributes = set (ciphertext_item .keys ())
@@ -374,12 +381,20 @@ def _nop_transformer(item):
374
381
return item
375
382
376
383
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
+
377
390
def assert_equal_lists_of_items (actual , expected , transformer = _nop_transformer ):
378
391
assert len (actual ) == len (expected )
392
+ assert_items_exist_in_list (actual , expected , transformer )
379
393
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 )
383
398
384
399
385
400
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
479
494
del items
480
495
481
496
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
+
482
519
def cycle_item_check (plaintext_item , crypto_config ):
483
520
"""Check that cycling (plaintext->encrypted->decrypted) an item has the expected results."""
484
521
ciphertext_item = encrypt_python_item (plaintext_item , crypto_config )
@@ -527,6 +564,34 @@ def table_cycle_batch_writer_check(materials_provider, initial_actions, initial_
527
564
cycle_batch_writer_check (table , e_table , initial_actions , initial_item )
528
565
529
566
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
+
530
595
def resource_cycle_batch_items_check (materials_provider , initial_actions , initial_item , table_name , region_name = None ):
531
596
kwargs = {}
532
597
if region_name is not None :
@@ -550,6 +615,31 @@ def resource_cycle_batch_items_check(materials_provider, initial_actions, initia
550
615
assert not e_scan_result ["Items" ]
551
616
552
617
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
+
553
643
def client_cycle_single_item_check (materials_provider , initial_actions , initial_item , table_name , region_name = None ):
554
644
check_attribute_actions = initial_actions .copy ()
555
645
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_
600
690
assert not e_scan_result ["Items" ]
601
691
602
692
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
+
603
718
def client_cycle_batch_items_check_paginators (
604
719
materials_provider , initial_actions , initial_item , table_name , region_name = None
605
720
):
0 commit comments