Modern QA2026Query Depth Testing — tiles
Log inJoin
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"]
            )