Skip to content

Commit 6dd88b4

Browse files
authored
New tests cases and some documentation (#32)
1 parent 2246418 commit 6dd88b4

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

firebase_admin/db.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ def set_limit_first(self, limit):
327327
Raises:
328328
ValueError: If the value is not an integer, or set_limit_last() was called previously.
329329
"""
330-
if not isinstance(limit, int):
331-
raise ValueError('Limit must be an integer.')
330+
if not isinstance(limit, int) or limit < 0:
331+
raise ValueError('Limit must be a non-negative integer.')
332332
if 'limitToLast' in self._params:
333333
raise ValueError('Cannot set both first and last limits.')
334334
self._params['limitToFirst'] = limit
@@ -346,8 +346,8 @@ def set_limit_last(self, limit):
346346
Raises:
347347
ValueError: If the value is not an integer, or set_limit_first() was called previously.
348348
"""
349-
if not isinstance(limit, int):
350-
raise ValueError('Limit must be an integer.')
349+
if not isinstance(limit, int) or limit < 0:
350+
raise ValueError('Limit must be a non-negative integer.')
351351
if 'limitToFirst' in self._params:
352352
raise ValueError('Cannot set both first and last limits.')
353353
self._params['limitToLast'] = limit
@@ -608,6 +608,23 @@ def request_oneway(self, method, urlpath, **kwargs):
608608
self._do_request(method, urlpath, **kwargs)
609609

610610
def _do_request(self, method, urlpath, **kwargs):
611+
"""Makes an HTTP call using the Python requests library.
612+
613+
Refer to http://docs.python-requests.org/en/master/api/ for more information on supported
614+
options and features.
615+
616+
Args:
617+
method: HTTP method name as a string (e.g. get, post).
618+
urlpath: URL path of the remote endpoint. This will be appended to the server's base URL.
619+
kwargs: An additional set of keyword arguments to be passed into requests API
620+
(e.g. json, params).
621+
622+
Returns:
623+
Response: An HTTP response object.
624+
625+
Raises:
626+
ApiCallError: If an error occurs while making the HTTP call.
627+
"""
611628
try:
612629
resp = self._session.request(method, self._url + urlpath, auth=self._auth, **kwargs)
613630
resp.raise_for_status()
@@ -616,11 +633,29 @@ def _do_request(self, method, urlpath, **kwargs):
616633
raise ApiCallError(self._extract_error_message(error), error)
617634

618635
def _extract_error_message(self, error):
619-
if error.response is not None:
620-
data = json.loads(error.response.content)
636+
"""Extracts an error message from an exception.
637+
638+
If the server has not sent any response, simply converts the exception into a string.
639+
If the server has sent a JSON response with an 'error' field, which is the typical
640+
behavior of the Realtime Database REST API, parses the response to retrieve the error
641+
message. If the server has sent a non-JSON response, returns the full response
642+
as the error message.
643+
644+
Args:
645+
error: An exception raised by the requests library.
646+
647+
Returns:
648+
str: A string error message extracted from the exception.
649+
"""
650+
if error.response is None:
651+
return str(error)
652+
try:
653+
data = error.response.json()
621654
if isinstance(data, dict):
622655
return '{0}\nReason: {1}'.format(error, data.get('error', 'unknown'))
623-
return str(error)
656+
except ValueError:
657+
pass
658+
return '{0}\nReason: {1}'.format(error, error.response.content.decode())
624659

625660
def close(self):
626661
self._session.close()

tests/test_db.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(self, data, status, recorder):
3838
def send(self, request, **kwargs): # pylint: disable=unused-argument
3939
self._recorder.append(request)
4040
resp = models.Response()
41+
resp.url = request.url
4142
resp.status_code = self._status
4243
resp.raw = six.BytesIO(self._data.encode())
4344
return resp
@@ -336,6 +337,21 @@ def test_get_reference(self, path, expected):
336337
else:
337338
assert ref.parent.path == parent
338339

340+
@pytest.mark.parametrize('error_code', [400, 401, 500])
341+
def test_server_error(self, error_code):
342+
ref = db.get_reference('/test')
343+
self.instrument(ref, json.dumps({'error' : 'json error message'}), error_code)
344+
with pytest.raises(db.ApiCallError) as excinfo:
345+
ref.get_value()
346+
assert 'Reason: json error message' in str(excinfo.value)
347+
348+
@pytest.mark.parametrize('error_code', [400, 401, 500])
349+
def test_other_error(self, error_code):
350+
ref = db.get_reference('/test')
351+
self.instrument(ref, 'custom error message', error_code)
352+
with pytest.raises(db.ApiCallError) as excinfo:
353+
ref.get_value()
354+
assert 'Reason: custom error message' in str(excinfo.value)
339355

340356
class TestDatabseInitialization(object):
341357
"""Test cases for database initialization."""
@@ -462,6 +478,14 @@ def test_multiple_limits(self):
462478
with pytest.raises(ValueError):
463479
query.set_limit_first(1)
464480

481+
@pytest.mark.parametrize('limit', [None, -1, 'foo', 1.2, list(), dict(), tuple()])
482+
def test_invalid_limit(self, limit):
483+
query = self.ref.order_by_child('foo')
484+
with pytest.raises(ValueError):
485+
query.set_limit_first(limit)
486+
with pytest.raises(ValueError):
487+
query.set_limit_last(limit)
488+
465489
def test_start_at_none(self):
466490
query = self.ref.order_by_child('foo')
467491
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)