Repository: getmoto/moto

Issue:
Incorrect and misleading error message while performing conditional UpdateItem operation on nonexistent item in dynamodb2
Calling `update_item` on `Table` object obtained from `boto3.resource('dynamodb')` with an optional `ConditionalExpression` throws an exception with very misleading error message. The message differs from the one actually returned by the real API.

```python
import boto3
from moto import mock_dynamodb2

if __name__ == '__main__':
    with mock_dynamodb2():
        dynamodb = boto3.resource('dynamodb')
        dynamodb.create_table(
            TableName='test-table',
            AttributeDefinitions=[
                {
                    'AttributeName': 'id',
                    'AttributeType': 'S'
                }
            ],
            KeySchema=[
                {
                    'AttributeName': 'id',
                    'KeyType': 'HASH'
                }
            ]
        )
        table = dynamodb.Table('test-table')
        table.put_item(
            Item={
                'id': '1',
                'owner': 'ABC',
                'guests': set(['DEF', 'GHI', 'JKL']),
                'items': ['item0', 'item1', 'item2']
            }
        )

        test_user = 'DEF'
        new_item = 'item3'

        res = table.update_item(
            Key={
                'id': '2'
            },
            UpdateExpression='SET #i = list_append(#i, :vals)',
            ConditionExpression='#u = :userid Or contains(#g, :userid)',
            ExpressionAttributeNames={
                '#i': 'items',
                '#u': 'user_id',
                '#g': 'guests'
            },
            ExpressionAttributeValues={
                ':vals': [new_item],
                ':userid': test_user
            },
            ReturnValues='UPDATED_NEW'
        )

```

In `put_item()` function call item with PK `id=1` is being inserted to the table. Then, `update_item()` is being called for an item with PK `id=2` that obviously does not exist at this point. In such a case, boto3 throws this exception:
```
botocore.errorfactory.ConditionalCheckFailedException: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed
```
which, in my opinion, is not perfect either, as it does not provide any hint for a reason behind request failure. However, the one returned with moto mocks is much more misleading:
```
botocore.errorfactory.ConditionalCheckFailedException: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: A condition specified in the operation could not be evaluated.
```

I know this is might be a detail, but to me those two messages have completely different meaning. It is not only misleading to inform user that it is an update expression that lead request to be failed, it completely fails to point at the core of the problem. While the real message is much more general and doesn't point directly at the issue, it doesn't cause any confusion.

In order to fix this issue, I'd expect `update_item()` function of `dynamodb` resource called with `ConditionExpression` on `Key` that cannot be resolved to an item to throw an exception with message saying `An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed`.

My moto version is 2.2.8


Discussion:
Thanks for raising this @k0mmsussert0d - you're right, we should return the correct error message. Marking it as a bug.

Resolving patch:
diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py
--- a/moto/dynamodb2/responses.py
+++ b/moto/dynamodb2/responses.py
@@ -855,9 +855,7 @@ def update_item(self):
             return self.error(er, mve.exception_msg)
         except ValueError:
             er = "com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException"
-            return self.error(
-                er, "A condition specified in the operation could not be evaluated."
-            )
+            return self.error(er, "The conditional request failed")
         except TypeError:
             er = "com.amazonaws.dynamodb.v20111205#ValidationException"
             return self.error(er, "Validation Exception")


Test patch:
diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py
--- a/tests/test_dynamodb2/test_dynamodb.py
+++ b/tests/test_dynamodb2/test_dynamodb.py
@@ -2877,7 +2877,7 @@ def test_condition_expressions():
         ExpressionAttributeValues={":match": {"S": "match"}},
     )
 
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
         client.update_item(
             TableName="test1",
             Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
@@ -2886,6 +2886,18 @@ def test_condition_expressions():
             ExpressionAttributeValues={":match": {"S": "match"}},
             ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
         )
+    _assert_conditional_check_failed_exception(exc)
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
+        client.update_item(
+            TableName="test1",
+            Key={"client": {"S": "client2"}, "app": {"S": "app1"}},
+            UpdateExpression="set #match=:match",
+            ConditionExpression="attribute_exists(#existing)",
+            ExpressionAttributeValues={":match": {"S": "match"}},
+            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
+        )
+    _assert_conditional_check_failed_exception(exc)
 
     with pytest.raises(client.exceptions.ConditionalCheckFailedException):
         client.delete_item(
@@ -2897,6 +2909,12 @@ def test_condition_expressions():
         )
 
 
+def _assert_conditional_check_failed_exception(exc):
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ConditionalCheckFailedException")
+    err["Message"].should.equal("The conditional request failed")
+
+
 @mock_dynamodb2
 def test_condition_expression_numerical_attribute():
     dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
@@ -2972,8 +2990,9 @@ def update_if_attr_doesnt_exist():
     update_if_attr_doesnt_exist()
 
     # Second time should fail
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
         update_if_attr_doesnt_exist()
+    _assert_conditional_check_failed_exception(exc)
 
 
 @mock_dynamodb2
@@ -3012,7 +3031,7 @@ def test_condition_expression__and_order():
 
     # ensure that the RHS of the AND expression is not evaluated if the LHS
     # returns true (as it would result an error)
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
         client.update_item(
             TableName="test",
             Key={"forum_name": {"S": "the-key"}},
@@ -3021,6 +3040,7 @@ def test_condition_expression__and_order():
             ExpressionAttributeNames={"#ttl": "ttl"},
             ExpressionAttributeValues={":ttl": {"N": "6"}, ":old_ttl": {"N": "5"}},
         )
+    _assert_conditional_check_failed_exception(exc)
 
 
 @mock_dynamodb2