Repository: getmoto/moto (python)

Issue:
ValidationException not detected on DynamoDB query
### Bug description

Making a query to a DynamoDB Table Index with two different key values throws the following error:

```
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the Query operation: Query key condition not supported
```

However, if the same query is made against a mocked environment with `moto` this exception is never raised.

To test this behavior, I used the following script (comment the the `mock_aws` statement to execute against AWS):

```py
import boto3
from moto import mock_aws

table_name = 'Threads'
forum_name = 'ForumName'
subject = 'Subject'
author = 'Author'
index_name = 'Author'
table_definition = {
    "AttributeDefinitions": [
        {
            'AttributeName': forum_name,
            'AttributeType': 'S'
        },
        {
            'AttributeName': subject,
            'AttributeType': 'S'
        },
        {
            'AttributeName': author,
            'AttributeType': 'S'
        }
    ],
    "TableName": table_name,
    "KeySchema": [
        {
            'AttributeName': forum_name,
            'KeyType': 'HASH'
        },
        {
            'AttributeName': subject,
            'KeyType': 'RANGE'
        }
    ],
    "GlobalSecondaryIndexes": [
        {
            'IndexName': index_name,
            'KeySchema': [
                {
                    'AttributeName': author,
                    'KeyType': 'HASH'
                }
            ],
            'Projection': {
                'ProjectionType': 'ALL'
            }
        }
    ],
    "BillingMode": 'PAY_PER_REQUEST',
}


@mock_aws
def create_load_query_delete(deleteTable=False):
    ddb_client = boto3.client("dynamodb")

    try:
        ddb_client.create_table(**table_definition)
        waiter = ddb_client.get_waiter('table_exists')
        waiter.wait(TableName=table_name)
    except ddb_client.exceptions.ResourceInUseException:
        print("Table exists not creating.")

    ddb_client.put_item(
        TableName=table_name,
        Item={
            forum_name: {'S': 'forum1'},
            subject: {'S': 'subject1'},
            author: {'S': 'author1'}
        }
    )

    ddb_client.put_item(
        TableName=table_name,
        Item={
            forum_name: {'S': 'forum1'},
            subject: {'S': 'subject2'},
            author: {'S': 'author2'}
        }
    )

    query_args = {
        'TableName': table_name,
        'IndexName': index_name,
        'KeyConditionExpression': f'{author} = :author AND {subject} = :subject',
        'ExpressionAttributeValues': {
            ':author': {
                'S': 'author1'
            },
            ':subject': {
                'S': 'subject1'
            }
        }
    }

    response = ddb_client.query(**query_args)
    items = response.get('Items', [])

    if deleteTable:
        ddb_client.delete_table(TableName=table_name)

    return items


def test_query():
    assert len(create_load_query_delete(deleteTable=True)) == 1

```

### Environment details
 
- **moto**: 5.0.28
- **boto3**: 1.36.26
- **python**: 3.12.9
- **OS**: Ubuntu 22.04.5 LTS

Pull request:
DynamoDB: Query() should validate extra Attrs in KeyExpression
Fixes #8655 

All tests are validated against AWS.

Patch:
diff --git a/moto/dynamodb/parsing/key_condition_expression.py b/moto/dynamodb/parsing/key_condition_expression.py
index e7454c88e..ee0ecd4ae 100644
--- a/moto/dynamodb/parsing/key_condition_expression.py
+++ b/moto/dynamodb/parsing/key_condition_expression.py
@@ -242,4 +242,9 @@ def validate_schema(
         if {"S": ""} in range_values:
             raise KeyIsEmptyStringException(index_range_key)
 
+    provided_keys = [key for key, _, _ in results]
+    schema_keys = [x["AttributeName"] for x in schema]
+    if any([x not in schema_keys for x in provided_keys]):
+        raise MockValidationException("Query key condition not supported")
+
     return hash_value, range_comparison, range_values


Test patch:
diff --git a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
index 51769d4fc..9aa994560 100644
--- a/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb/exceptions/test_dynamodb_exceptions.py
@@ -142,29 +142,80 @@ def test_query_gsi_with_wrong_key_attribute_names_throws_exception():
     assert err["Message"] == "Query condition missed key schema element: gsiK1SortKey"
 
 
-@mock_aws
-def test_query_table_with_wrong_key_attribute_names_throws_exception():
-    item = {
-        "partitionKey": "pk-1",
-        "someAttribute": "lore ipsum",
-    }
-
+@pytest.mark.aws_verified
+@dynamodb_aws_verified(add_range=True, add_gsi=True)
+def test_query_table_with_wrong_attrs_used_in_key_condition_expression(table_name=None):
     dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
-    dynamodb.create_table(
-        TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
-    )
-    table = dynamodb.Table("test-table")
-    table.put_item(Item=item)
+    table = dynamodb.Table(table_name)
 
     # check using wrong name for sort key throws exception
     with pytest.raises(ClientError) as exc:
         table.query(
             KeyConditionExpression="wrongName = :pk",
             ExpressionAttributeValues={":pk": "pk"},
-        )["Items"]
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == "Query condition missed key schema element: pk"
+
+    # Using PK of Index when querying Table is wrong
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            KeyConditionExpression="gsi_pk = :pk",
+            ExpressionAttributeValues={":pk": "pk"},
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == "Query condition missed key schema element: pk"
+
+    # Using PK of Table when querying Index is actually correct
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            IndexName="test_gsi",
+            KeyConditionExpression="pk = :pk",
+            ExpressionAttributeValues={":pk": "pk"},
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == "Query condition missed key schema element: gsi_pk"
+
+    # Using both PK and SK is acceptable
+    items = table.query(
+        KeyConditionExpression="pk = :pk AND sk = :sk",
+        ExpressionAttributeValues={":pk": "pk", ":sk": "sk"},
+    )["Items"]
+    assert items == []
+
+    # Using PK and SK on the Index is wrong
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            IndexName="test_gsi",
+            KeyConditionExpression="pk = :pk AND sk = :sk",
+            ExpressionAttributeValues={":pk": "pk", ":sk": "sk"},
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+
+    # Using GSI PK and SK on the Index is wrong
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            IndexName="test_gsi",
+            KeyConditionExpression="gsi_pk = :pk AND sk = :sk",
+            ExpressionAttributeValues={":pk": "pk", ":sk": "sk"},
+        )
+    err = exc.value.response["Error"]
+    assert err["Code"] == "ValidationException"
+    assert err["Message"] == "Query key condition not supported"
+
+    # Using GSI PK and SK on the Table is wrong
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            KeyConditionExpression="gsi_pk = :pk AND sk = :sk",
+            ExpressionAttributeValues={":pk": "pk", ":sk": "sk"},
+        )
     err = exc.value.response["Error"]
     assert err["Code"] == "ValidationException"
-    assert err["Message"] == "Query condition missed key schema element: partitionKey"
+    assert err["Message"] == "Query condition missed key schema element: pk"
 
 
 @pytest.mark.aws_verified
@@ -487,8 +538,8 @@ def test_query_unused_attribute_name(table_name=None):
 
     with pytest.raises(ClientError) as exc:
         table.query(
-            KeyConditionExpression="(#0 = x) AND (begins_with(#1, a))",
-            ExpressionAttributeNames={"#0": "pk", "#1": "sk", "#count": "count"},
+            KeyConditionExpression="(#0 = x)",
+            ExpressionAttributeNames={"#0": "pk", "#count": "count"},
         )
     err = exc.value.response["Error"]
     assert err["Code"] == "ValidationException"