From 92891386746dea02a832360769870e83610dd3bf Mon Sep 17 00:00:00 2001
From: beckerfy <fynn.becker@hs-hannover.de>
Date: Thu, 4 Jun 2020 14:17:38 +0200
Subject: [PATCH] Close #9 Add tests

---
 README.md                     |   6 +
 tests/__init__.py             |   0
 tests/test_postgrestclient.py | 317 ++++++++++++++++++++++++++++++++++
 3 files changed, 323 insertions(+)
 create mode 100644 tests/__init__.py
 create mode 100644 tests/test_postgrestclient.py

diff --git a/README.md b/README.md
index 15e0a7e..8a13d23 100644
--- a/README.md
+++ b/README.md
@@ -212,3 +212,9 @@ def your_callback_func(sender, **kwargs):
 ```
 
 For more information on signals refer to the django docs. They are great. Really.
+
+### Testing
+
+`postgrestutils` has a bunch of unittests because manually testing it has become quite time-consuming.
+The tests aim to ensure functional correctness as well as some performance related concerns i.e. caching and lazyness.
+With `requests-mock` installed, running is as easy as `python -m unittest`.
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_postgrestclient.py b/tests/test_postgrestclient.py
new file mode 100644
index 0000000..1cb39c8
--- /dev/null
+++ b/tests/test_postgrestclient.py
@@ -0,0 +1,317 @@
+import datetime
+from unittest import TestCase
+
+from requests_mock import Mocker
+
+from postgrestutils.client import (
+    Count, MultipleObjectsReturned, ObjectDoesNotExist,
+)
+from postgrestutils.client.postgrestclient import LazyPostgrestJsonResult
+
+TOKEN = 'JWT_token'
+DEFAULT_HEADERS = {
+    'Authorization': 'Bearer {}'.format(TOKEN),
+    'Accept': 'application/json'
+}
+SUPERHERO_TEST_DATA = [
+    {
+        'id': 68,
+        'name': 'Batman',
+        'gender': 'Male',
+        'eye_color': 'blue',
+        'race': 'Human',
+        'hair_color': 'black',
+        'height': 188,
+        'publisher': 'DC Comics',
+        'skin_color': None,
+        'alignment': 'good',
+        'weight': 95
+    }, {
+        'id': 212,
+        'name': 'Deadpool',
+        'gender': 'Male',
+        'eye_color': 'brown',
+        'race': 'Mutant',
+        'hair_color': 'No Hair',
+        'height': 188,
+        'publisher': 'Marvel Comics',
+        'skin_color': None,
+        'alignment': 'neutral',
+        'weight': 95
+    }, {
+        'id': 345,
+        'name': 'Iron Man',
+        'gender': 'Male',
+        'eye_color': 'blue',
+        'race': 'Human',
+        'hair_color': 'Black',
+        'height': 198,
+        'publisher': 'Marvel Comics',
+        'skin_color': None,
+        'alignment': 'good',
+        'weight': 191
+    }, {
+        'id': 369,
+        'name': 'Joker',
+        'gender': 'Male',
+        'eye_color': 'green',
+        'race': 'Human',
+        'hair_color': 'Green',
+        'height': 196,
+        'publisher': 'DC Comics',
+        'skin_color': 'white',
+        'alignment': 'bad',
+        'weight': 86
+    }, {
+        'id': 423,
+        'name': 'Magneto',
+        'gender': 'Male',
+        'eye_color': 'grey',
+        'race': 'Mutant',
+        'hair_color': 'White',
+        'height': 188,
+        'publisher': 'Marvel Comics',
+        'skin_color': None,
+        'alignment': 'bad',
+        'weight': 86
+    }
+]
+
+
+@Mocker()
+class TestPgrestClientGet(TestCase):
+    def setUp(self):
+        super().setUp()
+        from postgrestutils.client import pgrest_client
+        pgrest_client.configure(TOKEN, base_uri='http://example.com/')
+
+        self.pgrest_client = pgrest_client
+        self.data = SUPERHERO_TEST_DATA[0]
+
+    def test_single_object_returned(self, mock):
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero?id=eq.1000000000',
+            request_headers={**DEFAULT_HEADERS, **{'Accept': 'application/vnd.pgrst.object+json'}},
+            status_code=200,
+            reason='OK',
+            json=self.data
+        )
+        params = {'id': 'eq.1000000000'}
+        res = self.pgrest_client.get('superhero', params=params)
+
+        self.assertDictEqual(res, self.data)
+        self.assertTrue(mock.called_once)
+
+    def test_object_does_not_exist(self, mock):
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero?id=eq.1337',
+            request_headers={**DEFAULT_HEADERS, **{'Accept': 'application/vnd.pgrst.object+json'}},
+            status_code=406,
+            reason='Not Acceptable',
+            text="""{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"""
+                 """"JSON object requested, multiple (or no) rows returned"}"""
+        )
+        params = {'id': 'eq.1337'}
+
+        with self.assertRaises(ObjectDoesNotExist):
+            self.pgrest_client.get('superhero', params=params)
+        self.assertTrue(mock.called_once)
+
+    def test_multiple_objects_returned(self, mock):
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers={**DEFAULT_HEADERS, **{'Accept': 'application/vnd.pgrst.object+json'}},
+            status_code=406,
+            reason='Not Acceptable',
+            text="""{"details":"Results contain 5 rows, application/vnd.pgrst.object+json requires 1 row","message":"""
+                 """"JSON object requested, multiple (or no) rows returned"}"""
+        )
+
+        with self.assertRaises(MultipleObjectsReturned):
+            self.pgrest_client.get('superhero')
+        self.assertTrue(mock.called_once)
+
+    def test_datetime_parser(self, mock):
+        expected = {
+            'id': 1337,
+            'random': datetime.datetime(2020, 5, 20, 8, 35, 6, 659425, tzinfo=datetime.timezone.utc)
+        }
+        mock.register_uri(
+            'GET',
+            'http://example.com/random_datetime',
+            request_headers={**DEFAULT_HEADERS, **{'Accept': 'application/vnd.pgrst.object+json'}},
+            status_code=200,
+            reason='OK',
+            json={'id': 1337, 'random': "2020-05-20T08:35:06.659425+00:00"}
+        )
+        params = {'id': 'eq.1337'}
+        res = self.pgrest_client.get('random_datetime', params=params)
+
+        self.assertDictEqual(res, expected)
+        self.assertTrue(mock.called_once)
+
+    def test_without_datetime_parser(self, mock):
+        test_json = {'id': 1337, 'random': "2020-05-20T08:35:06.659425+00:00"}
+        mock.register_uri(
+            'GET',
+            'http://example.com/random_datetime',
+            request_headers={**DEFAULT_HEADERS, **{'Accept': 'application/vnd.pgrst.object+json'}},
+            status_code=200,
+            reason='OK',
+            json=test_json
+        )
+        params = {'select': 'id,random', 'id': 'eq.1337'}
+        res = self.pgrest_client.get('random_datetime', params=params, parse_dt=False)
+
+        self.assertDictEqual(res, test_json)
+        self.assertTrue(mock.called_once)
+
+
+@Mocker()
+class TestPgrestClientFilterStrategyNone(TestCase):
+    def setUp(self):
+        super().setUp()
+        from postgrestutils.client import pgrest_client
+        pgrest_client.configure(TOKEN, base_uri='http://example.com/')
+
+        self.pgrest_client = pgrest_client
+        self.data = SUPERHERO_TEST_DATA
+
+    def test_fetch_all_first(self, mock):
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers=DEFAULT_HEADERS,
+            status_code=200,
+            reason='OK',
+            json=self.data
+        )
+        res = self.pgrest_client.filter('superhero')
+
+        self.assertIsInstance(res, LazyPostgrestJsonResult)  # should return lazy object
+        self.assertFalse(mock.called)  # no request should have been made yet
+
+        self.assertListEqual(list(res), self.data)  # fetch data
+        self.assertTrue(mock.called_once)  # should have been called once
+        self.assertListEqual(res._result_cache, self.data)  # fetched data should be cached
+        self.assertEqual(res._len_cache, len(self.data))  # len of fetched data should be cached
+        self.assertListEqual(list(res), self.data)  # should utilize cache
+        self.assertListEqual(res[2:], self.data[2:])  # should utilize cache
+        self.assertDictEqual(res[0], self.data[0])  # should utilize cache
+        self.assertTrue(mock.called_once)  # should not have been called again
+
+    def test_fetch_len_first(self, mock):
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers=DEFAULT_HEADERS,
+            status_code=200,
+            reason='OK',
+            json=self.data
+        )
+        res = self.pgrest_client.filter('superhero')
+
+        self.assertIsInstance(res, LazyPostgrestJsonResult)  # should return lazy object
+        self.assertFalse(mock.called)  # no request should have been made yet
+
+        self.assertEqual(len(res), len(self.data))  # should fetch len
+        self.assertTrue(mock.called_once)  # should have been called once
+        self.assertEqual(res._len_cache, len(self.data))  # len of fetched data should be cached
+        self.assertListEqual(res._result_cache, self.data)  # results should be cached (counting strategy none)
+        self.assertListEqual(res[2:], self.data[2:])  # should utilize cache
+        self.assertDictEqual(res[0], self.data[0])  # should utilize cache
+        self.assertListEqual(list(res), self.data)  # should utilize cache
+        self.assertTrue(mock.called_once)  # should not have been called again
+
+
+@Mocker()
+class TestPgrestClientFilterStrategyExact(TestCase):
+    def setUp(self):
+        super().setUp()
+        from postgrestutils.client import pgrest_client
+        pgrest_client.configure(TOKEN, base_uri='http://example.com/')
+
+        self.pgrest_client = pgrest_client
+        self.data = SUPERHERO_TEST_DATA
+
+    def test_fetch_all_first(self, mock):
+        # in order to fetch all
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers=DEFAULT_HEADERS,
+            status_code=200,
+            reason='OK',
+            json=self.data
+        )
+        res = self.pgrest_client.filter('superhero', count=Count.EXACT)
+
+        self.assertIsInstance(res, LazyPostgrestJsonResult)  # should return lazy object
+        self.assertFalse(mock.called)  # no request should have been made yet
+
+        self.assertListEqual(list(res), self.data)  # fetch data
+        self.assertTrue(mock.called_once)  # should have been called once
+        self.assertListEqual(res._result_cache, self.data)  # fetched data should be cached
+        self.assertEqual(res._len_cache, len(self.data))  # len of fetched data should also be cached
+        self.assertListEqual(list(res), self.data)  # should utilize cache
+        self.assertListEqual(res[2:], self.data[2:])  # should utilize cache
+        self.assertDictEqual(res[0], self.data[0])  # should utilize cache
+        self.assertTrue(mock.called_once)  # should not have been called again
+
+    def test_fetch_len_first(self, mock):
+        # in order to fetch all
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers=DEFAULT_HEADERS,
+            status_code=200,
+            reason='OK',
+            json=self.data
+        )
+        # in order to fetch first
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers={**DEFAULT_HEADERS, **{'Range-Unit': 'items', 'Range': '0-0'}},
+            status_code=200,
+            reason='OK',
+            headers={'Content-Range': '0-0/*'},
+            json=self.data[0]
+        )
+        # in order to fetch range since index 2
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers={**DEFAULT_HEADERS, **{'Range-Unit': 'items', 'Range': '2-'}},
+            status_code=200,
+            reason='OK',
+            headers={'Content-Range': '2-4/*'},
+            json=self.data[2:]
+        )
+        # in order to fetch length
+        mock.register_uri(
+            'GET',
+            'http://example.com/superhero',
+            request_headers={**DEFAULT_HEADERS, **{'Range-Unit': 'items', 'Range': '0-0', 'Prefer': 'count=exact'}},
+            status_code=206,
+            reason='Partial Content',
+            headers={'Content-Range': '0-0/5'},
+            json=self.data[0]
+        )
+        res = self.pgrest_client.filter('superhero', count=Count.EXACT)
+
+        self.assertIsInstance(res, LazyPostgrestJsonResult)  # should return lazy object
+        self.assertFalse(mock.called)  # no request should have been made yet
+
+        self.assertEqual(len(res), len(self.data))  # should fetch len
+        self.assertTrue(mock.called_once)  # should have been called once
+        self.assertEqual(res._len_cache, len(self.data))  # len of fetched data should be cached
+        self.assertListEqual(res[2:], self.data[2:])  # should fetch range starting at index 2
+        self.assertDictEqual(res[0], self.data[0])  # should fetch first element as range
+        self.assertListEqual(list(res), self.data)  # should fetch all elements
+        self.assertListEqual(res._result_cache, self.data)  # should cache all elements
+        self.assertTrue(mock.called)  # should have been called at least once
+        self.assertEqual(mock.call_count, 4)  # should have only been called 4 times (fetch len, range, first and all)
-- 
GitLab