From: Christian Heller Date: Thu, 2 May 2024 00:13:36 +0000 (+0200) Subject: Improve Condition tests and do minor fixes on the way. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/static/add_task?a=commitdiff_plain;h=9ad40c43627334d7294c07bf55d196dd6760cfde;p=plomtask Improve Condition tests and do minor fixes on the way. --- diff --git a/plomtask/conditions.py b/plomtask/conditions.py index 539db9c..629510a 100644 --- a/plomtask/conditions.py +++ b/plomtask/conditions.py @@ -19,6 +19,11 @@ class Condition(BaseModel[int]): self.description = VersionedAttribute(self, 'condition_descriptions', '') + def __lt__(self, other: Condition) -> bool: + assert isinstance(self.id_, int) + assert isinstance(other.id_, int) + return self.id_ < other.id_ + @classmethod def from_table_row(cls, db_conn: DatabaseConnection, row: Row | list[Any]) -> Condition: @@ -37,8 +42,13 @@ class Condition(BaseModel[int]): self.description.save(db_conn) def remove(self, db_conn: DatabaseConnection) -> None: - """Remove from DB, with dependencies.""" - assert isinstance(self.id_, int) + """Remove from DB, with VersionedAttributes. + + Checks for Todos and Processes that depend on Condition, prohibits + deletion if found. + """ + if self.id_ is None: + raise HandledException('cannot remove unsaved item') for item in ('process', 'todo'): for attr in ('conditions', 'enables', 'disables'): table_name = f'{item}_{attr}' diff --git a/tests/conditions.py b/tests/conditions.py index 40d7c48..51f7cc2 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -1,57 +1,140 @@ """Test Conditions module.""" +from unittest import TestCase from tests.utils import TestCaseWithDB, TestCaseWithServer from plomtask.conditions import Condition from plomtask.processes import Process +from plomtask.todos import Todo from plomtask.exceptions import NotFoundException, HandledException +class TestsSansDB(TestCase): + """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) + + class TestsWithDB(TestCaseWithDB): """Tests requiring DB, but not server setup.""" + def check_storage(self, content: list[Condition]) -> None: + """Test cache and DB equal content.""" + expected_cache = {} + for item in content: + expected_cache[item.id_] = item + self.assertEqual(Condition.get_cache(), expected_cache) + db_found: list[Condition] = [] + for item in content: + assert isinstance(item.id_, int) + for row in self.db_conn.row_where(Condition.table_name, 'id', + item.id_): + db_found += [Condition.from_table_row(self.db_conn, row)] + self.assertEqual(sorted(content), sorted(db_found)) + + def test_Condition_saving_and_caching(self) -> None: + """Test .save/.save_core.""" + c = Condition(None, False) + c.title.set('title1') + c.title.set('title2') + c.description.set('desc1') + c.description.set('desc2') + # check object init itself doesn't store anything yet + self.check_storage([]) + # check saving stores in cache and DB + c.save(self.db_conn) + self.check_storage([c]) + # check attributes set properly (and not unset by saving) + self.assertEqual(c.id_, 1) + self.assertEqual(c.is_active, False) + self.assertEqual(sorted(c.title.history.values()), + ['title1', 'title2']) + self.assertEqual(sorted(c.description.history.values()), + ['desc1', 'desc2']) + + def test_Condition_from_table_row(self) -> None: + """Test .from_table_row() properly reads in class from DB""" + c = Condition(1, True) + c.title.set('title1') + c.title.set('title2') + c.description.set('desc1') + c.description.set('desc2') + 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) + assert isinstance(retrieved, Condition) + self.assertEqual(c, retrieved) + self.assertEqual({c.id_: c}, Condition.get_cache()) + # 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']) + def test_Condition_by_id(self) -> None: - """Test creation and findability.""" - condition = Condition(None, False) - condition.save(self.db_conn) - self.assertEqual(Condition.by_id(self.db_conn, 1), condition) - with self.assertRaises(NotFoundException): - self.assertEqual(Condition.by_id(self.db_conn, 0), condition) + """Test .by_id(), including creation.""" + # check failure if not yet saved + c = Condition(3, False) with self.assertRaises(NotFoundException): - self.assertEqual(Condition.by_id(self.db_conn, 2), condition) + Condition.by_id(self.db_conn, 3) + # check identity of saved and retrieved + c.save(self.db_conn) + self.assertEqual(c, Condition.by_id(self.db_conn, 3)) + # check create=True acts like normal instantiation (sans saving) + by_id_created = Condition.by_id(self.db_conn, 4, create=True) + self.assertEqual(Condition(4), by_id_created) + self.check_storage([c]) def test_Condition_all(self) -> None: """Test .all().""" + c_1 = Condition(None, False) + # check pre-save .all() returns empty list self.assertEqual(Condition.all(self.db_conn), []) - condition_1 = Condition(None, False) - condition_1.save(self.db_conn) - self.assertEqual(Condition.all(self.db_conn), [condition_1]) - condition_2 = Condition(None, False) - condition_2.save(self.db_conn) - self.assertEqual(Condition.all(self.db_conn), [condition_1, - condition_2]) + # check .save() fills .all() result + c_1.save(self.db_conn) + self.assertEqual(Condition.all(self.db_conn), [c_1]) + c_2 = Condition(None, True) + c_2.save(self.db_conn) + self.assertEqual(sorted(Condition.all(self.db_conn)), + sorted([c_1, c_2])) def test_Condition_singularity(self) -> None: """Test pointers made for single object keep pointing to it.""" - condition_1 = Condition(None, False) - condition_1.save(self.db_conn) - condition_1.is_active = True - condition_retrieved = Condition.by_id(self.db_conn, 1) - self.assertEqual(True, condition_retrieved.is_active) - - def test_Condition_removal(self) -> None: - """Test removal of Condition.""" - cond = Condition(None, False) - cond.save(self.db_conn) - assert isinstance(cond.id_, int) - proc = Process(None) - proc.save(self.db_conn) - proc.set_conditions(self.db_conn, [cond.id_], 'conditions') - proc.save(self.db_conn) + c = Condition(None, False) + c.save(self.db_conn) + c.is_active = True + retrieved = Condition.by_id(self.db_conn, 1) + self.assertEqual(True, retrieved.is_active) + + def test_Condition_remove(self) -> None: + """Test .remove() effects on DB and cache.""" + # check only saved item can be removed + c = Condition(None, False) with self.assertRaises(HandledException): - cond.remove(self.db_conn) - proc.set_conditions(self.db_conn, [], 'conditions') - proc.save(self.db_conn) - cond.remove(self.db_conn) - self.assertEqual(Condition.all(self.db_conn), []) + c.remove(self.db_conn) + c.save(self.db_conn) + c.remove(self.db_conn) + self.check_storage([]) + # check guard against deleting dependencies of other classes + proc = Process(None) + todo = Todo(None, proc, False, '2024-01-01') + for depender in (proc, todo): + assert hasattr(depender, 'save') + assert hasattr(depender, 'set_conditions') + c.save(self.db_conn) + depender.save(self.db_conn) + depender.set_conditions(self.db_conn, [c.id_], 'conditions') + depender.save(self.db_conn) + with self.assertRaises(HandledException): + c.remove(self.db_conn) + depender.set_conditions(self.db_conn, [], 'conditions') + depender.save(self.db_conn) + c.remove(self.db_conn) class TestsWithServer(TestCaseWithServer):