home · contact · privacy
Extend and refactor tests. master
authorChristian Heller <c.heller@plomlompom.de>
Tue, 13 Aug 2024 04:24:05 +0000 (06:24 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 13 Aug 2024 04:24:05 +0000 (06:24 +0200)
tests/conditions.py
tests/days.py
tests/processes.py
tests/todos.py
tests/utils.py

index 58fa18b09610c28a71956e1b3118367bb3dfa154..3b6495980cc4fc5ab334bd98eee57e85bd1b8d75 100644 (file)
@@ -77,13 +77,10 @@ class TestsWithServer(TestCaseWithServer):
     def test_fail_POST_condition(self) -> None:
         """Test malformed/illegal POST /condition requests."""
         # check incomplete POST payloads
-        url = '/condition'
-        self.check_post({}, url, 400)
-        self.check_post({'title': ''}, url, 400)
-        self.check_post({'description': ''}, url, 400)
+        valid_payload = {'title': '', 'description': ''}
+        self.check_minimal_inputs('/condition', valid_payload)
         # check valid POST payload on bad paths
-        valid_payload = {'title': '', 'description': '', 'is_active': 0}
-        self.check_post(valid_payload, f'{url}?id=foo', 400)
+        self.check_post(valid_payload, '/condition?id=foo', 400)
 
     def test_POST_condition(self) -> None:
         """Test (valid) POST /condition and its effect on GET /condition[s]."""
index f79284c4b8e2c4d0fcaa9855a7245b65deb2d49d..c1952373aa55518b2e02856bb3397bc1e255bb47 100644 (file)
@@ -56,7 +56,9 @@ class TestsWithDB(TestCaseWithDB):
     def test_Day_with_filled_gaps(self) -> None:
         """Test .with_filled_gaps."""
 
-        def test(range_indexes: tuple[int, int], indexes_to_provide: list[int]
+        def expect_within_full_range_as_commented(
+                range_indexes: tuple[int, int],
+                indexes_to_provide: list[int]
                  ) -> None:
             start_i, end_i = range_indexes
             days_provided = []
@@ -78,32 +80,24 @@ class TestsWithDB(TestCaseWithDB):
         days_with_comment = [Day(date, comment=date[-1:]) for date in dates]
         days_sans_comment = [Day(date, comment='') for date in dates]
         # check provided Days recognizable in (full-range) interval
-        test((0, 8), [0, 4, 8])
+        expect_within_full_range_as_commented((0, 8), [0, 4, 8])
         # check limited range, but limiting Days provided
-        test((2, 6), [2, 5, 6])
+        expect_within_full_range_as_commented((2, 6), [2, 5, 6])
         # check Days within range but beyond provided Days also filled in
-        test((1, 7), [2, 5])
+        expect_within_full_range_as_commented((1, 7), [2, 5])
         # check provided Days beyond range ignored
-        test((3, 5), [1, 2, 4, 6, 7])
+        expect_within_full_range_as_commented((3, 5), [1, 2, 4, 6, 7])
         # check inversion of start_date and end_date returns empty list
-        test((5, 3), [2, 4, 6])
+        expect_within_full_range_as_commented((5, 3), [2, 4, 6])
         # check empty provision still creates filler elements in interval
-        test((3, 5), [])
+        expect_within_full_range_as_commented((3, 5), [])
         # check single-element selection creating only filler beyond provided
-        test((1, 1), [2, 4, 6])
+        expect_within_full_range_as_commented((1, 1), [2, 4, 6])
         # check (un-saved) filler Days don't show up in cache or DB
-        # dates = [f'2024-02-0{n}' for n in range(1, 6)]
         day = Day(dates[3])
         day.save(self.db_conn)
         self.checked_class.with_filled_gaps([day], dates[0], dates[-1])
         self.check_identity_with_cache_and_db([day])
-        # check 'today', 'yesterday', 'tomorrow' are interpreted
-        yesterday = Day('yesterday')
-        tomorrow = Day('tomorrow')
-        today = Day('today')
-        result = self.checked_class.with_filled_gaps([today], 'yesterday',
-                                                     'tomorrow')
-        self.assertEqual(result, [yesterday, today, tomorrow])
 
 
 class ExpectedGetCalendar(Expected):
@@ -167,10 +161,9 @@ class TestsWithServer(TestCaseWithServer):
         self.check_get_defaults('/day', '2024-01-01', 'date')
         self.check_get('/day?date=2024-02-30', 400)
         # check undefined day
-        date = _testing_date_in_n_days(0)
-        exp = ExpectedGetDay(date)
+        exp = ExpectedGetDay(_testing_date_in_n_days(0))
         self.check_json_get('/day', exp)
-        # check defined day, with and without make_type parameter
+        # check defined day with make_type parameter
         date = '2024-01-01'
         exp = ExpectedGetDay(date)
         exp.set('make_type', 'bar')
@@ -185,22 +178,19 @@ class TestsWithServer(TestCaseWithServer):
         """Test malformed/illegal POST /day requests."""
         # check payloads lacking minimum expecteds
         url = '/day?date=2024-01-01'
-        self.check_post({}, url, 400)
-        self.check_post({'day_comment': ''}, url, 400)
-        self.check_post({'make_type': ''}, url, 400)
+        minimal_post = {'make_type': '', 'day_comment': ''}
+        self.check_minimal_inputs(url, minimal_post)
         # to next check illegal new_todo values, we need an actual Process
         self.post_exp_process([], {}, 1)
         # check illegal new_todo values
-        post: dict[str, object]
-        post = {'make_type': '', 'day_comment': '', 'new_todo': ['foo']}
-        self.check_post(post, url, 400)
-        post['new_todo'] = [1, 2]  # no Process of .id_=2 exists
+        self.check_post(minimal_post | {'new_todo': ['foo']}, url, 400)
+        self.check_post(minimal_post | {'new_todo': [1, 2]}, url, 404)
         # to next check illegal old_todo inputs, we need to first post Todo
-        post['new_todo'] = [1]
-        self.check_post(post, url, 302, '/day?date=2024-01-01&make_type=')
+        self.check_post(minimal_post | {'new_todo': [1]}, url, 302,
+                        '/day?date=2024-01-01&make_type=')
         # check illegal old_todo inputs (equal list lengths though)
-        post = {'make_type': '', 'day_comment': '', 'comment': ['foo'],
-                'effort': [3.3], 'done': [], 'todo_id': [1]}
+        post = minimal_post | {'comment': ['foo'], 'effort': [3.3],
+                               'done': [], 'todo_id': [1]}
         self.check_post(post, url, 302, '/day?date=2024-01-01&make_type=')
         post['todo_id'] = [2]  # reference to non-existant Process
         self.check_post(post, url, 404)
@@ -268,7 +258,7 @@ class TestsWithServer(TestCaseWithServer):
         for i, proc_post in enumerate(proc_posts):
             self.post_exp_process([exp], proc_post, i+1)
         self.check_json_get(f'/day?date={date}', exp)
-        # post Todos of either process and check their display
+        # post Todos of either Process and check their display
         self.post_exp_day([exp], {'new_todo': [1, 2]})
         self.check_json_get(f'/day?date={date}', exp)
         # test malformed Todo manipulation posts
@@ -302,7 +292,8 @@ class TestsWithServer(TestCaseWithServer):
         # create two Processes, with second one step of first one
         self.post_exp_process([exp], {}, 2)
         self.post_exp_process([exp], {'new_top_step': 2}, 1)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(1, 1, 2, None)])
+        exp.lib_set('ProcessStep', [
+            exp.procstep_as_dict(1, owner_id=1, step_process_id=2)])
         self.check_json_get(f'/day?date={date}', exp)
         # post Todo of adopting Process, with make_type=full
         self.post_exp_day([exp], {'make_type': 'full', 'new_todo': [1]})
@@ -325,7 +316,7 @@ class TestsWithServer(TestCaseWithServer):
                                      'children': []}]}]
         exp.force('top_nodes', top_nodes)
         self.check_json_get(f'/day?date={date}', exp)
-        # post another Todo of adopting Process, make_type=empty
+        # post another Todo of adopting Process, no adopt with make_type=empty
         self.post_exp_day([exp], {'make_type': 'empty', 'new_todo': [1]})
         exp.lib_set('Todo', [exp.todo_as_dict(4, 1)])
         top_nodes += [{'todo': 4,
@@ -340,7 +331,8 @@ class TestsWithServer(TestCaseWithServer):
         exp = ExpectedGetDay(date)
         self.post_exp_process([exp], {}, 2)
         self.post_exp_process([exp], {'new_top_step': 2}, 1)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(1, 1, 2, None)])
+        exp.lib_set('ProcessStep', [
+            exp.procstep_as_dict(1, owner_id=1, step_process_id=2)])
         # make-full-day-post batch of Todos of both Processes in one order …,
         self.post_exp_day([exp], {'make_type': 'full', 'new_todo': [1, 2]})
         top_nodes: list[dict[str, Any]] = [{'todo': 1,
@@ -364,6 +356,23 @@ class TestsWithServer(TestCaseWithServer):
         exp.lib_get('Todo', 3)['children'] = [4]
         self.check_json_get(f'/day?date={date}', exp)
 
+    def test_POST_day_todo_deletion_by_negative_effort(self) -> None:
+        """Test POST /day removal of Todos by setting negative effort."""
+        date = '2024-01-01'
+        exp = ExpectedGetDay(date)
+        self.post_exp_process([exp], {}, 1)
+        self.post_exp_day([exp], {'new_todo': [1]})
+        # check cannot remove Todo if commented
+        self.post_exp_day([exp],
+                          {'todo_id': [1], 'comment': ['foo'], 'effort': [-1]})
+        self.check_json_get(f'/day?date={date}', exp)
+        # check *can* remove Todo while getting done
+        self.post_exp_day([exp],
+                          {'todo_id': [1], 'comment': [''], 'effort': [-1],
+                           'done': [1]})
+        exp.lib_del('Todo', 1)
+        self.check_json_get(f'/day?date={date}', exp)
+
     def test_GET_day_with_conditions(self) -> None:
         """Test GET /day displaying Conditions and their relations."""
         date = '2024-01-01'
index 2561fbbaa1e1615a082c124d1fe20b64265a53be..0c2c1a935a2009abad2d2d460295bc360a5458ec 100644 (file)
@@ -300,10 +300,7 @@ class TestsWithServer(TestCaseWithServer):
         """Test POST /process and its effect on the database."""
         valid_post = {'title': '', 'description': '', 'effort': 1.0}
         # check payloads lacking minimum expecteds
-        self.check_post({}, '/process', 400)
-        self.check_post({'title': '', 'description': ''}, '/process', 400)
-        self.check_post({'title': '', 'effort': 1}, '/process', 400)
-        self.check_post({'description': '', 'effort': 1}, '/process', 400)
+        self.check_minimal_inputs('/process', valid_post)
         # check payloads of bad data types
         self.check_post(valid_post | {'effort': ''}, '/process', 400)
         # check references to non-existant items
index 9f3874d72efe6995bf8ffbc63e7f4b86ccfd9eb3..6118ab774f4e8f5a0edd216bc7b80532bca7439b 100644 (file)
@@ -251,14 +251,14 @@ class ExpectedGetTodo(Expected):
 
     @staticmethod
     def step_as_dict(node_id: int,
-                     children: list[dict[str, object]],
                      process: int | None = None,
                      todo: int | None = None,
                      fillable: bool = False,
+                     children: None | list[dict[str, object]] = None
                      ) -> dict[str, object]:
         """Return JSON of TodoOrProcStepsNode to expect."""
         return {'node_id': node_id,
-                'children': children,
+                'children': children if children is not None else [],
                 'process': process,
                 'fillable': fillable,
                 'todo': todo}
@@ -371,7 +371,8 @@ class TestsWithServer(TestCaseWithServer):
         self.post_exp_day([exp], {'new_todo': [1]})
         self.post_exp_day([exp], {'new_todo': [1]})
         self._post_exp_todo(1, {'adopt': 2}, exp)
-        exp.set('steps_todo_to_process', [exp.step_as_dict(1, [], todo=2)])
+        exp.set('steps_todo_to_process', [
+            exp.step_as_dict(node_id=1, process=None, todo=2)])
         self.check_json_get('/todo?id=1', exp)
         # test Todo un-adopting by just not sending an adopt
         self._post_exp_todo(1, {}, exp)
@@ -393,19 +394,20 @@ class TestsWithServer(TestCaseWithServer):
         self.post_exp_process([exp], {}, 2)
         self.post_exp_process([exp], {}, 3)
         self.post_exp_process([exp], {'new_top_step': [2, 3]}, 1)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(1, 1, 2),
-                                    exp.procstep_as_dict(2, 1, 3)])
-        step1_proc2 = exp.step_as_dict(1, [], 2, None, True)
-        step2_proc3 = exp.step_as_dict(2, [], 3, None, True)
-        exp.set('steps_todo_to_process', [step1_proc2, step2_proc3])
+        exp.lib_set('ProcessStep',
+                    [exp.procstep_as_dict(1, owner_id=1, step_process_id=2),
+                     exp.procstep_as_dict(2, owner_id=1, step_process_id=3)])
+        slots = [
+            exp.step_as_dict(node_id=1, process=2, todo=None, fillable=True),
+            exp.step_as_dict(node_id=2, process=3, todo=None, fillable=True)]
+        exp.set('steps_todo_to_process', slots)
         self.post_exp_day([exp], {'new_todo': [2]})
         self.post_exp_day([exp], {'new_todo': [3]})
         self.check_json_get('/todo?id=1', exp)
         self._post_exp_todo(1, {'step_filler_to_1': 5, 'adopt': [4]}, exp)
         exp.lib_get('Todo', 1)['children'] += [5]
-        step1_proc2 = exp.step_as_dict(1, [], 2, 4, True)
-        step2_proc3 = exp.step_as_dict(2, [], 3, 5, True)
-        exp.set('steps_todo_to_process', [step1_proc2, step2_proc3])
+        slots[0]['todo'] = 4
+        slots[1]['todo'] = 5
         self.check_json_get('/todo?id=1', exp)
         # test 'ignore' values for 'step_filler' are ignored, and intable
         # 'step_filler' values are interchangeable with those of 'adopt'
@@ -416,15 +418,14 @@ class TestsWithServer(TestCaseWithServer):
         # creating new top-level steps when adopting of respective Process
         self.post_exp_process([exp], {}, 4)
         self.post_exp_process([exp], {'new_top_step': 4, 'step_of': [1]}, 3)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(3, 3, 4)])
-        step3_proc4 = exp.step_as_dict(3, [], 4, None, True)
-        step2_proc3 = exp.step_as_dict(2, [step3_proc4], 3, 5, True)
-        exp.set('steps_todo_to_process', [step1_proc2, step2_proc3])
+        exp.lib_set('ProcessStep',
+                    [exp.procstep_as_dict(3, owner_id=3, step_process_id=4)])
+        slots[1]['children'] = [exp.step_as_dict(
+            node_id=3, process=4, todo=None, fillable=True)]
         self.post_exp_day([exp], {'new_todo': [4]})
         self._post_exp_todo(1, {'adopt': [4, 5, 6]}, exp)
-        step4_todo6 = exp.step_as_dict(4, [], None, 6, False)
-        exp.set('steps_todo_to_process', [step1_proc2, step2_proc3,
-                                          step4_todo6])
+        slots += [exp.step_as_dict(
+            node_id=4, process=None, todo=6, fillable=False)]
         self.check_json_get('/todo?id=1', exp)
 
     def test_POST_todo_make_empty(self) -> None:
@@ -434,29 +435,33 @@ class TestsWithServer(TestCaseWithServer):
         self.post_exp_process([exp], {}, 1)
         for i in range(1, 4):
             self.post_exp_process([exp], {'new_top_step': i}, i+1)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(1, 2, 1),
-                                    exp.procstep_as_dict(2, 3, 2),
-                                    exp.procstep_as_dict(3, 4, 3)])
+        exp.lib_set('ProcessStep',
+                    [exp.procstep_as_dict(1, owner_id=2, step_process_id=1),
+                     exp.procstep_as_dict(2, owner_id=3, step_process_id=2),
+                     exp.procstep_as_dict(3, owner_id=4, step_process_id=3)])
         # post (childless) Todo of chain end, then make empty on next in line
         self.post_exp_day([exp], {'new_todo': [4]})
-        step3_proc1 = exp.step_as_dict(3, [], 1)
-        step2_proc2 = exp.step_as_dict(2, [step3_proc1], 2)
-        step1_proc3 = exp.step_as_dict(1, [step2_proc2], 3, None, True)
-        exp.set('steps_todo_to_process', [step1_proc3])
+        slots = [exp.step_as_dict(
+            node_id=1, process=3, todo=None, fillable=True,
+            children=[exp.step_as_dict(
+                node_id=2, process=2, todo=None, fillable=False,
+                children=[exp.step_as_dict(
+                    node_id=3, process=1, todo=None, fillable=False)])])]
+        exp.set('steps_todo_to_process', slots)
         self.check_json_get('/todo?id=1', exp)
         self.check_post({'step_filler_to_1': 'make_3'}, '/todo?id=1')
         exp.set_todo_from_post(2, {'process_id': 3})
         exp.set_todo_from_post(1, {'process_id': 4, 'children': [2]})
-        step2_proc2 = exp.step_as_dict(2, [step3_proc1], 2, None, True)
-        step1_proc3 = exp.step_as_dict(1, [step2_proc2], 3, 2, True)
-        exp.set('steps_todo_to_process', [step1_proc3])
+        slots[0]['todo'] = 2
+        assert isinstance(slots[0]['children'], list)
+        slots[0]['children'][0]['fillable'] = True
         self.check_json_get('/todo?id=1', exp)
         # make new top-level Todo without chain implied by its Process
         self.check_post({'make_empty': 2, 'adopt': [2]}, '/todo?id=1')
         exp.set_todo_from_post(3, {'process_id': 2})
         exp.set_todo_from_post(1, {'process_id': 4, 'children': [2, 3]})
-        step4_todo3 = exp.step_as_dict(4, [], None, 3)
-        exp.set('steps_todo_to_process', [step1_proc3, step4_todo3])
+        slots += [exp.step_as_dict(
+            node_id=4, process=None, todo=3, fillable=False)]
         self.check_json_get('/todo?id=1', exp)
         # fail on trying to call make_empty on non-existing Process
         self.check_post({'make_full': 5}, '/todo?id=1', 404)
@@ -477,20 +482,25 @@ class TestsWithServer(TestCaseWithServer):
         self.post_exp_process([exp], {'new_top_step': 2}, 1)
         self.post_exp_process([exp], {'new_top_step': 3, 'step_of': [1]}, 2)
         self.post_exp_process([exp], {'new_top_step': 4, 'step_of': [2]}, 3)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(1, 1, 2, None),
-                                    exp.procstep_as_dict(2, 2, 3, None),
-                                    exp.procstep_as_dict(3, 3, 4, None)])
-        step3_proc4 = exp.step_as_dict(3, [], 4)
-        step2_proc3 = exp.step_as_dict(2, [step3_proc4], 3)
-        step1_proc2 = exp.step_as_dict(1, [step2_proc3], 2, fillable=True)
-        exp.set('steps_todo_to_process', [step1_proc2])
+        exp.lib_set('ProcessStep', [
+            exp.procstep_as_dict(1, owner_id=1, step_process_id=2),
+            exp.procstep_as_dict(2, owner_id=2, step_process_id=3),
+            exp.procstep_as_dict(3, owner_id=3, step_process_id=4)])
+        slots = [exp.step_as_dict(
+            node_id=1, process=2, todo=None, fillable=True,
+            children=[exp.step_as_dict(
+                node_id=2, process=3, todo=None, fillable=False,
+                children=[exp.step_as_dict(
+                    node_id=3, process=4, todo=None, fillable=False)])])]
+        exp.set('steps_todo_to_process', slots)
         self.check_json_get('/todo?id=1', exp)
         # test display of parallel chains
         proc_steps_post = {'new_top_step': 4, 'kept_steps': [1, 3]}
         self.post_exp_process([], proc_steps_post, 1)
-        step4_proc4 = exp.step_as_dict(4, [], 4, fillable=True)
-        exp.lib_set('ProcessStep', [exp.procstep_as_dict(4, 1, 4, None)])
-        exp.set('steps_todo_to_process', [step1_proc2, step4_proc4])
+        exp.lib_set('ProcessStep', [
+            exp.procstep_as_dict(4, owner_id=1, step_process_id=4)])
+        slots += [exp.step_as_dict(
+            node_id=4, process=4, todo=None, fillable=True)]
         self.check_json_get('/todo?id=1', exp)
 
     def test_POST_todo_doneness_relations(self) -> None:
index 9d5f88e6abb28ca1876188fc2d6264c6fda1cb8c..e706ec0b242b4bfff9dbd09af92657b8279df4da 100644 (file)
@@ -679,7 +679,7 @@ class Expected:
                 for todo in self.lib_all('Todo'):
                     if next_id <= todo['id']:
                         next_id = todo['id'] + 1
-                for proc_id in sorted([id_ for id_  in v if id_]):
+                for proc_id in sorted([id_ for id_ in v if id_]):
                     todo = self.todo_as_dict(next_id, proc_id, date)
                     self.lib_set('Todo', [todo])
                     next_id += 1
@@ -947,6 +947,15 @@ class TestCaseWithServer(TestCaseWithDB):
         self.conn.request('GET', target)
         self.assertEqual(self.conn.getresponse().status, expected_code)
 
+    def check_minimal_inputs(self,
+                             url: str,
+                             minimal_inputs: dict[str, Any]
+                             ) -> None:
+        """Check that url 400's unless all of minimal_inputs provided."""
+        for to_hide in minimal_inputs.keys():
+            to_post = {k: v for k, v in minimal_inputs.items() if k != to_hide}
+            self.check_post(to_post, url, 400)
+
     def check_post(self, data: Mapping[str, object], target: str,
                    expected_code: int = 302, redir: str = '') -> None:
         """Check that POST of data to target yields expected_code."""