Repository: getmoto/moto (python)

Issue:
API Gateway: query ignored in get_api_keys
As stated in the `get_api_keys` [documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/apigateway/client/get_api_keys.html#APIGateway.Client.get_api_keys), the parameter `nameQuery` is used to filter API Keys by name.

Moto is completely ignoring the query and returning all the keys independently of value passed.

In the following example, the second assert fails because all keys are returned when only the second should match the nameQuery.

Even if `nameQuery` does not guarantee an exact match, Moto should have a similar behavior to AWS.

```python
import boto3
import pytest
from moto import mock_aws


@pytest.fixture()
def api_gateway_mock():
    with mock_aws():
        ag_client = boto3.client("apigateway")

        ag_client.create_api_key(
            name="123-456",
            description="Mock API key 123-456",
            enabled=True,
            tags={"user": "123"},
        )

        ag_client.create_api_key(
            name="654-321",
            description="Mock API key 654-321",
            enabled=True,
            tags={"user": "654"},
        )

        yield ag_client


def test_get_api_keys(api_gateway_mock):
    all_keys = api_gateway_mock.get_api_keys(limit=10).get("items", [])
    assert len(all_keys) == 2
    key_123 = api_gateway_mock.get_api_keys(nameQuery="123-456", limit=10).get("items", [])
    assert len(key_123) == 1
```

Moto version: 5.0.15

`get_api_keys` is marked as implemented in https://docs.getmoto.org/en/latest/docs/services/apigateway.html


Pull request description:
APIGateway: get_api_keys()/get_usage_plan_keys() now support the nameQuery param
Fixes #8161

Patch:
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index 08d89884b..0973dcbc3 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -2150,8 +2150,8 @@ class APIGatewayBackend(BaseBackend):
         self.keys[key.id] = key
         return key
 
-    def get_api_keys(self) -> List[ApiKey]:
-        return list(self.keys.values())
+    def get_api_keys(self, name: Optional[str] = None) -> List[ApiKey]:
+        return [key for key in self.keys.values() if not name or name == key.name]
 
     def get_api_key(self, api_key_id: str) -> ApiKey:
         if api_key_id not in self.keys:
@@ -2222,11 +2222,16 @@ class APIGatewayBackend(BaseBackend):
         self.usage_plan_keys[usage_plan_id][usage_plan_key.id] = usage_plan_key
         return usage_plan_key
 
-    def get_usage_plan_keys(self, usage_plan_id: str) -> List[UsagePlanKey]:
+    def get_usage_plan_keys(
+        self, usage_plan_id: str, name: Optional[str] = None
+    ) -> List[UsagePlanKey]:
         if usage_plan_id not in self.usage_plan_keys:
             return []
 
-        return list(self.usage_plan_keys[usage_plan_id].values())
+        plan_keys = self.usage_plan_keys[usage_plan_id].values()
+        if name:
+            return [key for key in plan_keys if not name or key.name == name]
+        return list(plan_keys)
 
     def get_usage_plan_key(self, usage_plan_id: str, key_id: str) -> UsagePlanKey:
         # first check if is a valid api key
diff --git a/moto/apigateway/responses.py b/moto/apigateway/responses.py
index 8ffa618b9..e4eb292d6 100644
--- a/moto/apigateway/responses.py
+++ b/moto/apigateway/responses.py
@@ -616,7 +616,8 @@ class APIGatewayResponse(BaseResponse):
 
     def get_api_keys(self) -> str:
         include_values = self._get_bool_param("includeValues") or False
-        apikeys_response = self.backend.get_api_keys()
+        name = self._get_param("name")
+        apikeys_response = self.backend.get_api_keys(name=name)
         resp = [a.to_json() for a in apikeys_response]
         if not include_values:
             for key in resp:
@@ -680,8 +681,9 @@ class APIGatewayResponse(BaseResponse):
 
     def get_usage_plan_keys(self) -> str:
         usage_plan_id = self.path.split("/")[2]
-        usage_plans_response = self.backend.get_usage_plan_keys(usage_plan_id)
-        return json.dumps({"item": [u.to_json() for u in usage_plans_response]})
+        name = self._get_param("name")
+        usage_plans = self.backend.get_usage_plan_keys(usage_plan_id, name=name)
+        return json.dumps({"item": [u.to_json() for u in usage_plans]})
 
     def create_domain_name(self) -> TYPE_RESPONSE:
         domain_name = self._get_param("domainName")
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index 9a37d709f..b6c823cd9 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -558,6 +558,7 @@ class DynamoDBBackend(BaseBackend):
         expression_attribute_names: Optional[Dict[str, Any]] = None,
         expression_attribute_values: Optional[Dict[str, Any]] = None,
         condition_expression: Optional[str] = None,
+        return_values_on_condition_check_failure: Optional[str] = None,
     ) -> Optional[Item]:
         table = self.get_table(table_name)
 
@@ -570,7 +571,13 @@ class DynamoDBBackend(BaseBackend):
             expression_attribute_values,
         )
         if not condition_op.expr(item):
-            raise ConditionalCheckFailed
+            if (
+                return_values_on_condition_check_failure == "ALL_OLD"
+                and item is not None
+            ):
+                raise ConditionalCheckFailed(item=item.to_json()["Attributes"])
+            else:
+                raise ConditionalCheckFailed
 
         return table.delete_item(hash_value, range_value)
 
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index 8ef022c63..a2c9c02f0 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -870,6 +870,9 @@ class DynamoHandler(BaseResponse):
         condition_expression = self.body.get("ConditionExpression")
         expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
         expression_attribute_values = self._get_expr_attr_values()
+        return_values_on_condition_check_failure = self.body.get(
+            "ReturnValuesOnConditionCheckFailure"
+        )
 
         item = self.dynamodb_backend.delete_item(
             name,
@@ -877,6 +880,7 @@ class DynamoHandler(BaseResponse):
             expression_attribute_names,
             expression_attribute_values,
             condition_expression,
+            return_values_on_condition_check_failure,
         )
 
         if item and return_values == "ALL_OLD":


Test patch:
diff --git a/tests/test_apigateway/test_apigateway.py b/tests/test_apigateway/test_apigateway.py
index 2b0a3fb06..5c325a4e7 100644
--- a/tests/test_apigateway/test_apigateway.py
+++ b/tests/test_apigateway/test_apigateway.py
@@ -1522,36 +1522,42 @@ def test_get_api_key_include_value():
 
 
 @mock_aws
-def test_get_api_keys_include_values():
+def test_get_api_keys():
     region_name = "us-west-2"
     client = boto3.client("apigateway", region_name=region_name)
 
-    apikey_value = "01234567890123456789"
-    apikey_name = "TESTKEY1"
-    payload = {"value": apikey_value, "name": apikey_name}
+    payload = {"value": "aaaaaaaaaaaaaaaaaaaa", "name": "TESTKEY1"}
 
-    apikey_value2 = "01234567890123456789123"
-    apikey_name2 = "TESTKEY1"
-    payload2 = {"value": apikey_value2, "name": apikey_name2}
+    payload2 = {"value": "aaaaaaaacccaaaaaaaaa", "name": "TESTKEY2"}
 
     client.create_api_key(**payload)
     client.create_api_key(**payload2)
 
-    response = client.get_api_keys()
-    assert len(response["items"]) == 2
-    for api_key in response["items"]:
+    keys = client.get_api_keys()["items"]
+    assert len(keys) == 2
+    for api_key in keys:
         assert "value" not in api_key
 
-    response = client.get_api_keys(includeValues=True)
-    assert len(response["items"]) == 2
-    for api_key in response["items"]:
-        assert "value" in api_key
+    keys = client.get_api_keys(includeValues=True)["items"]
+    short_keys = [{"name": key["name"], "value": key["value"]} for key in keys]
+    assert payload in short_keys
+    assert payload2 in short_keys
 
-    response = client.get_api_keys(includeValues=False)
-    assert len(response["items"]) == 2
-    for api_key in response["items"]:
+    keys = client.get_api_keys(includeValues=False)["items"]
+    assert len(keys) == 2
+    for api_key in keys:
         assert "value" not in api_key
 
+    # Query By Name
+    keys = client.get_api_keys(nameQuery="TESTKEY1")["items"]
+    assert [key["name"] for key in keys] == ["TESTKEY1"]
+
+    keys = client.get_api_keys(nameQuery="TESTKEY2")["items"]
+    assert [key["name"] for key in keys] == ["TESTKEY2"]
+
+    keys = client.get_api_keys(nameQuery="TESTKEY3")["items"]
+    assert keys == []
+
 
 @mock_aws
 def test_create_api_key():
@@ -1782,6 +1788,12 @@ def test_usage_plan_keys():
     response = client.get_usage_plan_keys(usagePlanId=usage_plan_id)
     assert len(response["items"]) == 1
 
+    # Query By Name
+    plans = client.get_usage_plan_keys(usagePlanId=usage_plan_id, nameQuery=key_name)
+    assert len(plans["items"]) == 1
+    plans = client.get_usage_plan_keys(usagePlanId=usage_plan_id, nameQuery="unknown")
+    assert len(plans["items"]) == 0
+
     # Get a single usage plan key and check it matches the created one
     usage_plan_key = client.get_usage_plan_key(
         usagePlanId=usage_plan_id, keyId=usage_plan_key_id
diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
index d2db55a4a..0757efa11 100644
--- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
@@ -1285,6 +1285,52 @@ def test_update_item_returns_old_item(table_name=None):
     }
 
 
+@pytest.mark.aws_verified
+@dynamodb_aws_verified()
+def test_delete_item_returns_old_item(table_name=None):
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
+    table = dynamodb.Table(table_name)
+    table.put_item(Item={"pk": "mark", "lock": {"acquired_at": 123}})
+
+    with pytest.raises(ClientError) as exc:
+        table.delete_item(
+            Key={"pk": "mark"},
+            ExpressionAttributeNames={
+                "#lock": "lock",
+                "#acquired_at": "acquired_at",
+            },
+            ConditionExpression="attribute_not_exists(#lock.#acquired_at)",
+        )
+    resp = exc.value.response
+    assert resp["Error"] == {
+        "Message": "The conditional request failed",
+        "Code": "ConditionalCheckFailedException",
+    }
+    assert resp["message"] == "The conditional request failed"
+    assert "Item" not in resp
+
+    with pytest.raises(ClientError) as exc:
+        table.delete_item(
+            Key={"pk": "mark"},
+            ExpressionAttributeNames={
+                "#lock": "lock",
+                "#acquired_at": "acquired_at",
+            },
+            ReturnValuesOnConditionCheckFailure="ALL_OLD",
+            ConditionExpression="attribute_not_exists(#lock.#acquired_at)",
+        )
+    resp = exc.value.response
+    assert resp["Error"] == {
+        "Message": "The conditional request failed",
+        "Code": "ConditionalCheckFailedException",
+    }
+    assert "message" not in resp
+    assert resp["Item"] == {
+        "lock": {"M": {"acquired_at": {"N": "123"}}},
+        "pk": {"S": "mark"},
+    }
+
+
 @pytest.mark.aws_verified
 @dynamodb_aws_verified()
 def test_scan_with_missing_value(table_name=None):