Repository: getmoto/moto
Pull request #7362 (2024-02-18 21:38:00)

Techdebt: Update Cloudformation Coverage (to use mock_aws)


Patch:
diff --git a/docs/docs/services/apigateway.rst b/docs/docs/services/apigateway.rst
index 988edd6ab..4d3797589 100644
--- a/docs/docs/services/apigateway.rst
+++ b/docs/docs/services/apigateway.rst
@@ -151,5 +151,13 @@ apigateway
 - [X] update_stage
 - [ ] update_usage
 - [X] update_usage_plan
+  
+        The following PatchOperations are currently supported:
+        add    : Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        replace: Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        remove : Nothing yet
+        copy   : Nothing yet
+        
+
 - [ ] update_vpc_link
 
diff --git a/docs/docs/services/cf.rst b/docs/docs/services/cf.rst
index 11af8d1af..b3e227189 100644
--- a/docs/docs/services/cf.rst
+++ b/docs/docs/services/cf.rst
@@ -29,11 +29,7 @@ Please let us know if you'd like support for a resource not yet listed here.
   +---------------------------------------+--------+--------+--------+----------------------------------------+
   |AWS::Batch::ComputeEnvironment         |    x   |        |        | - [ ] ComputeEnvironmentArn            |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
-  |AWS::Batch::JobDefinition              |    x   |        |        | - [ ] Status                           |
-  +---------------------------------------+--------+--------+--------+ - [ ] Revision                         |
-  |                                       |        |        |        | - [ ] JobDefinitionArn                 |
-  +---------------------------------------+--------+--------+--------+ - [ ] ContainerOrchestrationType       |
-  |                                       |        |        |        |                                        |
+  |AWS::Batch::JobDefinition              |    x   |        |        |                                        |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
   |AWS::Batch::JobQueue                   |    x   |        |        | - [ ] JobQueueArn                      |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
@@ -73,7 +69,7 @@ Please let us know if you'd like support for a resource not yet listed here.
   +---------------------------------------+--------+--------+--------+ - [ ] VpcId                            |
   |                                       |        |        |        |                                        |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
-  |AWS::EC2::SecurityGroupIngress         |    x   |        |        |                                        |
+  |AWS::EC2::SecurityGroupIngress         |    x   |        |        | - [ ] Id                               |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
   |AWS::EC2::Subnet                       |    x   |        |    x   | - [ ] VpcId                            |
   +---------------------------------------+--------+--------+--------+ - [ ] NetworkAclAssociationId          |
@@ -167,11 +163,11 @@ Please let us know if you'd like support for a resource not yet listed here.
   +---------------------------------------+--------+--------+--------+----------------------------------------+
   |AWS::RDS::DBParameterGroup             |    x   |        |        | - [ ] DBParameterGroupName             |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
-  |AWS::Redshift::Cluster                 |    x   |        |        | - [x] Endpoint.Address                 |
-  +---------------------------------------+--------+--------+--------+ - [x] Endpoint.Port                    |
-  |                                       |        |        |        | - [ ] Id                               |
-  +---------------------------------------+--------+--------+--------+ - [ ] DeferMaintenanceIdentifier       |
-  |                                       |        |        |        |                                        |
+  |AWS::Redshift::Cluster                 |    x   |        |        | - [ ] MasterPasswordSecretArn          |
+  +---------------------------------------+--------+--------+--------+ - [x] Endpoint.Address                 |
+  |                                       |        |        |        | - [x] Endpoint.Port                    |
+  +---------------------------------------+--------+--------+--------+ - [ ] ClusterNamespaceArn              |
+  |                                       |        |        |        | - [ ] DeferMaintenanceIdentifier       |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
   |AWS::Route53::HealthCheck              |    x   |        |        | - [ ] HealthCheckId                    |
   +---------------------------------------+--------+--------+--------+----------------------------------------+
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index d207bbd83..14cf41b39 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -874,21 +874,29 @@ class UsagePlan(BaseModel):
         for op in patch_operations:
             path = op["path"]
             value = op["value"]
+            if op["op"] == "add":
+                if path == "/apiStages":
+                    self.api_stages.append(
+                        {"apiId": value.split(":")[0], "stage": value.split(":")[1]}
+                    )
             if op["op"] == "replace":
                 if "/name" in path:
                     self.name = value
-                if "/productCode" in path:
-                    self.product_code = value
                 if "/description" in path:
                     self.description = value
+            if op["op"] in ["add", "replace"]:
+                if "/productCode" in path:
+                    self.product_code = value
                 if "/quota/limit" in path:
                     self.quota["limit"] = value
                 if "/quota/period" in path:
                     self.quota["period"] = value
+                if path == "/quota/offset":
+                    self.quota["offset"] = value
                 if "/throttle/rateLimit" in path:
-                    self.throttle["rateLimit"] = value
+                    self.throttle["rateLimit"] = int(value)
                 if "/throttle/burstLimit" in path:
-                    self.throttle["burstLimit"] = value
+                    self.throttle["burstLimit"] = int(value)
 
 
 class RequestValidator(BaseModel):
@@ -2103,6 +2111,13 @@ class APIGatewayBackend(BaseBackend):
         return self.usage_plans[usage_plan_id]
 
     def update_usage_plan(self, usage_plan_id: str, patch_operations: Any) -> UsagePlan:
+        """
+        The following PatchOperations are currently supported:
+        add    : Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        replace: Everything except /apiStages/{apidId:stageName}/throttle/ and children
+        remove : Nothing yet
+        copy   : Nothing yet
+        """
         if usage_plan_id not in self.usage_plans:
             raise UsagePlanNotFoundException()
         self.usage_plans[usage_plan_id].apply_patch_operations(patch_operations)
diff --git a/scripts/cloudformation_coverage.py b/scripts/cloudformation_coverage.py
index df61bfc25..89a4ae005 100755
--- a/scripts/cloudformation_coverage.py
+++ b/scripts/cloudformation_coverage.py
@@ -4,10 +4,15 @@ import requests
 import os
 
 import moto
+from moto.backends import get_backend, list_of_moto_modules
 
-# Populate CloudFormationModel.__subclasses__()
-moto.mock_all()
 
+# Populate CloudFormationModel.__subclasses__()
+for service_name in list_of_moto_modules():
+    try:
+        get_backend(service_name)
+    except AttributeError:
+        pass
 
 script_dir = os.path.dirname(os.path.abspath(__file__))
 
@@ -59,7 +64,9 @@ class CloudFormationChecklist:
 
     @property
     def moto_model(self):
-        for subclass in moto.core.common_models.CloudFormationModel.__subclasses__():
+        from moto.core.common_models import CloudFormationModel
+
+        for subclass in CloudFormationModel.__subclasses__():
             subclass_service = subclass.__module__.split(".")[1]
             subclass_model = subclass.__name__
 


Test patch:
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
index 88bfc2a2b..1abc572c8 100644
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -1701,13 +1701,28 @@ def test_update_usage_plan():
     payload = {
         "name": "TEST-PLAN-2",
         "description": "Description",
-        "quota": {"limit": 10, "period": "DAY", "offset": 0},
-        "throttle": {"rateLimit": 2, "burstLimit": 1},
-        "apiStages": [{"apiId": "foo", "stage": "bar"}],
         "tags": {"tag_key": "tag_value"},
     }
     response = client.create_usage_plan(**payload)
     usage_plan_id = response["id"]
+
+    add = client.update_usage_plan(
+        usagePlanId=usage_plan_id,
+        patchOperations=[
+            {"op": "add", "path": "/apiStages", "value": "foo:bar"},
+            {"op": "add", "path": "/productCode", "value": "mypc"},
+            {"op": "add", "path": "/quota/limit", "value": "1"},
+            {"op": "add", "path": "/quota/offset", "value": "2"},
+            {"op": "add", "path": "/quota/period", "value": "DAY"},
+            {"op": "add", "path": "/throttle/burstLimit", "value": "11"},
+            {"op": "add", "path": "/throttle/rateLimit", "value": "12"},
+        ],
+    )
+    assert add["apiStages"] == [{"apiId": "foo", "stage": "bar"}]
+    assert add["productCode"] == "mypc"
+    assert add["quota"] == {"limit": 1, "offset": 2, "period": "DAY"}
+    assert add["throttle"] == {"burstLimit": 11, "rateLimit": 12}
+
     response = client.update_usage_plan(
         usagePlanId=usage_plan_id,
         patchOperations=[