Repository: getmoto/moto
Pull request #6521 (2023-07-14 10:08:23)

Techdebt: Replace sure with regular assertions in DynamoDB v20111205
See #6035 


Patch:
diff --git a/moto/logs/models.py b/moto/logs/models.py
index f4a76433e..368a332e5 100644
--- a/moto/logs/models.py
+++ b/moto/logs/models.py
@@ -18,6 +18,34 @@ from .utils import PAGINATION_MODEL, EventMessageFilter
 MAX_RESOURCE_POLICIES_PER_REGION = 10
 
 
+class Destination(BaseModel):
+    def __init__(
+        self,
+        account_id: str,
+        region: str,
+        destination_name: str,
+        role_arn: str,
+        target_arn: str,
+        access_policy: Optional[str] = None,
+    ):
+        self.access_policy = access_policy
+        self.arn = f"arn:aws:logs:{region}:{account_id}:destination:{destination_name}"
+        self.creation_time = int(unix_time_millis())
+        self.destination_name = destination_name
+        self.role_arn = role_arn
+        self.target_arn = target_arn
+
+    def to_dict(self) -> Dict[str, Any]:
+        return {
+            "accessPolicy": self.access_policy,
+            "arn": self.arn,
+            "creationTime": self.creation_time,
+            "destinationName": self.destination_name,
+            "roleArn": self.role_arn,
+            "targetArn": self.target_arn,
+        }
+
+
 class LogQuery(BaseModel):
     def __init__(self, query_id: str, start_time: str, end_time: str, query: str):
         self.query_id = query_id
@@ -655,6 +683,7 @@ class LogsBackend(BaseBackend):
         self.filters = MetricFilters()
         self.queries: Dict[str, LogQuery] = dict()
         self.resource_policies: Dict[str, LogResourcePolicy] = dict()
+        self.destinations: Dict[str, Destination] = dict()
 
     @staticmethod
     def default_vpc_endpoint_service(
@@ -706,6 +735,60 @@ class LogsBackend(BaseBackend):
 
         return groups
 
+    def get_destination(self, destination_name: str) -> Destination:
+        for destination in self.destinations:
+            if self.destinations[destination].destination_name == destination_name:
+                return self.destinations[destination]
+        raise ResourceNotFoundException()
+
+    def put_destination(
+        self, destination_name: str, role_arn: str, target_arn: str
+    ) -> Destination:
+        for _, destination in self.destinations.items():
+            if destination.destination_name == destination_name:
+                if role_arn:
+                    destination.role_arn = role_arn
+                if target_arn:
+                    destination.target_arn = target_arn
+                return destination
+        destination = Destination(
+            self.account_id, self.region_name, destination_name, role_arn, target_arn
+        )
+        self.destinations[destination.arn] = destination
+        return destination
+
+    def delete_destination(self, destination_name: str) -> None:
+        destination = self.get_destination(destination_name)
+        self.destinations.pop(destination.arn)
+        return
+
+    def describe_destinations(
+        self, destination_name_prefix: str, limit: int, next_token: Optional[int] = None
+    ) -> Tuple[List[Dict[str, Any]], Optional[int]]:
+        if limit > 50:
+            raise InvalidParameterException(
+                constraint="Member must have value less than or equal to 50",
+                parameter="limit",
+                value=limit,
+            )
+
+        result = []
+        for destination in self.destinations:
+            result.append(self.destinations[destination].to_dict())
+        if next_token:
+            result = result[: int(next_token)]
+        result = [
+            destination
+            for destination in result
+            if destination["destinationName"].startswith(destination_name_prefix)
+        ]
+        return result, next_token
+
+    def put_destination_policy(self, destination_name: str, access_policy: str) -> None:
+        destination = self.get_destination(destination_name)
+        destination.access_policy = access_policy
+        return
+
     def create_log_stream(self, log_group_name: str, log_stream_name: str) -> None:
         if log_group_name not in self.groups:
             raise ResourceNotFoundException()
diff --git a/moto/logs/responses.py b/moto/logs/responses.py
index 62e9f5c55..836166e83 100644
--- a/moto/logs/responses.py
+++ b/moto/logs/responses.py
@@ -183,6 +183,41 @@ class LogsResponse(BaseResponse):
             result["nextToken"] = next_token
         return json.dumps(result)
 
+    def put_destination(self) -> str:
+        destination_name = self._get_param("destinationName")
+        role_arn = self._get_param("roleArn")
+        target_arn = self._get_param("targetArn")
+
+        destination = self.logs_backend.put_destination(
+            destination_name, role_arn, target_arn
+        )
+        result = {"destination": destination.to_dict()}
+        return json.dumps(result)
+
+    def delete_destination(self) -> str:
+        destination_name = self._get_param("destinationName")
+        self.logs_backend.delete_destination(destination_name)
+        return ""
+
+    def describe_destinations(self) -> str:
+        destination_name_prefix = self._get_param("DestinationNamePrefix")
+        limit = self._get_param("limit", 50)
+        next_token = self._get_param("nextToken")
+
+        destinations, next_token = self.logs_backend.describe_destinations(
+            destination_name_prefix, int(limit), next_token
+        )
+
+        result = {"destinations": destinations, "nextToken": next_token}
+        return json.dumps(result)
+
+    def put_destination_policy(self) -> str:
+        access_policy = self._get_param("accessPolicy")
+        destination_name = self._get_param("destinationName")
+
+        self.logs_backend.put_destination_policy(destination_name, access_policy)
+        return ""
+
     def create_log_stream(self) -> str:
         log_group_name = self._get_param("logGroupName")
         log_stream_name = self._get_param("logStreamName")
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index d9dc5ad77..cf1044953 100644
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1923,7 +1923,7 @@ class S3Response(BaseResponse):
 
         tags = {}
         for tag in parsed_xml["Tagging"]["TagSet"]["Tag"]:
-            tags[tag["Key"]] = tag["Value"]
+            tags[tag["Key"]] = tag["Value"] or ""
 
         return tags
 


Test patch:
diff --git a/tests/test_dynamodb_v20111205/test_server.py b/tests/test_dynamodb_v20111205/test_server.py
index af2ab454d..e544a8981 100644
--- a/tests/test_dynamodb_v20111205/test_server.py
+++ b/tests/test_dynamodb_v20111205/test_server.py
@@ -1,5 +1,4 @@
 import json
-import sure  # noqa # pylint: disable=unused-import
 import pytest
 
 import moto.server as server
@@ -27,64 +26,59 @@ def fixture_test_client():
 
 
 def test_404(test_client):
-
     res = test_client.get("/")
-    res.status_code.should.equal(404)
+    assert res.status_code == 404
 
 
 def test_table_list(test_client):
     headers = {"X-Amz-Target": "TestTable.ListTables"}
     res = test_client.get("/", headers=headers)
-    json.loads(res.data).should.equal({"TableNames": []})
+    assert json.loads(res.data) == {"TableNames": []}
 
 
 def test_create_table(test_client):
     res = create_table(test_client)
     res = json.loads(res.data)["Table"]
-    res.should.have.key("CreationDateTime")
+    assert "CreationDateTime" in res
     del res["CreationDateTime"]
-    res.should.equal(
-        {
-            "KeySchema": {
-                "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
-                "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
-            },
-            "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
-            "TableName": TABLE_WITH_RANGE_NAME,
-            "TableStatus": "ACTIVE",
-            "ItemCount": 0,
-            "TableSizeBytes": 0,
-        }
-    )
+    assert res == {
+        "KeySchema": {
+            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
+            "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
+        },
+        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
+        "TableName": TABLE_WITH_RANGE_NAME,
+        "TableStatus": "ACTIVE",
+        "ItemCount": 0,
+        "TableSizeBytes": 0,
+    }
 
     headers = {"X-Amz-Target": "TestTable.ListTables"}
     res = test_client.get("/", headers=headers)
     res = json.loads(res.data)
-    res.should.equal({"TableNames": [TABLE_WITH_RANGE_NAME]})
+    assert res == {"TableNames": [TABLE_WITH_RANGE_NAME]}
 
 
 def test_create_table_without_range_key(test_client):
     res = create_table(test_client, use_range_key=False)
     res = json.loads(res.data)["Table"]
-    res.should.have.key("CreationDateTime")
+    assert "CreationDateTime" in res
     del res["CreationDateTime"]
-    res.should.equal(
-        {
-            "KeySchema": {
-                "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
-            },
-            "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
-            "TableName": TABLE_NAME,
-            "TableStatus": "ACTIVE",
-            "ItemCount": 0,
-            "TableSizeBytes": 0,
-        }
-    )
+    assert res == {
+        "KeySchema": {
+            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
+        },
+        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
+        "TableName": TABLE_NAME,
+        "TableStatus": "ACTIVE",
+        "ItemCount": 0,
+        "TableSizeBytes": 0,
+    }
 
     headers = {"X-Amz-Target": "TestTable.ListTables"}
     res = test_client.get("/", headers=headers)
     res = json.loads(res.data)
-    res.should.equal({"TableNames": [TABLE_NAME]})
+    assert res == {"TableNames": [TABLE_NAME]}
 
 
 # This test is pointless, as we treat DynamoDB as a global resource
@@ -95,11 +89,10 @@ def test_create_table_in_different_regions(test_client):
     headers = {"X-Amz-Target": "TestTable.ListTables"}
     res = test_client.get("/", headers=headers)
     res = json.loads(res.data)
-    res.should.equal({"TableNames": [TABLE_WITH_RANGE_NAME, "Table2"]})
+    assert res == {"TableNames": [TABLE_WITH_RANGE_NAME, "Table2"]}
 
 
 def test_update_item(test_client):
-
     create_table(test_client)
 
     headers, res = put_item(test_client)
@@ -129,16 +122,14 @@ def test_update_item(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Attributes"].should.equal(
-        {
-            "hkey": "customer",
-            "name": "myname",
-            "rkey": "12341234",
-            "new_att": ["val"],
-            "new_n": "42",
-        }
-    )
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Attributes"] == {
+        "hkey": "customer",
+        "name": "myname",
+        "rkey": "12341234",
+        "new_att": ["val"],
+        "new_n": "42",
+    }
 
     # UpdateItem - multiples
     headers["X-Amz-Target"] = "DynamoDB_20111205.UpdateItem"
@@ -157,15 +148,13 @@ def test_update_item(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Attributes"].should.equal(
-        {
-            "hkey": "customer",
-            "rkey": "12341234",
-            "new_att": ["val", "val2"],
-            "new_n": "49",
-        }
-    )
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Attributes"] == {
+        "hkey": "customer",
+        "rkey": "12341234",
+        "new_att": ["val", "val2"],
+        "new_n": "49",
+    }
 
     # GetItem
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -178,9 +167,9 @@ def test_update_item(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res["Item"].should.have.key("new_att").equal({"SS": ["val", "val2"]})
-    res["Item"].should.have.key("new_n").equal({"N": "49"})
-    res["Item"].shouldnt.have.key("name")
+    assert res["Item"]["new_att"] == {"SS": ["val", "val2"]}
+    assert res["Item"]["new_n"] == {"N": "49"}
+    assert "name" not in res["Item"]
 
 
 @pytest.mark.parametrize(
@@ -196,17 +185,17 @@ def test_delete_table(use_range_key, test_client):
     headers = {"X-Amz-Target": "DynamoDB_20111205.ListTables"}
     res = test_client.post("/", headers=headers)
     res = json.loads(res.data)
-    res.should.equal({"TableNames": []})
+    assert res == {"TableNames": []}
 
 
 def test_delete_unknown_table(test_client):
     headers = {"X-Amz-Target": "DynamoDB_20111205.DeleteTable"}
     res = test_client.post("/", headers=headers, json={"TableName": "unknown_table"})
-    res.status_code.should.equal(400)
+    assert res.status_code == 400
 
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_describe_table(test_client):
@@ -220,21 +209,19 @@ def test_describe_table(test_client):
         "/", headers=headers, json={"TableName": TABLE_WITH_RANGE_NAME}
     )
     res = json.loads(res.data)["Table"]
-    res.should.have.key("CreationDateTime")
+    assert "CreationDateTime" in res
     del res["CreationDateTime"]
-    res.should.equal(
-        {
-            "KeySchema": {
-                "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
-                "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
-            },
-            "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
-            "TableName": TABLE_WITH_RANGE_NAME,
-            "TableStatus": "ACTIVE",
-            "ItemCount": 0,
-            "TableSizeBytes": 0,
-        }
-    )
+    assert res == {
+        "KeySchema": {
+            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"},
+            "RangeKeyElement": {"AttributeName": "rkey", "AttributeType": "N"},
+        },
+        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
+        "TableName": TABLE_WITH_RANGE_NAME,
+        "TableStatus": "ACTIVE",
+        "ItemCount": 0,
+        "TableSizeBytes": 0,
+    }
 
 
 def test_describe_missing_table(test_client):
@@ -243,10 +230,10 @@ def test_describe_missing_table(test_client):
         "Content-Type": "application/x-amz-json-1.0",
     }
     res = test_client.post("/", headers=headers, json={"TableName": "unknown_table"})
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 @pytest.mark.parametrize(
@@ -274,7 +261,7 @@ def test_update_table(test_client, use_range_key):
     res = test_client.post("/", headers=headers, json={"TableName": table_name})
     throughput = json.loads(res.data)["Table"]["ProvisionedThroughput"]
 
-    throughput.should.equal({"ReadCapacityUnits": 5, "WriteCapacityUnits": 15})
+    assert throughput == {"ReadCapacityUnits": 5, "WriteCapacityUnits": 15}
 
 
 def test_put_return_none(test_client):
@@ -296,9 +283,11 @@ def test_put_return_none(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
     # This seems wrong - it should return nothing, considering return_values is set to none
-    res["Attributes"].should.equal(
-        {"hkey": "customer", "name": "myname", "rkey": "12341234"}
-    )
+    assert res["Attributes"] == {
+        "hkey": "customer",
+        "name": "myname",
+        "rkey": "12341234",
+    }
 
 
 def test_put_return_none_without_range_key(test_client):
@@ -316,7 +305,7 @@ def test_put_return_none_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
     # This seems wrong - it should return nothing, considering return_values is set to none
-    res["Attributes"].should.equal({"hkey": "customer", "name": "myname"})
+    assert res["Attributes"] == {"hkey": "customer", "name": "myname"}
 
 
 def test_put_item_from_unknown_table(test_client):
@@ -335,10 +324,10 @@ def test_put_item_from_unknown_table(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
 
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_get_item_from_unknown_table(test_client):
@@ -355,10 +344,10 @@ def test_get_item_from_unknown_table(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
 
-    res.status_code.should.equal(404)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 404
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 @pytest.mark.parametrize(
@@ -380,10 +369,10 @@ def test_get_unknown_item_from_table(use_range_key, test_client):
         request_body["Key"]["RangeKeyElement"] = {"N": "12341234"}
     res = test_client.post("/", headers=headers, json=request_body)
 
-    res.status_code.should.equal(404)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 404
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_get_item_without_range_key(test_client):
@@ -399,10 +388,10 @@ def test_get_item_without_range_key(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
 
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazon.coral.validate#ValidationException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazon.coral.validate#ValidationException"
+    }
 
 
 def test_put_and_get_item(test_client):
@@ -410,10 +399,12 @@ def test_put_and_get_item(test_client):
 
     headers, res = put_item(test_client)
 
-    res["ConsumedCapacityUnits"].should.equal(1)
-    res["Attributes"].should.equal(
-        {"hkey": "customer", "name": "myname", "rkey": "12341234"}
-    )
+    assert res["ConsumedCapacityUnits"] == 1
+    assert res["Attributes"] == {
+        "hkey": "customer",
+        "name": "myname",
+        "rkey": "12341234",
+    }
 
     # GetItem
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -426,10 +417,12 @@ def test_put_and_get_item(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Item"].should.equal(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341234"}}
-    )
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Item"] == {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341234"},
+    }
 
     # GetItem - return single attribute
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -443,8 +436,8 @@ def test_put_and_get_item(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Item"].should.equal({"name": {"S": "myname"}})
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Item"] == {"name": {"S": "myname"}}
 
 
 def test_put_and_get_item_without_range_key(test_client):
@@ -452,8 +445,8 @@ def test_put_and_get_item_without_range_key(test_client):
 
     headers, res = put_item(test_client, use_range_key=False)
 
-    res["ConsumedCapacityUnits"].should.equal(1)
-    res["Attributes"].should.equal({"hkey": "customer", "name": "myname"})
+    assert res["ConsumedCapacityUnits"] == 1
+    assert res["Attributes"] == {"hkey": "customer", "name": "myname"}
 
     # GetItem
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -463,8 +456,8 @@ def test_put_and_get_item_without_range_key(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Item"].should.equal({"hkey": {"S": "customer"}, "name": {"S": "myname"}})
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Item"] == {"hkey": {"S": "customer"}, "name": {"S": "myname"}}
 
     # GetItem - return single attribute
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -475,8 +468,8 @@ def test_put_and_get_item_without_range_key(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res["ConsumedCapacityUnits"].should.equal(0.5)
-    res["Item"].should.equal({"name": {"S": "myname"}})
+    assert res["ConsumedCapacityUnits"] == 0.5
+    assert res["Item"] == {"name": {"S": "myname"}}
 
 
 def test_scan_simple(test_client):
@@ -494,21 +487,27 @@ def test_scan_simple(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(3)
-    res.should.have.key("ScannedCount").equal(3)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(3)
+    assert res["Count"] == 3
+    assert res["ScannedCount"] == 3
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 3
 
     items = res["Items"]
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341234"}}
-    )
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341235"}}
-    )
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341236"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341234"},
+    } in items
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341235"},
+    } in items
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341236"},
+    } in items
 
 
 def test_scan_with_filter(test_client):
@@ -532,13 +531,13 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(1)
-    res.should.have.key("ScannedCount").equal(3)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(1)
+    assert res["Count"] == 1
+    assert res["ScannedCount"] == 3
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 1
 
     items = res["Items"]
-    items.should.contain({"hkey": {"S": "customer"}, "rkey": {"N": "1234"}})
+    assert {"hkey": {"S": "customer"}, "rkey": {"N": "1234"}} in items
 
     # SCAN begins_with
     headers = {
@@ -557,9 +556,11 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1246"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "1246"},
+    } in items
 
     # SCAN contains
     headers = {
@@ -578,9 +579,11 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "somename"}, "rkey": {"N": "1230"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "somename"},
+        "rkey": {"N": "1230"},
+    } in items
 
     # SCAN null
     headers = {
@@ -594,7 +597,7 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.contain({"hkey": {"S": "customer"}, "rkey": {"N": "1234"}})
+    assert {"hkey": {"S": "customer"}, "rkey": {"N": "1234"}} in items
 
     # SCAN NOT NULL
     headers = {
@@ -608,16 +611,14 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.equal(
-        [
-            {
-                "hkey": {"S": "customer"},
-                "name": {"S": "somename"},
-                "rkey": {"N": "1230"},
-            },
-            {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1246"}},
-        ]
-    )
+    assert items == [
+        {
+            "hkey": {"S": "customer"},
+            "name": {"S": "somename"},
+            "rkey": {"N": "1230"},
+        },
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1246"}},
+    ]
 
     # SCAN between
     headers = {
@@ -636,9 +637,11 @@ def test_scan_with_filter(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "somename"}, "rkey": {"N": "1230"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "somename"},
+        "rkey": {"N": "1230"},
+    } in items
 
 
 def test_scan_with_filter_in_table_without_range_key(test_client):
@@ -665,13 +668,13 @@ def test_scan_with_filter_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(1)
-    res.should.have.key("ScannedCount").equal(3)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(1)
+    assert res["Count"] == 1
+    assert res["ScannedCount"] == 3
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 1
 
     items = res["Items"]
-    items.should.contain({"hkey": {"S": "customer3"}, "name": {"S": "special"}})
+    assert {"hkey": {"S": "customer3"}, "name": {"S": "special"}} in items
 
     # SCAN begins_with
     headers = {
@@ -690,7 +693,7 @@ def test_scan_with_filter_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.have.length_of(3)  # all customers start with cust
+    assert len(items) == 3  # all customers start with cust
 
     # SCAN contains
     headers = {
@@ -709,7 +712,7 @@ def test_scan_with_filter_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.have.equal([{"hkey": {"S": "customer2"}, "name": {"S": "myname"}}])
+    assert items == [{"hkey": {"S": "customer2"}, "name": {"S": "myname"}}]
 
     # SCAN null
     headers = {
@@ -723,7 +726,7 @@ def test_scan_with_filter_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.equal([{"hkey": {"S": "customer1"}}])
+    assert items == [{"hkey": {"S": "customer1"}}]
 
     # SCAN NOT NULL
     headers = {
@@ -737,9 +740,9 @@ def test_scan_with_filter_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.have.length_of(2)
-    items.should.contain({"hkey": {"S": "customer2"}, "name": {"S": "myname"}})
-    items.should.contain({"hkey": {"S": "customer3"}, "name": {"S": "special"}})
+    assert len(items) == 2
+    assert {"hkey": {"S": "customer2"}, "name": {"S": "myname"}} in items
+    assert {"hkey": {"S": "customer3"}, "name": {"S": "special"}} in items
 
 
 def test_scan_with_undeclared_table(test_client):
@@ -749,10 +752,10 @@ def test_scan_with_undeclared_table(test_client):
     }
     request_body = {"TableName": "unknown_table"}
     res = test_client.post("/", headers=headers, json=request_body)
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_query_in_table_without_range_key(test_client):
@@ -768,12 +771,12 @@ def test_query_in_table_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(1)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(1)
+    assert res["Count"] == 1
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 1
 
     items = res["Items"]
-    items.should.contain({"hkey": {"S": "customer"}, "name": {"S": "myname"}})
+    assert {"hkey": {"S": "customer"}, "name": {"S": "myname"}} in items
 
     # QUERY for unknown value
     headers = {
@@ -785,8 +788,8 @@ def test_query_in_table_without_range_key(test_client):
     res = json.loads(res.data)
 
     # TODO: We should not get any results here
-    # res.should.have.key("Count").equal(0)
-    # res.should.have.key("Items").length_of(0)
+    # assert res["Count"] == 0
+    # assert len(res["Items"]) == 0
 
 
 def test_query_item_by_hash_only(test_client):
@@ -807,20 +810,26 @@ def test_query_item_by_hash_only(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(3)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(3)
+    assert res["Count"] == 3
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 3
 
     items = res["Items"]
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341234"}}
-    )
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341235"}}
-    )
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341236"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341234"},
+    } in items
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341235"},
+    } in items
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341236"},
+    } in items
 
 
 def test_query_item_by_range_key(test_client):
@@ -846,14 +855,16 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(1)
-    res.should.have.key("ConsumedCapacityUnits").equal(1)
-    res.should.have.key("Items").length_of(1)
+    assert res["Count"] == 1
+    assert res["ConsumedCapacityUnits"] == 1
+    assert len(res["Items"]) == 1
 
     items = res["Items"]
-    items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1247"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "1247"},
+    } in items
 
     # GT all
     headers = {
@@ -871,8 +882,8 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(3)
-    res.should.have.key("Items").length_of(3)
+    assert res["Count"] == 3
+    assert len(res["Items"]) == 3
 
     # GT none
     headers = {
@@ -890,9 +901,9 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res["ConsumedCapacityUnits"].should.equal(1)
-    res["Items"].should.equal([])
-    res["Count"].should.equal(0)
+    assert res["ConsumedCapacityUnits"] == 1
+    assert res["Items"] == []
+    assert res["Count"] == 0
 
     # CONTAINS some
     headers = {
@@ -910,9 +921,9 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.equal(
-        [{"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1247"}}]
-    )
+    assert items == [
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1247"}}
+    ]
 
     # BEGINS_WITH
     headers = {
@@ -930,12 +941,10 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.equal(
-        [
-            {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
-            {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
-        ]
-    )
+    assert items == [
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
+    ]
 
     # CONTAINS
     headers = {
@@ -953,12 +962,10 @@ def test_query_item_by_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     items = json.loads(res.data)["Items"]
 
-    items.should.equal(
-        [
-            {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
-            {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
-        ]
-    )
+    assert items == [
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1234"}},
+        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "1235"}},
+    ]
 
 
 def test_query_item_with_undeclared_table(test_client):
@@ -975,10 +982,10 @@ def test_query_item_with_undeclared_table(test_client):
         },
     }
     res = test_client.post("/", headers=headers, json=request_body)
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_delete_item(test_client):
@@ -1001,7 +1008,7 @@ def test_delete_item(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res.should.equal({"Attributes": [], "ConsumedCapacityUnits": 0.5})
+    assert res == {"Attributes": [], "ConsumedCapacityUnits": 0.5}
 
     # GetItem
     headers["X-Amz-Target"] = "DynamoDB_20111205.GetItem"
@@ -1015,9 +1022,9 @@ def test_delete_item(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res["Item"].should.have.key("hkey").equal({"S": "customer"})
-    res["Item"].should.have.key("rkey").equal({"N": "12341234"})
-    res["Item"].should.have.key("name").equal({"S": "myname"})
+    assert res["Item"]["hkey"] == {"S": "customer"}
+    assert res["Item"]["rkey"] == {"N": "12341234"}
+    assert res["Item"]["name"] == {"S": "myname"}
 
 
 def test_update_item_that_doesnt_exist(test_client):
@@ -1035,9 +1042,9 @@ def test_update_item_that_doesnt_exist(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res.should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_delete_item_without_range_key(test_client):
@@ -1055,7 +1062,7 @@ def test_delete_item_without_range_key(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res.should.equal({"Attributes": [], "ConsumedCapacityUnits": 0.5})
+    assert res == {"Attributes": [], "ConsumedCapacityUnits": 0.5}
 
 
 def test_delete_item_with_return_values(test_client):
@@ -1079,12 +1086,10 @@ def test_delete_item_with_return_values(test_client):
     }
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
-    res.should.equal(
-        {
-            "Attributes": {"hkey": "customer", "name": "myname", "rkey": "12341236"},
-            "ConsumedCapacityUnits": 0.5,
-        }
-    )
+    assert res == {
+        "Attributes": {"hkey": "customer", "name": "myname", "rkey": "12341236"},
+        "ConsumedCapacityUnits": 0.5,
+    }
 
 
 def test_delete_unknown_item(test_client):
@@ -1103,10 +1108,10 @@ def test_delete_unknown_item(test_client):
         "ReturnValues": "ALL_OLD",
     }
     res = test_client.post("/", headers=headers, json=request_body)
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_update_item_in_nonexisting_table(test_client):
@@ -1121,10 +1126,10 @@ def test_update_item_in_nonexisting_table(test_client):
         "AttributeUpdates": {"new_att": {"Value": {"SS": ["val"]}, "Action": "PUT"}},
     }
     res = test_client.post("/", headers=headers, json=request_body)
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_delete_from_unknown_table(test_client):
@@ -1141,10 +1146,10 @@ def test_delete_from_unknown_table(test_client):
         "ReturnValues": "ALL_OLD",
     }
     res = test_client.post("/", headers=headers, json=request_body)
-    res.status_code.should.equal(400)
-    json.loads(res.data).should.equal(
-        {"__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"}
-    )
+    assert res.status_code == 400
+    assert json.loads(res.data) == {
+        "__type": "com.amazonaws.dynamodb.v20111205#ResourceNotFoundException"
+    }
 
 
 def test_batch_get_item(test_client):
@@ -1177,16 +1182,20 @@ def test_batch_get_item(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)["Responses"]
 
-    res.should.have.key("UnprocessedKeys").equal({})
+    assert res["UnprocessedKeys"] == {}
     table_items = [i["Item"] for i in res[TABLE_WITH_RANGE_NAME]["Items"]]
-    table_items.should.have.length_of(2)
+    assert len(table_items) == 2
 
-    table_items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341235"}}
-    )
-    table_items.should.contain(
-        {"hkey": {"S": "customer"}, "name": {"S": "myname"}, "rkey": {"N": "12341236"}}
-    )
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341235"},
+    } in table_items
+    assert {
+        "hkey": {"S": "customer"},
+        "name": {"S": "myname"},
+        "rkey": {"N": "12341236"},
+    } in table_items
 
 
 def test_batch_get_item_without_range_key(test_client):
@@ -1213,12 +1222,12 @@ def test_batch_get_item_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)["Responses"]
 
-    res.should.have.key("UnprocessedKeys").equal({})
+    assert res["UnprocessedKeys"] == {}
     table_items = [i["Item"] for i in res[TABLE_NAME]["Items"]]
-    table_items.should.have.length_of(2)
+    assert len(table_items) == 2
 
-    table_items.should.contain({"hkey": {"S": "customer1"}, "name": {"S": "myname"}})
-    table_items.should.contain({"hkey": {"S": "customer3"}, "name": {"S": "myname"}})
+    assert {"hkey": {"S": "customer1"}, "name": {"S": "myname"}} in table_items
+    assert {"hkey": {"S": "customer3"}, "name": {"S": "myname"}} in table_items
 
 
 def test_batch_write_item(test_client):
@@ -1256,7 +1265,7 @@ def test_batch_write_item(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(2)
+    assert res["Count"] == 2
 
 
 def test_batch_write_item_without_range_key(test_client):
@@ -1286,7 +1295,7 @@ def test_batch_write_item_without_range_key(test_client):
     res = test_client.post("/", headers=headers, json=request_body)
     res = json.loads(res.data)
 
-    res.should.have.key("Count").equal(2)
+    assert res["Count"] == 2
 
 
 def put_item(
diff --git a/tests/test_dynamodb_v20111205/test_servermode.py b/tests/test_dynamodb_v20111205/test_servermode.py
index cd5588bce..692ddf7c2 100644
--- a/tests/test_dynamodb_v20111205/test_servermode.py
+++ b/tests/test_dynamodb_v20111205/test_servermode.py
@@ -1,5 +1,4 @@
 import json
-import sure  # noqa # pylint: disable=unused-import
 import requests
 
 from moto import settings
@@ -22,8 +21,8 @@ def test_table_list():
     }
     requests.post(settings.test_server_mode_endpoint() + "/moto-api/reset")
     res = requests.get(settings.test_server_mode_endpoint(), headers=headers)
-    res.status_code.should.equal(200)
-    json.loads(res.content).should.equal({"TableNames": []})
+    assert res.status_code == 200
+    assert json.loads(res.content) == {"TableNames": []}
 
 
 def test_create_table():
@@ -49,22 +48,20 @@ def test_create_table():
     )
 
     res = json.loads(res.content)["Table"]
-    res.should.have.key("CreationDateTime")
+    assert "CreationDateTime" in res
     del res["CreationDateTime"]
-    res.should.equal(
-        {
-            "KeySchema": {
-                "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
-            },
-            "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
-            "TableName": table_name,
-            "TableStatus": "ACTIVE",
-            "ItemCount": 0,
-            "TableSizeBytes": 0,
-        }
-    )
+    assert res == {
+        "KeySchema": {
+            "HashKeyElement": {"AttributeName": "hkey", "AttributeType": "S"}
+        },
+        "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 10},
+        "TableName": table_name,
+        "TableStatus": "ACTIVE",
+        "ItemCount": 0,
+        "TableSizeBytes": 0,
+    }
 
     headers["X-Amz-Target"] = "DynamoDB_20111205.ListTables"
     res = requests.get(settings.test_server_mode_endpoint(), headers=headers)
     res = json.loads(res.content)
-    table_name.should.be.within(res["TableNames"])
+    assert table_name in res["TableNames"]
diff --git a/tests/test_ebs/test_ebs.py b/tests/test_ebs/test_ebs.py
index f4a19f291..561811a77 100644
--- a/tests/test_ebs/test_ebs.py
+++ b/tests/test_ebs/test_ebs.py
@@ -1,7 +1,6 @@
 """Unit tests for ebs-supported APIs."""
 import boto3
 import hashlib
-import sure  # noqa # pylint: disable=unused-import
 from moto import mock_ebs, mock_ec2
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 
@@ -14,12 +13,12 @@ def test_start_snapshot__minimal():
     client = boto3.client("ebs", region_name="eu-west-1")
     resp = client.start_snapshot(VolumeSize=720)
 
-    resp.should.have.key("SnapshotId")
-    resp.should.have.key("OwnerId").equals(ACCOUNT_ID)
-    resp.should.have.key("Status").equals("pending")
-    resp.should.have.key("StartTime")
-    resp.should.have.key("VolumeSize").equals(720)
-    resp.should.have.key("BlockSize").equals(512)
+    assert "SnapshotId" in resp
+    assert resp["OwnerId"] == ACCOUNT_ID
+    assert resp["Status"] == "pending"
+    assert "StartTime" in resp
+    assert resp["VolumeSize"] == 720
+    assert resp["BlockSize"] == 512
 
 
 @mock_ebs
@@ -31,14 +30,14 @@ def test_start_snapshot():
         Description="my fancy snapshot",
     )
 
-    resp.should.have.key("SnapshotId")
-    resp.should.have.key("OwnerId").equals(ACCOUNT_ID)
-    resp.should.have.key("Status").equals("pending")
-    resp.should.have.key("StartTime")
-    resp.should.have.key("VolumeSize").equals(120)
-    resp.should.have.key("BlockSize").equals(512)
-    resp.should.have.key("Tags").equals([{"Key": "kt", "Value": "vt"}])
-    resp.should.have.key("Description").equals("my fancy snapshot")
+    assert "SnapshotId" in resp
+    assert resp["OwnerId"] == ACCOUNT_ID
+    assert resp["Status"] == "pending"
+    assert "StartTime" in resp
+    assert resp["VolumeSize"] == 120
+    assert resp["BlockSize"] == 512
+    assert resp["Tags"] == [{"Key": "kt", "Value": "vt"}]
+    assert resp["Description"] == "my fancy snapshot"
 
 
 @mock_ebs
@@ -47,7 +46,7 @@ def test_complete_snapshot():
     snapshot_id = client.start_snapshot(VolumeSize=720)["SnapshotId"]
 
     resp = client.complete_snapshot(SnapshotId=snapshot_id, ChangedBlocksCount=0)
-    resp.should.have.key("Status").equals("completed")
+    assert resp["Status"] == "completed"
 
 
 @mock_ebs
@@ -65,8 +64,8 @@ def test_put_snapshot_block():
         ChecksumAlgorithm="SHA256",
     )
 
-    resp.should.have.key("Checksum").equals(checksum)
-    resp.should.have.key("ChecksumAlgorithm").equals("SHA256")
+    assert resp["Checksum"] == checksum
+    assert resp["ChecksumAlgorithm"] == "SHA256"
 
 
 @mock_ebs
@@ -88,11 +87,11 @@ def test_get_snapshot_block():
         SnapshotId=snapshot_id, BlockIndex=2, BlockToken="n/a"
     )
 
-    resp.should.have.key("DataLength").equals(524288)
-    resp.should.have.key("BlockData")
-    resp["BlockData"].read().should.equal(b"data 2")
-    resp.should.have.key("Checksum")
-    resp.should.have.key("ChecksumAlgorithm").equals("SHA256")
+    assert resp["DataLength"] == 524288
+    assert "BlockData" in resp
+    assert resp["BlockData"].read() == b"data 2"
+    assert "Checksum" in resp
+    assert resp["ChecksumAlgorithm"] == "SHA256"
 
 
 @mock_ebs
@@ -125,13 +124,13 @@ def test_list_changed_blocks():
     )
     changed_blocks = resp["ChangedBlocks"]
     changed_idxes = [b["BlockIndex"] for b in changed_blocks]
-    changed_idxes.should.equal([1, 3])
+    assert changed_idxes == [1, 3]
 
-    changed_blocks[0].should.have.key("FirstBlockToken")
-    changed_blocks[0].should.have.key("SecondBlockToken")
+    assert "FirstBlockToken" in changed_blocks[0]
+    assert "SecondBlockToken" in changed_blocks[0]
 
-    changed_blocks[1].should.have.key("FirstBlockToken")
-    changed_blocks[1].shouldnt.have.key("SecondBlockToken")
+    assert "FirstBlockToken" in changed_blocks[1]
+    assert "SecondBlockToken" not in changed_blocks[1]
 
 
 @mock_ebs
@@ -151,11 +150,11 @@ def test_list_snapshot_blocks():
 
     resp = client.list_snapshot_blocks(SnapshotId=snapshot_id)
 
-    resp.should.have.key("VolumeSize").equals(415)
-    resp.should.have.key("BlockSize").equals(512)
-    resp.should.have.key("Blocks").length_of(3)
+    assert resp["VolumeSize"] == 415
+    assert resp["BlockSize"] == 512
+    assert len(resp["Blocks"]) == 3
 
-    [b["BlockIndex"] for b in resp["Blocks"]].should.equal([1, 2, 3])
+    assert [b["BlockIndex"] for b in resp["Blocks"]] == [1, 2, 3]
 
 
 @mock_ebs
@@ -165,6 +164,6 @@ def test_start_snapshot__should_be_created_in_ec2():
     ec2 = boto3.client("ec2", region_name="eu-north-1")
     snapshot_id = ebs.start_snapshot(VolumeSize=720)["SnapshotId"]
     resp = ec2.describe_snapshots(SnapshotIds=[snapshot_id])["Snapshots"]
-    resp.should.have.length_of(1)
+    assert len(resp) == 1
 
-    resp[0].should.have.key("VolumeSize").equals(720)
+    assert resp[0]["VolumeSize"] == 720
diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py
index 6b8bc6afb..851081196 100644
--- a/tests/test_logs/test_logs.py
+++ b/tests/test_logs/test_logs.py
@@ -35,6 +35,20 @@ json_policy_doc = json.dumps(
     }
 )
 
+access_policy_doc = json.dumps(
+    {
+        "Version": "2012-10-17",
+        "Statement": [
+            {
+                "Effect": "Allow",
+                "Principal": {"AWS": "logs.us-east-1.amazonaws.com"},
+                "Action": "logs:PutSubscriptionFilter",
+                "Resource": "destination_arn",
+            }
+        ],
+    }
+)
+
 
 @mock_logs
 def test_describe_metric_filters_happy_prefix():
@@ -255,6 +269,82 @@ def test_delete_metric_filter_invalid_log_group_name(
     response["Error"]["Message"].should.contain(failing_constraint)
 
 
+@mock_logs
+def test_destinations():
+    conn = boto3.client("logs", "us-west-2")
+    destination_name = "test-destination"
+    role_arn = "arn:aws:iam::123456789012:role/my-subscription-role"
+    target_arn = "arn:aws:kinesis:us-east-1:123456789012:stream/my-kinesis-stream"
+    role_arn_updated = "arn:aws:iam::123456789012:role/my-subscription-role-updated"
+    target_arn_updated = (
+        "arn:aws:kinesis:us-east-1:123456789012:stream/my-kinesis-stream-updated"
+    )
+
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert len(response["destinations"]) == 0
+
+    response = conn.put_destination(
+        destinationName=destination_name,
+        targetArn=target_arn,
+        roleArn=role_arn,
+        tags={"Name": destination_name},
+    )
+    assert response["destination"]["destinationName"] == destination_name
+    assert response["destination"]["targetArn"] == target_arn
+    assert response["destination"]["roleArn"] == role_arn
+
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert len(response["destinations"]) == 1
+    assert response["destinations"][0]["destinationName"] == destination_name
+    assert response["destinations"][0]["targetArn"] == target_arn
+    assert response["destinations"][0]["roleArn"] == role_arn
+
+    response = conn.put_destination(
+        destinationName=destination_name,
+        targetArn=target_arn_updated,
+        roleArn=role_arn_updated,
+        tags={"Name": destination_name},
+    )
+    assert response["destination"]["destinationName"] == destination_name
+    assert response["destination"]["targetArn"] == target_arn_updated
+    assert response["destination"]["roleArn"] == role_arn_updated
+
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert len(response["destinations"]) == 1
+    assert response["destinations"][0]["destinationName"] == destination_name
+    assert response["destinations"][0]["targetArn"] == target_arn_updated
+    assert response["destinations"][0]["roleArn"] == role_arn_updated
+
+    response = conn.put_destination_policy(
+        destinationName=destination_name, accessPolicy=access_policy_doc
+    )
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert response["destinations"][0]["accessPolicy"] == access_policy_doc
+
+    conn.put_destination(
+        destinationName=f"{destination_name}-1",
+        targetArn=target_arn,
+        roleArn=role_arn,
+        tags={"Name": destination_name},
+    )
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert len(response["destinations"]) == 2
+
+    response = conn.describe_destinations(DestinationNamePrefix=f"{destination_name}-1")
+    assert len(response["destinations"]) == 1
+
+    response = conn.delete_destination(destinationName=f"{destination_name}-1")
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+
+    response = conn.describe_destinations(DestinationNamePrefix=destination_name)
+    assert len(response["destinations"]) == 1
+
+    response = conn.delete_destination(destinationName=destination_name)
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+
+
 def put_metric_filter(conn, count=1):
     count = str(count)
     return conn.put_metric_filter(
diff --git a/tests/test_s3/test_s3_tagging.py b/tests/test_s3/test_s3_tagging.py
index f6e6236bd..dc85e41b1 100644
--- a/tests/test_s3/test_s3_tagging.py
+++ b/tests/test_s3/test_s3_tagging.py
@@ -69,6 +69,7 @@ def test_put_bucket_tagging():
             "TagSet": [
                 {"Key": "TagOne", "Value": "ValueOne"},
                 {"Key": "TagTwo", "Value": "ValueTwo"},
+                {"Key": "TagThree", "Value": ""},
             ]
         },
     )
@@ -222,6 +223,7 @@ def test_put_object_tagging():
             "TagSet": [
                 {"Key": "item1", "Value": "foo"},
                 {"Key": "item2", "Value": "bar"},
+                {"Key": "item3", "Value": ""},
             ]
         },
     )