home · contact · privacy
Extend Conditions POST test to use new JSON interface.
[plomtask] / tests / conditions.py
index dabcf06f767d02732b972d5b4bf40972f7d9fe66..2b522e65abbd4c1ef160f5bd1d9191b21e53d102 100644 (file)
@@ -1,64 +1,30 @@
 """Test Conditions module."""
 """Test Conditions module."""
-from unittest import TestCase
-from tests.utils import TestCaseWithDB, TestCaseWithServer
+from json import loads as json_loads
+from tests.utils import TestCaseWithDB, TestCaseWithServer, TestCaseSansDB
 from plomtask.conditions import Condition
 from plomtask.processes import Process
 from plomtask.todos import Todo
 from plomtask.exceptions import HandledException
 
 
 from plomtask.conditions import Condition
 from plomtask.processes import Process
 from plomtask.todos import Todo
 from plomtask.exceptions import HandledException
 
 
-class TestsSansDB(TestCase):
+class TestsSansDB(TestCaseSansDB):
     """Tests requiring no DB setup."""
     """Tests requiring no DB setup."""
-
-    def test_Condition_id_setting(self) -> None:
-        """Test .id_ being set and its legal range being enforced."""
-        with self.assertRaises(HandledException):
-            Condition(0)
-        condition = Condition(5)
-        self.assertEqual(condition.id_, 5)
+    checked_class = Condition
+    do_id_test = True
+    versioned_defaults_to_test = {'title': 'UNNAMED', 'description': ''}
 
 
 class TestsWithDB(TestCaseWithDB):
     """Tests requiring DB, but not server setup."""
     checked_class = Condition
 
 
 class TestsWithDB(TestCaseWithDB):
     """Tests requiring DB, but not server setup."""
     checked_class = Condition
-    default_ids = (1, 2, 3)
-
-    def versioned_condition(self) -> Condition:
-        """Create Condition with some VersionedAttribute values."""
-        c = Condition(None)
-        c.title.set('title1')
-        c.title.set('title2')
-        c.description.set('desc1')
-        c.description.set('desc2')
-        return c
-
-    def test_Condition_saving_and_caching(self) -> None:
-        """Test .save/.save_core."""
-        kwargs = {'id_': 1, 'is_active': False}
-        self.check_saving_and_caching(**kwargs)
-        # check .id_ set if None, and versioned attributes too
-        c = self.versioned_condition()
-        c.save(self.db_conn)
-        self.assertEqual(c.id_, 2)
-        self.assertEqual(sorted(c.title.history.values()),
-                         ['title1', 'title2'])
-        self.assertEqual(sorted(c.description.history.values()),
-                         ['desc1', 'desc2'])
+    default_init_kwargs = {'is_active': False}
+    test_versioneds = {'title': str, 'description': str}
 
     def test_Condition_from_table_row(self) -> None:
         """Test .from_table_row() properly reads in class from DB"""
         self.check_from_table_row()
 
     def test_Condition_from_table_row(self) -> None:
         """Test .from_table_row() properly reads in class from DB"""
         self.check_from_table_row()
-        c = self.versioned_condition()
-        c.save(self.db_conn)
-        assert isinstance(c.id_, int)
-        for row in self.db_conn.row_where(Condition.table_name, 'id', c.id_):
-            retrieved = Condition.from_table_row(self.db_conn, row)
-            # pylint: disable=no-member
-            self.assertEqual(sorted(retrieved.title.history.values()),
-                             ['title1', 'title2'])
-            # pylint: disable=no-member
-            self.assertEqual(sorted(retrieved.description.history.values()),
-                             ['desc1', 'desc2'])
+        self.check_versioned_from_table_row('title', str)
+        self.check_versioned_from_table_row('description', str)
 
     def test_Condition_by_id(self) -> None:
         """Test .by_id(), including creation."""
 
     def test_Condition_by_id(self) -> None:
         """Test .by_id(), including creation."""
@@ -72,15 +38,20 @@ class TestsWithDB(TestCaseWithDB):
         """Test pointers made for single object keep pointing to it."""
         self.check_singularity('is_active', True)
 
         """Test pointers made for single object keep pointing to it."""
         self.check_singularity('is_active', True)
 
+    def test_Condition_versioned_attributes_singularity(self) -> None:
+        """Test behavior of VersionedAttributes on saving (with .title)."""
+        self.check_versioned_singularity()
+
     def test_Condition_remove(self) -> None:
         """Test .remove() effects on DB and cache."""
         self.check_remove()
     def test_Condition_remove(self) -> None:
         """Test .remove() effects on DB and cache."""
         self.check_remove()
-        c = Condition(None)
         proc = Process(None)
         proc = Process(None)
+        proc.save(self.db_conn)
         todo = Todo(None, proc, False, '2024-01-01')
         for depender in (proc, todo):
             assert hasattr(depender, 'save')
             assert hasattr(depender, 'set_conditions')
         todo = Todo(None, proc, False, '2024-01-01')
         for depender in (proc, todo):
             assert hasattr(depender, 'save')
             assert hasattr(depender, 'set_conditions')
+            c = Condition(None)
             c.save(self.db_conn)
             depender.save(self.db_conn)
             depender.set_conditions(self.db_conn, [c.id_], 'conditions')
             c.save(self.db_conn)
             depender.save(self.db_conn)
             depender.set_conditions(self.db_conn, [c.id_], 'conditions')
@@ -97,18 +68,115 @@ class TestsWithServer(TestCaseWithServer):
 
     def test_do_POST_condition(self) -> None:
         """Test POST /condition and its effect on the database."""
 
     def test_do_POST_condition(self) -> None:
         """Test POST /condition and its effect on the database."""
-        form_data = {'title': 'foo', 'description': 'foo'}
-        self.check_post(form_data, '/condition', 302, '/condition?id=1')
-        self.assertEqual(1, len(Condition.all(self.db_conn)))
-        form_data['delete'] = ''
-        self.check_post(form_data, '/condition?id=', 404)
-        self.check_post(form_data, '/condition?id=2', 404)
-        self.check_post(form_data, '/condition?id=1', 302, '/conditions')
-        self.assertEqual(0, len(Condition.all(self.db_conn)))
-
-    def test_do_GET(self) -> None:
-        """Test /condition and /conditions response codes."""
-        form_data = {'title': 'foo', 'description': 'foo'}
+
+        def check(path: str, expected: dict[str, object]) -> None:
+            self.conn.request('GET', path)
+            response = self.conn.getresponse()
+            self.assertEqual(response.status, 200)
+            retrieved = json_loads(response.read().decode())
+            self.blank_history_keys_in(retrieved)
+            self.assertEqual(expected, retrieved)
+
+        # check empty POST fails
+        self.check_post({}, '/condition', 400)
+        # test valid POST's effect on …
+        post = {'title': 'foo', 'description': 'oof', 'is_active': False}
+        self.check_post(post, '/condition', 302, '/condition?id=1')
+        # … single /condition
+        cond = {'id': 1, 'is_active': False,
+                'title': {'parent_id': 1, 'history': {'[0]': 'foo'}},
+                'description': {'parent_id': 1, 'history': {'[0]': 'oof'}}}
+        expected_single = {'is_new': False,
+                           'enabled_processes': [],
+                           'disabled_processes': [],
+                           'enabling_processes': [],
+                           'disabling_processes': [],
+                           'condition': cond}
+        check('/condition?id=1', expected_single)
+        # … full /conditions
+        expected_all = {'conditions': [cond], 'sort_by': 'title', 'pattern': ''}
+        check('/conditions', expected_all)
+        # test effect of invalid POST to existing Condition on /condition
+        self.check_post({}, '/condition?id=1', 400)
+        check('/condition?id=1', expected_single)
+        # test deletion POST's effect on …
+        self.check_post({'delete': ''}, '/condition?id=1', 302, '/conditions')
+        cond['title']['history'] = {}
+        cond['description']['history'] = {}
+        check('/condition?id=1', expected_single)
+        # … full /conditions
+        expected_all['conditions'] = []
+        check('/conditions', expected_all)
+
+    def test_do_GET_condition(self) -> None:
+        """Test GET /condition."""
+        form_data = {'title': 'foo', 'description': 'foo', 'is_active': False}
         self.check_post(form_data, '/condition', 302, '/condition?id=1')
         self.check_get_defaults('/condition')
         self.check_post(form_data, '/condition', 302, '/condition?id=1')
         self.check_get_defaults('/condition')
-        self.check_get('/conditions', 200)
+
+    def test_do_GET_conditions(self) -> None:
+        """Test GET /conditions."""
+
+        def check(params: str, expected_json: dict[str, object]) -> None:
+            self.conn.request('GET', f'/conditions{params}')
+            response = self.conn.getresponse()
+            self.assertEqual(response.status, 200)
+            retrieved_json = json_loads(response.read().decode())
+            self.blank_history_keys_in(retrieved_json)
+            self.assertEqual(expected_json, retrieved_json)
+
+        # test empty result on empty DB, default-settings on empty params
+        expected_json: dict[str, object] = {'conditions': [],
+                                            'sort_by': 'title',
+                                            'pattern': ''}
+        check('', expected_json)
+        # test on meaningless non-empty params (incl. entirely un-used key)
+        expected_json = {'conditions': [],
+                         'sort_by': 'title',  # nonsense "foo" defaulting
+                         'pattern': 'bar'}  # preserved despite zero effect
+        check('?sort_by=foo&pattern=bar&foo=x', expected_json)
+        # test non-empty result, automatic (positive) sorting by title
+        post_1 = {'title': 'foo', 'description': 'oof', 'is_active': False}
+        self.check_post(post_1, '/condition', 302, '/condition?id=1')
+        post_2 = {'title': 'bar', 'description': 'rab', 'is_active': False}
+        self.check_post(post_2, '/condition', 302, '/condition?id=2')
+        post_3 = {'title': 'baz', 'description': 'zab', 'is_active': True}
+        self.check_post(post_3, '/condition', 302, '/condition?id=3')
+        cond_1 = {'id': 1, 'is_active': False,
+                  'title': {'history': {'[0]': 'foo'},
+                            'parent_id': 1},
+                           'description': {'history': {'[0]': 'oof'},
+                                           'parent_id': 1}}
+        cond_2 = {'id': 2, 'is_active': False,
+                  'title': {'history': {'[0]': 'bar'},
+                            'parent_id': 2},
+                  'description': {'history': {'[0]': 'rab'},
+                                  'parent_id': 2}}
+        cond_3 = {'id': 3, 'is_active': True,
+                  'title': {'history': {'[0]': 'baz'},
+                            'parent_id': 3},
+                  'description': {'history': {'[0]': 'zab'},
+                                  'parent_id': 3}}
+        cons = [cond_2, cond_3, cond_1]
+        expected_json = {'conditions': cons, 'sort_by': 'title', 'pattern': ''}
+        check('', expected_json)
+        # test other sortings
+        # (NB: by .is_active has two items of =False, their order currently
+        # is not explicitly made predictable, so mail fail until we do)
+        expected_json['conditions'] = [cond_1, cond_3, cond_2]
+        expected_json['sort_by'] = '-title'
+        check('?sort_by=-title', expected_json)
+        expected_json['conditions'] = [cond_1, cond_2, cond_3]
+        expected_json['sort_by'] = 'is_active'
+        check('?sort_by=is_active', expected_json)
+        expected_json['conditions'] = [cond_3, cond_1, cond_2]
+        expected_json['sort_by'] = '-is_active'
+        check('?sort_by=-is_active', expected_json)
+        # test pattern matching on title
+        expected_json = {'conditions': [cond_2, cond_3],
+                         'sort_by': 'title', 'pattern': 'ba'}
+        check('?pattern=ba', expected_json)
+        # test pattern matching on description
+        expected_json['conditions'] = [cond_1]
+        expected_json['pattern'] = 'oo'
+        check('?pattern=oo', expected_json)