Skip to content

Commit 18a28c1

Browse files
committed
feat(fdc): Add internal GraphQL request helper method and tests
Implemented _make_gql_request on _DataConnectApiClient to execute and handle responses/errors for GraphQL operations. Added corresponding unit tests in tests/test_data_connect.py.
1 parent 099697a commit 18a28c1

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

firebase_admin/dataconnect.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222
from dataclasses import dataclass, asdict, is_dataclass
2323
import typing
2424
from typing import Any, Dict, Generic, Optional, Type, TypeVar, Union
25+
26+
import requests
27+
2528
import firebase_admin
26-
from firebase_admin import _utils, _http_client, App
29+
from firebase_admin import _utils, _http_client, App, exceptions
2730

2831
__all__ = [
2932
'ConnectorConfig',
@@ -334,3 +337,39 @@ def _get_headers(self) -> Dict[str, str]:
334337
"X-Firebase-Client": f"fire-admin-python/{firebase_admin.__version__}",
335338
"x-goog-api-client": _utils.get_metrics_header(),
336339
}
340+
341+
def _make_gql_request(
342+
self,
343+
url: str,
344+
headers: Dict[str, str],
345+
payload: Dict[str, Any]
346+
) -> Dict[str, Any]:
347+
"""Make a GraphQL request to the Data Connect service."""
348+
if url is None or headers is None or payload is None:
349+
raise ValueError("url, headers, and payload must all be specified.")
350+
351+
try:
352+
resp_dict, resp = self._http_client.body_and_response(
353+
'post',
354+
url=url,
355+
headers=headers,
356+
json=payload
357+
)
358+
except requests.exceptions.RequestException as error:
359+
raise _utils.handle_platform_error_from_requests(error)
360+
361+
if resp_dict and "errors" in resp_dict:
362+
errors = resp_dict["errors"]
363+
if isinstance(errors, list) and len(errors) > 0:
364+
messages = [
365+
err.get("message") for err in errors
366+
if isinstance(err, dict) and err.get("message")
367+
]
368+
all_messages = " ".join(messages)
369+
raise exceptions.FirebaseError(
370+
code="query-error",
371+
message=all_messages,
372+
http_response=resp
373+
)
374+
375+
return resp_dict

tests/test_data_connect.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020

2121
from google.auth import credentials as google_auth_credentials
2222
import pytest
23-
23+
import requests
2424

2525
import firebase_admin
26-
from firebase_admin import _utils
26+
from firebase_admin import _utils, _http_client, exceptions
2727
from firebase_admin import dataconnect
2828
from tests import testutils
2929

@@ -724,3 +724,59 @@ def test_get_headers(self):
724724
assert isinstance(headers, dict)
725725
assert headers.get("X-Firebase-Client") == f"fire-admin-python/{firebase_admin.__version__}"
726726
assert headers.get("x-goog-api-client") == _utils.get_metrics_header()
727+
728+
729+
class TestDataConnectApiClientMakeGqlRequest:
730+
731+
def setup_method(self):
732+
self.cred = testutils.MockCredential()
733+
self.app = firebase_admin.initialize_app(
734+
self.cred, options={'projectId': 'test-project'}
735+
)
736+
self.api_client = dataconnect._DataConnectApiClient(BASE_CONFIG, self.app)
737+
738+
def teardown_method(self, method):
739+
del method
740+
testutils.cleanup_apps()
741+
742+
@mock.patch.object(_http_client.JsonHttpClient, "body_and_response")
743+
def test_make_gql_request_success(self, mock_body_and_response):
744+
mock_response = mock.Mock(spec=requests.Response)
745+
mock_body_and_response.return_value = ({"data": "val"}, mock_response)
746+
url = "https://example.com/endpoint"
747+
headers = {"key": "val"}
748+
payload = {"query": "foo"}
749+
750+
res = self.api_client._make_gql_request(url, headers, payload)
751+
assert res == {"data": "val"}
752+
mock_body_and_response.assert_called_once_with(
753+
"post", url=url, headers=headers, json=payload
754+
)
755+
756+
def test_make_gql_request_missing_url(self):
757+
headers = {"key": "val"}
758+
payload = {"query": "foo"}
759+
with pytest.raises(ValueError, match="url, headers, and payload must all be specified."):
760+
self.api_client._make_gql_request(None, headers, payload)
761+
762+
def test_make_gql_request_missing_headers(self):
763+
url = "https://example.com/endpoint"
764+
payload = {"query": "foo"}
765+
with pytest.raises(ValueError, match="url, headers, and payload must all be specified."):
766+
self.api_client._make_gql_request(url, None, payload)
767+
768+
def test_make_gql_request_missing_payload(self):
769+
url = "https://example.com/endpoint"
770+
headers = {"key": "val"}
771+
with pytest.raises(ValueError, match="url, headers, and payload must all be specified."):
772+
self.api_client._make_gql_request(url, headers, None)
773+
774+
@mock.patch.object(_http_client.JsonHttpClient, "body_and_response")
775+
def test_make_gql_request_error(self, mock_body_and_response):
776+
mock_body_and_response.side_effect = requests.exceptions.RequestException()
777+
url = "https://example.com/endpoint"
778+
headers = {"key": "val"}
779+
payload = {"query": "foo"}
780+
781+
with pytest.raises(exceptions.FirebaseError):
782+
self.api_client._make_gql_request(url, headers, payload)

0 commit comments

Comments
 (0)