home · contact · privacy
On posting a new Todo to a Day, auto-adopt existing ones per its Process' .explicit_s...
[plomtask] / tests / todos.py
index 98c2aaf8da3e158f2709a4ef1f3251d63f7b9068..b5953dc69c0d593113095ddd3e82c463e03ec253 100644 (file)
@@ -1,7 +1,6 @@
 """Test Todos module."""
 from tests.utils import TestCaseWithDB, TestCaseWithServer
 """Test Todos module."""
 from tests.utils import TestCaseWithDB, TestCaseWithServer
-from plomtask.todos import Todo
-from plomtask.days import Day
+from plomtask.todos import Todo, TodoStepsNode
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
 from plomtask.processes import Process
 from plomtask.conditions import Condition
 from plomtask.exceptions import (NotFoundException, BadFormatException,
@@ -11,14 +10,24 @@ from plomtask.exceptions import (NotFoundException, BadFormatException,
 class TestsWithDB(TestCaseWithDB):
     """Tests requiring DB, but not server setup."""
 
 class TestsWithDB(TestCaseWithDB):
     """Tests requiring DB, but not server setup."""
 
+    def setUp(self) -> None:
+        super().setUp()
+        self.date1 = '2024-01-01'
+        self.date2 = '2024-01-02'
+        self.proc = Process(None)
+        self.proc.save(self.db_conn)
+        self.cond1 = Condition(None)
+        self.cond1.save(self.db_conn)
+        self.cond2 = Condition(None)
+        self.cond2.save(self.db_conn)
+
     def test_Todo_by_id(self) -> None:
         """Test creation and findability of Todos."""
     def test_Todo_by_id(self) -> None:
         """Test creation and findability of Todos."""
-        day = Day('2024-01-01')
-        process = Process(None)
-        todo = Todo(None, process, False, day)
+        process_unsaved = Process(None)
+        todo = Todo(None, process_unsaved, False, self.date1)
         with self.assertRaises(NotFoundException):
             todo.save(self.db_conn)
         with self.assertRaises(NotFoundException):
             todo.save(self.db_conn)
-        process.save_without_steps(self.db_conn)
+        process_unsaved.save(self.db_conn)
         todo.save(self.db_conn)
         self.assertEqual(Todo.by_id(self.db_conn, 1), todo)
         with self.assertRaises(NotFoundException):
         todo.save(self.db_conn)
         self.assertEqual(Todo.by_id(self.db_conn, 1), todo)
         with self.assertRaises(NotFoundException):
@@ -28,142 +37,112 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Todo_by_date(self) -> None:
         """Test findability of Todos by date."""
 
     def test_Todo_by_date(self) -> None:
         """Test findability of Todos by date."""
-        day1 = Day('2024-01-01')
-        day2 = Day('2024-01-02')
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        todo1 = Todo(None, process, False, day1)
-        todo1.save(self.db_conn)
-        todo2 = Todo(None, process, False, day1)
-        todo2.save(self.db_conn)
-        self.assertEqual(Todo.by_date(self.db_conn, day1.date), [todo1, todo2])
-        self.assertEqual(Todo.by_date(self.db_conn, day2.date), [])
+        t1 = Todo(None, self.proc, False, self.date1)
+        t1.save(self.db_conn)
+        t2 = Todo(None, self.proc, False, self.date1)
+        t2.save(self.db_conn)
+        self.assertEqual(Todo.by_date(self.db_conn, self.date1), [t1, t2])
+        self.assertEqual(Todo.by_date(self.db_conn, self.date2), [])
         self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
 
     def test_Todo_from_process(self) -> None:
         """Test spawning of Todo attributes from Process."""
         self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
 
     def test_Todo_from_process(self) -> None:
         """Test spawning of Todo attributes from Process."""
-        day = Day('2024-01-01')
-        process = Process(None)
-        c1 = Condition(None, False)
-        c1.save(self.db_conn)
-        assert c1.id_ is not None
-        c2 = Condition(None, True)
-        c2.save(self.db_conn)
-        assert c2.id_ is not None
-        process.set_conditions(self.db_conn, [c1.id_])
-        todo = Todo(None, process, False, day)
-        self.assertEqual(todo.conditions, [c1])
-        todo.set_conditions(self.db_conn, [c2.id_])
-        self.assertEqual(todo.conditions, [c2])
-        self.assertEqual(process.conditions, [c1])
-        process.set_fulfills(self.db_conn, [c1.id_])
-        todo = Todo(None, process, False, day)
-        self.assertEqual(todo.fulfills, [c1])
-        todo.set_fulfills(self.db_conn, [c2.id_])
-        self.assertEqual(todo.fulfills, [c2])
-        self.assertEqual(process.fulfills, [c1])
-        process.set_undoes(self.db_conn, [c1.id_])
-        todo = Todo(None, process, False, day)
-        self.assertEqual(todo.undoes, [c1])
-        todo.set_undoes(self.db_conn, [c2.id_])
-        self.assertEqual(todo.undoes, [c2])
-        self.assertEqual(process.undoes, [c1])
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        self.proc.set_conditions(self.db_conn, [self.cond1.id_])
+        todo = Todo(None, self.proc, False, self.date1)
+        self.assertEqual(todo.conditions, [self.cond1])
+        todo.set_conditions(self.db_conn, [self.cond2.id_])
+        self.assertEqual(todo.conditions, [self.cond2])
+        self.assertEqual(self.proc.conditions, [self.cond1])
+        self.proc.set_enables(self.db_conn, [self.cond1.id_])
+        todo = Todo(None, self.proc, False, self.date1)
+        self.assertEqual(todo.enables, [self.cond1])
+        todo.set_enables(self.db_conn, [self.cond2.id_])
+        self.assertEqual(todo.enables, [self.cond2])
+        self.assertEqual(self.proc.enables, [self.cond1])
+        self.proc.set_disables(self.db_conn, [self.cond1.id_])
+        todo = Todo(None, self.proc, False, self.date1)
+        self.assertEqual(todo.disables, [self.cond1])
+        todo.set_disables(self.db_conn, [self.cond2.id_])
+        self.assertEqual(todo.disables, [self.cond2])
+        self.assertEqual(self.proc.disables, [self.cond1])
 
     def test_Todo_on_conditions(self) -> None:
         """Test effect of Todos on Conditions."""
 
     def test_Todo_on_conditions(self) -> None:
         """Test effect of Todos on Conditions."""
-        day = Day('2024-01-01')
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        c1 = Condition(None, False)
-        c2 = Condition(None, True)
-        c1.save(self.db_conn)
-        c2.save(self.db_conn)
-        assert c1.id_ is not None
-        assert c2.id_ is not None
-        todo = Todo(None, process, False, day)
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        todo = Todo(None, self.proc, False, self.date1)
         todo.save(self.db_conn)
         todo.save(self.db_conn)
-        todo.set_fulfills(self.db_conn, [c1.id_])
-        todo.set_undoes(self.db_conn, [c2.id_])
+        todo.set_enables(self.db_conn, [self.cond1.id_])
+        todo.set_disables(self.db_conn, [self.cond2.id_])
         todo.is_done = True
         todo.is_done = True
-        self.assertEqual(c1.is_active, True)
-        self.assertEqual(c2.is_active, False)
+        self.assertEqual(self.cond1.is_active, True)
+        self.assertEqual(self.cond2.is_active, False)
         todo.is_done = False
         todo.is_done = False
-        self.assertEqual(c1.is_active, True)
-        self.assertEqual(c2.is_active, False)
+        self.assertEqual(self.cond1.is_active, True)
+        self.assertEqual(self.cond2.is_active, False)
 
     def test_Todo_enablers_disablers(self) -> None:
         """Test Todo.enablers_for_at/disablers_for_at."""
 
     def test_Todo_enablers_disablers(self) -> None:
         """Test Todo.enablers_for_at/disablers_for_at."""
-        day1 = Day('2024-01-01')
-        day2 = Day('2024-01-02')
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        c1 = Condition(None, False)
-        c2 = Condition(None, True)
-        c1.save(self.db_conn)
-        c2.save(self.db_conn)
-        todo1 = Todo(None, process, False, day1)
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        todo1 = Todo(None, self.proc, False, self.date1)
         todo1.save(self.db_conn)
         todo1.save(self.db_conn)
-        assert c1.id_ is not None
-        assert c2.id_ is not None
-        todo1.set_fulfills(self.db_conn, [c1.id_])
-        todo1.set_undoes(self.db_conn, [c2.id_])
+        todo1.set_enables(self.db_conn, [self.cond1.id_])
+        todo1.set_disables(self.db_conn, [self.cond2.id_])
         todo1.save(self.db_conn)
         todo1.save(self.db_conn)
-        assert todo1.id_ is not None
-        todo2 = Todo(None, process, False, day1)
+        todo2 = Todo(None, self.proc, False, self.date1)
         todo2.save(self.db_conn)
         todo2.save(self.db_conn)
-        assert todo2.id_ is not None
-        todo2.set_fulfills(self.db_conn, [c2.id_])
+        todo2.set_enables(self.db_conn, [self.cond2.id_])
         todo2.save(self.db_conn)
         todo2.save(self.db_conn)
-        todo3 = Todo(None, process, False, day2)
+        todo3 = Todo(None, self.proc, False, self.date2)
         todo3.save(self.db_conn)
         todo3.save(self.db_conn)
-        assert todo3.id_ is not None
-        todo3.set_fulfills(self.db_conn, [c2.id_])
+        todo3.set_enables(self.db_conn, [self.cond2.id_])
         todo3.save(self.db_conn)
         todo3.save(self.db_conn)
-        self.assertEqual(Todo.enablers_for_at(self.db_conn, c1, day1.date),
-                         [todo1])
-        self.assertEqual(Todo.enablers_for_at(self.db_conn, c1, day2.date),
-                         [])
-        self.assertEqual(Todo.disablers_for_at(self.db_conn, c1, day1.date),
-                         [])
-        self.assertEqual(Todo.disablers_for_at(self.db_conn, c1, day2.date),
-                         [])
-        self.assertEqual(Todo.enablers_for_at(self.db_conn, c2, day1.date),
-                         [todo2])
-        self.assertEqual(Todo.enablers_for_at(self.db_conn, c2, day2.date),
-                         [todo3])
-        self.assertEqual(Todo.disablers_for_at(self.db_conn, c2, day1.date),
-                         [todo1])
-        self.assertEqual(Todo.disablers_for_at(self.db_conn, c2, day2.date),
-                         [])
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, self.date1)
+        self.assertEqual(enablers, [todo1])
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond1, self.date2)
+        self.assertEqual(enablers, [])
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, self.date1)
+        self.assertEqual(disablers, [])
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond1, self.date2)
+        self.assertEqual(disablers, [])
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, self.date1)
+        self.assertEqual(enablers, [todo2])
+        enablers = Todo.enablers_for_at(self.db_conn, self.cond2, self.date2)
+        self.assertEqual(enablers, [todo3])
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, self.date1)
+        self.assertEqual(disablers, [todo1])
+        disablers = Todo.disablers_for_at(self.db_conn, self.cond2, self.date2)
+        self.assertEqual(disablers, [])
 
     def test_Todo_children(self) -> None:
         """Test Todo.children relations."""
 
     def test_Todo_children(self) -> None:
         """Test Todo.children relations."""
-        day = Day('2024-01-01')
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        todo_1 = Todo(None, process, False, day)
-        todo_2 = Todo(None, process, False, day)
+        todo_1 = Todo(None, self.proc, False, self.date1)
+        todo_2 = Todo(None, self.proc, False, self.date1)
+        todo_2.save(self.db_conn)
         with self.assertRaises(HandledException):
             todo_1.add_child(todo_2)
         todo_1.save(self.db_conn)
         with self.assertRaises(HandledException):
             todo_1.add_child(todo_2)
         todo_1.save(self.db_conn)
+        todo_3 = Todo(None, self.proc, False, self.date1)
         with self.assertRaises(HandledException):
         with self.assertRaises(HandledException):
-            todo_1.add_child(todo_2)
-        todo_2.save(self.db_conn)
-        todo_1.add_child(todo_2)
+            todo_1.add_child(todo_3)
+        todo_3.save(self.db_conn)
+        todo_1.add_child(todo_3)
         todo_1.save(self.db_conn)
         todo_1.save(self.db_conn)
+        assert isinstance(todo_1.id_, int)
         todo_retrieved = Todo.by_id(self.db_conn, todo_1.id_)
         todo_retrieved = Todo.by_id(self.db_conn, todo_1.id_)
-        self.assertEqual(todo_retrieved.children, [todo_2])
+        self.assertEqual(todo_retrieved.children, [todo_3])
         with self.assertRaises(BadFormatException):
         with self.assertRaises(BadFormatException):
-            todo_2.add_child(todo_1)
+            todo_3.add_child(todo_1)
 
     def test_Todo_conditioning(self) -> None:
         """Test Todo.doability conditions."""
 
     def test_Todo_conditioning(self) -> None:
         """Test Todo.doability conditions."""
-        day = Day('2024-01-01')
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        todo_1 = Todo(None, process, False, day)
+        assert isinstance(self.cond1.id_, int)
+        todo_1 = Todo(None, self.proc, False, self.date1)
         todo_1.save(self.db_conn)
         todo_1.save(self.db_conn)
-        todo_2 = Todo(None, process, False, day)
+        todo_2 = Todo(None, self.proc, False, self.date1)
         todo_2.save(self.db_conn)
         todo_2.add_child(todo_1)
         with self.assertRaises(BadFormatException):
         todo_2.save(self.db_conn)
         todo_2.add_child(todo_1)
         with self.assertRaises(BadFormatException):
@@ -171,27 +150,73 @@ class TestsWithDB(TestCaseWithDB):
         todo_1.is_done = True
         todo_2.is_done = True
         todo_2.is_done = False
         todo_1.is_done = True
         todo_2.is_done = True
         todo_2.is_done = False
-        condition = Condition(None)
-        condition.save(self.db_conn)
-        assert condition.id_ is not None
-        todo_2.set_conditions(self.db_conn, [condition.id_])
+        todo_2.set_conditions(self.db_conn, [self.cond1.id_])
         with self.assertRaises(BadFormatException):
             todo_2.is_done = True
         with self.assertRaises(BadFormatException):
             todo_2.is_done = True
-        condition.is_active = True
+        self.cond1.is_active = True
         todo_2.is_done = True
 
         todo_2.is_done = True
 
+    def test_Todo_step_tree(self) -> None:
+        """Test self-configuration of TodoStepsNode tree for Day view."""
+        assert isinstance(self.cond1.id_, int)
+        assert isinstance(self.cond2.id_, int)
+        todo_1 = Todo(None, self.proc, False, self.date1)
+        todo_1.save(self.db_conn)
+        assert isinstance(todo_1.id_, int)
+        # test minimum
+        node_0 = TodoStepsNode(todo_1, True, [], False)
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test non_emtpy seen_todo does something
+        node_0.seen = True
+        self.assertEqual(todo_1.get_step_tree({todo_1.id_}, set()), node_0)
+        # test child shows up
+        todo_2 = Todo(None, self.proc, False, self.date1)
+        todo_2.save(self.db_conn)
+        assert isinstance(todo_2.id_, int)
+        todo_1.add_child(todo_2)
+        node_2 = TodoStepsNode(todo_2, True, [], False)
+        node_0.children = [node_2]
+        node_0.seen = False
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test child shows up with child
+        todo_3 = Todo(None, self.proc, False, self.date1)
+        todo_3.save(self.db_conn)
+        assert isinstance(todo_3.id_, int)
+        todo_2.add_child(todo_3)
+        node_3 = TodoStepsNode(todo_3, True, [], False)
+        node_2.children = [node_3]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test same todo can be child-ed multiple times at different locations
+        todo_1.add_child(todo_3)
+        node_4 = TodoStepsNode(todo_3, True, [], True)
+        node_0.children += [node_4]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test condition shows up
+        todo_1.set_conditions(self.db_conn, [self.cond1.id_])
+        node_5 = TodoStepsNode(self.cond1, False, [], False)
+        node_0.children += [node_5]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test second condition shows up
+        todo_2.set_conditions(self.db_conn, [self.cond2.id_])
+        node_6 = TodoStepsNode(self.cond2, False, [], False)
+        node_2.children += [node_6]
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test second condition is not hidden if fulfilled by non-sibling
+        todo_1.set_enables(self.db_conn, [self.cond2.id_])
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+        # test second condition is hidden if fulfilled by sibling
+        todo_3.set_enables(self.db_conn, [self.cond2.id_])
+        node_2.children.remove(node_6)
+        self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
+
     def test_Todo_singularity(self) -> None:
         """Test pointers made for single object keep pointing to it."""
     def test_Todo_singularity(self) -> None:
         """Test pointers made for single object keep pointing to it."""
-        day = Day('2024-01-01')
-        day.save(self.db_conn)
-        process = Process(None)
-        process.save_without_steps(self.db_conn)
-        todo = Todo(None, process, False, day)
+        todo = Todo(None, self.proc, False, self.date1)
         todo.save(self.db_conn)
         retrieved_todo = Todo.by_id(self.db_conn, 1)
         todo.is_done = True
         self.assertEqual(retrieved_todo.is_done, True)
         todo.save(self.db_conn)
         retrieved_todo = Todo.by_id(self.db_conn, 1)
         todo.is_done = True
         self.assertEqual(retrieved_todo.is_done, True)
-        retrieved_todo = Todo.by_date(self.db_conn, '2024-01-01')[0]
+        retrieved_todo = Todo.by_date(self.db_conn, self.date1)[0]
         retrieved_todo.is_done = False
         self.assertEqual(todo.is_done, False)
 
         retrieved_todo.is_done = False
         self.assertEqual(todo.is_done, False)
 
@@ -204,25 +229,25 @@ class TestsWithServer(TestCaseWithServer):
         form_data = {'title': '', 'description': '', 'effort': 1}
         self.check_post(form_data, '/process?id=', 302, '/')
         self.check_post(form_data, '/process?id=', 302, '/')
         form_data = {'title': '', 'description': '', 'effort': 1}
         self.check_post(form_data, '/process?id=', 302, '/')
         self.check_post(form_data, '/process?id=', 302, '/')
-        process1 = Process.by_id(self.db_conn, 1)
-        process2 = Process.by_id(self.db_conn, 2)
+        proc = Process.by_id(self.db_conn, 1)
+        proc2 = Process.by_id(self.db_conn, 2)
         form_data = {'comment': ''}
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         self.assertEqual(Todo.by_date(self.db_conn, '2024-01-01'), [])
         form_data = {'comment': ''}
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         self.assertEqual(Todo.by_date(self.db_conn, '2024-01-01'), [])
-        form_data['new_todo'] = str(process1.id_)
+        form_data['new_todo'] = str(proc.id_)
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         todos = Todo.by_date(self.db_conn, '2024-01-01')
         self.assertEqual(1, len(todos))
         todo1 = todos[0]
         self.assertEqual(todo1.id_, 1)
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         todos = Todo.by_date(self.db_conn, '2024-01-01')
         self.assertEqual(1, len(todos))
         todo1 = todos[0]
         self.assertEqual(todo1.id_, 1)
-        self.assertEqual(todo1.process.id_, process1.id_)
+        self.assertEqual(todo1.process.id_, proc.id_)
         self.assertEqual(todo1.is_done, False)
         self.assertEqual(todo1.is_done, False)
-        form_data['new_todo'] = str(process2.id_)
+        form_data['new_todo'] = str(proc2.id_)
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         todos = Todo.by_date(self.db_conn, '2024-01-01')
         todo1 = todos[1]
         self.assertEqual(todo1.id_, 2)
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         todos = Todo.by_date(self.db_conn, '2024-01-01')
         todo1 = todos[1]
         self.assertEqual(todo1.id_, 2)
-        self.assertEqual(todo1.process.id_, process2.id_)
+        self.assertEqual(todo1.process.id_, proc2.id_)
         self.assertEqual(todo1.is_done, False)
 
     def test_do_POST_todo(self) -> None:
         self.assertEqual(todo1.is_done, False)
 
     def test_do_POST_todo(self) -> None:
@@ -232,33 +257,41 @@ class TestsWithServer(TestCaseWithServer):
             self.check_post(form_data, '/todo?id=1', status, '/')
             self.db_conn.cached_todos = {}
             return Todo.by_date(self.db_conn, '2024-01-01')[0]
             self.check_post(form_data, '/todo?id=1', status, '/')
             self.db_conn.cached_todos = {}
             return Todo.by_date(self.db_conn, '2024-01-01')[0]
+        # test minimum
         form_data = {'title': '', 'description': '', 'effort': 1}
         form_data = {'title': '', 'description': '', 'effort': 1}
-        self.check_post(form_data, '/process', 302, '/')
+        self.check_post(form_data, '/process', 302)
         form_data = {'comment': '', 'new_todo': 1}
         form_data = {'comment': '', 'new_todo': 1}
-        self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
+        self.check_post(form_data, '/day?date=2024-01-01', 302)
+        # test posting to bad URLs
         form_data = {}
         self.check_post(form_data, '/todo=', 404)
         form_data = {}
         self.check_post(form_data, '/todo=', 404)
-        self.check_post(form_data, '/todo?id=', 404)
+        self.check_post(form_data, '/todo?id=', 400)
         self.check_post(form_data, '/todo?id=FOO', 400)
         self.check_post(form_data, '/todo?id=0', 404)
         self.check_post(form_data, '/todo?id=FOO', 400)
         self.check_post(form_data, '/todo?id=0', 404)
+        # test posting naked entity
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.children, [])
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo1.is_done, False)
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.children, [])
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo1.is_done, False)
+        # test posting doneness
         form_data = {'done': ''}
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.is_done, True)
         form_data = {'done': ''}
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.is_done, True)
+        # test implicitly posting non-doneness
         form_data = {}
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.is_done, False)
         form_data = {}
         todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.is_done, False)
+        # test malformed adoptions
         form_data = {'adopt': 'foo'}
         self.check_post(form_data, '/todo?id=1', 400)
         form_data = {'adopt': 1}
         self.check_post(form_data, '/todo?id=1', 400)
         form_data = {'adopt': 2}
         self.check_post(form_data, '/todo?id=1', 404)
         form_data = {'adopt': 'foo'}
         self.check_post(form_data, '/todo?id=1', 400)
         form_data = {'adopt': 1}
         self.check_post(form_data, '/todo?id=1', 400)
         form_data = {'adopt': 2}
         self.check_post(form_data, '/todo?id=1', 404)
+        # test posting second todo of same process
         form_data = {'comment': '', 'new_todo': 1}
         form_data = {'comment': '', 'new_todo': 1}
-        self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
+        self.check_post(form_data, '/day?date=2024-01-01', 302)
+        # test todo 1 adopting todo 2
         form_data = {'adopt': 2}
         todo1 = post_and_reload(form_data)
         todo2 = Todo.by_date(self.db_conn, '2024-01-01')[1]
         form_data = {'adopt': 2}
         todo1 = post_and_reload(form_data)
         todo2 = Todo.by_date(self.db_conn, '2024-01-01')[1]
@@ -266,10 +299,35 @@ class TestsWithServer(TestCaseWithServer):
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo2.children, [])
         self.assertEqual(todo2.parents, [todo1])
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo2.children, [])
         self.assertEqual(todo2.parents, [todo1])
-        self.check_post(form_data, '/todo?id=1', 400, '/')
-        form_data = {'done': ''}
+        # test todo1 cannot be set done with todo2 not done yet
+        form_data = {'done': '', 'adopt': 2}
         todo1 = post_and_reload(form_data, 400)
         self.assertEqual(todo1.is_done, False)
         todo1 = post_and_reload(form_data, 400)
         self.assertEqual(todo1.is_done, False)
+        # test todo1 un-adopting todo 2 by just not sending an adopt
+        form_data = {}
+        todo1 = post_and_reload(form_data, 302)
+        todo2 = Todo.by_date(self.db_conn, '2024-01-01')[1]
+        self.assertEqual(todo1.children, [])
+        self.assertEqual(todo1.parents, [])
+        self.assertEqual(todo2.children, [])
+        self.assertEqual(todo2.parents, [])
+
+    def test_do_POST_day_todo_adoption(self) -> None:
+        """Test Todos posted to Day view may adopt existing Todos."""
+        form_data = {'title': '', 'description': '', 'effort': 1}
+        self.check_post(form_data, '/process', 302, '/')
+        form_data['new_top_step'] = 1
+        self.check_post(form_data, '/process', 302, '/')
+        form_data = {'comment': '', 'new_todo': 1}
+        self.check_post(form_data, '/day?date=2024-01-01', 302)
+        form_data = {'comment': '', 'new_todo': 2}
+        self.check_post(form_data, '/day?date=2024-01-01', 302)
+        todo1 = Todo.by_date(self.db_conn, '2024-01-01')[0]
+        todo2 = Todo.by_date(self.db_conn, '2024-01-01')[1]
+        self.assertEqual(todo1.children, [])
+        self.assertEqual(todo1.parents, [todo2])
+        self.assertEqual(todo2.children, [todo1])
+        self.assertEqual(todo2.parents, [])
 
     def test_do_GET_todo(self) -> None:
         """Test GET /todo response codes."""
 
     def test_do_GET_todo(self) -> None:
         """Test GET /todo response codes."""
@@ -277,8 +335,8 @@ class TestsWithServer(TestCaseWithServer):
         self.check_post(form_data, '/process?id=', 302, '/')
         form_data = {'comment': '', 'new_todo': 1}
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         self.check_post(form_data, '/process?id=', 302, '/')
         form_data = {'comment': '', 'new_todo': 1}
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
-        self.check_get('/todo', 404)
-        self.check_get('/todo?id=', 404)
+        self.check_get('/todo', 400)
+        self.check_get('/todo?id=', 400)
         self.check_get('/todo?id=foo', 400)
         self.check_get('/todo?id=0', 404)
         self.check_get('/todo?id=1', 200)
         self.check_get('/todo?id=foo', 400)
         self.check_get('/todo?id=0', 404)
         self.check_get('/todo?id=1', 200)