41 / 89 · 04 API & Contract Testing with AI · GraphQL Query Depth and N+1 Detection← prev⊞ allnext →☰ Read as one page
7.2Query Depth Testing
The Attack Vector
A malicious (or naive) client can construct deeply nested queries that cause exponential database load:
# Depth attack: recursive query
query DepthAttack {
user(id: "1") {
friends {
friends {
friends {
friends {
friends {
name # Depth 6 -- should be blocked
}
}
}
}
}
}
}
Each level of nesting can multiply database queries. Without depth limits, a query with depth 10 on a friends field with 100 friends per user could trigger 100^10 = 10^20 database lookups.
AI-Generated Depth Test Suite
class TestGraphQLDepthLimits:
"""Verify that the GraphQL server enforces query depth limits."""
def _build_nested_query(self, field: str, depth: int) -> str:
"""Build a query with n levels of nesting."""
query = "{ " + f'{field} {{ '
for _ in range(depth - 1):
query += f'{field} {{ '
query += "id name "
query += "} " * depth
query += "}"
return query
@pytest.mark.parametrize("depth,should_succeed", [
(1, True), # Shallow query -- always allowed
(3, True), # Normal depth -- allowed
(5, True), # Moderate depth -- typically allowed
(10, False), # Deep query -- should be blocked
(20, False), # Very deep -- definitely blocked
])
def test_query_depth_limit(self, graphql_client, depth, should_succeed):
"""Verify depth limits are enforced."""
query = self._build_nested_query("friends", depth)
response = graphql_client.execute(query)
if should_succeed:
assert "errors" not in response or not any(
"depth" in str(e).lower() for e in response.get("errors", [])
)
else:
assert "errors" in response
assert any(
"depth" in str(e).lower() or "complexity" in str(e).lower()
for e in response["errors"]
)
def test_depth_limit_returns_clear_error(self, graphql_client):
"""Blocked queries should return a descriptive error, not a crash."""
query = self._build_nested_query("friends", 20)
response = graphql_client.execute(query)
assert "errors" in response
error = response["errors"][0]
assert "message" in error
# Error should mention depth or complexity, not be a generic server error
assert any(
keyword in error["message"].lower()
for keyword in ["depth", "complexity", "limit", "exceeded"]
)
def test_breadth_limit(self, graphql_client):
"""Wide queries (many fields at same level) should also be bounded."""
# Request every field on every type -- breadth attack
query = """
{
users(first: 100) { id name email role created_at updated_at
friends(first: 100) { id name email role }
orders(first: 100) { id total status items { id name price } }
notifications(first: 100) { id message read }
preferences { theme language timezone }
}
}
"""
response = graphql_client.execute(query)
# This should either succeed with reasonable data
# or be blocked by a complexity limit
if "errors" in response:
assert any(
"complexity" in str(e).lower()
for e in response["errors"]
)