--- /dev/null
+"""Actionables."""
+from __future__ import annotations
+from sqlite3 import Row
+from plomtask.db import DatabaseConnection
+from plomtask.days import Day
+from plomtask.processes import Process
+from plomtask.exceptions import NotFoundException
+
+
+class Todo:
+ """Individual actionable."""
+
+ def __init__(self, id_: int | None, process: Process,
+ is_done: bool, day: Day) -> None:
+ self.id_ = id_
+ self.process = process
+ self.is_done = is_done
+ self.day = day
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, self.__class__) and self.id_ == other.id_
+
+ @classmethod
+ def from_table_row(cls, row: Row, db_conn: DatabaseConnection) -> Todo:
+ """Make Todo from database row."""
+ return cls(id_=row[0],
+ process=Process.by_id(db_conn, row[1]),
+ is_done=row[2],
+ day=Day.by_date(db_conn, row[3]))
+
+ @classmethod
+ def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]:
+ """Collect all Todos for Day of date."""
+ todos = []
+ for row in db_conn.exec('SELECT * FROM todos WHERE day = ?', (date,)):
+ todos += [cls.from_table_row(row, db_conn)]
+ return todos
+
+ def save(self, db_conn: DatabaseConnection) -> None:
+ """Write self to DB."""
+ if self.process.id_ is None:
+ raise NotFoundException('Process of Todo without ID (not saved?)')
+ cursor = db_conn.exec('REPLACE INTO todos VALUES (?,?,?,?)',
+ (self.id_, self.process.id_,
+ self.is_done, self.day.date))
+ self.id_ = cursor.lastrowid
--- /dev/null
+"""Test Todos module."""
+from tests.utils import TestCaseWithDB, TestCaseWithServer
+from plomtask.todos import Todo
+from plomtask.days import Day
+from plomtask.processes import Process
+from plomtask.exceptions import NotFoundException
+
+
+class TestsWithDB(TestCaseWithDB):
+ """Tests not requiring DB setup."""
+
+ def test_Todo_by_date(self) -> None:
+ """Test creation and findability of Todos."""
+ day1 = Day('2024-01-01')
+ day2 = Day('2024-01-02')
+ process1 = Process(None)
+ todo1 = Todo(None, process1, False, day1)
+ with self.assertRaises(NotFoundException):
+ todo1.save(self.db_conn)
+ process1.save_without_steps(self.db_conn)
+ todo1.save(self.db_conn)
+ todo2 = Todo(None, process1, False, day1)
+ todo2.save(self.db_conn)
+ with self.assertRaises(NotFoundException):
+ Todo.by_date(self.db_conn, day1.date),
+ day1.save(self.db_conn)
+ day2.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), [])
+ self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
+
+
+class TestsWithServer(TestCaseWithServer):
+ """Tests against our HTTP server/handler (and database)."""
+
+ def test_do_POST_todo(self) -> None:
+ """Test Todo posting of POST /day."""
+ 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)
+ 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_)
+ 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.is_done, False)
+ form_data['new_todo'] = str(process2.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.assertEqual(todo1.process.id_, process2.id_)
+ self.assertEqual(todo1.is_done, False)