Repository: getmoto/moto
Pull request #6459 (2023-06-28 22:24:53)

Techdebt: Replace sure with regular asserts in DataBrew
See #6035 


Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 3a8ac6584..d2def447b 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -204,7 +204,7 @@
 
 ## apigatewayv2
 <details>
-<summary>69% implemented</summary>
+<summary>75% implemented</summary>
 
 - [X] create_api
 - [X] create_api_mapping
@@ -216,7 +216,7 @@
 - [X] create_model
 - [X] create_route
 - [X] create_route_response
-- [ ] create_stage
+- [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
 - [X] delete_api
@@ -232,7 +232,7 @@
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
-- [ ] delete_stage
+- [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
 - [X] get_api
@@ -256,8 +256,8 @@
 - [X] get_route_response
 - [ ] get_route_responses
 - [X] get_routes
-- [ ] get_stage
-- [ ] get_stages
+- [X] get_stage
+- [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
diff --git a/docs/docs/services/apigatewayv2.rst b/docs/docs/services/apigatewayv2.rst
index 452254ce5..7be34f0ff 100644
--- a/docs/docs/services/apigatewayv2.rst
+++ b/docs/docs/services/apigatewayv2.rst
@@ -46,7 +46,7 @@ apigatewayv2
         The following parameters are not yet implemented: ResponseModels, ResponseParameters
         
 
-- [ ] create_stage
+- [X] create_stage
 - [X] create_vpc_link
 - [ ] delete_access_log_settings
 - [X] delete_api
@@ -62,7 +62,7 @@ apigatewayv2
 - [X] delete_route_request_parameter
 - [X] delete_route_response
 - [ ] delete_route_settings
-- [ ] delete_stage
+- [X] delete_stage
 - [X] delete_vpc_link
 - [ ] export_api
 - [X] get_api
@@ -102,8 +102,8 @@ apigatewayv2
         Pagination is not yet implemented
         
 
-- [ ] get_stage
-- [ ] get_stages
+- [X] get_stage
+- [X] get_stages
 - [X] get_tags
 - [X] get_vpc_link
 - [X] get_vpc_links
diff --git a/moto/apigatewayv2/exceptions.py b/moto/apigatewayv2/exceptions.py
index 1a63b65a9..44ef021d6 100644
--- a/moto/apigatewayv2/exceptions.py
+++ b/moto/apigatewayv2/exceptions.py
@@ -123,3 +123,13 @@ class ApiMappingNotFound(APIGatewayV2Error):
             "NotFoundException",
             "The api mapping resource specified in the request was not found.",
         )
+
+
+class StageNotFound(APIGatewayV2Error):
+    code = 404
+
+    def __init__(self) -> None:
+        super().__init__(
+            "NotFoundException",
+            "Invalid stage identifier specified",
+        )
diff --git a/moto/apigatewayv2/models.py b/moto/apigatewayv2/models.py
index 7842e5a2b..5ec2b3992 100644
--- a/moto/apigatewayv2/models.py
+++ b/moto/apigatewayv2/models.py
@@ -22,9 +22,57 @@ from .exceptions import (
     VpcLinkNotFound,
     DomainNameNotFound,
     DomainNameAlreadyExists,
+    StageNotFound,
 )
 
 
+class Stage(BaseModel):
+    def __init__(self, api: "Api", config: Dict[str, Any]):
+        self.config = config
+        self.name = config["stageName"]
+        if api.protocol_type == "HTTP":
+            self.default_route_settings = config.get(
+                "defaultRouteSettings", {"detailedMetricsEnabled": False}
+            )
+        elif api.protocol_type == "WEBSOCKET":
+            self.default_route_settings = config.get(
+                "defaultRouteSettings",
+                {
+                    "dataTraceEnabled": False,
+                    "detailedMetricsEnabled": False,
+                    "loggingLevel": "OFF",
+                },
+            )
+        self.access_log_settings = config.get("accessLogSettings")
+        self.auto_deploy = config.get("autoDeploy")
+        self.client_certificate_id = config.get("clientCertificateId")
+        self.description = config.get("description")
+        self.route_settings = config.get("routeSettings", {})
+        self.stage_variables = config.get("stageVariables", {})
+        self.tags = config.get("tags", {})
+        self.created = self.updated = unix_time()
+
+    def to_json(self) -> Dict[str, Any]:
+        dct = {
+            "stageName": self.name,
+            "defaultRouteSettings": self.default_route_settings,
+            "createdDate": self.created,
+            "lastUpdatedDate": self.updated,
+            "routeSettings": self.route_settings,
+            "stageVariables": self.stage_variables,
+            "tags": self.tags,
+        }
+        if self.access_log_settings:
+            dct["accessLogSettings"] = self.access_log_settings
+        if self.auto_deploy is not None:
+            dct["autoDeploy"] = self.auto_deploy
+        if self.client_certificate_id:
+            dct["clientCertificateId"] = self.client_certificate_id
+        if self.description:
+            dct["description"] = self.description
+        return dct
+
+
 class Authorizer(BaseModel):
     def __init__(
         self,
@@ -541,6 +589,7 @@ class Api(BaseModel):
         self.integrations: Dict[str, Integration] = dict()
         self.models: Dict[str, Model] = dict()
         self.routes: Dict[str, Route] = dict()
+        self.stages: Dict[str, Stage] = dict()
 
         self.arn = f"arn:aws:apigateway:{region}::/apis/{self.api_id}"
         self.backend.tag_resource(self.arn, tags)
@@ -550,6 +599,7 @@ class Api(BaseModel):
         self.integrations.clear()
         self.models.clear()
         self.routes.clear()
+        self.stages.clear()
 
     def delete_cors_configuration(self) -> None:
         self.cors_configuration = None
@@ -962,6 +1012,19 @@ class Api(BaseModel):
         route = self.get_route(route_id)
         return route.get_route_response(route_response_id)
 
+    def create_stage(self, config: Dict[str, Any]) -> Stage:
+        stage = Stage(api=self, config=config)
+        self.stages[stage.name] = stage
+        return stage
+
+    def get_stage(self, stage_name: str) -> Stage:
+        if stage_name not in self.stages:
+            raise StageNotFound
+        return self.stages[stage_name]
+
+    def delete_stage(self, stage_name: str) -> None:
+        self.stages.pop(stage_name, None)
+
     def to_json(self) -> Dict[str, Any]:
         return {
             "apiId": self.api_id,
@@ -1599,7 +1662,6 @@ class ApiGatewayV2Backend(BaseBackend):
         mutual_tls_authentication: Dict[str, str],
         tags: Dict[str, str],
     ) -> DomainName:
-
         if domain_name in self.domain_names.keys():
             raise DomainNameAlreadyExists
 
@@ -1701,5 +1763,21 @@ class ApiGatewayV2Backend(BaseBackend):
 
         del self.api_mappings[api_mapping_id]
 
+    def create_stage(self, api_id: str, config: Dict[str, Any]) -> Stage:
+        api = self.get_api(api_id)
+        return api.create_stage(config)
+
+    def get_stage(self, api_id: str, stage_name: str) -> Stage:
+        api = self.get_api(api_id)
+        return api.get_stage(stage_name)
+
+    def delete_stage(self, api_id: str, stage_name: str) -> None:
+        api = self.get_api(api_id)
+        api.delete_stage(stage_name)
+
+    def get_stages(self, api_id: str) -> List[Stage]:
+        api = self.get_api(api_id)
+        return list(api.stages.values())
+
 
 apigatewayv2_backends = BackendDict(ApiGatewayV2Backend, "apigatewayv2")
diff --git a/moto/apigatewayv2/responses.py b/moto/apigatewayv2/responses.py
index 91ac11669..82f1c143c 100644
--- a/moto/apigatewayv2/responses.py
+++ b/moto/apigatewayv2/responses.py
@@ -212,6 +212,22 @@ class ApiGatewayV2Response(BaseResponse):
         if request.method == "DELETE":
             return self.delete_api_mapping()
 
+    def stages(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:  # type: ignore[return]
+        self.setup_class(request, full_url, headers)
+
+        if self.method == "POST":
+            return self.create_stage()
+        if self.method == "GET":
+            return self.get_stages()
+
+    def stage(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE:  # type: ignore[return]
+        self.setup_class(request, full_url, headers)
+
+        if self.method == "GET":
+            return self.get_stage()
+        if self.method == "DELETE":
+            return self.delete_stage()
+
     def create_api(self) -> TYPE_RESPONSE:
         params = json.loads(self.body)
 
@@ -854,3 +870,26 @@ class ApiGatewayV2Response(BaseResponse):
             domain_name=domain_name,
         )
         return 204, {}, ""
+
+    def create_stage(self) -> TYPE_RESPONSE:
+        api_id = self.path.split("/")[-2]
+        config = json.loads(self.body)
+        stage = self.apigatewayv2_backend.create_stage(api_id, config)
+        return 200, {}, json.dumps(stage.to_json())
+
+    def get_stage(self) -> TYPE_RESPONSE:
+        api_id = self.path.split("/")[-3]
+        stage_name = self.path.split("/")[-1]
+        stage = self.apigatewayv2_backend.get_stage(api_id, stage_name)
+        return 200, {}, json.dumps(stage.to_json())
+
+    def delete_stage(self) -> TYPE_RESPONSE:
+        api_id = self.path.split("/")[-3]
+        stage_name = self.path.split("/")[-1]
+        self.apigatewayv2_backend.delete_stage(api_id, stage_name)
+        return 200, {}, "{}"
+
+    def get_stages(self) -> TYPE_RESPONSE:
+        api_id = self.path.split("/")[-2]
+        stages = self.apigatewayv2_backend.get_stages(api_id)
+        return 200, {}, json.dumps({"items": [st.to_json() for st in stages]})
diff --git a/moto/apigatewayv2/urls.py b/moto/apigatewayv2/urls.py
index b737112e8..7826904a6 100644
--- a/moto/apigatewayv2/urls.py
+++ b/moto/apigatewayv2/urls.py
@@ -26,6 +26,8 @@ url_paths = {
     "{0}/v2/apis/(?P<api_id>[^/]+)/routes/(?P<route_id>[^/]+)/routeresponses$": response_v2.route_responses,
     "{0}/v2/apis/(?P<api_id>[^/]+)/routes/(?P<route_id>[^/]+)/routeresponses/(?P<route_response_id>[^/]+)$": response_v2.route_response,
     "{0}/v2/apis/(?P<api_id>[^/]+)/routes/(?P<route_id>[^/]+)/requestparameters/(?P<request_parameter>[^/]+)$": response_v2.route_request_parameter,
+    "{0}/v2/apis/(?P<api_id>[^/]+)/stages$": response_v2.stages,
+    "{0}/v2/apis/(?P<api_id>[^/]+)/stages/(?P<stage_name>[^/]+)$": response_v2.stage,
     "{0}/v2/tags/(?P<resource_arn>[^/]+)$": response_v2.tags,
     "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/apis/(?P<resource_arn_pt2>[^/]+)$": response_v2.tags,
     "{0}/v2/tags/(?P<resource_arn_pt1>[^/]+)/vpclinks/(?P<resource_arn_pt2>[^/]+)$": response_v2.tags,
diff --git a/moto/ec2/models/security_groups.py b/moto/ec2/models/security_groups.py
index 1e458e751..0e3c91665 100644
--- a/moto/ec2/models/security_groups.py
+++ b/moto/ec2/models/security_groups.py
@@ -550,16 +550,19 @@ class SecurityGroupBackend:
 
     def describe_security_group_rules(
         self, group_ids: Optional[List[str]] = None, filters: Any = None
-    ) -> List[SecurityRule]:
+    ) -> Dict[str, List[SecurityRule]]:
         matches = self.describe_security_groups(group_ids=group_ids, filters=filters)
         if not matches:
             raise InvalidSecurityGroupNotFoundError(
                 "No security groups found matching the filters provided."
             )
-        rules = []
+        rules = {}
         for group in matches:
-            rules.extend(group.ingress_rules)
-            rules.extend(group.egress_rules)
+            group_rules = []
+            group_rules.extend(group.ingress_rules)
+            group_rules.extend(group.egress_rules)
+            if group_rules:
+                rules[group.group_id] = group_rules
 
         return rules
 
diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py
index 2742f6518..04f2f8b54 100644
--- a/moto/ec2/responses/security_groups.py
+++ b/moto/ec2/responses/security_groups.py
@@ -254,22 +254,25 @@ DESCRIBE_SECURITY_GROUP_RULES_RESPONSE = """
 <DescribeSecurityGroupRulesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
   <requestId>{{ request_id }}</requestId>
   <securityGroupRuleSet>
-    {% for rule in rules %}
-    <item>
-        {% if rule.from_port is not none %}
-        <fromPort>{{ rule.from_port }}</fromPort>
-        {% endif %}
-        {% if rule.to_port is not none %}
-          <toPort>{{ rule.to_port }}</toPort>
-        {% endif %}
-        {% if rule.ip_ranges %}
-          <cidrIpv4>{{ rule.ip_ranges[0]['CidrIp'] }}</cidrIpv4>
-        {% endif %}
-        <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
-        <groupOwnerId>{{ rule.owner_id }}</groupOwnerId>
-        <isEgress>{{ 'true' if rule.is_egress else 'false' }}</isEgress>
-        <securityGroupRuleId>{{ rule.id }}</securityGroupRuleId>
-    </item>
+    {% for group, rule_list in rules.items() %}
+        {% for rule in rule_list %}
+            <item>
+                {% if rule.from_port is not none %}
+                <fromPort>{{ rule.from_port }}</fromPort>
+                {% endif %}
+                {% if rule.to_port is not none %}
+                  <toPort>{{ rule.to_port }}</toPort>
+                {% endif %}
+                {% if rule.ip_ranges %}
+                  <cidrIpv4>{{ rule.ip_ranges[0]['CidrIp'] }}</cidrIpv4>
+                {% endif %}
+                <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
+                <groupId>{{ group }}</groupId>
+                <groupOwnerId>{{ rule.owner_id }}</groupOwnerId>
+                <isEgress>{{ 'true' if rule.is_egress else 'false' }}</isEgress>
+                <securityGroupRuleId>{{ rule.id }}</securityGroupRuleId>
+            </item>
+        {% endfor %}
     {% endfor %}
   </securityGroupRuleSet>
 </DescribeSecurityGroupRulesResponse>"""


Test patch:
diff --git a/tests/terraformtests/terraform-tests.success.txt b/tests/terraformtests/terraform-tests.success.txt
index bd41fd494..4a5430eb5 100644
--- a/tests/terraformtests/terraform-tests.success.txt
+++ b/tests/terraformtests/terraform-tests.success.txt
@@ -30,6 +30,9 @@ apigatewayv2:
   - TestAccAPIGatewayV2IntegrationResponse
   - TestAccAPIGatewayV2Model
   - TestAccAPIGatewayV2Route
+  - TestAccAPIGatewayV2Stage_basicHTTP
+  - TestAccAPIGatewayV2Stage_basicWebSocket
+  - TestAccAPIGatewayV2Stage_disappears
   - TestAccAPIGatewayV2VPCLink
 appconfig:
   - TestAccAppConfigConfigurationProfileDataSource_basic
diff --git a/tests/test_apigatewayv2/test_apigatewayv2_stages.py b/tests/test_apigatewayv2/test_apigatewayv2_stages.py
new file mode 100644
index 000000000..3dfe47332
--- /dev/null
+++ b/tests/test_apigatewayv2/test_apigatewayv2_stages.py
@@ -0,0 +1,95 @@
+import boto3
+import pytest
+from botocore.exceptions import ClientError
+from moto import mock_apigatewayv2
+
+
+@mock_apigatewayv2
+def test_create_stage__defaults():
+    client = boto3.client("apigatewayv2", "us-east-2")
+
+    api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+    stages = client.get_stages(ApiId=api_id)
+    assert stages["Items"] == []
+
+    stage = client.create_stage(ApiId=api_id, StageName="my_stage")
+    assert stage["StageName"] == "my_stage"
+
+    resp = client.get_stage(ApiId=api_id, StageName="my_stage")
+    assert resp["StageName"] == "my_stage"
+
+    stages = client.get_stages(ApiId=api_id)
+    assert len(stages["Items"]) == 1
+
+    assert stages["Items"][0]["StageName"] == "my_stage"
+    assert stages["Items"][0]["DefaultRouteSettings"] == {
+        "DetailedMetricsEnabled": False
+    }
+    assert stages["Items"][0]["CreatedDate"]
+    assert stages["Items"][0]["LastUpdatedDate"]
+    assert stages["Items"][0]["RouteSettings"] == {}
+    assert stages["Items"][0]["StageVariables"] == {}
+    assert stages["Items"][0]["Tags"] == {}
+
+    client.delete_stage(ApiId=api_id, StageName="my_stage")
+
+    with pytest.raises(ClientError) as exc:
+        client.get_stage(ApiId=api_id, StageName="my_stage")
+    err = exc.value.response["Error"]
+    assert err["Code"] == "NotFoundException"
+    assert err["Message"] == "Invalid stage identifier specified"
+
+
+@mock_apigatewayv2
+def test_create_stage__defaults_for_websocket_api():
+    client = boto3.client("apigatewayv2", "us-east-2")
+
+    api_id = client.create_api(Name="test-api", ProtocolType="WEBSOCKET")["ApiId"]
+
+    client.create_stage(ApiId=api_id, StageName="my_stage")
+
+    stage = client.get_stage(ApiId=api_id, StageName="my_stage")
+    assert stage["StageName"] == "my_stage"
+    assert stage["DefaultRouteSettings"] == {
+        "DataTraceEnabled": False,
+        "DetailedMetricsEnabled": False,
+        "LoggingLevel": "OFF",
+    }
+
+
+@mock_apigatewayv2
+def test_create_stage():
+    client = boto3.client("apigatewayv2", "us-east-2")
+
+    api_id = client.create_api(Name="test-api", ProtocolType="HTTP")["ApiId"]
+
+    stage = client.create_stage(
+        ApiId=api_id,
+        StageName="my_stage",
+        AccessLogSettings={"Format": "JSON"},
+        AutoDeploy=True,
+        ClientCertificateId="ccid",
+        DefaultRouteSettings={"LoggingLevel": "INFO", "ThrottlingBurstLimit": 9000},
+        Description="my shiny stage",
+        RouteSettings={
+            "route1": {"DataTraceEnabled": True},
+        },
+        StageVariables={"sv1": "val1"},
+        Tags={"k1": "v1"},
+    )
+
+    assert stage["StageName"] == "my_stage"
+    assert stage["AccessLogSettings"] == {"Format": "JSON"}
+    assert stage["AutoDeploy"] is True
+    assert stage["ClientCertificateId"] == "ccid"
+    assert stage["DefaultRouteSettings"] == {
+        "LoggingLevel": "INFO",
+        "ThrottlingBurstLimit": 9000,
+    }
+    assert stage["Description"] == "my shiny stage"
+    assert stage["CreatedDate"]
+    assert stage["LastUpdatedDate"]
+    assert stage["RouteSettings"] == {"route1": {"DataTraceEnabled": True}}
+    assert stage["StageVariables"] == {"sv1": "val1"}
+    assert stage["Tags"] == {"k1": "v1"}
diff --git a/tests/test_budgets/test_budgets.py b/tests/test_budgets/test_budgets.py
index 90e2f8715..a58c7093a 100644
--- a/tests/test_budgets/test_budgets.py
+++ b/tests/test_budgets/test_budgets.py
@@ -2,8 +2,6 @@ import boto3
 import pytest
 
 from botocore.exceptions import ClientError
-import sure  # noqa # pylint: disable=unused-import
-from datetime import datetime
 from moto import mock_budgets
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 
@@ -20,7 +18,7 @@ def test_create_and_describe_budget_minimal_params():
             "BudgetType": "COST",
         },
     )
-    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
 
     try:
         budget = client.describe_budget(AccountId=ACCOUNT_ID, BudgetName="testbudget")[
@@ -28,42 +26,41 @@ def test_create_and_describe_budget_minimal_params():
         ]
     except OverflowError:
         pytest.skip("This test requires 64-bit time_t")
-    budget.should.have.key("BudgetLimit")
-    budget["BudgetLimit"].should.have.key("Amount")
-    budget["BudgetLimit"]["Amount"].should.equal("10")
-    budget["BudgetLimit"].should.have.key("Unit")
-    budget["BudgetLimit"]["Unit"].should.equal("USD")
-    budget.should.have.key("BudgetName").equals("testbudget")
-    budget.should.have.key("TimeUnit").equals("DAILY")
-    budget.should.have.key("BudgetType").equals("COST")
-    budget.should.have.key("CalculatedSpend")
-    budget["CalculatedSpend"].should.have.key("ActualSpend")
-    budget["CalculatedSpend"]["ActualSpend"].should.equal(
-        {"Amount": "0", "Unit": "USD"}
-    )
-    budget["CalculatedSpend"].should.have.key("ForecastedSpend")
-    budget["CalculatedSpend"]["ForecastedSpend"].should.equal(
-        {"Amount": "0", "Unit": "USD"}
-    )
-    budget.should.have.key("CostTypes")
-    budget["CostTypes"].should.have.key("IncludeCredit").equals(True)
-    budget["CostTypes"].should.have.key("IncludeDiscount").equals(True)
-    budget["CostTypes"].should.have.key("IncludeOtherSubscription").equals(True)
-    budget["CostTypes"].should.have.key("IncludeRecurring").equals(True)
-    budget["CostTypes"].should.have.key("IncludeRefund").equals(True)
-    budget["CostTypes"].should.have.key("IncludeSubscription").equals(True)
-    budget["CostTypes"].should.have.key("IncludeSupport").equals(True)
-    budget["CostTypes"].should.have.key("IncludeTax").equals(True)
-    budget["CostTypes"].should.have.key("IncludeUpfront").equals(True)
-    budget["CostTypes"].should.have.key("UseAmortized").equals(False)
-    budget["CostTypes"].should.have.key("UseBlended").equals(False)
-    budget.should.have.key("LastUpdatedTime").should.be.a(datetime)
-    budget.should.have.key("TimePeriod")
-    budget["TimePeriod"].should.have.key("Start")
-    budget["TimePeriod"]["Start"].should.be.a(datetime)
-    budget["TimePeriod"].should.have.key("End")
-    budget["TimePeriod"]["End"].should.be.a(datetime)
-    budget.should.have.key("TimeUnit").equals("DAILY")
+    assert "BudgetLimit" in budget
+    assert "Amount" in budget["BudgetLimit"]
+    assert budget["BudgetLimit"]["Amount"] == "10"
+    assert "Unit" in budget["BudgetLimit"]
+    assert budget["BudgetLimit"]["Unit"] == "USD"
+    assert budget["BudgetName"] == "testbudget"
+    assert budget["TimeUnit"] == "DAILY"
+    assert budget["BudgetType"] == "COST"
+    assert "CalculatedSpend" in budget
+    assert "ActualSpend" in budget["CalculatedSpend"]
+    assert budget["CalculatedSpend"]["ActualSpend"] == {"Amount": "0", "Unit": "USD"}
+    assert "ForecastedSpend" in budget["CalculatedSpend"]
+    assert budget["CalculatedSpend"]["ForecastedSpend"] == {
+        "Amount": "0",
+        "Unit": "USD",
+    }
+    assert "CostTypes" in budget
+    assert budget["CostTypes"]["IncludeCredit"] is True
+    assert budget["CostTypes"]["IncludeDiscount"] is True
+    assert budget["CostTypes"]["IncludeOtherSubscription"] is True
+    assert budget["CostTypes"]["IncludeRecurring"] is True
+    assert budget["CostTypes"]["IncludeRefund"] is True
+    assert budget["CostTypes"]["IncludeSubscription"] is True
+    assert budget["CostTypes"]["IncludeSupport"] is True
+    assert budget["CostTypes"]["IncludeTax"] is True
+    assert budget["CostTypes"]["IncludeUpfront"] is True
+    assert budget["CostTypes"]["UseAmortized"] is False
+    assert budget["CostTypes"]["UseBlended"] is False
+    assert "LastUpdatedTime" in budget
+    assert "TimePeriod" in budget
+    assert "Start" in budget["TimePeriod"]
+    assert budget["TimePeriod"]["Start"]
+    assert "End" in budget["TimePeriod"]
+    assert budget["TimePeriod"]["End"]
+    assert budget["TimeUnit"] == "DAILY"
 
 
 @mock_budgets
@@ -90,10 +87,8 @@ def test_create_existing_budget():
             },
         )
     err = exc.value.response["Error"]
-    err["Code"].should.equal("DuplicateRecordException")
-    err["Message"].should.equal(
-        "Error creating budget: testb - the budget already exists."
-    )
+    assert err["Code"] == "DuplicateRecordException"
+    assert err["Message"] == "Error creating budget: testb - the budget already exists."
 
 
 @mock_budgets
@@ -105,9 +100,10 @@ def test_create_budget_without_limit_param():
             Budget={"BudgetName": "testb", "TimeUnit": "DAILY", "BudgetType": "COST"},
         )
     err = exc.value.response["Error"]
-    err["Code"].should.equal("InvalidParameterException")
-    err["Message"].should.equal(
-        "Unable to create/update budget - please provide one of the followings: Budget Limit/ Planned Budget Limit/ Auto Adjust Data"
+    assert err["Code"] == "InvalidParameterException"
+    assert (
+        err["Message"]
+        == "Unable to create/update budget - please provide one of the followings: Budget Limit/ Planned Budget Limit/ Auto Adjust Data"
     )
 
 
@@ -117,17 +113,15 @@ def test_describe_unknown_budget():
     with pytest.raises(ClientError) as exc:
         client.describe_budget(AccountId=ACCOUNT_ID, BudgetName="unknown")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("NotFoundException")
-    err["Message"].should.equal(
-        "Unable to get budget: unknown - the budget doesn't exist."
-    )
+    assert err["Code"] == "NotFoundException"
+    assert err["Message"] == "Unable to get budget: unknown - the budget doesn't exist."
 
 
 @mock_budgets
 def test_describe_no_budgets():
     client = boto3.client("budgets", region_name="us-east-1")
     resp = client.describe_budgets(AccountId=ACCOUNT_ID)
-    resp.should.have.key("Budgets").equals([])
+    assert resp["Budgets"] == []
 
 
 @mock_budgets
@@ -147,7 +141,7 @@ def test_create_and_describe_all_budgets():
         res = client.describe_budgets(AccountId=ACCOUNT_ID)
     except OverflowError:
         pytest.skip("This test requires 64-bit time_t")
-    res["Budgets"].should.have.length_of(1)
+    assert len(res["Budgets"]) == 1
 
 
 @mock_budgets
@@ -164,10 +158,10 @@ def test_delete_budget():
     )
 
     resp = client.delete_budget(AccountId=ACCOUNT_ID, BudgetName="b1")
-    resp["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
 
     res = client.describe_budgets(AccountId=ACCOUNT_ID)
-    res["Budgets"].should.have.length_of(0)
+    assert len(res["Budgets"]) == 0
 
 
 @mock_budgets
@@ -176,7 +170,8 @@ def test_delete_unknown_budget():
     with pytest.raises(ClientError) as exc:
         client.delete_budget(AccountId=ACCOUNT_ID, BudgetName="unknown")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("NotFoundException")
-    err["Message"].should.equal(
-        "Unable to delete budget: unknown - the budget doesn't exist. Try creating it first. "
+    assert err["Code"] == "NotFoundException"
+    assert (
+        err["Message"]
+        == "Unable to delete budget: unknown - the budget doesn't exist. Try creating it first. "
     )
diff --git a/tests/test_budgets/test_notifications.py b/tests/test_budgets/test_notifications.py
index 7e1de0747..f9a306f25 100644
--- a/tests/test_budgets/test_notifications.py
+++ b/tests/test_budgets/test_notifications.py
@@ -2,7 +2,6 @@ import boto3
 import pytest
 
 from botocore.exceptions import ClientError
-import sure  # noqa # pylint: disable=unused-import
 from moto import mock_budgets
 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
 
@@ -37,13 +36,13 @@ def test_create_and_describe_notification():
     res = client.describe_notifications_for_budget(
         AccountId=ACCOUNT_ID, BudgetName="testbudget"
     )
-    res.should.have.key("Notifications").length_of(1)
+    assert len(res["Notifications"]) == 1
     notification = res["Notifications"][0]
-    notification.should.have.key("NotificationType").should.equal("ACTUAL")
-    notification.should.have.key("ComparisonOperator").should.equal("EQUAL_TO")
-    notification.should.have.key("Threshold").should.equal(123)
-    notification.should.have.key("ThresholdType").should.equal("ABSOLUTE_VALUE")
-    notification.should.have.key("NotificationState").should.equal("ALARM")
+    assert notification["NotificationType"] == "ACTUAL"
+    assert notification["ComparisonOperator"] == "EQUAL_TO"
+    assert notification["Threshold"] == 123
+    assert notification["ThresholdType"] == "ABSOLUTE_VALUE"
+    assert notification["NotificationState"] == "ALARM"
 
 
 @mock_budgets
@@ -85,24 +84,24 @@ def test_create_notification():
         },
         Subscribers=[{"SubscriptionType": "SNS", "Address": "arn:sns:topic:mytopic"}],
     )
-    res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert res["ResponseMetadata"]["HTTPStatusCode"] == 200
 
     res = client.describe_notifications_for_budget(
         AccountId=ACCOUNT_ID, BudgetName="testbudget"
     )
-    res.should.have.key("Notifications").length_of(2)
+    assert len(res["Notifications"]) == 2
     n_1 = res["Notifications"][0]
-    n_1.should.have.key("NotificationType").should.equal("ACTUAL")
-    n_1.should.have.key("ComparisonOperator").should.equal("EQUAL_TO")
-    n_1.should.have.key("Threshold").should.equal(123)
-    n_1.should.have.key("ThresholdType").should.equal("ABSOLUTE_VALUE")
-    n_1.should.have.key("NotificationState").should.equal("ALARM")
+    assert n_1["NotificationType"] == "ACTUAL"
+    assert n_1["ComparisonOperator"] == "EQUAL_TO"
+    assert n_1["Threshold"] == 123
+    assert n_1["ThresholdType"] == "ABSOLUTE_VALUE"
+    assert n_1["NotificationState"] == "ALARM"
     n_2 = res["Notifications"][1]
-    n_2.should.have.key("NotificationType").should.equal("ACTUAL")
-    n_2.should.have.key("ComparisonOperator").should.equal("GREATER_THAN")
-    n_2.should.have.key("Threshold").should.equal(0)
-    n_2.should.have.key("ThresholdType").should.equal("ABSOLUTE_VALUE")
-    n_2.should.have.key("NotificationState").should.equal("OK")
+    assert n_2["NotificationType"] == "ACTUAL"
+    assert n_2["ComparisonOperator"] == "GREATER_THAN"
+    assert n_2["Threshold"] == 0
+    assert n_2["ThresholdType"] == "ABSOLUTE_VALUE"
+    assert n_2["NotificationState"] == "OK"
 
 
 @mock_budgets
@@ -123,10 +122,8 @@ def test_create_notification_unknown_budget():
             Subscribers=[{"SubscriptionType": "EMAIL", "Address": "admin@moto.com"}],
         )
     err = exc.value.response["Error"]
-    err["Code"].should.equal("NotFoundException")
-    err["Message"].should.equal(
-        "Unable to create notification - the budget doesn't exist."
-    )
+    assert err["Code"] == "NotFoundException"
+    assert err["Message"] == "Unable to create notification - the budget doesn't exist."
 
 
 @mock_budgets
@@ -171,7 +168,7 @@ def test_delete_notification():
     res = client.describe_notifications_for_budget(
         AccountId=ACCOUNT_ID, BudgetName="testbudget"
     )
-    res.should.have.key("Notifications").length_of(0)
+    assert len(res["Notifications"]) == 0
 
 
 @mock_budgets
@@ -191,7 +188,5 @@ def test_delete_notification_unknown_budget():
             },
         )
     err = exc.value.response["Error"]
-    err["Code"].should.equal("NotFoundException")
-    err["Message"].should.equal(
-        "Unable to delete notification - the budget doesn't exist."
-    )
+    assert err["Code"] == "NotFoundException"
+    assert err["Message"] == "Unable to delete notification - the budget doesn't exist."
diff --git a/tests/test_budgets/test_server.py b/tests/test_budgets/test_server.py
index 3bfec7297..3f7333939 100644
--- a/tests/test_budgets/test_server.py
+++ b/tests/test_budgets/test_server.py
@@ -1,5 +1,4 @@
 import json
-import sure  # noqa # pylint: disable=unused-import
 
 import moto.server as server
 from moto import mock_budgets
@@ -12,5 +11,5 @@ def test_budgets_describe_budgets():
 
     headers = {"X-Amz-Target": "AWSBudgetServiceGateway.DescribeBudgets"}
     resp = test_client.post("/", headers=headers, json={})
-    resp.status_code.should.equal(200)
-    json.loads(resp.data).should.equal({"Budgets": [], "nextToken": None})
+    assert resp.status_code == 200
+    assert json.loads(resp.data) == {"Budgets": [], "nextToken": None}
diff --git a/tests/test_ce/test_ce.py b/tests/test_ce/test_ce.py
index ee572557e..0b3ff82e1 100644
--- a/tests/test_ce/test_ce.py
+++ b/tests/test_ce/test_ce.py
@@ -1,7 +1,6 @@
 """Unit tests for ce-supported APIs."""
 import boto3
 import pytest
-import sure  # noqa # pylint: disable=unused-import
 
 from botocore.exceptions import ClientError
 from moto import mock_ce
@@ -21,10 +20,8 @@ def test_create_cost_category_definition():
             {"Value": "v", "Rule": {"CostCategories": {"Key": "k", "Values": ["v"]}}}
         ],
     )
-    resp.should.have.key("CostCategoryArn").match(
-        f"arn:aws:ce::{ACCOUNT_ID}:costcategory/"
-    )
-    resp.should.have.key("EffectiveStart")
+    assert resp["CostCategoryArn"].startswith(f"arn:aws:ce::{ACCOUNT_ID}:costcategory/")
+    assert "EffectiveStart" in resp
 
 
 @mock_ce
@@ -38,10 +35,8 @@ def test_create_cost_category_definition_with_effective_start():
         ],
         EffectiveStart="2022-11-01T00:00:00Z",
     )
-    resp.should.have.key("CostCategoryArn").match(
-        f"arn:aws:ce::{ACCOUNT_ID}:costcategory/"
-    )
-    resp.should.have.key("EffectiveStart").equals("2022-11-01T00:00:00Z")
+    assert resp["CostCategoryArn"].startswith(f"arn:aws:ce::{ACCOUNT_ID}:costcategory/")
+    assert resp["EffectiveStart"] == "2022-11-01T00:00:00Z"
 
 
 @mock_ce
@@ -58,13 +53,14 @@ def test_describe_cost_category_definition():
     resp = client.describe_cost_category_definition(CostCategoryArn=ccd_arn)[
         "CostCategory"
     ]
-    resp.should.have.key("Name").equals("ccd")
-    resp.should.have.key("CostCategoryArn").equals(ccd_arn)
-    resp.should.have.key("RuleVersion").equals("CostCategoryExpression.v1")
-    resp.should.have.key("Rules").length_of(1)
-    resp["Rules"][0].should.equal(
-        {"Value": "v", "Rule": {"CostCategories": {"Key": "k", "Values": ["v"]}}}
-    )
+    assert resp["Name"] == "ccd"
+    assert resp["CostCategoryArn"] == ccd_arn
+    assert resp["RuleVersion"] == "CostCategoryExpression.v1"
+    assert len(resp["Rules"]) == 1
+    assert resp["Rules"][0] == {
+        "Value": "v",
+        "Rule": {"CostCategories": {"Key": "k", "Values": ["v"]}},
+    }
 
 
 @mock_ce
@@ -84,8 +80,8 @@ def test_delete_cost_category_definition():
     with pytest.raises(ClientError) as exc:
         client.describe_cost_category_definition(CostCategoryArn=ccd_arn)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(f"No Cost Categories found with ID {ccd_id}")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == f"No Cost Categories found with ID {ccd_id}"
 
 
 @mock_ce
@@ -111,16 +107,19 @@ def test_update_cost_category_definition():
     resp = client.describe_cost_category_definition(CostCategoryArn=ccd_arn)[
         "CostCategory"
     ]
-    resp.should.have.key("Name").equals("ccd")
-    resp.should.have.key("CostCategoryArn").equals(ccd_arn)
-    resp.should.have.key("RuleVersion").equals("CostCategoryExpression.v1")
-
-    resp.should.have.key("Rules").length_of(1)
-    resp["Rules"][0].should.equal(
-        {"Value": "v", "Rule": {"CostCategories": {"Key": "k", "Values": ["v"]}}}
-    )
-
-    resp.should.have.key("SplitChargeRules").length_of(1)
-    resp["SplitChargeRules"][0].should.equal(
-        {"Source": "s", "Targets": ["t1"], "Method": "EVEN"}
-    )
+    assert resp["Name"] == "ccd"
+    assert resp["CostCategoryArn"] == ccd_arn
+    assert resp["RuleVersion"] == "CostCategoryExpression.v1"
+
+    assert len(resp["Rules"]) == 1
+    assert resp["Rules"][0] == {
+        "Value": "v",
+        "Rule": {"CostCategories": {"Key": "k", "Values": ["v"]}},
+    }
+
+    assert len(resp["SplitChargeRules"]) == 1
+    assert resp["SplitChargeRules"][0] == {
+        "Source": "s",
+        "Targets": ["t1"],
+        "Method": "EVEN",
+    }
diff --git a/tests/test_ce/test_ce_tags.py b/tests/test_ce/test_ce_tags.py
index a1ee5b43c..ce01b9b01 100644
--- a/tests/test_ce/test_ce_tags.py
+++ b/tests/test_ce/test_ce_tags.py
@@ -1,5 +1,4 @@
 import boto3
-import sure  # noqa # pylint: disable=unused-import
 
 from moto import mock_ce
 
@@ -19,12 +18,12 @@ def test_list_tags_if_none_exist():
     )["CostCategoryArn"]
 
     resp = client.list_tags_for_resource(ResourceArn=arn)
-    resp.should.have.key("ResourceTags").equals([])
+    assert resp["ResourceTags"] == []
 
     client.tag_resource(ResourceArn=arn, ResourceTags=[{"Key": "t1", "Value": "v1"}])
 
     resp = client.list_tags_for_resource(ResourceArn=arn)
-    resp.should.have.key("ResourceTags").equals([{"Key": "t1", "Value": "v1"}])
+    assert resp["ResourceTags"] == [{"Key": "t1", "Value": "v1"}]
 
 
 @mock_ce
@@ -40,16 +39,17 @@ def test_cost_category_tags_workflow():
     )["CostCategoryArn"]
 
     resp = client.list_tags_for_resource(ResourceArn=arn)
-    resp.should.have.key("ResourceTags").equals([{"Key": "t1", "Value": "v1"}])
+    assert resp["ResourceTags"] == [{"Key": "t1", "Value": "v1"}]
 
     client.tag_resource(ResourceArn=arn, ResourceTags=[{"Key": "t2", "Value": "v2"}])
 
     resp = client.list_tags_for_resource(ResourceArn=arn)
-    resp.should.have.key("ResourceTags").equals(
-        [{"Key": "t1", "Value": "v1"}, {"Key": "t2", "Value": "v2"}]
-    )
+    assert resp["ResourceTags"] == [
+        {"Key": "t1", "Value": "v1"},
+        {"Key": "t2", "Value": "v2"},
+    ]
 
     client.untag_resource(ResourceArn=arn, ResourceTagKeys=["t2"])
 
     resp = client.list_tags_for_resource(ResourceArn=arn)
-    resp.should.have.key("ResourceTags").equals([{"Key": "t1", "Value": "v1"}])
+    assert resp["ResourceTags"] == [{"Key": "t1", "Value": "v1"}]
diff --git a/tests/test_databrew/test_databrew_datasets.py b/tests/test_databrew/test_databrew_datasets.py
index 27f16c154..2c501f470 100644
--- a/tests/test_databrew/test_databrew_datasets.py
+++ b/tests/test_databrew/test_databrew_datasets.py
@@ -99,8 +99,8 @@ def test_dataset_list_when_empty():
     client = _create_databrew_client()
 
     response = client.list_datasets()
-    response.should.have.key("Datasets")
-    response["Datasets"].should.have.length_of(0)
+    assert "Datasets" in response
+    assert len(response["Datasets"]) == 0
 
 
 @mock_databrew
@@ -109,9 +109,9 @@ def test_list_datasets_with_max_results():
 
     _create_test_datasets(client, 4)
     response = client.list_datasets(MaxResults=2)
-    response["Datasets"].should.have.length_of(2)
-    response["Datasets"][0].should.have.key("ResourceArn")
-    response.should.have.key("NextToken")
+    assert len(response["Datasets"]) == 2
+    assert "ResourceArn" in response["Datasets"][0]
+    assert "NextToken" in response
 
 
 @mock_databrew
@@ -120,7 +120,7 @@ def test_list_datasets_from_next_token():
     _create_test_datasets(client, 10)
     first_response = client.list_datasets(MaxResults=3)
     response = client.list_datasets(NextToken=first_response["NextToken"])
-    response["Datasets"].should.have.length_of(7)
+    assert len(response["Datasets"]) == 7
 
 
 @mock_databrew
@@ -128,7 +128,7 @@ def test_list_datasets_with_max_results_greater_than_actual_results():
     client = _create_databrew_client()
     _create_test_datasets(client, 4)
     response = client.list_datasets(MaxResults=10)
-    response["Datasets"].should.have.length_of(4)
+    assert len(response["Datasets"]) == 4
 
 
 @mock_databrew
@@ -138,16 +138,17 @@ def test_describe_dataset():
     # region basic test
     response = _create_test_dataset(client)
     dataset = client.describe_dataset(Name=response["Name"])
-    dataset["Name"].should.equal(response["Name"])
-    dataset.should.have.key("ResourceArn").equal(
-        f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:dataset/{response['Name']}"
+    assert dataset["Name"] == response["Name"]
+    assert (
+        dataset["ResourceArn"]
+        == f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:dataset/{response['Name']}"
     )
     # endregion
 
     # region JSON test
     response = _create_test_dataset(client, dataset_format="CSV")
     dataset = client.describe_dataset(Name=response["Name"])
-    dataset["Format"].should.equal("CSV")
+    assert dataset["Format"] == "CSV"
     # endregion
 
 
@@ -158,8 +159,8 @@ def test_describe_dataset_that_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.describe_dataset(Name="DoseNotExist")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("One or more resources can't be found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "One or more resources can't be found."
 
 
 @mock_databrew
@@ -171,8 +172,8 @@ def test_create_dataset_that_already_exists():
     with pytest.raises(ClientError) as exc:
         _create_test_dataset(client, dataset_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("AlreadyExistsException")
-    err["Message"].should.equal(f"{response['Name']} already exists.")
+    assert err["Code"] == "AlreadyExistsException"
+    assert err["Message"] == f"{response['Name']} already exists."
 
 
 @mock_databrew
@@ -182,7 +183,7 @@ def test_delete_dataset():
 
     # Check dataset exists
     dataset = client.describe_dataset(Name=response["Name"])
-    dataset["Name"].should.equal(response["Name"])
+    assert dataset["Name"] == response["Name"]
 
     # Delete the dataset
     client.delete_dataset(Name=response["Name"])
@@ -192,15 +193,15 @@ def test_delete_dataset():
         client.describe_dataset(Name=response["Name"])
 
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("One or more resources can't be found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "One or more resources can't be found."
 
     # Check that a dataset that does not exist errors
     with pytest.raises(ClientError) as exc:
         client.delete_dataset(Name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("One or more resources can't be found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "One or more resources can't be found."
 
 
 @mock_databrew
@@ -231,14 +232,15 @@ def test_update_dataset():
             },
         },
     )
-    dataset["Name"].should.equal(response["Name"])
+    assert dataset["Name"] == response["Name"]
 
     # Describe the dataset and check the changes
     dataset = client.describe_dataset(Name=response["Name"])
-    dataset["Name"].should.equal(response["Name"])
-    dataset["Format"].should.equal("TEST")
-    dataset.should.have.key("ResourceArn").equal(
-        f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:dataset/{response['Name']}"
+    assert dataset["Name"] == response["Name"]
+    assert dataset["Format"] == "TEST"
+    assert (
+        dataset["ResourceArn"]
+        == f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:dataset/{response['Name']}"
     )
 
 
@@ -272,5 +274,5 @@ def test_update_dataset_that_does_not_exist():
         )
 
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("One or more resources can't be found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "One or more resources can't be found."
diff --git a/tests/test_databrew/test_databrew_jobs.py b/tests/test_databrew/test_databrew_jobs.py
index d559fe23c..e28744e97 100644
--- a/tests/test_databrew/test_databrew_jobs.py
+++ b/tests/test_databrew/test_databrew_jobs.py
@@ -78,9 +78,9 @@ def test_create_profile_job_that_already_exists():
     with pytest.raises(ClientError) as exc:
         _create_test_profile_job(client, job_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ConflictException")
-    err["Message"].should.equal(f"The job {job_name} profile job already exists.")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
+    assert err["Code"] == "ConflictException"
+    assert err["Message"] == f"The job {job_name} profile job already exists."
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 409
 
 
 @mock_databrew
@@ -92,9 +92,9 @@ def test_create_recipe_job_that_already_exists():
     with pytest.raises(ClientError) as exc:
         _create_test_recipe_job(client, job_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ConflictException")
-    err["Message"].should.equal(f"The job {job_name} recipe job already exists.")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
+    assert err["Code"] == "ConflictException"
+    assert err["Message"] == f"The job {job_name} recipe job already exists."
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 409
 
 
 @mock_databrew
@@ -104,12 +104,12 @@ def test_create_recipe_job_with_invalid_encryption_mode():
     with pytest.raises(ClientError) as exc:
         _create_test_recipe_job(client, encryption_mode="INVALID")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        "1 validation error detected: Value 'INVALID' at 'encryptionMode' failed to satisfy constraint: "
-        "Member must satisfy enum value set: [SSE-S3, SSE-KMS]"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == "1 validation error detected: Value 'INVALID' at 'encryptionMode' failed to satisfy constraint: Member must satisfy enum value set: [SSE-S3, SSE-KMS]"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -119,12 +119,12 @@ def test_create_recipe_job_with_invalid_log_subscription_value():
     with pytest.raises(ClientError) as exc:
         _create_test_recipe_job(client, log_subscription="INVALID")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        "1 validation error detected: Value 'INVALID' at 'logSubscription' failed to satisfy constraint: "
-        "Member must satisfy enum value set: [ENABLE, DISABLE]"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == "1 validation error detected: Value 'INVALID' at 'logSubscription' failed to satisfy constraint: Member must satisfy enum value set: [ENABLE, DISABLE]"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -136,9 +136,9 @@ def test_create_recipe_job_with_same_name_as_profile_job():
     with pytest.raises(ClientError) as exc:
         _create_test_recipe_job(client, job_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ConflictException")
-    err["Message"].should.equal(f"The job {job_name} profile job already exists.")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
+    assert err["Code"] == "ConflictException"
+    assert err["Message"] == f"The job {job_name} profile job already exists."
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 409
 
 
 @mock_databrew
@@ -148,12 +148,12 @@ def test_describe_recipe_job():
     response = _create_test_recipe_job(client)
     job_name = response["Name"]
     job = client.describe_job(Name=job_name)
-    job.should.have.key("Name").equal(response["Name"])
-    job.should.have.key("Type").equal("RECIPE")
-    job.should.have.key("ResourceArn").equal(
-        f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:job/{job_name}"
+    assert job["Name"] == response["Name"]
+    assert job["Type"] == "RECIPE"
+    assert (
+        job["ResourceArn"] == f"arn:aws:databrew:us-west-1:{ACCOUNT_ID}:job/{job_name}"
     )
-    job["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert job["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -163,9 +163,9 @@ def test_describe_job_that_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.describe_job(Name="DoesNotExist")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("Job DoesNotExist wasn't found.")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "Job DoesNotExist wasn't found."
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -175,12 +175,12 @@ def test_describe_job_with_long_name():
     with pytest.raises(ClientError) as exc:
         client.describe_job(Name=name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
-        f"Member must have length less than or equal to 240"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: Member must have length less than or equal to 240"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -195,13 +195,13 @@ def test_update_profile_job():
     update_response = client.update_profile_job(
         Name=job_name, RoleArn="a" * 20, OutputLocation={"Bucket": "b" * 20}
     )
-    update_response.should.have.key("Name").equals(job_name)
-    update_response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert update_response["Name"] == job_name
+    assert update_response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
     # Describe the job to check that RoleArn was updated
     job = client.describe_job(Name=job_name)
-    job.should.have.key("Name").equal(response["Name"])
-    job.should.have.key("RoleArn").equal("a" * 20)
+    assert job["Name"] == response["Name"]
+    assert job["RoleArn"] == "a" * 20
 
 
 @mock_databrew
@@ -214,13 +214,13 @@ def test_update_recipe_job():
 
     # Update the job by changing RoleArn
     update_response = client.update_recipe_job(Name=job_name, RoleArn="a" * 20)
-    update_response.should.have.key("Name").equals(job_name)
-    update_response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert update_response["Name"] == job_name
+    assert update_response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
     # Describe the job to check that RoleArn was updated
     job = client.describe_job(Name=job_name)
-    job.should.have.key("Name").equal(response["Name"])
-    job.should.have.key("RoleArn").equal("a" * 20)
+    assert job["Name"] == response["Name"]
+    assert job["RoleArn"] == "a" * 20
 
 
 @mock_databrew
@@ -232,9 +232,9 @@ def test_update_profile_job_does_not_exist():
             Name="DoesNotExist", RoleArn="a" * 20, OutputLocation={"Bucket": "b" * 20}
         )
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("The job DoesNotExist wasn't found")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "The job DoesNotExist wasn't found"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -244,9 +244,9 @@ def test_update_recipe_job_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.update_recipe_job(Name="DoesNotExist", RoleArn="a" * 20)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("The job DoesNotExist wasn't found")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "The job DoesNotExist wasn't found"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -259,16 +259,16 @@ def test_delete_job():
 
     # Delete the job
     response = client.delete_job(Name=job_name)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
-    response["Name"].should.equal(job_name)
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert response["Name"] == job_name
 
     # Check the job does not exist anymore
     with pytest.raises(ClientError) as exc:
         client.describe_job(Name=job_name)
 
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(f"Job {job_name} wasn't found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == f"Job {job_name} wasn't found."
 
 
 @mock_databrew
@@ -278,11 +278,11 @@ def test_delete_job_does_not_exist():
     # Delete the job
     with pytest.raises(ClientError) as exc:
         client.delete_job(Name="DoesNotExist")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal("The job DoesNotExist wasn't found.")
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == "The job DoesNotExist wasn't found."
 
 
 @mock_databrew
@@ -292,12 +292,12 @@ def test_delete_job_with_long_name():
     with pytest.raises(ClientError) as exc:
         client.delete_job(Name=name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
-        f"Member must have length less than or equal to 240"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: Member must have length less than or equal to 240"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -305,9 +305,8 @@ def test_job_list_when_empty():
     client = _create_databrew_client()
 
     response = client.list_jobs()
-    response.should.have.key("Jobs")
-    response["Jobs"].should.have.length_of(0)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 0
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -316,9 +315,9 @@ def test_list_jobs_with_max_results():
 
     _create_test_recipe_jobs(client, 4)
     response = client.list_jobs(MaxResults=2)
-    response["Jobs"].should.have.length_of(2)
-    response.should.have.key("NextToken")
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 2
+    assert "NextToken" in response
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -327,8 +326,8 @@ def test_list_jobs_from_next_token():
     _create_test_recipe_jobs(client, 10)
     first_response = client.list_jobs(MaxResults=3)
     response = client.list_jobs(NextToken=first_response["NextToken"])
-    response["Jobs"].should.have.length_of(7)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 7
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -336,8 +335,8 @@ def test_list_jobs_with_max_results_greater_than_actual_results():
     client = _create_databrew_client()
     _create_test_recipe_jobs(client, 4)
     response = client.list_jobs(MaxResults=10)
-    response["Jobs"].should.have.length_of(4)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 4
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -347,8 +346,8 @@ def test_list_jobs_recipe_and_profile():
     _create_test_recipe_jobs(client, 4)
     _create_test_profile_jobs(client, 2)
     response = client.list_jobs()
-    response["Jobs"].should.have.length_of(6)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 6
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -361,8 +360,8 @@ def test_list_jobs_dataset_name_filter():
     _create_test_profile_jobs(client, 1)
 
     response = client.list_jobs(DatasetName="TEST")
-    response["Jobs"].should.have.length_of(7)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 7
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -374,8 +373,8 @@ def test_list_jobs_project_name_filter():
     _create_test_profile_jobs(client, 1)
 
     response = client.list_jobs(ProjectName="TEST_PROJECT")
-    response["Jobs"].should.have.length_of(3)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 3
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -391,5 +390,5 @@ def test_list_jobs_dataset_name_and_project_name_filter():
     _create_test_profile_jobs(client, 1)
 
     response = client.list_jobs(DatasetName="TEST", ProjectName="TEST_PROJECT")
-    response["Jobs"].should.have.length_of(10)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert len(response["Jobs"]) == 10
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
diff --git a/tests/test_databrew/test_databrew_recipes.py b/tests/test_databrew/test_databrew_recipes.py
index 60e486cc8..8f9233635 100644
--- a/tests/test_databrew/test_databrew_recipes.py
+++ b/tests/test_databrew/test_databrew_recipes.py
@@ -55,8 +55,8 @@ def test_recipe_list_when_empty():
     client = _create_databrew_client()
 
     response = client.list_recipes()
-    response.should.have.key("Recipes")
-    response["Recipes"].should.have.length_of(0)
+    assert "Recipes" in response
+    assert len(response["Recipes"]) == 0
 
 
 @mock_databrew
@@ -67,14 +67,14 @@ def test_recipe_list_with_invalid_version():
     with pytest.raises(ClientError) as exc:
         client.list_recipes(RecipeVersion=recipe_version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"Invalid version {recipe_version}. "
-        "Valid versions are LATEST_PUBLISHED and LATEST_WORKING."
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"Invalid version {recipe_version}. Valid versions are LATEST_PUBLISHED and LATEST_WORKING."
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
+    assert err["Code"] == "ValidationException"
 
 
 @mock_databrew
@@ -83,8 +83,8 @@ def test_list_recipes_with_max_results():
 
     _create_test_recipes(client, 4)
     response = client.list_recipes(MaxResults=2, RecipeVersion="LATEST_WORKING")
-    response["Recipes"].should.have.length_of(2)
-    response.should.have.key("NextToken")
+    assert len(response["Recipes"]) == 2
+    assert "NextToken" in response
 
 
 @mock_databrew
@@ -95,7 +95,7 @@ def test_list_recipes_from_next_token():
     response = client.list_recipes(
         NextToken=first_response["NextToken"], RecipeVersion="LATEST_WORKING"
     )
-    response["Recipes"].should.have.length_of(7)
+    assert len(response["Recipes"]) == 7
 
 
 @mock_databrew
@@ -103,7 +103,7 @@ def test_list_recipes_with_max_results_greater_than_actual_results():
     client = _create_databrew_client()
     _create_test_recipes(client, 4)
     response = client.list_recipes(MaxResults=10, RecipeVersion="LATEST_WORKING")
-    response["Recipes"].should.have.length_of(4)
+    assert len(response["Recipes"]) == 4
 
 
 @mock_databrew
@@ -111,7 +111,7 @@ def test_list_recipe_versions_no_recipe():
     client = _create_databrew_client()
     recipe_name = "NotExist"
     response = client.list_recipe_versions(Name=recipe_name)
-    response["Recipes"].should.have.length_of(0)
+    assert len(response["Recipes"]) == 0
 
 
 @mock_databrew
@@ -120,7 +120,7 @@ def test_list_recipe_versions_none_published():
     response = _create_test_recipe(client)
     recipe_name = response["Name"]
     response = client.list_recipe_versions(Name=recipe_name)
-    response["Recipes"].should.have.length_of(0)
+    assert len(response["Recipes"]) == 0
 
 
 @mock_databrew
@@ -130,8 +130,8 @@ def test_list_recipe_versions_one_published():
     recipe_name = response["Name"]
     client.publish_recipe(Name=recipe_name)
     response = client.list_recipe_versions(Name=recipe_name)
-    response["Recipes"].should.have.length_of(1)
-    response["Recipes"][0]["RecipeVersion"].should.equal("1.0")
+    assert len(response["Recipes"]) == 1
+    assert response["Recipes"][0]["RecipeVersion"] == "1.0"
 
 
 @mock_databrew
@@ -142,9 +142,9 @@ def test_list_recipe_versions_two_published():
     client.publish_recipe(Name=recipe_name)
     client.publish_recipe(Name=recipe_name)
     response = client.list_recipe_versions(Name=recipe_name)
-    response["Recipes"].should.have.length_of(2)
-    response["Recipes"][0]["RecipeVersion"].should.equal("1.0")
-    response["Recipes"][1]["RecipeVersion"].should.equal("2.0")
+    assert len(response["Recipes"]) == 2
+    assert response["Recipes"][0]["RecipeVersion"] == "1.0"
+    assert response["Recipes"][1]["RecipeVersion"] == "2.0"
 
 
 @mock_databrew
@@ -156,9 +156,9 @@ def test_describe_recipe_latest_working():
         Name=response["Name"], RecipeVersion="LATEST_WORKING"
     )
 
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Steps"].should.have.length_of(1)
-    recipe["RecipeVersion"].should.equal("0.1")
+    assert recipe["Name"] == response["Name"]
+    assert len(recipe["Steps"]) == 1
+    assert recipe["RecipeVersion"] == "0.1"
 
 
 @mock_databrew
@@ -168,9 +168,9 @@ def test_describe_recipe_with_version():
 
     recipe = client.describe_recipe(Name=response["Name"], RecipeVersion="0.1")
 
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Steps"].should.have.length_of(1)
-    recipe["RecipeVersion"].should.equal("0.1")
+    assert recipe["Name"] == response["Name"]
+    assert len(recipe["Steps"]) == 1
+    assert recipe["RecipeVersion"] == "0.1"
 
 
 @mock_databrew
@@ -183,9 +183,9 @@ def test_describe_recipe_latest_published():
         Name=response["Name"], RecipeVersion="LATEST_PUBLISHED"
     )
 
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Steps"].should.have.length_of(1)
-    recipe["RecipeVersion"].should.equal("1.0")
+    assert recipe["Name"] == response["Name"]
+    assert len(recipe["Steps"]) == 1
+    assert recipe["RecipeVersion"] == "1.0"
 
 
 @mock_databrew
@@ -196,9 +196,9 @@ def test_describe_recipe_implicit_latest_published():
     client.publish_recipe(Name=response["Name"])
     recipe = client.describe_recipe(Name=response["Name"])
 
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Steps"].should.have.length_of(1)
-    recipe["RecipeVersion"].should.equal("1.0")
+    assert recipe["Name"] == response["Name"]
+    assert len(recipe["Steps"]) == 1
+    assert recipe["RecipeVersion"] == "1.0"
 
 
 @mock_databrew
@@ -208,11 +208,12 @@ def test_describe_recipe_that_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name="DoseNotExist")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(
-        "The recipe DoseNotExist for version LATEST_PUBLISHED wasn't found."
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        err["Message"]
+        == "The recipe DoseNotExist for version LATEST_PUBLISHED wasn't found."
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -222,12 +223,12 @@ def test_describe_recipe_with_long_name():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name=name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
-        f"Member must have length less than or equal to 255"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: Member must have length less than or equal to 255"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -237,12 +238,12 @@ def test_describe_recipe_with_long_version():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name="AnyName", RecipeVersion=version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"1 validation error detected: Value '{version}' at 'recipeVersion' failed to satisfy constraint: "
-        f"Member must have length less than or equal to 16"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"1 validation error detected: Value '{version}' at 'recipeVersion' failed to satisfy constraint: Member must have length less than or equal to 16"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -253,9 +254,9 @@ def test_describe_recipe_with_invalid_version():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name=name, RecipeVersion=version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(f"Recipe {name} version {version} isn't valid.")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == f"Recipe {name} version {version} isn't valid."
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -289,15 +290,15 @@ def test_update_recipe():
         ],
     )
 
-    recipe["Name"].should.equal(response["Name"])
+    assert recipe["Name"] == response["Name"]
 
     # Describe the recipe and check the changes
     recipe = client.describe_recipe(
         Name=response["Name"], RecipeVersion="LATEST_WORKING"
     )
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Steps"].should.have.length_of(1)
-    recipe["Steps"][0]["Action"]["Parameters"]["removeCustomValue"].should.equal("true")
+    assert recipe["Name"] == response["Name"]
+    assert len(recipe["Steps"]) == 1
+    assert recipe["Steps"][0]["Action"]["Parameters"]["removeCustomValue"] == "true"
 
 
 @mock_databrew
@@ -310,14 +311,14 @@ def test_update_recipe_description():
         Name=response["Name"], Steps=[], Description=description
     )
 
-    recipe["Name"].should.equal(response["Name"])
+    assert recipe["Name"] == response["Name"]
 
     # Describe the recipe and check the changes
     recipe = client.describe_recipe(
         Name=response["Name"], RecipeVersion="LATEST_WORKING"
     )
-    recipe["Name"].should.equal(response["Name"])
-    recipe["Description"].should.equal(description)
+    assert recipe["Name"] == response["Name"]
+    assert recipe["Description"] == description
 
 
 @mock_databrew
@@ -328,9 +329,9 @@ def test_update_recipe_invalid():
     with pytest.raises(ClientError) as exc:
         client.update_recipe(Name=recipe_name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(f"The recipe {recipe_name} wasn't found")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == f"The recipe {recipe_name} wasn't found"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -342,9 +343,9 @@ def test_create_recipe_that_already_exists():
     with pytest.raises(ClientError) as exc:
         _create_test_recipe(client, recipe_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ConflictException")
-    err["Message"].should.equal(f"The recipe {recipe_name} already exists")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(409)
+    assert err["Code"] == "ConflictException"
+    assert err["Message"] == f"The recipe {recipe_name} already exists"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 409
 
 
 @mock_databrew
@@ -358,29 +359,29 @@ def test_publish_recipe():
     with pytest.raises(ClientError) as exc:
         recipe = client.describe_recipe(Name=recipe_name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
+    assert err["Code"] == "ResourceNotFoundException"
 
     dt_before_publish = datetime.now().astimezone()
 
     # Publish the recipe
     publish_response = client.publish_recipe(Name=recipe_name, Description="1st desc")
-    publish_response["Name"].should.equal(recipe_name)
+    assert publish_response["Name"] == recipe_name
 
     # Recipe is now published, so check we can retrieve the published version
     recipe = client.describe_recipe(Name=recipe_name)
-    recipe["Description"].should.equal("1st desc")
-    recipe["RecipeVersion"].should.equal("1.0")
-    recipe["PublishedDate"].should.be.greater_than(dt_before_publish)
+    assert recipe["Description"] == "1st desc"
+    assert recipe["RecipeVersion"] == "1.0"
+    assert recipe["PublishedDate"] > dt_before_publish
     first_published_date = recipe["PublishedDate"]
 
     # Publish the recipe a 2nd time
     publish_response = client.publish_recipe(Name=recipe_name, Description="2nd desc")
-    publish_response["Name"].should.equal(recipe_name)
+    assert publish_response["Name"] == recipe_name
 
     recipe = client.describe_recipe(Name=recipe_name)
-    recipe["Description"].should.equal("2nd desc")
-    recipe["RecipeVersion"].should.equal("2.0")
-    recipe["PublishedDate"].should.be.greater_than(first_published_date)
+    assert recipe["Description"] == "2nd desc"
+    assert recipe["RecipeVersion"] == "2.0"
+    assert recipe["PublishedDate"] > first_published_date
 
 
 @mock_databrew
@@ -389,8 +390,8 @@ def test_publish_recipe_that_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.publish_recipe(Name="DoesNotExist")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -400,14 +401,14 @@ def test_publish_long_recipe_name():
     with pytest.raises(ClientError) as exc:
         client.publish_recipe(Name=name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: "
-        f"Member must have length less than or equal to 255"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"]
+        == f"1 validation error detected: Value '{name}' at 'name' failed to satisfy constraint: Member must have length less than or equal to 255"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
+    assert err["Code"] == "ValidationException"
 
 
 @mock_databrew
@@ -419,8 +420,8 @@ def test_delete_recipe_version():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name=recipe_name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -433,10 +434,10 @@ def test_delete_recipe_version_published():
     with pytest.raises(ClientError) as exc:
         client.describe_recipe(Name=recipe_name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
     recipe = client.describe_recipe(Name=recipe_name, RecipeVersion="1.1")
-    recipe["RecipeVersion"].should.equal("1.1")
+    assert recipe["RecipeVersion"] == "1.1"
 
 
 @mock_databrew
@@ -448,11 +449,11 @@ def test_delete_recipe_version_latest_working_after_publish():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion="LATEST_WORKING")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        "Recipe version LATEST_WORKING is not allowed to be deleted"
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"] == "Recipe version LATEST_WORKING is not allowed to be deleted"
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -464,9 +465,9 @@ def test_delete_recipe_version_latest_working_numeric_after_publish():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion="1.1")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal("Recipe version 1.1 is not allowed to be deleted")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == "Recipe version 1.1 is not allowed to be deleted"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -479,11 +480,11 @@ def test_delete_recipe_version_invalid_version_string():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"Recipe {recipe_name} version {recipe_version} is invalid."
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"] == f"Recipe {recipe_name} version {recipe_version} is invalid."
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -496,11 +497,11 @@ def test_delete_recipe_version_invalid_version_length():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ValidationException")
-    err["Message"].should.equal(
-        f"Recipe {recipe_name} version {recipe_version} is invalid."
+    assert err["Code"] == "ValidationException"
+    assert (
+        err["Message"] == f"Recipe {recipe_name} version {recipe_version} is invalid."
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 400
 
 
 @mock_databrew
@@ -510,9 +511,9 @@ def test_delete_recipe_version_unknown_recipe():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion="1.1")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(f"The recipe {recipe_name} wasn't found")
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert err["Code"] == "ResourceNotFoundException"
+    assert err["Message"] == f"The recipe {recipe_name} wasn't found"
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
 
 
 @mock_databrew
@@ -524,8 +525,9 @@ def test_delete_recipe_version_unknown_version():
     with pytest.raises(ClientError) as exc:
         client.delete_recipe_version(Name=recipe_name, RecipeVersion=recipe_version)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("ResourceNotFoundException")
-    err["Message"].should.equal(
-        f"The recipe {recipe_name} version {recipe_version} wasn't found."
+    assert err["Code"] == "ResourceNotFoundException"
+    assert (
+        err["Message"]
+        == f"The recipe {recipe_name} version {recipe_version} wasn't found."
     )
-    exc.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(404)
+    assert exc.value.response["ResponseMetadata"]["HTTPStatusCode"] == 404
diff --git a/tests/test_databrew/test_databrew_rulesets.py b/tests/test_databrew/test_databrew_rulesets.py
index a59a38c01..70ba06a36 100644
--- a/tests/test_databrew/test_databrew_rulesets.py
+++ b/tests/test_databrew/test_databrew_rulesets.py
@@ -8,8 +8,7 @@ from moto import mock_databrew
 
 
 def _create_databrew_client():
-    client = boto3.client("databrew", region_name="us-west-1")
-    return client
+    return boto3.client("databrew", region_name="us-west-1")
 
 
 def _create_test_ruleset(client, tags=None, ruleset_name=None):
@@ -46,8 +45,8 @@ def test_ruleset_list_when_empty():
     client = _create_databrew_client()
 
     response = client.list_rulesets()
-    response.should.have.key("Rulesets")
-    response["Rulesets"].should.have.length_of(0)
+    assert "Rulesets" in response
+    assert len(response["Rulesets"]) == 0
 
 
 @mock_databrew
@@ -56,8 +55,8 @@ def test_list_ruleset_with_max_results():
 
     _create_test_rulesets(client, 4)
     response = client.list_rulesets(MaxResults=2)
-    response["Rulesets"].should.have.length_of(2)
-    response.should.have.key("NextToken")
+    assert len(response["Rulesets"]) == 2
+    assert "NextToken" in response
 
 
 @mock_databrew
@@ -66,7 +65,7 @@ def test_list_rulesets_from_next_token():
     _create_test_rulesets(client, 10)
     first_response = client.list_rulesets(MaxResults=3)
     response = client.list_rulesets(NextToken=first_response["NextToken"])
-    response["Rulesets"].should.have.length_of(7)
+    assert len(response["Rulesets"]) == 7
 
 
 @mock_databrew
@@ -74,7 +73,7 @@ def test_list_rulesets_with_max_results_greater_than_actual_results():
     client = _create_databrew_client()
     _create_test_rulesets(client, 4)
     response = client.list_rulesets(MaxResults=10)
-    response["Rulesets"].should.have.length_of(4)
+    assert len(response["Rulesets"]) == 4
 
 
 @mock_databrew
@@ -84,9 +83,9 @@ def test_describe_ruleset():
 
     ruleset = client.describe_ruleset(Name=response["Name"])
 
-    ruleset["Name"].should.equal(response["Name"])
-    ruleset["Rules"].should.have.length_of(1)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
+    assert ruleset["Name"] == response["Name"]
+    assert len(ruleset["Rules"]) == 1
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
 
 
 @mock_databrew
@@ -96,8 +95,8 @@ def test_describe_ruleset_that_does_not_exist():
     with pytest.raises(ClientError) as exc:
         client.describe_ruleset(Name="DoseNotExist")
     err = exc.value.response["Error"]
-    err["Code"].should.equal("EntityNotFoundException")
-    err["Message"].should.equal("Ruleset DoseNotExist not found.")
+    assert err["Code"] == "EntityNotFoundException"
+    assert err["Message"] == "Ruleset DoseNotExist not found."
 
 
 @mock_databrew
@@ -109,8 +108,8 @@ def test_create_ruleset_that_already_exists():
     with pytest.raises(ClientError) as exc:
         _create_test_ruleset(client, ruleset_name=response["Name"])
     err = exc.value.response["Error"]
-    err["Code"].should.equal("AlreadyExistsException")
-    err["Message"].should.equal("Ruleset already exists.")
+    assert err["Code"] == "AlreadyExistsException"
+    assert err["Message"] == "Ruleset already exists."
 
 
 @mock_databrew
@@ -121,27 +120,27 @@ def test_delete_ruleset():
 
     # Check ruleset exists
     ruleset = client.describe_ruleset(Name=ruleset_name)
-    ruleset["Name"].should.equal(response["Name"])
+    assert ruleset["Name"] == response["Name"]
 
     # Delete the ruleset
     response = client.delete_ruleset(Name=ruleset_name)
-    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
-    response["Name"].should.equal(ruleset_name)
+    assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
+    assert response["Name"] == ruleset_name
 
     # Check it does not exist anymore
     with pytest.raises(ClientError) as exc:
         client.describe_ruleset(Name=ruleset_name)
 
     err = exc.value.response["Error"]
-    err["Code"].should.equal("EntityNotFoundException")
-    err["Message"].should.equal(f"Ruleset {ruleset_name} not found.")
+    assert err["Code"] == "EntityNotFoundException"
+    assert err["Message"] == f"Ruleset {ruleset_name} not found."
 
     # Check that a ruleset that does not exist errors
     with pytest.raises(ClientError) as exc:
         client.delete_ruleset(Name=ruleset_name)
     err = exc.value.response["Error"]
-    err["Code"].should.equal("EntityNotFoundException")
-    err["Message"].should.equal(f"Ruleset {ruleset_name} not found.")
+    assert err["Code"] == "EntityNotFoundException"
+    assert err["Message"] == f"Ruleset {ruleset_name} not found."
 
 
 @mock_databrew
@@ -166,10 +165,10 @@ def test_update_ruleset():
             }
         ],
     )
-    ruleset["Name"].should.equal(response["Name"])
+    assert ruleset["Name"] == response["Name"]
 
     # Describe the ruleset and check the changes
     ruleset = client.describe_ruleset(Name=response["Name"])
-    ruleset["Name"].should.equal(response["Name"])
-    ruleset["Rules"].should.have.length_of(1)
-    ruleset["Rules"][0]["SubstitutionMap"][":val1"].should.equal("10")
+    assert ruleset["Name"] == response["Name"]
+    assert len(ruleset["Rules"]) == 1
+    assert ruleset["Rules"][0]["SubstitutionMap"][":val1"] == "10"
diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py
index c7cc6dc87..70d859d8e 100644
--- a/tests/test_ec2/test_security_groups.py
+++ b/tests/test_ec2/test_security_groups.py
@@ -586,6 +586,7 @@ def test_create_and_describe_security_grp_rule():
     assert rules[0]["IsEgress"] is True
     assert rules[0]["IpProtocol"] == "-1"
     assert rules[0]["CidrIpv4"] == "0.0.0.0/0"
+    assert "GroupId" in rules[0]
 
 
 @mock_ec2