From 9d1dd79813258cca46e755a434d56703d29bec2c Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Wed, 22 Sep 2021 18:13:28 +0000 Subject: [PATCH] Rewrite deprecated DynamoDB2 tests (#3904) --- tests/test_core/test_socket.py | 1 + tests/test_dynamodb2/test_dynamodb.py | 55 ++++ .../test_dynamodb_table_with_range_key.py | 214 +++++++++++++ .../test_dynamodb_table_without_range_key.py | 290 ++++++++++++++++++ 4 files changed, 560 insertions(+) diff --git a/tests/test_core/test_socket.py b/tests/test_core/test_socket.py index 12d960ceb..c8b829ed4 100644 --- a/tests/test_core/test_socket.py +++ b/tests/test_core/test_socket.py @@ -15,6 +15,7 @@ class TestSocketPair(unittest.TestCase): self.assertIsNotNone(asyncio.get_event_loop()) + # Has boto3 equivalent @mock_dynamodb2_deprecated def test_socket_pair_deprecated(self): diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 6fe989475..d7ed12528 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -28,6 +28,7 @@ except ImportError: @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_list_tables(): name = "TestTable" @@ -45,7 +46,26 @@ def test_list_tables(): assert conn.list_tables()["TableNames"] == [name] +@mock_dynamodb2 +@pytest.mark.parametrize( + "names", + [[], ["TestTable"], ["TestTable1", "TestTable2"]], + ids=["no-table", "one-table", "multiple-tables"], +) +def test_list_tables_boto3(names): + conn = boto3.client("dynamodb", region_name="us-west-2") + for name in names: + conn.create_table( + TableName=name, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + conn.list_tables()["TableNames"].should.equal(names) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_list_tables_layer_1(): # Should make tables properly with boto @@ -68,7 +88,32 @@ def test_list_tables_layer_1(): res.should.equal(expected) +@mock_dynamodb2 +def test_list_tables_paginated(): + conn = boto3.client("dynamodb", region_name="us-west-2") + for name in ["name1", "name2", "name3"]: + conn.create_table( + TableName=name, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + res = conn.list_tables(Limit=2) + res.should.have.key("TableNames").equal(["name1", "name2"]) + res.should.have.key("LastEvaluatedTableName").equal("name2") + + res = conn.list_tables(Limit=1, ExclusiveStartTableName="name1") + res.should.have.key("TableNames").equal(["name2"]) + res.should.have.key("LastEvaluatedTableName").equal("name2") + + res = conn.list_tables(ExclusiveStartTableName="name1") + res.should.have.key("TableNames").equal(["name2", "name3"]) + res.shouldnt.have.key("LastEvaluatedTableName") + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_describe_missing_table(): conn = boto.dynamodb2.connect_to_region( @@ -78,6 +123,16 @@ def test_describe_missing_table(): conn.describe_table("messages") +@mock_dynamodb2 +def test_describe_missing_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + with pytest.raises(ClientError) as ex: + conn.describe_table(TableName="messages") + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal("Requested resource not found") + + @requires_boto_gte("2.9") @mock_dynamodb2 def test_list_table_tags(): diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py index 845c0587d..11cbf2c63 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py @@ -6,6 +6,7 @@ import boto import boto3 from boto3.dynamodb.conditions import Key from botocore.exceptions import ClientError +from datetime import datetime import sure # noqa from freezegun import freeze_time import pytest @@ -57,6 +58,7 @@ def iterate_results(res): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated @freeze_time("2012-01-14") def test_create_table(): @@ -89,7 +91,52 @@ def test_create_table(): table.describe().should.equal(expected) +@mock_dynamodb2 +def test_create_table_boto3(): + client = boto3.client("dynamodb", region_name="us-east-1") + client.create_table( + TableName="messages", + KeySchema=[ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5}, + ) + actual = client.describe_table(TableName="messages")["Table"] + + actual.should.have.key("AttributeDefinitions").equal( + [ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + ] + ) + actual.should.have.key("CreationDateTime").be.a(datetime) + actual.should.have.key("GlobalSecondaryIndexes").equal([]) + actual.should.have.key("LocalSecondaryIndexes").equal([]) + actual.should.have.key("ProvisionedThroughput").equal( + {"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5} + ) + actual.should.have.key("TableSizeBytes").equal(0) + actual.should.have.key("TableName").equal("messages") + actual.should.have.key("TableStatus").equal("ACTIVE") + actual.should.have.key("TableArn").equal( + "arn:aws:dynamodb:us-east-1:123456789011:table/messages" + ) + actual.should.have.key("KeySchema").equal( + [ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ] + ) + actual.should.have.key("ItemCount").equal(0) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated @freeze_time("2012-01-14") def test_create_table_with_local_index(): @@ -132,7 +179,75 @@ def test_create_table_with_local_index(): table.describe().should.equal(expected) +@mock_dynamodb2 +def test_create_table_with_local_index_boto3(): + client = boto3.client("dynamodb", region_name="us-east-1") + client.create_table( + TableName="messages", + KeySchema=[ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + {"AttributeName": "threads", "AttributeType": "S"}, + ], + LocalSecondaryIndexes=[ + { + "IndexName": "threads_index", + "KeySchema": [ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "threads", "KeyType": "RANGE"}, + ], + "Projection": {"ProjectionType": "ALL"}, + } + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5}, + ) + actual = client.describe_table(TableName="messages")["Table"] + + actual.should.have.key("AttributeDefinitions").equal( + [ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + {"AttributeName": "threads", "AttributeType": "S"}, + ] + ) + actual.should.have.key("CreationDateTime").be.a(datetime) + actual.should.have.key("GlobalSecondaryIndexes").equal([]) + actual.should.have.key("LocalSecondaryIndexes").equal( + [ + { + "IndexName": "threads_index", + "KeySchema": [ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "threads", "KeyType": "RANGE"}, + ], + "Projection": {"ProjectionType": "ALL"}, + } + ] + ) + actual.should.have.key("ProvisionedThroughput").equal( + {"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5} + ) + actual.should.have.key("TableSizeBytes").equal(0) + actual.should.have.key("TableName").equal("messages") + actual.should.have.key("TableStatus").equal("ACTIVE") + actual.should.have.key("TableArn").equal( + "arn:aws:dynamodb:us-east-1:123456789011:table/messages" + ) + actual.should.have.key("KeySchema").equal( + [ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ] + ) + actual.should.have.key("ItemCount").equal(0) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -145,6 +260,7 @@ def test_delete_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_update_table_throughput(): table = create_table() @@ -164,6 +280,7 @@ def test_update_table_throughput(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_add_and_describe_and_update(): table = create_table() @@ -209,6 +326,7 @@ def test_item_add_and_describe_and_update(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_partial_save(): table = create_table() @@ -238,6 +356,7 @@ def test_item_partial_save(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_put_without_table(): table = Table("undeclared-table") @@ -252,6 +371,7 @@ def test_item_put_without_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_missing_item(): table = create_table() @@ -262,6 +382,7 @@ def test_get_missing_item(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_item_with_undeclared_table(): table = Table("undeclared-table") @@ -271,6 +392,7 @@ def test_get_item_with_undeclared_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_item_without_range_key(): table = Table.create( @@ -287,7 +409,35 @@ def test_get_item_without_range_key(): ) +@mock_dynamodb2 +def test_get_item_without_range_key_boto3(): + client = boto3.resource("dynamodb", region_name="us-east-1") + table = client.create_table( + TableName="messages", + KeySchema=[ + {"AttributeName": "id", "KeyType": "HASH"}, + {"AttributeName": "subject", "KeyType": "RANGE"}, + ], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "subject", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5}, + ) + + hash_key = 3241526475 + range_key = 1234567890987 + table.put_item(Item={"id": hash_key, "subject": range_key}) + + with pytest.raises(ClientError) as ex: + table.get_item(Key={"id": hash_key}) + + ex.value.response["Error"]["Code"].should.equal("ValidationException") + ex.value.response["Error"]["Message"].should.equal("Validation Exception") + + @requires_boto_gte("2.30.0") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_item(): table = create_table() @@ -311,6 +461,7 @@ def test_delete_item(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_item_with_undeclared_table(): table = Table("undeclared-table") @@ -325,6 +476,7 @@ def test_delete_item_with_undeclared_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query(): table = create_table() @@ -384,6 +536,7 @@ def test_query(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_with_undeclared_table(): table = Table("undeclared") @@ -394,6 +547,7 @@ def test_query_with_undeclared_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_scan(): table = create_table() @@ -449,6 +603,7 @@ def test_scan(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_scan_with_undeclared_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -464,6 +619,7 @@ def test_scan_with_undeclared_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_write_batch(): table = create_table() @@ -495,6 +651,7 @@ def test_write_batch(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_batch_read(): table = create_table() @@ -539,6 +696,7 @@ def test_batch_read(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_key_fields(): table = create_table() @@ -547,6 +705,7 @@ def test_get_key_fields(): @mock_dynamodb2_deprecated +# Has boto3 equivalent def test_create_with_global_indexes(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -582,6 +741,7 @@ def test_create_with_global_indexes(): ) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_with_global_indexes(): table = Table.create( @@ -617,6 +777,7 @@ def test_query_with_global_indexes(): list(results).should.have.length_of(0) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_with_local_indexes(): table = create_table_with_local_indexes() @@ -639,6 +800,7 @@ def test_query_with_local_indexes(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_filter_eq(): table = create_table_with_local_indexes() @@ -672,6 +834,7 @@ def test_query_filter_eq(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_filter_lt(): table = create_table_with_local_indexes() @@ -707,6 +870,7 @@ def test_query_filter_lt(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_filter_gt(): table = create_table_with_local_indexes() @@ -741,6 +905,7 @@ def test_query_filter_gt(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_filter_lte(): table = create_table_with_local_indexes() @@ -775,6 +940,7 @@ def test_query_filter_lte(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_filter_gte(): table = create_table_with_local_indexes() @@ -808,7 +974,51 @@ def test_query_filter_gte(): list(results).should.have.length_of(2) +@mock_dynamodb2 +def test_query_filter_boto3(): + table_schema = { + "KeySchema": [ + {"AttributeName": "pk", "KeyType": "HASH"}, + {"AttributeName": "sk", "KeyType": "RANGE"}, + ], + "AttributeDefinitions": [ + {"AttributeName": "pk", "AttributeType": "S"}, + {"AttributeName": "sk", "AttributeType": "S"}, + ], + } + + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") + table = dynamodb.create_table( + TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema + ) + + for i in range(0, 3): + table.put_item( + Item={"pk": "pk".format(i), "sk": "sk-{}".format(i),} + ) + + res = table.query(KeyConditionExpression=Key("pk").eq("pk")) + res["Items"].should.have.length_of(3) + + res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lt("sk-1")) + res["Items"].should.have.length_of(1) + res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}]) + + res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").lte("sk-1")) + res["Items"].should.have.length_of(2) + res["Items"].should.equal([{"pk": "pk", "sk": "sk-0"}, {"pk": "pk", "sk": "sk-1"}]) + + res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gt("sk-1")) + res["Items"].should.have.length_of(1) + res["Items"].should.equal([{"pk": "pk", "sk": "sk-2"}]) + + res = table.query(KeyConditionExpression=Key("pk").eq("pk") & Key("sk").gte("sk-1")) + res["Items"].should.have.length_of(2) + res["Items"].should.equal([{"pk": "pk", "sk": "sk-1"}, {"pk": "pk", "sk": "sk-2"}]) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_non_hash_range_key(): table = create_table_with_local_indexes() @@ -845,6 +1055,7 @@ def test_query_non_hash_range_key(): results.should.have.length_of(2) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_reverse_query(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -862,6 +1073,7 @@ def test_reverse_query(): [r["created_at"] for r in results].should.equal(expected) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_lookup(): from decimal import Decimal @@ -881,6 +1093,7 @@ def test_lookup(): message.get("test_range").should.equal(Decimal(range_key)) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_failed_overwrite(): table = Table.create( @@ -910,6 +1123,7 @@ def test_failed_overwrite(): dict(returned_item).should.equal(data4) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_conflicting_writes(): table = Table.create("messages", schema=[HashKey("id"), RangeKey("range")]) diff --git a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py index eaa392df3..bb7e51306 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_without_range_key.py @@ -3,9 +3,12 @@ from __future__ import unicode_literals import boto import boto3 from boto3.dynamodb.conditions import Key +import pytest import sure # noqa +from datetime import datetime from freezegun import freeze_time from boto.exception import JSONResponseError +from botocore.exceptions import ClientError from moto import mock_dynamodb2, mock_dynamodb2_deprecated from tests.helpers import requires_boto_gte import botocore @@ -27,6 +30,7 @@ def create_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated @freeze_time("2012-01-14") def test_create_table(): @@ -59,7 +63,71 @@ def test_create_table(): conn.describe_table("messages").should.equal(expected) +@mock_dynamodb2 +def test_create_table_boto3(): + client = boto3.client("dynamodb", region_name="us-east-1") + client.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "gsi_col", "AttributeType": "S"}, + ], + ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1}, + GlobalSecondaryIndexes=[ + { + "IndexName": "test_gsi", + "KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1, + }, + } + ], + ) + + actual = client.describe_table(TableName="messages")["Table"] + + actual.should.have.key("AttributeDefinitions").equal( + [ + {"AttributeName": "id", "AttributeType": "S"}, + {"AttributeName": "gsi_col", "AttributeType": "S"}, + ] + ) + actual.should.have.key("CreationDateTime").be.a(datetime) + actual.should.have.key("GlobalSecondaryIndexes").equal( + [ + { + "IndexName": "test_gsi", + "KeySchema": [{"AttributeName": "gsi_col", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "IndexStatus": "ACTIVE", + "ProvisionedThroughput": { + "ReadCapacityUnits": 1, + "WriteCapacityUnits": 1, + }, + } + ] + ) + actual.should.have.key("LocalSecondaryIndexes").equal([]) + actual.should.have.key("ProvisionedThroughput").equal( + {"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 1} + ) + actual.should.have.key("TableSizeBytes").equal(0) + actual.should.have.key("TableName").equal("messages") + actual.should.have.key("TableStatus").equal("ACTIVE") + actual.should.have.key("TableArn").equal( + "arn:aws:dynamodb:us-east-1:123456789011:table/messages" + ) + actual.should.have.key("KeySchema").equal( + [{"AttributeName": "id", "KeyType": "HASH"}] + ) + actual.should.have.key("ItemCount").equal(0) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_table(): create_table() @@ -72,7 +140,30 @@ def test_delete_table(): conn.delete_table.when.called_with("messages").should.throw(JSONResponseError) +@mock_dynamodb2 +def test_delete_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + conn.list_tables()["TableNames"].should.have.length_of(1) + + conn.delete_table(TableName="messages") + conn.list_tables()["TableNames"].should.have.length_of(0) + + with pytest.raises(ClientError) as ex: + conn.delete_table(TableName="messages") + + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal("Requested resource not found") + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_update_table_throughput(): table = create_table() @@ -85,7 +176,28 @@ def test_update_table_throughput(): table.throughput["write"].should.equal(6) +@mock_dynamodb2 +def test_update_table_throughput_boto3(): + conn = boto3.resource("dynamodb", region_name="us-west-2") + table = conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + table.provisioned_throughput["ReadCapacityUnits"].should.equal(5) + table.provisioned_throughput["WriteCapacityUnits"].should.equal(5) + + table.update( + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 6} + ) + + table.provisioned_throughput["ReadCapacityUnits"].should.equal(5) + table.provisioned_throughput["WriteCapacityUnits"].should.equal(6) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_add_and_describe_and_update(): table = create_table() @@ -121,7 +233,44 @@ def test_item_add_and_describe_and_update(): ) +@mock_dynamodb2 +def test_item_add_and_describe_and_update_boto3(): + conn = boto3.resource("dynamodb", region_name="us-west-2") + table = conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + data = { + "id": "LOLCat Forum", + "Body": "http://url_to_lolcat.gif", + "SentBy": "User A", + } + + table.put_item(Item=data) + returned_item = table.get_item(Key={"id": "LOLCat Forum"}) + returned_item.shouldnt.have.key("ConsumedCapacity") + + dict(returned_item["Item"]).should.equal( + {"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User A",} + ) + + table.update_item( + Key={"id": "LOLCat Forum"}, + UpdateExpression="SET SentBy=:user", + ExpressionAttributeValues={":user": "User B"}, + ) + + returned_item = table.get_item(Key={"id": "LOLCat Forum"}) + returned_item["Item"].should.equal( + {"id": "LOLCat Forum", "Body": "http://url_to_lolcat.gif", "SentBy": "User B",} + ) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_partial_save(): table = create_table() @@ -149,6 +298,7 @@ def test_item_partial_save(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_item_put_without_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -163,7 +313,27 @@ def test_item_put_without_table(): ).should.throw(JSONResponseError) +@mock_dynamodb2 +def test_item_put_without_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + + with pytest.raises(ClientError) as ex: + conn.put_item( + TableName="messages", + Item={ + "forum_name": {"S": "LOLCat Forum"}, + "Body": {"S": "http://url_to_lolcat.gif"}, + "SentBy": {"S": "User A"}, + }, + ) + + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal("Requested resource not found") + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_item_with_undeclared_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -173,7 +343,20 @@ def test_get_item_with_undeclared_table(): ).should.throw(JSONResponseError) +@mock_dynamodb2 +def test_get_item_with_undeclared_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + + with pytest.raises(ClientError) as ex: + conn.get_item(TableName="messages", Key={"forum_name": {"S": "LOLCat Forum"}}) + + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal("Requested resource not found") + + @requires_boto_gte("2.30.0") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_item(): table = create_table() @@ -198,7 +381,35 @@ def test_delete_item(): item.delete().should.equal(True) +@mock_dynamodb2 +def test_delete_item_boto3(): + conn = boto3.resource("dynamodb", region_name="us-west-2") + table = conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + item_data = { + "id": "LOLCat Forum", + "Body": "http://url_to_lolcat.gif", + "SentBy": "User A", + "ReceivedTime": "12/9/2011 11:36:03 PM", + } + table.put_item(Item=item_data) + + table.item_count.should.equal(1) + + table.delete_item(Key={"id": "LOLCat Forum"}) + + table.item_count.should.equal(0) + + table.delete_item(Key={"id": "LOLCat Forum"}) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_delete_item_with_undeclared_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -208,7 +419,24 @@ def test_delete_item_with_undeclared_table(): ).should.throw(JSONResponseError) +@mock_dynamodb2 +def test_delete_item_with_undeclared_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + + with pytest.raises(ClientError) as ex: + conn.delete_item( + TableName="messages", Key={"forum_name": {"S": "LOLCat Forum"}} + ) + + ex.value.response["Error"]["Code"].should.equal("ConditionalCheckFailedException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal( + "A condition specified in the operation could not be evaluated." + ) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query(): table = create_table() @@ -229,6 +457,7 @@ def test_query(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_query_with_undeclared_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -245,6 +474,7 @@ def test_query_with_undeclared_table(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_scan(): table = create_table() @@ -296,6 +526,7 @@ def test_scan(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_scan_with_undeclared_table(): conn = boto.dynamodb2.layer1.DynamoDBConnection() @@ -311,7 +542,20 @@ def test_scan_with_undeclared_table(): ).should.throw(JSONResponseError) +@mock_dynamodb2 +def test_scan_with_undeclared_table_boto3(): + conn = boto3.client("dynamodb", region_name="us-west-2") + + with pytest.raises(ClientError) as ex: + conn.scan(TableName="messages") + + ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException") + ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.value.response["Error"]["Message"].should.equal("Requested resource not found") + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_write_batch(): table = create_table() @@ -344,6 +588,7 @@ def test_write_batch(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_batch_read(): table = create_table() @@ -382,6 +627,7 @@ def test_batch_read(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_key_fields(): table = create_table() @@ -389,7 +635,21 @@ def test_get_key_fields(): kf[0].should.equal("forum_name") +@mock_dynamodb2 +def test_get_key_schema(): + conn = boto3.resource("dynamodb", region_name="us-west-2") + table = conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + table.key_schema.should.equal([{"AttributeName": "id", "KeyType": "HASH"}]) + + @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_missing_item(): table = create_table() @@ -397,6 +657,7 @@ def test_get_missing_item(): @requires_boto_gte("2.9") +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_get_special_item(): table = Table.create( @@ -411,6 +672,7 @@ def test_get_special_item(): dict(returned_item).should.equal(data) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_update_item_remove(): conn = boto.dynamodb2.connect_to_region("us-east-1") @@ -427,6 +689,7 @@ def test_update_item_remove(): dict(returned_item).should.equal({"username": "steve"}) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_update_item_nested_remove(): conn = boto.dynamodb2.connect_to_region("us-east-1") @@ -478,6 +741,7 @@ def test_update_item_double_nested_remove(): dict(returned_item["Item"]).should.equal(expected_item) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_update_item_set(): conn = boto.dynamodb2.connect_to_region("us-east-1") @@ -498,6 +762,31 @@ def test_update_item_set(): dict(returned_item).should.equal({"username": "steve", "foo": "bar", "blah": "baz"}) +@mock_dynamodb2 +def test_update_item_set_boto3(): + conn = boto3.resource("dynamodb", region_name="us-east-1") + table = conn.create_table( + TableName="messages", + KeySchema=[{"AttributeName": "username", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "username", "AttributeType": "S"}], + BillingMode="PAY_PER_REQUEST", + ) + + data = {"username": "steve", "SentBy": "User A"} + table.put_item(Item=data) + key_map = {"username": "steve"} + + table.update_item( + Key=key_map, + UpdateExpression="SET foo=:bar, blah=:baz REMOVE SentBy", + ExpressionAttributeValues={":bar": "bar", ":baz": "baz"}, + ) + + returned_item = table.get_item(Key=key_map)["Item"] + dict(returned_item).should.equal({"username": "steve", "foo": "bar", "blah": "baz"}) + + +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_failed_overwrite(): table = Table.create( @@ -525,6 +814,7 @@ def test_failed_overwrite(): dict(returned_item).should.equal(data4) +# Has boto3 equivalent @mock_dynamodb2_deprecated def test_conflicting_writes(): table = Table.create("messages", schema=[HashKey("id")])