From: Christian Heller <c.heller@plomlompom.de>
Date: Wed, 17 Jul 2024 20:19:54 +0000 (+0200)
Subject: Extend Todo tests.
X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/%22https:/validator.w3.org/ledger?a=commitdiff_plain;h=5cb55b2945857dc891142172c46c7234a97bac31;p=plomtask

Extend Todo tests.
---

diff --git a/tests/processes.py b/tests/processes.py
index 501a163..973ba3b 100644
--- a/tests/processes.py
+++ b/tests/processes.py
@@ -415,18 +415,6 @@ class TestsWithServer(TestCaseWithServer):
                                 '_library': library}
         return d
 
-    @staticmethod
-    def procstep_as_dict(id_: int,
-                         owner_id: int,
-                         step_process_id: int,
-                         parent_step_id: int | None = None
-                         ) -> dict[str, object]:
-        """Return JSON of Process to expect."""
-        return {'id': id_,
-                'owner_id': owner_id,
-                'step_process_id': step_process_id,
-                'parent_step_id': parent_step_id}
-
     def test_GET_processes(self) -> None:
         """Test GET /processes."""
         # pylint: disable=too-many-statements
diff --git a/tests/todos.py b/tests/todos.py
index aec7366..7e27636 100644
--- a/tests/todos.py
+++ b/tests/todos.py
@@ -254,16 +254,20 @@ class TestsWithServer(TestCaseWithServer):
     def setUp(self) -> None:
         super().setUp()
         self._proc1_form_data: Any = self.post_process(1)
+        self._date = '2024-01-01'
 
     @classmethod
     def GET_todo_dict(cls,
                       target_id: int,
                       todos: list[dict[str, object]],
-                      processes: list[dict[str, object]]
+                      processes: list[dict[str, object]],
+                      process_steps: list[dict[str, object]] | None = None,
                       ) -> dict[str, object]:
         """Return JSON of GET /todo to expect."""
         library = {'Todo': cls.as_refs(todos),
                    'Process': cls.as_refs(processes)}
+        if process_steps:
+            library['ProcessStep'] = cls.as_refs(process_steps)
         return {'todo': target_id,
                 'steps_todo_to_process': [],
                 'adoption_candidates_for': {},
@@ -272,6 +276,25 @@ class TestsWithServer(TestCaseWithServer):
                 'condition_candidates': [],
                 '_library': library}
 
+    @staticmethod
+    def _step_as_dict(node_id: int,
+                      children: list[dict[str, object]],
+                      process: int | None = None,
+                      todo: int | None = None,
+                      fillable: bool = False,
+                      ) -> dict[str, object]:
+        return {'node_id': node_id,
+                'children': children,
+                'process': process,
+                'fillable': fillable,
+                'todo': todo}
+
+    def _make_todo_via_day_post(self, proc_id: int) -> None:
+        payload = {'day_comment': '',
+                   'new_todo': proc_id,
+                   'make_type': 'empty'}
+        self.check_post(payload, f'/day?date={self._date}&make_type=empty')
+
     def test_basic_fail_POST_todo(self) -> None:
         """Test basic malformed/illegal POST /todo requests."""
         # test we cannot just POST into non-existing Todo
@@ -280,8 +303,7 @@ class TestsWithServer(TestCaseWithServer):
         self.check_post({}, '/todo?id=0', 404)
         self.check_post({}, '/todo?id=1', 404)
         # test malformed values on existing Todo
-        day_post = {'day_comment': '', 'new_todo': 1, 'make_type': 'full'}
-        self.check_post(day_post, '/day?date=2024-01-01&make_type=full')
+        self._make_todo_via_day_post(1)
         for name in ['adopt', 'effort', 'make_full', 'make_empty',
                      'conditions', 'disables', 'blockers', 'enables']:
             self.check_post({name: 'x'}, '/todo?id=1', 400, '/todo')
@@ -291,12 +313,10 @@ class TestsWithServer(TestCaseWithServer):
                                 '/todo?id=1', 400, '/todo')
 
     def test_basic_POST_todo(self) -> None:
-        """Test POST /todo."""
-        date = '2024-01-01'
-        day_post = {'day_comment': '', 'new_todo': 1, 'make_type': 'full'}
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
+        """Test basic POST /todo manipulations."""
+        self._make_todo_via_day_post(1)
         # test posting naked entity at first changes nothing
-        todo_dict = self.todo_as_dict(1, process_id=1, date=date)
+        todo_dict = self.todo_as_dict(1, 1, self._date)
         proc_dict = self.proc_as_dict(**self._proc1_form_data)
         expected = self.GET_todo_dict(1, [todo_dict], [proc_dict])
         self.check_json_get('/todo?id=1', expected)
@@ -306,33 +326,40 @@ class TestsWithServer(TestCaseWithServer):
         todo_post = {'done': '', 'calendarize': '', 'comment': 'foo',
                      'effort': 2.3}
         todo_dict = self.todo_as_dict(
-                1, process_id=1, date=date, is_done=True, calendarize=True,
+                1, 1, self._date, is_done=True, calendarize=True,
                 comment='foo', effort=2.3)
         expected = self.GET_todo_dict(1, [todo_dict], [proc_dict])
         self.check_post(todo_post, '/todo?id=1')
         self.check_json_get('/todo?id=1', expected)
         # test implicitly un-setting all of those except effort by empty post
         self.check_post({}, '/todo?id=1')
-        todo_dict = self.todo_as_dict(1, process_id=1, date=date, effort=2.3)
+        todo_dict = self.todo_as_dict(1, 1, date=self._date, effort=2.3)
         expected = self.GET_todo_dict(1, [todo_dict], [proc_dict])
         self.check_json_get('/todo?id=1', expected)
         # test empty effort post can be explicitly unset by "" post
         self.check_post({'effort': ''}, '/todo?id=1')
         todo_dict['effort'] = None
         self.check_json_get('/todo?id=1', expected)
+
+    def test_POST_todo_deletion(self) -> None:
+        """Test deletions via POST /todo."""
+        self._make_todo_via_day_post(1)
+        todo_dict = self.todo_as_dict(1, process_id=1, date=self._date)
+        proc_dict = self.proc_as_dict(**self._proc1_form_data)
+        expected = self.GET_todo_dict(1, [todo_dict], [proc_dict])
         # test failure of deletion on non-existing Todo
         self.check_post({'delete': ''}, '/todo?id=2', 404, '/')
         # test deletion of existing Todo
         self.check_post({'delete': ''}, '/todo?id=1', 302, '/')
         self.check_get('/todo?id=1', 404)
         # test deletion of adopted Todo
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
+        self._make_todo_via_day_post(1)
+        self._make_todo_via_day_post(1)
         self.check_post({'adopt': 2}, '/todo?id=1')
         self.check_post({'delete': ''}, '/todo?id=2', 302, '/')
         self.check_json_get('/todo?id=1', expected)
         # test deletion of adopting Todo
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
+        self._make_todo_via_day_post(1)
         self.check_post({'adopt': 2}, '/todo?id=1')
         self.check_post({'delete': ''}, '/todo?id=1', 302, '/')
         todo_dict['id'] = 2
@@ -340,22 +367,18 @@ class TestsWithServer(TestCaseWithServer):
         self.check_json_get('/todo?id=2', expected)
 
     def test_POST_todo_adoption(self) -> None:
-        """Test POST /todo."""
-        date = '2024-01-01'
+        """Test adoption via POST /todo with "adopt"."""
         # post two Todos to Day, have first adopt second
+        self._make_todo_via_day_post(1)
+        self._make_todo_via_day_post(1)
         proc_dict = self.proc_as_dict(**self._proc1_form_data)
-        day_post = {'day_comment': '', 'new_todo': 1, 'make_type': 'full'}
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
-        todo1_dict = self.todo_as_dict(1, process_id=1, date=date)
+        todo1_dict = self.todo_as_dict(1, process_id=1, date=self._date)
         todo1_dict['children'] = [2]
-        todo2_dict = self.todo_as_dict(2, process_id=1, date=date)
+        todo2_dict = self.todo_as_dict(2, process_id=1, date=self._date)
         todo2_dict['parents'] = [1]
         expected = self.GET_todo_dict(1, [todo1_dict, todo2_dict], [proc_dict])
         expected['todo_candidates'] = [2]
-        expected['steps_todo_to_process'] = [{
-            'children': [], 'fillable': False,
-            'node_id': 1, 'process': None, 'todo': 2}]
+        expected['steps_todo_to_process'] = [self._step_as_dict(1, [], todo=2)]
         self.check_post({'adopt': 2}, '/todo?id=1')
         self.check_json_get('/todo?id=1', expected)
         # test Todo cannot be set done with adopted Todo not done yet
@@ -375,16 +398,56 @@ class TestsWithServer(TestCaseWithServer):
         self.check_post({'adopt': 1}, '/todo?id=2')
         self.check_post({'adopt': 2}, '/todo?id=1', 400)
         # test cannot do 2-step circular adoption
-        day_post = {'day_comment': '', 'new_todo': 1, 'make_type': 'full'}
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
+        self._make_todo_via_day_post(1)
         self.check_post({'adopt': 2}, '/todo?id=3')
         self.check_post({'adopt': 3}, '/todo?id=1', 400)
 
+    def test_POST_todo_make_full(self) -> None:
+        """Test creation and adoption via POST /todo with "make_full"."""
+        # pylint: disable=too-many-locals
+        # create chain of Processes
+        proc_post = {'title': '', 'description': '', 'effort': 0.9}
+        self.post_process(2, proc_post | {'new_top_step': 1})
+        self.post_process(3, proc_post | {'new_top_step': 2})
+        self.post_process(4, proc_post | {'new_top_step': 3})
+        proc1_dict = self.proc_as_dict(**self._proc1_form_data)
+        proc2_dict = self.proc_as_dict(2, '', '', 0.9)
+        proc2_dict['explicit_steps'] = [1]
+        proc3_dict = self.proc_as_dict(3, '', '', 0.9)
+        proc3_dict['explicit_steps'] = [2]
+        proc4_dict = self.proc_as_dict(4, '', '', 0.9)
+        proc4_dict['explicit_steps'] = [3]
+        proc_dicts = [proc1_dict, proc2_dict, proc3_dict, proc4_dict]
+        procstep_dicts = [self.procstep_as_dict(1, 2, 1),
+                          self.procstep_as_dict(2, 3, 2),
+                          self.procstep_as_dict(3, 4, 3)]
+        # post (childless) Todo of chain end, then make_full on next in line
+        self._make_todo_via_day_post(4)
+        todo1_dict = self.todo_as_dict(1, 4, self._date)
+        todo1_dict['children'] = [2]
+        todo2_dict = self.todo_as_dict(2, 3, self._date)
+        todo2_dict['parents'] = [1]
+        todo2_dict['children'] = [3]
+        todo3_dict = self.todo_as_dict(3, 2, self._date)
+        todo3_dict['parents'] = [2]
+        todo3_dict['children'] = [4]
+        todo4_dict = self.todo_as_dict(4, 1, self._date)
+        todo4_dict['parents'] = [3]
+        todo_dicts = [todo1_dict, todo2_dict, todo3_dict, todo4_dict]
+        expected = self.GET_todo_dict(
+                1, todo_dicts, proc_dicts, procstep_dicts)
+        step_proc1 = self._step_as_dict(3, [], 1, 4, True)
+        step_proc2 = self._step_as_dict(2, [step_proc1], 2, 3, True)
+        step_proc3 = self._step_as_dict(1, [step_proc2], 3, 2, True)
+        expected['steps_todo_to_process'] = [step_proc3]
+        expected['process_candidates'] = [4, 3, 2, 1]
+        expected['todo_candidates'] = [2, 3, 4]
+        self.check_post({'fill_for_3': 'make_full_3'}, '/todo?id=1')
+        self.check_json_get('/todo?id=1', expected)
+
     def test_do_GET_todo(self) -> None:
         """Test GET /todo response codes."""
-        date = '2024-01-01'
-        day_post = {'day_comment': '', 'new_todo': 1, 'make_type': 'full'}
-        self.check_post(day_post, f'/day?date={date}&make_type=full')
+        self._make_todo_via_day_post(1)
         # test malformed or illegal parameter values
         self.check_get('/todo', 404)
         self.check_get('/todo?id=', 404)
@@ -392,13 +455,48 @@ class TestsWithServer(TestCaseWithServer):
         self.check_get('/todo?id=0', 404)
         self.check_get('/todo?id=2', 404)
         # test all existing Processes are shown as available
-        p2_post: Any = {'title': 'bar', 'description': 'baz', 'effort': 0.9}
-        self.post_process(2, p2_post)
-        todo1_dict = self.todo_as_dict(1, process_id=1, date=date)
+        proc_post = {'title': '', 'description': '', 'effort': 0.9}
+        self.post_process(2, proc_post)
+        todo1_dict = self.todo_as_dict(1, process_id=1, date=self._date)
         proc1_dict = self.proc_as_dict(1, **self._proc1_form_data)
-        proc2_dict = self.proc_as_dict(2, **p2_post)
-        expected = self.GET_todo_dict(1, [todo1_dict], [proc1_dict,
-                                                        proc2_dict])
+        proc2_dict = self.proc_as_dict(2, '', '', 0.9)
+        proc_dicts = [proc1_dict, proc2_dict]
+        expected = self.GET_todo_dict(1, [todo1_dict], proc_dicts)
+        self.check_json_get('/todo?id=1', expected)
+        # test chain of Processes shown as potential step nodes
+        self.post_process(2, proc_post)
+        self.post_process(3, proc_post)
+        self.post_process(4, proc_post)
+        self.post_process(1, self._proc1_form_data | {'new_top_step': 2})
+        self.post_process(2, proc_post | {'new_top_step': 3, 'step_of': [1]})
+        self.post_process(3, proc_post | {'new_top_step': 4, 'step_of': [2]})
+        proc1_dict['explicit_steps'] = [1]
+        proc2_dict['explicit_steps'] = [2]
+        proc3_dict = self.proc_as_dict(3, '', '', 0.9, explicit_steps=[3])
+        proc4_dict = self.proc_as_dict(4, '', '', 0.9)
+        proc_dicts = [proc1_dict, proc2_dict, proc3_dict, proc4_dict]
+        procstep_dicts = [self.procstep_as_dict(1, 1, 2, None),
+                          self.procstep_as_dict(2, 2, 3, None),
+                          self.procstep_as_dict(3, 3, 4, None)]
+        expected = self.GET_todo_dict(
+                1, [todo1_dict], proc_dicts, procstep_dicts)
+        step_proc4 = self._step_as_dict(3, [], 4)
+        step_proc3 = self._step_as_dict(2, [step_proc4], 3)
+        step_proc2 = self._step_as_dict(1, [step_proc3], 2, fillable=True)
+        expected['steps_todo_to_process'] = [step_proc2]
+        expected['adoption_candidates_for'] = {'2': [], '3': [], '4': []}
+        self.check_json_get('/todo?id=1', expected)
+        # test display of parallel chains
+        steps_d = {'new_top_step': 4, 'keep_step': [1],
+                   'step_1_process_id': 2, 'steps': [1, 4]}
+        self.post_process(1, self._proc1_form_data | steps_d)
+        proc1_dict['explicit_steps'] = [1, 4]
+        step2_proc4 = self._step_as_dict(4, [], 4, fillable=True)
+        procstep_dicts += [self.procstep_as_dict(4, 1, 4, None)]
+        expected = self.GET_todo_dict(
+                1, [todo1_dict], proc_dicts, procstep_dicts)
+        expected['steps_todo_to_process'] = [step_proc2, step2_proc4]
+        expected['adoption_candidates_for'] = {'2': [], '3': [], '4': []}
         self.check_json_get('/todo?id=1', expected)
 
     def test_do_POST_day(self) -> None:
diff --git a/tests/utils.py b/tests/utils.py
index 19bcc59..dc78c15 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -575,6 +575,18 @@ class TestCaseWithServer(TestCaseWithDB):
             d['_versioned']['description'][i] = description
         return d
 
+    @staticmethod
+    def procstep_as_dict(id_: int,
+                         owner_id: int,
+                         step_process_id: int,
+                         parent_step_id: int | None = None
+                         ) -> dict[str, object]:
+        """Return JSON of Process to expect."""
+        return {'id': id_,
+                'owner_id': owner_id,
+                'step_process_id': step_process_id,
+                'parent_step_id': parent_step_id}
+
     @staticmethod
     def todo_as_dict(id_: int = 1,
                      process_id: int = 1,
@@ -613,14 +625,15 @@ class TestCaseWithServer(TestCaseWithDB):
                      conditions: None | list[int] = None,
                      disables: None | list[int] = None,
                      blockers: None | list[int] = None,
-                     enables: None | list[int] = None
+                     enables: None | list[int] = None,
+                     explicit_steps: None | list[int] = None
                      ) -> dict[str, object]:
         """Return JSON of Process to expect."""
         # pylint: disable=too-many-arguments
         d = {'id': id_,
              'calendarize': False,
              'suppressed_steps': [],
-             'explicit_steps': [],
+             'explicit_steps': explicit_steps if explicit_steps else [],
              '_versioned': {
                  'title': {0: title},
                  'description': {0: description},