refactor MockBug in preperation for adding `status` field to `Node`
authorJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Sep 2020 01:41:07 +0000 (18:41 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Sep 2020 01:41:07 +0000 (18:41 -0700)
src/budget_sync/test/mock_bug.py
src/budget_sync/test/test_mock_bug.py [new file with mode: 0644]
src/budget_sync/util.py

index acc2cae..e63ab46 100644 (file)
@@ -1,18 +1,19 @@
-from typing import Optional
+from typing import Optional, Union
+from budget_sync.util import BugStatus
 
 
 class MockBug:
     def __init__(self,
                  bug_id: int,
-                 cf_budget_parent: Optional[int],
-                 cf_budget: str,
-                 cf_total_budget: str,
-                 cf_nlnet_milestone: Optional[str],
-                 cf_payees_list: str,
-                 summary: str):
+                 cf_budget_parent: Optional[int] = None,
+                 cf_budget: str = "0",
+                 cf_total_budget: str = "0",
+                 cf_nlnet_milestone: Optional[str] = None,
+                 cf_payees_list: str = "",
+                 summary: str = "<default summary>",
+                 status: Union[str, BugStatus] = BugStatus.CONFIRMED):
         self.id = bug_id
-        if cf_budget_parent is not None:
-            self.cf_budget_parent = cf_budget_parent
+        self.__budget_parent = cf_budget_parent
         self.cf_budget = cf_budget
         self.cf_total_budget = cf_total_budget
         if cf_nlnet_milestone is None:
@@ -20,13 +21,35 @@ class MockBug:
         self.cf_nlnet_milestone = cf_nlnet_milestone
         self.cf_payees_list = cf_payees_list
         self.summary = summary
+        self.status = str(status)
+
+    @property
+    def cf_budget_parent(self) -> int:
+        if self.__budget_parent is None:
+            raise AttributeError(
+                "'MockBug' object has no attribute 'cf_budget_parent'")
+        return self.__budget_parent
+
+    @cf_budget_parent.setter
+    def cf_budget_parent(self, value: int):
+        if isinstance(value, int):
+            self.__budget_parent = value
+        else:
+            raise TypeError("cf_budget_parent must be an int")
+
+    @cf_budget_parent.deleter
+    def cf_budget_parent(self):
+        self.cf_budget_parent  # trigger AttributeError if property cleared
+        self.__budget_parent = None
 
     def __repr__(self):
         cf_budget_parent = getattr(self, "cf_budget_parent", None)
+        status = BugStatus.cast(self.status, unknown_allowed=True)
         return (f"MockBug(bug_id={self.id!r}, "
                 f"cf_budget_parent={cf_budget_parent!r}, "
                 f"cf_budget={self.cf_budget!r}, "
                 f"cf_total_budget={self.cf_total_budget!r}, "
                 f"cf_nlnet_milestone={self.cf_nlnet_milestone!r}, "
                 f"cf_payees_list={self.cf_payees_list!r}, "
-                f"summary={self.summary!r})")
+                f"summary={self.summary!r}, "
+                f"status={status!r})")
diff --git a/src/budget_sync/test/test_mock_bug.py b/src/budget_sync/test/test_mock_bug.py
new file mode 100644 (file)
index 0000000..fd3624f
--- /dev/null
@@ -0,0 +1,72 @@
+import unittest
+from budget_sync.test.mock_bug import MockBug
+from budget_sync.util import BugStatus
+
+
+class TestBugStatus(unittest.TestCase):
+    def test_values(self):
+        for i in BugStatus:
+            self.assertIs(i, getattr(BugStatus, i.value))
+            self.assertEqual(str(i), i.value)
+            self.assertEqual(repr(i), f"BugStatus.{i.value}")
+
+    def test_cast(self):
+        for i in BugStatus:
+            self.assertEqual(i, BugStatus.cast(i))
+            self.assertEqual(i, BugStatus.cast(str(i)))
+            self.assertEqual(i, BugStatus.cast(str(i), unknown_allowed=True))
+        with self.assertRaises(ValueError):
+            BugStatus.cast("<unknown>")
+        self.assertEqual("<unknown>",
+                         BugStatus.cast("<unknown>", unknown_allowed=True))
+
+
+class TestMockBug(unittest.TestCase):
+    maxDiff = None
+
+    def test_repr(self):
+        bug = MockBug(bug_id=12)
+        self.assertEqual(
+            repr(bug),
+            "MockBug(bug_id=12, cf_budget_parent=None, cf_budget='0', "
+            "cf_total_budget='0', cf_nlnet_milestone='---', "
+            "cf_payees_list='', summary='<default summary>', "
+            "status=BugStatus.CONFIRMED)")
+        bug = MockBug(bug_id=34,
+                      cf_budget_parent=1,
+                      cf_budget="45",
+                      cf_total_budget="23",
+                      cf_nlnet_milestone="abc",
+                      cf_payees_list="# a",
+                      summary="blah blah",
+                      status="blah")
+        self.assertEqual(
+            repr(bug),
+            "MockBug(bug_id=34, cf_budget_parent=1, cf_budget='45', "
+            "cf_total_budget='23', cf_nlnet_milestone='abc', "
+            "cf_payees_list='# a', summary='blah blah', status='blah')")
+
+    def test_cf_budget_parent(self):
+        bug = MockBug(bug_id=1, cf_budget_parent=None)
+        with self.assertRaises(AttributeError):
+            bug.cf_budget_parent
+        self.assertIsNone(getattr(bug, "cf_budget_parent", None))
+        bug.cf_budget_parent = 1
+        self.assertEqual(bug.cf_budget_parent, 1)
+        with self.assertRaises(TypeError):
+            bug.cf_budget_parent = "abc"
+        del bug.cf_budget_parent
+        with self.assertRaises(AttributeError):
+            bug.cf_budget_parent
+        with self.assertRaises(AttributeError):
+            del bug.cf_budget_parent
+        with self.assertRaises(AttributeError):
+            bug.cf_budget_parent
+        bug.cf_budget_parent = 5
+        self.assertEqual(bug.cf_budget_parent, 5)
+        bug = MockBug(bug_id=1, cf_budget_parent=2)
+        self.assertEqual(bug.cf_budget_parent, 2)
+
+
+if __name__ == "__main__":
+    unittest.main()
index ed1c300..20e7da6 100644 (file)
@@ -1,6 +1,34 @@
 from bugzilla import Bugzilla
 from bugzilla.bug import Bug
-from typing import Iterator
+from typing import Iterator, Union
+from enum import Enum
+
+
+class BugStatus(Enum):
+    UNCONFIRMED = "UNCONFIRMED"
+    CONFIRMED = "CONFIRMED"
+    IN_PROGRESS = "IN_PROGRESS"
+    DEFERRED = "DEFERRED"
+    RESOLVED = "RESOLVED"
+    VERIFIED = "VERIFIED"
+    PAYMENTPENDING = "PAYMENTPENDING"
+
+    def __str__(self):
+        return self.value
+
+    def __repr__(self):
+        return f"BugStatus.{self.value}"
+
+    @staticmethod
+    def cast(v: Union[str, "BugStatus"],
+             unknown_allowed: bool = False) -> Union[str, "BugStatus"]:
+        s = str(v)
+        try:
+            return BugStatus(s)
+        except ValueError:
+            if unknown_allowed:
+                return s
+            raise
 
 
 def all_bugs(bz: Bugzilla) -> Iterator[Bug]: