add assignee to budget_graph.Node
authorJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Sep 2020 03:18:00 +0000 (20:18 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Fri, 18 Sep 2020 03:18:00 +0000 (20:18 -0700)
src/budget_sync/budget_graph.py
src/budget_sync/test/mock_bug.py
src/budget_sync/test/test_budget_graph.py
src/budget_sync/test/test_mock_bug.py
src/budget_sync/test/test_write_budget_markdown.py

index 322b240..074447d 100644 (file)
@@ -30,6 +30,16 @@ class BudgetGraphPayeesParseError(BudgetGraphParseError):
             f"bug #{self.bug_id}: {self.msg}"
 
 
+class BudgetGraphUnknownAssignee(BudgetGraphParseError):
+    def __init__(self, bug_id: int, assignee: str):
+        super().__init__(bug_id)
+        self.assignee = assignee
+
+    def __str__(self):
+        return f"Bug #{self.bug_id} is assigned to an unknown person: " \
+            f"{self.assignee!r}"
+
+
 class BudgetGraphLoopError(BudgetGraphBaseError):
     def __init__(self, bug_ids: List[int]):
         self.bug_ids = bug_ids
@@ -253,6 +263,15 @@ class Node:
             new_err = BudgetGraphUnknownStatus(self.bug.id, self.bug.status)
             raise new_err.with_traceback(sys.exc_info()[2])
 
+    @cached_property
+    def assignee(self) -> Person:
+        try:
+            return self.graph.config.all_names[self.bug.assigned_to]
+        except KeyError:
+            raise BudgetGraphUnknownAssignee(self.bug.id,
+                                             self.bug.assigned_to) \
+                .with_traceback(sys.exc_info()[2])
+
     @cached_property
     def bug_url(self) -> str:
         return f"{self.graph.config.bugzilla_url_stripped}/show_bug.cgi?" \
@@ -351,6 +370,10 @@ class Node:
             status = repr(self.status)
         except BudgetGraphBaseError:
             status = f"<unknown status: {self.bug.status!r}>"
+        try:
+            assignee = f"Person<{self.assignee.identifier!r}>"
+        except BudgetGraphBaseError:
+            assignee = f"<unknown assignee: {self.bug.assigned_to!r}>"
         immediate_children = []
         for i in self.immediate_children:
             immediate_children.append(_NodeSimpleReprWrapper(i))
@@ -369,7 +392,8 @@ class Node:
                 f"milestone={milestone}, "
                 f"immediate_children={immediate_children!r}, "
                 f"payments={payments!r}, "
-                f"status={status})")
+                f"status={status}, "
+                f"assignee={assignee})")
 
 
 class BudgetGraphError(BudgetGraphBaseError):
@@ -525,6 +549,12 @@ class BudgetGraph:
         except BudgetGraphBaseError as e:
             errors.append(e)
 
+        try:
+            # check for assignee errors
+            node.assignee
+        except BudgetGraphBaseError as e:
+            errors.append(e)
+
         if node.milestone_str != root.milestone_str:
             errors.append(BudgetGraphMilestoneMismatch(
                 node.bug.id, root.bug.id))
index e63ab46..17be594 100644 (file)
@@ -11,7 +11,8 @@ class MockBug:
                  cf_nlnet_milestone: Optional[str] = None,
                  cf_payees_list: str = "",
                  summary: str = "<default summary>",
-                 status: Union[str, BugStatus] = BugStatus.CONFIRMED):
+                 status: Union[str, BugStatus] = BugStatus.CONFIRMED,
+                 assigned_to: str = "user@example.com"):
         self.id = bug_id
         self.__budget_parent = cf_budget_parent
         self.cf_budget = cf_budget
@@ -22,6 +23,7 @@ class MockBug:
         self.cf_payees_list = cf_payees_list
         self.summary = summary
         self.status = str(status)
+        self.assigned_to = assigned_to
 
     @property
     def cf_budget_parent(self) -> int:
@@ -52,4 +54,5 @@ class MockBug:
                 f"cf_nlnet_milestone={self.cf_nlnet_milestone!r}, "
                 f"cf_payees_list={self.cf_payees_list!r}, "
                 f"summary={self.summary!r}, "
-                f"status={status!r})")
+                f"status={status!r}, "
+                f"assigned_to={self.assigned_to!r})")
index 60ce3d0..9a71119 100644 (file)
@@ -8,7 +8,7 @@ from budget_sync.budget_graph import (
     BudgetGraphNegativePayeeMoney, BudgetGraphPayeesParseError,
     BudgetGraphPayeesMoneyMismatch, BudgetGraphUnknownMilestone,
     BudgetGraphDuplicatePayeesForTask, BudgetGraphIncorrectRootForMilestone,
-    BudgetGraphUnknownStatus)
+    BudgetGraphUnknownStatus, BudgetGraphUnknownAssignee)
 from budget_sync.money import Money
 from budget_sync.util import BugStatus
 from typing import List, Type
@@ -60,6 +60,12 @@ class TestErrorFormatting(unittest.TestCase):
             "failed to parse status field of bug "
             "#123: unknown status: 'fake status'")
 
+    def test_budget_graph_unknown_assignee(self):
+        self.assertEqual(str(BudgetGraphUnknownAssignee(
+            123, "unknown@example.com")),
+            "Bug #123 is assigned to an unknown person:"
+            " 'unknown@example.com'")
+
     def test_budget_graph_money_mismatch(self):
         self.assertEqual(str(
             BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
@@ -146,9 +152,11 @@ EXAMPLE_CONFIG = Config.from_str(
     aliases = ["person1_alias1", "alias1"]
     output_markdown_file = "person1.mdwn"
     [people."person2"]
+    email = "person2@example.com"
     aliases = ["person1_alias2", "alias2", "person 2"]
     output_markdown_file = "person2.mdwn"
     [people."person3"]
+    email = "user@example.com"
     output_markdown_file = "person3.mdwn"
     [milestones]
     "milestone 1" = { canonical_bug_id = 1 }
@@ -188,7 +196,8 @@ class TestBudgetGraph(unittest.TestCase):
             "milestone_str='milestone 1', milestone=Milestone(config=..., "
             "identifier='milestone 1', canonical_bug_id=1), "
             "immediate_children=[#2], payments=[], "
-            "status=BugStatus.CONFIRMED), Node(graph=..., id=#2, root=#1, "
+            "status=BugStatus.CONFIRMED, assignee=Person<'person3'>), "
+            "Node(graph=..., id=#2, root=#1, "
             "parent=#1, budget_excluding_subtasks=10, "
             "budget_including_subtasks=10, "
             "fixed_budget_excluding_subtasks=10, "
@@ -196,8 +205,10 @@ class TestBudgetGraph(unittest.TestCase):
             "milestone_str='milestone 1', milestone=Milestone(config=..., "
             "identifier='milestone 1', canonical_bug_id=1), "
             "immediate_children=[], payments=[], "
-            "status=BugStatus.CONFIRMED)], roots=[#1]}")
-        bg = BudgetGraph([MockBug(bug_id=1, status="blah")],
+            "status=BugStatus.CONFIRMED, assignee=Person<'person3'>)], "
+            "roots=[#1]}")
+        bg = BudgetGraph([MockBug(bug_id=1, status="blah",
+                                  assigned_to="unknown@example.com")],
                          EXAMPLE_CONFIG)
         self.assertEqual(
             repr(bg),
@@ -206,7 +217,9 @@ class TestBudgetGraph(unittest.TestCase):
             "fixed_budget_excluding_subtasks=0, "
             "fixed_budget_including_subtasks=0, milestone_str=None, "
             "milestone=None, immediate_children=[], payments=[], "
-            "status=<unknown status: 'blah'>)], roots=[#1]}")
+            "status=<unknown status: 'blah'>, "
+            "assignee=<unknown assignee: 'unknown@example.com'>)], "
+            "roots=[#1]}")
 
     def test_empty(self):
         bg = BudgetGraph([], EXAMPLE_CONFIG)
@@ -1243,6 +1256,21 @@ class TestBudgetGraph(unittest.TestCase):
             self.assertErrorTypesMatches(bg.get_errors(), [])
             self.assertEqual(bg.nodes[1].status, status)
 
+    def test_assignee(self):
+        bg = BudgetGraph([MockBug(bug_id=1, assigned_to="blah")],
+                         EXAMPLE_CONFIG)
+        errors = bg.get_errors()
+        self.assertErrorTypesMatches(errors,
+                                     [BudgetGraphUnknownAssignee])
+        self.assertEqual(errors[0].bug_id, 1)
+        self.assertEqual(errors[0].assignee, "blah")
+        bg = BudgetGraph([MockBug(bug_id=1,
+                                  assigned_to="person2@example.com")],
+                         EXAMPLE_CONFIG)
+        self.assertErrorTypesMatches(bg.get_errors(), [])
+        self.assertEqual(bg.nodes[1].assignee,
+                         EXAMPLE_CONFIG.people["person2"])
+
 
 if __name__ == "__main__":
     unittest.main()
index fd3624f..435280a 100644 (file)
@@ -31,7 +31,8 @@ class TestMockBug(unittest.TestCase):
             "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)")
+            "status=BugStatus.CONFIRMED, "
+            "assigned_to='user@example.com')")
         bug = MockBug(bug_id=34,
                       cf_budget_parent=1,
                       cf_budget="45",
@@ -39,12 +40,14 @@ class TestMockBug(unittest.TestCase):
                       cf_nlnet_milestone="abc",
                       cf_payees_list="# a",
                       summary="blah blah",
-                      status="blah")
+                      status="blah",
+                      assigned_to="fake-email@example.com")
         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')")
+            "cf_payees_list='# a', summary='blah blah', status='blah', "
+            "assigned_to='fake-email@example.com')")
 
     def test_cf_budget_parent(self):
         bug = MockBug(bug_id=1, cf_budget_parent=None)
index 8ff5956..1691edc 100644 (file)
@@ -14,6 +14,7 @@ class TestWriteBudgetMarkdown(unittest.TestCase):
             bugzilla_url = "https://bugzilla.example.com/"
             [milestones]
             [people."person1"]
+            email = "person1@example.com"
             output_markdown_file = "person1.mdwn"
             [people."person2"]
             output_markdown_file = "person2.mdwn"
@@ -25,7 +26,8 @@ class TestWriteBudgetMarkdown(unittest.TestCase):
                     cf_total_budget="0",
                     cf_nlnet_milestone=None,
                     cf_payees_list="",
-                    summary=""),
+                    summary="",
+                    assigned_to="person1@example.com"),
         ], config)
         self.assertEqual([], budget_graph.get_errors())
         with make_filesystem_and_report_if_error(self) as filesystem: