Repository: getmoto/moto

Issue:
APIGateway GetUsagePlans should filter returned UsagePlans by keyId
It's possible in boto to make the [get_usage_plans](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigateway.html#APIGateway.Client.get_usage_plans) call to API Gateway with a `keyId` parameter. The purpose is to filter All Usage Plans for a particular API Key. This is useful is you're looking a managing what usage plans an API Key is linked to. 

We see this get added to the queryString parameters in the call 

```
endpoint.py                147 DEBUG    Making request for OperationModel(name=GetUsagePlans) (verify_ssl=True) with params: {'url_path': '/usageplans', 'query_string': {'keyId': '7559324837'}, 'method': 'GET', 'headers': {'User-Agent': 'Boto3/1.7.3 Python/3.6.4 Darwin/18.0.0 Botocore/1.10.52', 'Accept': 'application/json'}, 'body': b'', 'url': 'https://apigateway.eu-west-1.amazonaws.com/usageplans?keyId=7559324837', 'context': {'client_region': 'eu-west-1', 'client_config': <botocore.config.Config object at 0x1146ff588>, 'has_streaming_input': False, 'auth_type': None}}
```

moto currently does not support filtering the returned list of usage plans by KeyId. this results in more usage plans being returned then expected. 

Below is a quick verification script, running the command through moto, then through an actual AWS Account to compare the difference in number of usage plans returned: 

```python
import moto
import boto3
import uuid

def test_usage_plans_filtering():
    client = boto3.client("apigateway")
    api_keys = {"a": {}, "b": {}}

    # create 2 different API Keys
    for k, v in api_keys.items():
        api_keys[k]["api_key"] = client.create_api_key(name=k)

    # create a usage plan per key, and attach the api key to the usage plan
    for api_key in api_keys:
        usage_plan = client.create_usage_plan(name=api_key)
        client.create_usage_plan_key(usagePlanId=usage_plan["id"],
                keyId=api_keys[api_key]["api_key"]["id"],
                keyType="API_KEY",
            )

    # get the plans, filtering by api key id
    for api_key in api_keys:
        api_key_id = api_keys[api_key]["api_key"]["id"]
        plans = client.get_usage_plans(keyId=api_key_id)["items"]
        total_usage_plans_per_key = len(plans)
        print("Total Plans Per Key: {}".format(total_usage_plans_per_key))

# Test with and without moto
with moto.mock_apigateway():
    print("With moto:")
    test_usage_plans_filtering()

print("Without moto:")
test_usage_plans_filtering()
```

Produces:
```
With moto:
Total Plans Per Key: 2
Total Plans Per Key: 2
Without moto:
Total Plans Per Key: 1
Total Plans Per Key: 1
```

I would expect the result of the get_usage_plans using the keyId parameter to only return usage plans of a particular keyId, and not be returned all the usage plans.

:) G



Discussion:

Resolving patch:
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -606,8 +606,15 @@ def create_usage_plan(self, payload):
         self.usage_plans[plan['id']] = plan
         return plan
 
-    def get_usage_plans(self):
-        return list(self.usage_plans.values())
+    def get_usage_plans(self, api_key_id=None):
+        plans = list(self.usage_plans.values())
+        if api_key_id is not None:
+            plans = [
+                plan
+                for plan in plans
+                if self.usage_plan_keys.get(plan['id'], {}).get(api_key_id, False)
+            ]
+        return plans
 
     def get_usage_plan(self, usage_plan_id):
         return self.usage_plans[usage_plan_id]
diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py
--- a/moto/apigateway/responses.py
+++ b/moto/apigateway/responses.py
@@ -255,7 +255,8 @@ def usage_plans(self, request, full_url, headers):
         if self.method == 'POST':
             usage_plan_response = self.backend.create_usage_plan(json.loads(self.body))
         elif self.method == 'GET':
-            usage_plans_response = self.backend.get_usage_plans()
+            api_key_id = self.querystring.get("keyId", [None])[0]
+            usage_plans_response = self.backend.get_usage_plans(api_key_id=api_key_id)
             return 200, {}, json.dumps({"item": usage_plans_response})
         return 200, {}, json.dumps(usage_plan_response)
 


Test patch:
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -1084,3 +1084,36 @@ def test_create_usage_plan_key_non_existent_api_key():
     # Attempt to create a usage plan key for a API key that doesn't exists
     payload = {'usagePlanId': usage_plan_id, 'keyId': 'non-existent', 'keyType': 'API_KEY' }
     client.create_usage_plan_key.when.called_with(**payload).should.throw(ClientError)
+
+
+@mock_apigateway
+def test_get_usage_plans_using_key_id():
+    region_name = 'us-west-2'
+    client = boto3.client('apigateway', region_name=region_name)
+
+    # Create 2 Usage Plans
+    # one will be attached to an API Key, the other will remain unattached
+    attached_plan = client.create_usage_plan(name='Attached')
+    unattached_plan = client.create_usage_plan(name='Unattached')
+
+    # Create an API key
+    # to attach to the usage plan
+    key_name = 'test-api-key'
+    response = client.create_api_key(name=key_name)
+    key_id = response["id"]
+
+    # Create a Usage Plan Key
+    # Attached the Usage Plan and API Key
+    key_type = 'API_KEY'
+    payload = {'usagePlanId': attached_plan['id'], 'keyId': key_id, 'keyType': key_type}
+    response = client.create_usage_plan_key(**payload)
+
+    # All usage plans should be returned when keyId is not included
+    all_plans = client.get_usage_plans()
+    len(all_plans['items']).should.equal(2)
+
+    # Only the usage plan attached to the given api key are included
+    only_plans_with_key = client.get_usage_plans(keyId=key_id)
+    len(only_plans_with_key['items']).should.equal(1)
+    only_plans_with_key['items'][0]['name'].should.equal(attached_plan['name'])
+    only_plans_with_key['items'][0]['id'].should.equal(attached_plan['id'])