Repository: getmoto/moto (python)

Issue:
DynamoDB: delete_item does not handle ReturnValuesOnConditionCheckFailure
This issue is very related to #6949 

Just like `put_item` and `update_item`, `delete_item` accepts a `ReturnValuesOnConditionCheckFailure` parameter.

When this is set to `ALL_OLD`, a `ConditionalCheckFailedException` should include the `Item` key with the conflicting item.

In other words, the following should work:

```python
import boto3
from moto import mock_aws

with mock_aws():
    client = boto3.client("dynamodb")
    client.create_table(
        TableName="test",
        KeySchema=[{"AttributeName": "Key", "KeyType": "HASH"}],
        AttributeDefinitions=[{"AttributeName": "Key", "AttributeType": "S"}],
        BillingMode="PAY_PER_REQUEST",
    )
    client.put_item(TableName="test", Item={"Key": {"S": "foo"}, "Other": {"N": "42"}})
    try:
        client.delete_item(
            TableName="test",
            Key={"Key": {"S": "foo"}},
            ConditionExpression="#O <> :o",
            ExpressionAttributeNames={"#O": "Other"},
            ExpressionAttributeValues={":o": {"N": "42"}},
            ReturnValuesOnConditionCheckFailure="ALL_OLD",
        )
    except client.exceptions.ConditionalCheckFailedException as exc:
        assert "Item" in exc.response
```

This fails with an `AssertionError` on Python 3.12.3 with moto 5.0.16

Pull request:
DynamoDB: return item on condition check failure in delete_item (#8192)
Fixes #8192 

The new test has been run on AWS

Patch:
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_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):