Skip to content

Commit 7577d4b

Browse files
author
Lucas McDonald
committed
sync
1 parent a60bbb8 commit 7577d4b

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
import boto3
4+
import types
5+
import aws_dbesdk_dynamodb_test_vectors.internaldafny.generated.CreateInterceptedDDBClient
6+
import aws_cryptography_internal_dynamodb.internaldafny.extern
7+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.dafny_to_smithy import aws_cryptography_dbencryptionsdk_dynamodb_DynamoDbTablesEncryptionConfig
8+
from aws_dbesdk_dynamodb.encrypted.resource import EncryptedResource
9+
from smithy_dafny_standard_library.internaldafny.generated import Wrappers
10+
from aws_dbesdk_dynamodb.smithygenerated.aws_cryptography_dbencryptionsdk_dynamodb.errors import _smithy_error_to_dafny_error
11+
from aws_dbesdk_dynamodb_test_vectors.waiting_boto3_ddb_client import WaitingLocalDynamoClient
12+
from aws_dbesdk_dynamodb.internal.resource_to_client import ResourceShapeToClientShapeConverter
13+
from aws_dbesdk_dynamodb.internal.client_to_resource import ClientShapeToResourceShapeConverter
14+
15+
class DynamoDBClientWrapperForDynamoDBResource:
16+
"""
17+
Internal-only wrapper class for DBESDK TestVectors.
18+
19+
TestVectors Dafny code only knows how to interact with DynamoDB clients.
20+
However, Python DDBEC and DBESDK have an EncryptedResource class that wraps boto3 DynamoDB Resources.
21+
These classes create EncryptedTables that wrap boto3 DynamoDB Table Resources.
22+
This class interfaces between Dafny TestVectors' DynamoDB client-calling code
23+
and Python DBESDK's EncryptedResource/EncryptedTable classes.
24+
25+
This class defers to a boto3 client for create_table and delete_table,
26+
which are not supported on boto3 DynamoDB Table resources.
27+
28+
TODO: Transact not supported on table. What do?
29+
"""
30+
31+
def __init__(self, resource, client):
32+
self._resource = resource
33+
self._client = client
34+
self._client_shape_to_resource_shape_converter = ClientShapeToResourceShapeConverter()
35+
self._resource_shape_to_client_shape_converter = ResourceShapeToClientShapeConverter()
36+
37+
def batch_write_item(self, **kwargs):
38+
# The input here is from the DBESDK TestVectors, which is in the shape of a client request.
39+
# Convert the client request to a resource request to be passed to the table.
40+
resource_input = self._client_shape_to_resource_shape_converter.batch_write_item_request(kwargs)
41+
resource_output = self._resource.batch_write_item(**resource_input)
42+
client_output = self._resource_shape_to_client_shape_converter.batch_write_item_response(resource_output)
43+
return client_output
44+
45+
def batch_get_item(self, **kwargs):
46+
resource_input = self._client_shape_to_resource_shape_converter.batch_get_item_request(kwargs)
47+
resource_output = self._resource.batch_get_item(**resource_input)
48+
client_output = self._resource_shape_to_client_shape_converter.batch_get_item_response(resource_output)
49+
return client_output
50+
51+
def scan(self, **kwargs):
52+
# Resources don't have scan, but EncryptedResources can provide EncryptedTables that do support scan.
53+
# This path tests that the EncryptedTables provided by EncryptedResources can used for scan.
54+
table_name = kwargs["TableName"]
55+
# Note: Any ConditionExpression strings are not converted to boto3 Condition objects
56+
# and are passed as-is to the resource.
57+
# They absolutely could be converted, but that is tested in the boto3 Table tests.
58+
# Not doing this conversion here expands test coverage to both cases.
59+
table_input = self._client_shape_to_resource_shape_converter.scan_request(kwargs)
60+
encrypted_table = self._resource.Table(table_name)
61+
table_output = encrypted_table.scan(**table_input)
62+
table_shape_converter = ResourceShapeToClientShapeConverter(table_name=table_name)
63+
client_output = table_shape_converter.scan_response(table_output)
64+
return client_output
65+
66+
def put_item(self, **kwargs):
67+
# Resources don't have put_item, but EncryptedResources can provide EncryptedTables that do support put_item.
68+
# This path tests that the EncryptedTables provided by EncryptedResources can used for put_item.
69+
table_name = kwargs["TableName"]
70+
table_input = self._client_shape_to_resource_shape_converter.put_item_request(kwargs)
71+
encrypted_table = self._resource.Table(table_name)
72+
table_output = encrypted_table.put_item(**table_input)
73+
table_shape_converter = ResourceShapeToClientShapeConverter(table_name=table_name)
74+
client_output = table_shape_converter.put_item_response(table_output)
75+
return client_output
76+
77+
def get_item(self, **kwargs):
78+
# Resources don't have get_item, but EncryptedResources can provide EncryptedTables that do support get_item.
79+
# This path tests that the EncryptedTables provided by EncryptedResources can used for get_item.
80+
table_name = kwargs["TableName"]
81+
table_input = self._client_shape_to_resource_shape_converter.get_item_request(kwargs)
82+
encrypted_table = self._resource.Table(table_name)
83+
table_output = encrypted_table.get_item(**table_input)
84+
table_shape_converter = ResourceShapeToClientShapeConverter(table_name=table_name)
85+
client_output = table_shape_converter.get_item_response(table_output)
86+
return client_output
87+
88+
def query(self, **kwargs):
89+
# Resources don't have query, but EncryptedResources can provide EncryptedTables that do support query.
90+
# This path tests that the EncryptedTables provided by EncryptedResources can used for query.
91+
table_name = kwargs["TableName"]
92+
# Note: Any ConditionExpression strings are not converted to boto3 Condition objects
93+
# and are passed as-is to the resource.
94+
# They absolutely could be converted, but that is tested in the boto3 Table tests.
95+
# Not doing this conversion here expands test coverage to both cases.
96+
table_input = self._client_shape_to_resource_shape_converter.query_request(kwargs)
97+
encrypted_table = self._resource.Table(table_name)
98+
table_output = encrypted_table.query(**table_input)
99+
table_shape_converter = ResourceShapeToClientShapeConverter(table_name=table_name)
100+
client_output = table_shape_converter.query_response(table_output)
101+
return client_output
102+
103+
def transact_get_items(self, **kwargs):
104+
raise NotImplementedError("transact_get_items not supported on resources")
105+
106+
def transact_write_items(self, **kwargs):
107+
raise NotImplementedError("transact_get_items not supported on resources")
108+
109+
def delete_table(self, **kwargs):
110+
# Resources don't have delete_table. Plus, DBESDK doesn't intercept DeleteTable calls.
111+
# TestVectors only use this to ensure a new, clean table is created for each test.
112+
# Defer to the underlying boto3 client to delete the table.
113+
return self._client.delete_table(**kwargs)
114+
115+
def create_table(self, **kwargs):
116+
# Resources don't have create_table. Plus, DBESDK doesn't intercept CreateTable calls.
117+
# TestVectors only use this to ensure a new, clean table is created for each test.
118+
# Defer to the underlying boto3 client to create a table.
119+
return self._client.create_table(**kwargs)
120+
121+
122+
class default__:
123+
@staticmethod
124+
def CreateVanillaDDBClient():
125+
try:
126+
return aws_cryptography_internal_dynamodb.internaldafny.extern.Com_Amazonaws_Dynamodb.default__.DynamoDBClient(WaitingLocalDynamoClient())
127+
except Exception as e:
128+
return Wrappers.Result_Failure(_smithy_error_to_dafny_error(e))
129+
130+
@staticmethod
131+
def CreateInterceptedDDBClient(dafny_encryption_config):
132+
try:
133+
native_encryption_config = aws_cryptography_dbencryptionsdk_dynamodb_DynamoDbTablesEncryptionConfig(dafny_encryption_config)
134+
boto3_client = WaitingLocalDynamoClient()
135+
table_config_names = list(native_encryption_config.table_encryption_configs.keys())
136+
if len(table_config_names) > 1:
137+
raise ValueError("TODO more than 1 table; need EncryptedTablesManager")
138+
# For TestVectors, use local DynamoDB endpoint
139+
resource = boto3.resource('dynamodb', endpoint_url="http://localhost:8000")
140+
encrypted_resource = EncryptedResource(resource = resource, encryption_config = native_encryption_config)
141+
wrapped_encrypted_resource = DynamoDBClientWrapperForDynamoDBResource(resource = encrypted_resource, client = boto3_client)
142+
return aws_cryptography_internal_dynamodb.internaldafny.extern.Com_Amazonaws_Dynamodb.default__.DynamoDBClient(wrapped_encrypted_resource)
143+
except Exception as e:
144+
return Wrappers.Result_Failure(_smithy_error_to_dafny_error(e))
145+
146+
aws_dbesdk_dynamodb_test_vectors.internaldafny.generated.CreateInterceptedDDBClient.default__ = default__
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0

TestVectors/runtimes/python/test/resource/__init__.py

Whitespace-only changes.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Wrapper file for executing Dafny tests from pytest.
5+
This allows us to import modules required by Dafny-generated tests
6+
before executing Dafny-generated tests.
7+
pytest will find and execute the `test_dafny` method below,
8+
which will execute the `internaldafny_test_executor.py` file in the `dafny` directory.
9+
"""
10+
11+
import sys
12+
from functools import partial
13+
14+
# Different from standard test_dafny_wrapper due to weird test structure.
15+
test_dir = '/'.join(__file__.split("/")[:-2])
16+
17+
sys.path.append(test_dir + "/internaldafny/extern")
18+
sys.path.append(test_dir + "/internaldafny/generated")
19+
20+
# Import extern to use an EncryptedResource as the wrapped DBESDK client.
21+
import aws_dbesdk_dynamodb_test_vectors.internaldafny.extern.CreateInterceptedDDBResource
22+
# Import extern to use the ItemEncryptor with Python dictionary-formatted items.
23+
# (EncryptedResources use Python dictionary-formatted items.)
24+
import aws_dbesdk_dynamodb_test_vectors.internaldafny.extern.CreateWrappedDictItemEncryptor
25+
26+
# Remove invalid tests.
27+
# Supported operations on Resources that are also supported by DBESDK are:
28+
# - batch_get_item
29+
# - batch_write_item
30+
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/service-resource/index.html
31+
#
32+
# However, Resources can provide Tables.
33+
# Unsupported operations on Resources are that are supported by provided Tables are:
34+
# - put_item
35+
# - get_item
36+
# - query
37+
# - scan
38+
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/table/index.html#DynamoDB.Table
39+
# These operations will be tested on EncryptedResources via provided EncryptedTables.
40+
#
41+
# Unsupported operations on both Resources and Tables are that are supported by DBESDK are:
42+
# - transact_get_items
43+
# - transact_write_items
44+
# Remove any tests that call unsupported operations by overriding the test method to do nothing..
45+
# If more tests that call these operations are added, remove them below.
46+
# If the list below becomes unmaintainable, or if other languages add clients with unsupported operations,
47+
# refactor the Dafny code to conditionally call tests based on whether the client supports the operation under test.
48+
49+
def EmptyTest(*args, **kwargs):
50+
print(f"Skipping test {kwargs['test_name']} because {kwargs['reason']}")
51+
52+
aws_dbesdk_dynamodb_test_vectors.internaldafny.generated.DdbEncryptionTestVectors.TestVectorConfig.BasicIoTestTransactWriteItems = partial(
53+
EmptyTest,
54+
test_name="BasicIoTestTransactWriteItems",
55+
reason="neither DDB resources nor DDB tables support transact_write_items"
56+
)
57+
aws_dbesdk_dynamodb_test_vectors.internaldafny.generated.DdbEncryptionTestVectors.TestVectorConfig.BasicIoTestTransactGetItems = partial(
58+
EmptyTest,
59+
test_name="BasicIoTestTransactGetItems",
60+
reason="neither DDB resources nor DDB tables support transact_get_items"
61+
)
62+
63+
def test_dafny():
64+
from ..internaldafny.generated import __main__

0 commit comments

Comments
 (0)