Skip to content

Commit

Permalink
Split out processing results for better testability.
Browse files Browse the repository at this point in the history
Better date deserialization
Added more unit tests
  • Loading branch information
aaront committed Jun 26, 2015
1 parent d60d888 commit 6858c1b
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 17 deletions.
26 changes: 18 additions & 8 deletions mygeotab/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ def _get_api_url(self):
base_url.replace('/', '')
return 'https://' + base_url + '/apiv1'

@staticmethod
def _process(data):
"""
Processes the returned JSON from the server.
:param data: The JSON data in dict form
:return: The result dict
:raise MyGeotabException: Raises when a server exception was encountered
"""
if data:
if 'error' in data:
raise MyGeotabException(data['error'])
if 'result' in data:
return data['result']
return data
return None

def _query(self, method, parameters):
"""
Formats and performs the query against the API
Expand All @@ -76,14 +93,7 @@ def _query(self, method, parameters):
is_live = not any(s in url for s in ['127.0.0.1', 'localhost'])
r = requests.post(url, data=json.dumps(params, default=mygeotab.serializers.object_serializer), headers=headers,
allow_redirects=True, verify=is_live)
data = r.json(object_hook=mygeotab.serializers.object_deserializer)
if data:
if 'error' in data:
raise MyGeotabException(data['error'])
if 'result' in data:
return data['result']
return data
return None
return self._process(r.json(object_hook=mygeotab.serializers.object_deserializer))

def call(self, method, type_name=None, **parameters):
"""
Expand Down
13 changes: 5 additions & 8 deletions mygeotab/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@

import mygeotab.utils

datetime_regex = re.compile(r'^\d{4}\-\d{2}\-\d{2}')


def object_serializer(obj):
return mygeotab.utils.date_to_iso_str(obj) if hasattr(obj, 'isoformat') else obj


def object_deserializer(obj):
for k, v in obj.items():
lower_k = k.lower()
if isinstance(v, six.string_types) and (
'date' in lower_k or 'time' in lower_k or 'active' in lower_k) and re.search(
r'^\d{4}\-\d{2}\-\d{2}', v):
# noinspection PyBroadException
if isinstance(v, six.string_types) and datetime_regex.search(v):
try:
obj[k] = parser.parse(v)
except:
pass
except ValueError:
obj[k] = v
return obj

57 changes: 57 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-

import unittest

from mygeotab import api


class TestProcessResults(unittest.TestCase):
def setUp(self):
self.api = api.API("test@example.com", session_id=123)

def test_handle_server_exception(self):
exception_response = dict(error=dict(errors=[dict(
message=u'The method "Get" could not be found. Verify the method name and ensure all method parameters are '
u'included. Request Json: {"params": {"typeName": "Passwords", "credentials": {"userName": '
u'"test@example.com", "sessionId": "12345678901234567890", "database": "my_company"}}, "method": '
u'"Get", "id": -1}',
name=u'MissingMethodException',
stackTrace=u' at Geotab.Checkmate.Web.APIV1.ProcessRequest(IHttpRequest httpRequest, HttpResponse '
u'httpResponse, String methodName, Dictionary`2 parameters, Action`2 parametersJSONToTokens, '
u'Action`1 handleException, IProfiler profile, Credentials credentials, Int32 requestIndex, '
u'Object requestJsonOrHashMap, Boolean& isAsync) in '
u'c:\\ProgramData\\GEOTAB\\Checkmate\\BuildServer\\master\\WorkingDirectory\\Checkmate\\CheckmateServer\\Geotab\\Checkmate\\Web\\APIV1.cs:line 813\r\n '
u'at Geotab.Checkmate.Web.APIV1.<>c__DisplayClass13.<ProcessRequest>b__b() '
u'in c:\\ProgramData\\GEOTAB\\Checkmate\\BuildServer\\master\\WorkingDirectory\\Checkmate\\CheckmateServer\\Geotab\\Checkmate\\Web\\APIV1.cs:line 558\r\n '
u'at Geotab.Checkmate.Web.APIV1.ExecuteHandleException(Action action) in '
u'c:\\ProgramData\\GEOTAB\\Checkmate\\BuildServer\\master\\WorkingDirectory\\Checkmate\\CheckmateServer\\Geotab\\Checkmate\\Web\\APIV1.cs:line 632')],
message=u'The method "Get" could not be found. Verify the method name and ensure all method parameters are '
u'included. Request Json: {"params": {"typeName": "Passwords", "credentials": {"userName": '
u'"test@example.com", "sessionId": "12345678901234567890", "database": "my_company"}}, "method": '
u'"Get", "id": -1}',
name=u'JSONRPCError'), requestIndex=0)
with self.assertRaises(api.MyGeotabException) as cm:
self.api._process(exception_response)
ex = cm.exception
self.assertEqual(ex.name, 'MissingMethodException')
self.assertEqual(ex.message,
'The method "Get" could not be found. Verify the method name and ensure all method '
'parameters are included. Request Json: {"params": {"typeName": "Passwords", '
'"credentials": {"userName": "test@example.com", "sessionId": "12345678901234567890", '
'"database": "my_company"}}, "method": "Get", "id": -1}')

def test_handle_server_results(self):
results_response = {'result': [
dict(
id='b123',
name='test@example.com'
)
]}
result = self.api._process(results_response)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['name'], 'test@example.com')
self.assertEqual(result[0]['id'], 'b123')


if __name__ == '__main__':
unittest.main()
77 changes: 77 additions & 0 deletions tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-

import unittest
import json
from datetime import datetime

import pytz

from mygeotab import serializers


def json_serialize(data):
return json.dumps(data, default=serializers.object_serializer)


def json_deserialize(data_str):
return json.loads(data_str, object_hook=serializers.object_deserializer)


class TestSerialization(unittest.TestCase):
def setUp(self):
pass

def test_top_level_utc_datetime(self):
data = dict(
dateTime=datetime(2015, 6, 5, 2, 3, 44)
)
expected_str = '{"dateTime": "2015-06-05T02:03:44Z"}'
data_str = json_serialize(data)
self.assertEqual(data_str, expected_str)

def test_top_level_zoned_datetime(self):
est = pytz.timezone('US/Eastern')
data = dict(
dateTime=est.localize(datetime(2015, 6, 4, 3, 3, 43))
)
expected_str = '{"dateTime": "2015-06-04T07:03:43Z"}'
data_str = json_serialize(data)
self.assertEqual(data_str, expected_str)


class TestDeserialization(unittest.TestCase):
def setUp(self):
pass

def test_top_level_datetime(self):
data_str = '{"dateTime": "2015-06-04T07:03:43Z"}'
data = json_deserialize(data_str)
utc_date = data.get('dateTime')
self.assertIsNotNone(utc_date)
check_date = datetime(2015, 6, 4, 7, 3, 43)
self.assertEqual(utc_date.year, check_date.year)
self.assertEqual(utc_date.month, check_date.month)
self.assertEqual(utc_date.day, check_date.day)
self.assertEqual(utc_date.hour, check_date.hour)
self.assertEqual(utc_date.minute, check_date.minute)
self.assertEqual(utc_date.second, check_date.second)

def test_second_level_datetime(self):
data_str = '[{"group": {"dateTime": "2015-06-04T07:03:43Z"}}]'
data = json_deserialize(data_str)
self.assertEqual(len(data), 1)
group = data[0].get('group')
self.assertIsNotNone(group)
utc_date = group.get('dateTime')
self.assertIsNotNone(utc_date)
check_date = datetime(2015, 6, 4, 7, 3, 43)
self.assertEqual(utc_date.year, check_date.year)
self.assertEqual(utc_date.month, check_date.month)
self.assertEqual(utc_date.day, check_date.day)
self.assertEqual(utc_date.hour, check_date.hour)
self.assertEqual(utc_date.minute, check_date.minute)
self.assertEqual(utc_date.second, check_date.second)


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_naive_datetime_to_utc(self):
self.assertEqual(utc_date.hour, date.hour)

def test_utc_datetime_to_utc(self):
date = datetime(2015, 3, 12, 2, 45, 34, tzinfo=pytz.utc)
date = pytz.utc.localize(datetime(2015, 3, 12, 2, 45, 34))
utc_date = utils.get_utc_date(date)
self.assertIsNotNone(utc_date.tzinfo)
self.assertIs(utc_date.tzinfo, pytz.utc)
Expand Down

0 comments on commit 6858c1b

Please sign in to comment.