Repository: mirumee/ariadne
Pull request #1224 (2025-02-03 11:19:55)

fix(relay): add missing key in return RelayObjectType.resolve_wrapper
The pull request fix missing `totalCount` key in relay connection response.

Additionally handle a scenario when empty list of edges is passed to `RelayConnection`


Patch:
diff --git a/ariadne/contrib/relay/connection.py b/ariadne/contrib/relay/connection.py
index 889cd37..9ed8f54 100644
--- a/ariadne/contrib/relay/connection.py
+++ b/ariadne/contrib/relay/connection.py
@@ -27,8 +27,8 @@ class RelayConnection:
         return {
             "hasNextPage": self.has_next_page,
             "hasPreviousPage": self.has_previous_page,
-            "startCursor": self.get_cursor(self.edges[0]),
-            "endCursor": self.get_cursor(self.edges[-1]),
+            "startCursor": self.get_cursor(self.edges[0]) if self.edges else None,
+            "endCursor": self.get_cursor(self.edges[-1]) if self.edges else None,
         }
 
     def get_edges(self):
diff --git a/ariadne/contrib/relay/objects.py b/ariadne/contrib/relay/objects.py
index 00c46cb..8d64367 100644
--- a/ariadne/contrib/relay/objects.py
+++ b/ariadne/contrib/relay/objects.py
@@ -45,6 +45,7 @@ class RelayObjectType(ObjectType):
                     if is_awaitable(relay_connection):
                         relay_connection = await relay_connection
                     return {
+                        "totalCount": relay_connection.total,
                         "edges": relay_connection.get_edges(),
                         "pageInfo": relay_connection.get_page_info(
                             connection_arguments
@@ -57,6 +58,7 @@ class RelayObjectType(ObjectType):
                 obj, info, connection_arguments, *args, **kwargs
             )
             return {
+                "totalCount": relay_connection.total,
                 "edges": relay_connection.get_edges(),
                 "pageInfo": relay_connection.get_page_info(connection_arguments),
             }


Test patch:
diff --git a/tests/relay/test_objects.py b/tests/relay/test_objects.py
index 2e75ef5..004c6fe 100644
--- a/tests/relay/test_objects.py
+++ b/tests/relay/test_objects.py
@@ -20,9 +20,10 @@ from ariadne.contrib.relay.types import (
 
 @pytest.fixture
 def friends_connection():
+    edges = [{"id": "VXNlcjox", "name": "Alice"}, {"id": "VXNlcjoy", "name": "Bob"}]
     return RelayConnection(
-        edges=[{"id": "VXNlcjox", "name": "Alice"}, {"id": "VXNlcjoy", "name": "Bob"}],
-        total=2,
+        edges=edges,
+        total=len(edges),
         has_next_page=False,
         has_previous_page=False,
     )
@@ -117,6 +118,7 @@ def test_relay_object_resolve_wrapper(mocker: MockFixture, friends_connection):
 
     result = wrapped_resolver(None, None, first=10)
     assert result == {
+        "totalCount": 2,
         "edges": [
             {"node": {"id": "VXNlcjox", "name": "Alice"}, "cursor": "VXNlcjox"},
             {"node": {"id": "VXNlcjoy", "name": "Bob"}, "cursor": "VXNlcjoy"},
@@ -145,6 +147,7 @@ async def test_relay_object_resolve_wrapper_async(friends_connection):
 
     result = await wrapped_resolver(None, None, first=10)
     assert result == {
+        "totalCount": 2,
         "edges": [
             {"node": {"id": "VXNlcjox", "name": "Alice"}, "cursor": "VXNlcjox"},
             {"node": {"id": "VXNlcjoy", "name": "Bob"}, "cursor": "VXNlcjoy"},
@@ -158,6 +161,40 @@ async def test_relay_object_resolve_wrapper_async(friends_connection):
     }
 
 
+def test_relay_object_resolve_wrapper_without_edges(
+    mocker: MockFixture, friends_connection
+):
+    edges = []
+    friends_connection.edges = edges
+    friends_connection.total = len(edges)
+    mock_resolver = mocker.Mock(return_value=friends_connection)
+    mock_connection_arguments = mocker.Mock()
+    mock_connection_arguments_class = mocker.Mock(
+        return_value=mock_connection_arguments
+    )
+    object_type = RelayObjectType(
+        "User", connection_arguments_class=mock_connection_arguments_class
+    )
+    wrapped_resolver = object_type.resolve_wrapper(mock_resolver)
+
+    result = wrapped_resolver(None, None, first=10)
+    assert result == {
+        "totalCount": 0,
+        "edges": [],
+        "pageInfo": {
+            "hasNextPage": False,
+            "hasPreviousPage": False,
+            "startCursor": None,
+            "endCursor": None,
+        },
+    }
+
+    mock_resolver.assert_called_once_with(
+        None, None, mock_connection_arguments, first=10
+    )
+    mock_connection_arguments_class.assert_called_once_with(first=10)
+
+
 def test_relay_object_resolve_wrapper_with_custom_arguments(mocker: MockFixture):
     object_type = RelayObjectType(
         "User", connection_arguments_class=ForwardConnectionArguments