From fa6409dc7af064ba3c868cb3cf0f0cfa3c92bcc5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 9 Sep 2020 17:44:14 -0700 Subject: [PATCH] budget-sync is now working after adding code to use a config file --- budget-sync-config.toml | 32 ++ src/budget_sync/budget_graph.py | 128 ++++- src/budget_sync/config.py | 6 + src/budget_sync/main.py | 22 +- src/budget_sync/test/test_budget_graph.py | 618 ++++++++++++++-------- 5 files changed, 583 insertions(+), 223 deletions(-) create mode 100644 budget-sync-config.toml diff --git a/budget-sync-config.toml b/budget-sync-config.toml new file mode 100644 index 0000000..60ae440 --- /dev/null +++ b/budget-sync-config.toml @@ -0,0 +1,32 @@ +bugzilla_url = "https://bugs.libre-soc.org" + +[people."Jacob R. Lifshay"] +email = "programmerjake@gmail.com" +aliases = ["programmerjake", "jacob", "Jacob", "Jacob Lifshay"] + +[people."Luke Kenneth Casson Leighton"] +email = "lkcl@lkcl.net" +aliases = ["lkcl", "luke", "Luke", "Luke Leighton"] + +[people."Samuel A. Falvo II"] +email = "kc5tja@arrl.net" +aliases = ["kc5tja", "samuel", "Samuel", "Samuel Falvo II", "sam.falvo"] + +[people."Vivek Pandya"] +email = "vivekvpandya@gmail.com" +aliases = ["vivekvpandya", "vivek pandya", "vivek", "Vivek"] + +[people."Florent Kermarrec"] +email = "florent@enjoy-digital.fr" +aliases = ["florent", "Florent"] + +[milestones] +"NLnet.2019.02" = { canonical_bug_id = 191 } +"NLnet.2019.10.Cells" = { canonical_bug_id = 153 } +"NLNet.2019.10.Formal" = { canonical_bug_id = 158 } +"NLNet.2019.10.Standards" = { canonical_bug_id = 174 } +"NLNet.2019.10.Wishbone" = { canonical_bug_id = 175 } +"NLNet.2019.Coriolis2" = { canonical_bug_id = 138 } +"NLNet.2019.Video" = { canonical_bug_id = 137 } +"NLNet.2019.Vulkan" = { canonical_bug_id = 140 } +"Future" = { canonical_bug_id = 487 } \ No newline at end of file diff --git a/src/budget_sync/budget_graph.py b/src/budget_sync/budget_graph.py index dd59fd4..c6b5272 100644 --- a/src/budget_sync/budget_graph.py +++ b/src/budget_sync/budget_graph.py @@ -3,6 +3,7 @@ from bugzilla import Bugzilla from typing import Set, Dict, Iterable, Optional, List, Union, Any from budget_sync.util import all_bugs from budget_sync.money import Money +from budget_sync.config import Config, Person, Milestone from functools import cached_property import toml import sys @@ -26,7 +27,8 @@ class BudgetGraphPayeesParseError(BudgetGraphParseError): self.msg = msg def __str__(self): - return f"Failed to parse cf_payees_list field of bug #{self.bug_id}: {self.msg}" + return f"Failed to parse cf_payees_list field of " \ + f"bug #{self.bug_id}: {self.msg}" class BudgetGraphLoopError(BudgetGraphBaseError): @@ -100,6 +102,16 @@ class Payment: self.paid = paid self.submitted = submitted + @cached_property + def payee(self) -> Person: + try: + return self.node.graph.config.all_names[self.payee_key] + except KeyError: + msg = f"unknown payee name: {self.payee_key!r} is not the name " \ + f"or an alias of any known person" + raise BudgetGraphPayeesParseError(self.node.bug.id, msg) \ + .with_traceback(sys.exc_info()[2]) + @property def state(self): if self.paid is not None: @@ -177,7 +189,12 @@ class Payment: paid=paid, submitted=submitted) def __repr__(self): + try: + payee = f"Person<{self.payee.identifier!r}>" + except BudgetGraphBaseError: + payee = "" return (f"Payment(node={_NodeSimpleReprWrapper(self.node)}, " + f"payee={payee}, " f"payee_key={self.payee_key!r}, " f"amount={self.amount}, " f"state={self.state.name}, " @@ -185,6 +202,16 @@ class Payment: f"submitted={str(self.submitted)})") +class BudgetGraphUnknownMilestone(BudgetGraphParseError): + def __init__(self, bug_id: int, milestone_str: str): + super().__init__(bug_id) + self.milestone_str = milestone_str + + def __str__(self): + return f"failed to parse cf_nlnet_milestone field of bug " \ + f"#{self.bug_id}: unknown milestone: {self.milestone_str!r}" + + class Node: graph: "BudgetGraph" bug: Bug @@ -194,7 +221,7 @@ class Node: budget_including_subtasks: Money fixed_budget_excluding_subtasks: Money fixed_budget_including_subtasks: Money - nlnet_milestone: Optional[str] + milestone_str: Optional[str] def __init__(self, graph: "BudgetGraph", bug: Bug): self.graph = graph @@ -205,9 +232,20 @@ class Node: self.fixed_budget_excluding_subtasks = self.budget_excluding_subtasks self.budget_including_subtasks = Money.from_str(bug.cf_total_budget) self.fixed_budget_including_subtasks = self.budget_including_subtasks - self.nlnet_milestone = bug.cf_nlnet_milestone - if self.nlnet_milestone == "---": - self.nlnet_milestone = None + self.milestone_str = bug.cf_nlnet_milestone + if self.milestone_str == "---": + self.milestone_str = None + + @cached_property + def milestone(self) -> Optional[Milestone]: + try: + if self.milestone_str is not None: + return self.graph.config.milestones[self.milestone_str] + return None + except KeyError: + new_err = BudgetGraphUnknownMilestone( + self.bug.id, self.milestone_str) + raise new_err.with_traceback(sys.exc_info()[2]) @cached_property def payments(self) -> Dict[str, Payment]: @@ -283,6 +321,10 @@ class Node: root = _NodeSimpleReprWrapper(self.root) except BudgetGraphLoopError: root = "" + try: + milestone = repr(self.milestone) + except BudgetGraphBaseError: + milestone = "" immediate_children = [] for i in self.immediate_children: immediate_children.append(_NodeSimpleReprWrapper(i)) @@ -297,7 +339,8 @@ class Node: f"budget_including_subtasks={self.budget_including_subtasks}, " f"fixed_budget_excluding_subtasks={self.fixed_budget_excluding_subtasks}, " f"fixed_budget_including_subtasks={self.fixed_budget_including_subtasks}, " - f"nlnet_milestone={self.nlnet_milestone!r}, " + f"milestone_str={self.milestone_str!r}, " + f"milestone={milestone}, " f"immediate_children={immediate_children!r}, " f"payments={payments!r}") @@ -380,11 +423,37 @@ class BudgetGraphNegativePayeeMoney(BudgetGraphError): f"bug #{self.bug_id}, payee {self.payee_key!r}") +class BudgetGraphDuplicatePayeesForTask(BudgetGraphError): + def __init__(self, bug_id: int, root_bug_id: int, payee1_key: str, payee2_key: str): + super().__init__(bug_id, root_bug_id) + self.payee1_key = payee1_key + self.payee2_key = payee2_key + + def __str__(self): + return (f"Budget assigned to multiple aliases of the same person in " + f"a single task: bug #{self.bug_id}, budget assigned to both " + f"{self.payee1_key!r} and {self.payee2_key!r}") + + +class BudgetGraphIncorrectRootForMilestone(BudgetGraphError): + def __init__(self, bug_id: int, milestone: str, milestone_canonical_bug_id: int): + super().__init__(bug_id, bug_id) + self.milestone = milestone + self.milestone_canonical_bug_id = milestone_canonical_bug_id + + def __str__(self): + return (f"Bug #{self.bug_id} is not the canonical root bug for " + f"assigned milestone {self.milestone!r} but has no parent " + f"bug set: the milestone's canonical root bug is " + f"#{self.milestone_canonical_bug_id}") + + class BudgetGraph: nodes: Dict[int, Node] - def __init__(self, bugs: Iterable[Bug]): + def __init__(self, bugs: Iterable[Bug], config: Config): self.nodes = {} + self.config = config for bug in bugs: self.nodes[bug.id] = Node(self, bug) for node in self.nodes.values(): @@ -403,13 +472,27 @@ class BudgetGraph: def _get_node_errors(self, root: Node, node: Node, errors: List[BudgetGraphBaseError]): - if node.nlnet_milestone is None: + if node.milestone_str is None: if node.budget_including_subtasks != 0 \ or node.budget_excluding_subtasks != 0: errors.append(BudgetGraphMoneyWithNoMilestone( node.bug.id, root.bug.id)) - if node.nlnet_milestone != root.nlnet_milestone: + try: + # check for milestone errors + node.milestone + if root == node and node.milestone is not None \ + and node.milestone.canonical_bug_id != node.bug.id: + if node.budget_including_subtasks != 0 \ + or node.budget_excluding_subtasks != 0: + errors.append(BudgetGraphIncorrectRootForMilestone( + node.bug.id, node.milestone.identifier, + node.milestone.canonical_bug_id + )) + except BudgetGraphBaseError as e: + errors.append(e) + + if node.milestone_str != root.milestone_str: errors.append(BudgetGraphMilestoneMismatch( node.bug.id, root.bug.id)) @@ -423,11 +506,24 @@ class BudgetGraph: subtasks_total += child.fixed_budget_including_subtasks payees_total = Money(0) + payee_payments = {} for payment in node.payments.values(): if payment.amount < 0: errors.append(BudgetGraphNegativePayeeMoney( node.bug.id, root.bug.id, payment.payee_key)) payees_total += payment.amount + try: + # check for payee errors + payment.payee + previous_payment = payee_payments.get(payment.payee) + if previous_payment is not None: + errors.append(BudgetGraphDuplicatePayeesForTask( + node.bug.id, root.bug.id, + previous_payment.payee_key, payment.payee_key + )) + payee_payments[payment.payee] = payment + except BudgetGraphBaseError as e: + errors.append(e) def set_including_from_excluding_and_error(): node.fixed_budget_including_subtasks = \ @@ -559,9 +655,15 @@ class BudgetGraph: return errors @cached_property - def payee_keys(self) -> Set[str]: - retval = set() + def payments(self) -> Dict[Person, Dict[Milestone, List[Payment]]]: + retval = {} + for person in self.config.people.values(): + milestone_payments = {} + for milestone in self.config.milestones.values(): + milestone_payments[milestone] = [] + retval[person] = milestone_payments for node in self.nodes.values(): - for payee_key in node.payments.keys(): - retval.add(payee_key) + if node.milestone is not None: + for payment in node.payments.values(): + retval[payment.payee][node.milestone].append(payment) return retval diff --git a/src/budget_sync/config.py b/src/budget_sync/config.py index 2f0e0f7..1bebca1 100644 --- a/src/budget_sync/config.py +++ b/src/budget_sync/config.py @@ -46,6 +46,12 @@ class Milestone: self.identifier = identifier self.canonical_bug_id = canonical_bug_id + def __eq__(self, other): + return self.identifier == other.identifier + + def __hash__(self): + return hash(self.identifier) + def __repr__(self): return f"Milestone(config=..., " \ f"identifier={self.identifier!r}, " \ diff --git a/src/budget_sync/main.py b/src/budget_sync/main.py index d2e3bf1..412f21c 100644 --- a/src/budget_sync/main.py +++ b/src/budget_sync/main.py @@ -1,6 +1,8 @@ from bugzilla import Bugzilla import logging +import argparse from budget_sync.util import all_bugs +from budget_sync.config import Config, ConfigParseError from budget_sync.budget_graph import BudgetGraph, BudgetGraphBaseError @@ -8,10 +10,24 @@ BUGZILLA_URL = "https://bugs.libre-soc.org" def main(): - logging.info("Using Bugzilla instance at %s", BUGZILLA_URL) - bz = Bugzilla(BUGZILLA_URL) + parser = argparse.ArgumentParser( + description="Check for errors in " + "Libre-SOC's style of budget tracking in Bugzilla.") + parser.add_argument( + "-c, --config", type=argparse.FileType('r'), + required=True, help="The path to the configuration TOML file", + dest="config", metavar="") + args = parser.parse_args() + try: + with args.config as config_file: + config = Config.from_file(config_file) + except (IOError, ConfigParseError) as e: + logging.error("Failed to parse config file: %s", e) + return + logging.info("Using Bugzilla instance at %s", config.bugzilla_url) + bz = Bugzilla(config.bugzilla_url) logging.debug("Connected to Bugzilla") - budget_graph = BudgetGraph(all_bugs(bz)) + budget_graph = BudgetGraph(all_bugs(bz), config) for error in budget_graph.get_errors(): logging.error("%s", error) diff --git a/src/budget_sync/test/test_budget_graph.py b/src/budget_sync/test/test_budget_graph.py index 885a20e..21786a1 100644 --- a/src/budget_sync/test/test_budget_graph.py +++ b/src/budget_sync/test/test_budget_graph.py @@ -1,20 +1,33 @@ from budget_sync.test.mock_bug import MockBug -from budget_sync.budget_graph import (BudgetGraphLoopError, BudgetGraph, - Node, BudgetGraphMoneyWithNoMilestone, - BudgetGraphBaseError, - BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, - BudgetGraphMoneyMismatchForBudgetIncludingSubtasks, - BudgetGraphNegativeMoney, - BudgetGraphMilestoneMismatch, - BudgetGraphNegativePayeeMoney, - BudgetGraphPayeesParseError, - BudgetGraphPayeesMoneyMismatch) +from budget_sync.config import Config +from budget_sync.budget_graph import ( + BudgetGraphLoopError, BudgetGraph, Node, BudgetGraphMoneyWithNoMilestone, + BudgetGraphBaseError, BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks, + BudgetGraphNegativeMoney, BudgetGraphMilestoneMismatch, + BudgetGraphNegativePayeeMoney, BudgetGraphPayeesParseError, + BudgetGraphPayeesMoneyMismatch, BudgetGraphUnknownMilestone, + BudgetGraphDuplicatePayeesForTask, BudgetGraphIncorrectRootForMilestone) from budget_sync.money import Money from typing import List, Type import unittest class TestErrorFormatting(unittest.TestCase): + def test_budget_graph_incorrect_root_for_milestone(self): + self.assertEqual(str(BudgetGraphIncorrectRootForMilestone( + 2, "milestone 1", 1)), + "Bug #2 is not the canonical root bug for assigned milestone " + "'milestone 1' but has no parent bug set: the milestone's " + "canonical root bug is #1") + + def test_budget_graph_duplicate_payees_for_task(self): + self.assertEqual(str(BudgetGraphDuplicatePayeesForTask( + 2, 1, "alias1", "alias2")), + "Budget assigned to multiple aliases of the same person in a " + "single task: bug #2, budget assigned to both 'alias1' " + "and 'alias2'") + def test_budget_graph_loop_error(self): self.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])), "Detected Loop in Budget Graph: #5 -> #1 " @@ -33,6 +46,12 @@ class TestErrorFormatting(unittest.TestCase): "milestone assigned to the root bug: descendant " "bug #1, root bug #5") + def test_budget_graph_unknown_milestone(self): + self.assertEqual(str(BudgetGraphUnknownMilestone( + 123, "fake milestone")), + "failed to parse cf_nlnet_milestone field of bug " + "#123: unknown milestone: 'fake milestone'") + def test_budget_graph_money_mismatch(self): self.assertEqual(str( BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -97,30 +116,57 @@ EXAMPLE_PARENT_BUG1 = MockBug(bug_id=1, cf_budget_parent=None, cf_budget="10", cf_total_budget="20", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list="") EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2, cf_budget_parent=1, cf_budget="10", cf_total_budget="10", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list="") +EXAMPLE_CONFIG = Config.from_str( + """ + bugzilla_url = "" + [people."person1"] + aliases = ["person1_alias1", "alias1"] + [people."person2"] + aliases = ["person1_alias2", "alias2", "person 2"] + [people."person3"] + [milestones] + "milestone 1" = { canonical_bug_id = 1 } + "milestone 2" = { canonical_bug_id = 2 } + """) + class TestBudgetGraph(unittest.TestCase): + maxDiff = None + def assertErrorTypesMatches(self, errors: List[BudgetGraphBaseError], template: List[Type]): + def wrap_type_list(type_list: List[Type]): + class TypeWrapper: + def __init__(self, t): + self.t = t + + def __repr__(self): + return self.t.__name__ + + def __eq__(self, other): + return self.t == other.t + return [TypeWrapper(i) for i in type_list] error_types = [] for error in errors: error_types.append(type(error)) - self.assertEqual(error_types, template) + self.assertEqual(wrap_type_list(error_types), wrap_type_list(template)) def test_empty(self): - bg = BudgetGraph([]) + bg = BudgetGraph([], EXAMPLE_CONFIG) self.assertEqual(len(bg.nodes), 0) self.assertEqual(len(bg.roots), 0) + self.assertIs(bg.config, EXAMPLE_CONFIG) def test_single(self): - bg = BudgetGraph([EXAMPLE_BUG1]) + bg = BudgetGraph([EXAMPLE_BUG1], EXAMPLE_CONFIG) self.assertEqual(len(bg.nodes), 1) node: Node = bg.nodes[1] self.assertEqual(bg.roots, {node}) @@ -132,21 +178,23 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(node.immediate_children, set()) self.assertEqual(node.budget_excluding_subtasks, Money(cents=0)) self.assertEqual(node.budget_including_subtasks, Money(cents=0)) - self.assertIsNone(node.nlnet_milestone) + self.assertIsNone(node.milestone) self.assertEqual(node.payments, {}) def test_loop1(self): with self.assertRaises(BudgetGraphLoopError) as cm: - BudgetGraph([EXAMPLE_LOOP1_BUG1]).roots + BudgetGraph([EXAMPLE_LOOP1_BUG1], EXAMPLE_CONFIG).roots self.assertEqual(cm.exception.bug_ids, [1]) def test_loop2(self): with self.assertRaises(BudgetGraphLoopError) as cm: - BudgetGraph([EXAMPLE_LOOP2_BUG1, EXAMPLE_LOOP2_BUG2]).roots + BudgetGraph([EXAMPLE_LOOP2_BUG1, EXAMPLE_LOOP2_BUG2], + EXAMPLE_CONFIG).roots self.assertEqual(cm.exception.bug_ids, [2, 1]) def test_parent_child(self): - bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2]) + bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2], + EXAMPLE_CONFIG) self.assertEqual(len(bg.nodes), 2) node1: Node = bg.nodes[1] node2: Node = bg.nodes[2] @@ -163,7 +211,7 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(node1.immediate_children, {node2}) self.assertEqual(node1.budget_excluding_subtasks, Money(cents=1000)) self.assertEqual(node1.budget_including_subtasks, Money(cents=2000)) - self.assertEqual(node1.nlnet_milestone, "abc") + self.assertEqual(node1.milestone_str, "milestone 1") self.assertEqual(list(node1.children()), [node2]) self.assertEqual(list(node1.children_breadth_first()), [node2]) self.assertEqual(node1.payments, {}) @@ -175,7 +223,7 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(node2.immediate_children, set()) self.assertEqual(node2.budget_excluding_subtasks, Money(cents=1000)) self.assertEqual(node2.budget_including_subtasks, Money(cents=1000)) - self.assertEqual(node2.nlnet_milestone, "abc") + self.assertEqual(node2.milestone_str, "milestone 1") self.assertEqual(node2.payments, {}) def test_children(self): @@ -222,7 +270,7 @@ class TestBudgetGraph(unittest.TestCase): cf_total_budget="0", cf_nlnet_milestone=None, cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) self.assertEqual(len(bg.nodes), 7) node1: Node = bg.nodes[1] node2: Node = bg.nodes[2] @@ -245,7 +293,7 @@ class TestBudgetGraph(unittest.TestCase): cf_total_budget="10", cf_nlnet_milestone=None, cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [ BudgetGraphMoneyWithNoMilestone, @@ -259,7 +307,7 @@ class TestBudgetGraph(unittest.TestCase): cf_total_budget="0", cf_nlnet_milestone=None, cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [ BudgetGraphMoneyWithNoMilestone, @@ -273,7 +321,7 @@ class TestBudgetGraph(unittest.TestCase): cf_total_budget="10", cf_nlnet_milestone=None, cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone]) self.assertEqual(errors[0].bug_id, 1) @@ -289,15 +337,15 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget=budget, cf_total_budget=total_budget, - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=payees_list), MockBug(bug_id=2, cf_budget_parent=1, cf_budget=child_budget, cf_total_budget=child_budget, - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) node1: Node = bg.nodes[1] errors = bg.get_errors() self.assertErrorTypesMatches(errors, @@ -310,15 +358,15 @@ class TestBudgetGraph(unittest.TestCase): cf_budget=str(node1.fixed_budget_excluding_subtasks), cf_total_budget=str( node1.fixed_budget_including_subtasks), - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=payees_list), MockBug(bug_id=2, cf_budget_parent=1, cf_budget=child_budget, cf_total_budget=child_budget, - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, expected_fixed_error_types) @@ -337,7 +385,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="0", total_budget="0", - payees_list="a=1", + payees_list="person1=1", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -347,7 +395,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="0", total_budget="0", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -357,7 +405,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="0", total_budget="0", - payees_list="a=10", + payees_list="person1=10", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -367,7 +415,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="0", total_budget="0", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -393,7 +441,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="0", total_budget="100", - payees_list="a=1", + payees_list="person1=1", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -403,7 +451,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="0", total_budget="100", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -413,7 +461,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="0", total_budget="100", - payees_list="a=10", + payees_list="person1=10", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -423,7 +471,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="0", total_budget="100", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -438,7 +486,7 @@ class TestBudgetGraph(unittest.TestCase): expected_errors=[]) helper(budget="0", total_budget="5", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)), @@ -446,7 +494,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="0", total_budget="5", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)), @@ -470,7 +518,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="10", total_budget="0", - payees_list="a=1", + payees_list="person1=1", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -480,7 +528,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="0", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -490,7 +538,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="0", - payees_list="a=10", + payees_list="person1=10", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -498,7 +546,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="10", total_budget="0", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -511,7 +559,7 @@ class TestBudgetGraph(unittest.TestCase): expected_errors=[]) helper(budget="10", total_budget="10", - payees_list="a=1", + payees_list="person1=1", child_budget="0", expected_errors=[ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)), @@ -519,7 +567,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="10", - payees_list="a=10", + payees_list="person1=10", child_budget="0", expected_errors=[]) helper(budget="10", @@ -540,7 +588,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="10", total_budget="100", - payees_list="a=1", + payees_list="person1=1", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -550,7 +598,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="100", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -560,7 +608,7 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="100", - payees_list="a=10", + payees_list="person1=10", child_budget="0", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -568,7 +616,7 @@ class TestBudgetGraph(unittest.TestCase): ]) helper(budget="10", total_budget="100", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks( @@ -581,7 +629,7 @@ class TestBudgetGraph(unittest.TestCase): expected_errors=[]) helper(budget="10", total_budget="15", - payees_list="a=1", + payees_list="person1=1", child_budget="5", expected_errors=[ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)), @@ -589,13 +637,13 @@ class TestBudgetGraph(unittest.TestCase): expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch]) helper(budget="10", total_budget="15", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[]) helper(budget="1", total_budget="15", - payees_list="a=10", + payees_list="person1=10", child_budget="5", expected_errors=[ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks( @@ -608,9 +656,9 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="0", cf_total_budget="-10", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [ BudgetGraphNegativeMoney, @@ -625,9 +673,9 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="-10", cf_total_budget="0", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [ BudgetGraphNegativeMoney, @@ -642,9 +690,9 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="-10", cf_total_budget="-10", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=""), - ]) + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [BudgetGraphNegativeMoney]) @@ -652,147 +700,231 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(errors[0].root_bug_id, 1) def test_payees_parse(self): - def check(cf_payees_list, expected_payments): + def check(cf_payees_list, error_types, expected_payments): bg = BudgetGraph([MockBug(bug_id=1, cf_budget_parent=None, cf_budget="0", cf_total_budget="0", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=cf_payees_list), - ]) + ], EXAMPLE_CONFIG) + self.assertErrorTypesMatches(bg.get_errors(), error_types) self.assertEqual(len(bg.nodes), 1) node: Node = bg.nodes[1] self.assertEqual([str(i) for i in node.payments.values()], expected_payments) - check(""" - abc = 123 - """, - ["Payment(node=#1, payee_key='abc', amount=123, " - + "state=NotYetSubmitted, paid=None, submitted=None)"]) - check(""" - abc = "123" - """, - ["Payment(node=#1, payee_key='abc', amount=123, " - + "state=NotYetSubmitted, paid=None, submitted=None)"]) - check(""" - abc = "123.45" - """, - ["Payment(node=#1, payee_key='abc', amount=123.45, " - + "state=NotYetSubmitted, paid=None, submitted=None)"]) - check(""" - abc = "123.45" - "d e f" = "21.35" - """, - ["Payment(node=#1, payee_key='abc', amount=123.45, " - + "state=NotYetSubmitted, paid=None, submitted=None)", - "Payment(node=#1, payee_key='d e f', amount=21.35, " - + "state=NotYetSubmitted, paid=None, submitted=None)"]) - check(""" - abc = "123.45" - # my comments - "AAA" = "-21.35" - """, - ["Payment(node=#1, payee_key='abc', amount=123.45, " - + "state=NotYetSubmitted, paid=None, submitted=None)", - "Payment(node=#1, payee_key='AAA', amount=-21.35, " - + "state=NotYetSubmitted, paid=None, submitted=None)"]) - check(""" - "not-an-email@example.com" = "-2345" - """, - ["Payment(node=#1, payee_key='not-an-email@example.com', " - + "amount=-2345, state=NotYetSubmitted, paid=None, " - + "submitted=None)"]) - check(""" - payee = { amount = 123 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=NotYetSubmitted, paid=None, " - + "submitted=None)"]) - check(""" - payee = { amount = 123, submitted = 2020-05-01 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Submitted, paid=None, " - + "submitted=2020-05-01)"]) - check(""" - payee = { amount = 123, submitted = 2020-05-01T00:00:00 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Submitted, paid=None, " - + "submitted=2020-05-01 00:00:00)"]) - check(""" - payee = { amount = 123, submitted = 2020-05-01T00:00:00Z } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Submitted, paid=None, " - + "submitted=2020-05-01 00:00:00+00:00)"]) - check(""" - payee = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Submitted, paid=None, " - + "submitted=2020-05-01 00:00:00-07:23)"]) - check(""" - payee = { amount = 123, paid = 2020-05-01 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01, " - + "submitted=None)"]) - check(""" - payee = { amount = 123, paid = 2020-05-01T00:00:00 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00, " - + "submitted=None)"]) - check(""" - payee = { amount = 123, paid = 2020-05-01T00:00:00Z } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, " - + "submitted=None)"]) - check(""" - payee = { amount = 123, paid = 2020-05-01T00:00:00-07:23 } - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, " - + "submitted=None)"]) - check(""" - [payee] - amount = 123 - submitted = 2020-05-23 - paid = 2020-05-01 - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01, " - + "submitted=2020-05-23)"]) - check(""" - [payee] - amount = 123 - submitted = 2020-05-23 - paid = 2020-05-01T00:00:00 - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00, " - + "submitted=2020-05-23)"]) - check(""" - [payee] - amount = 123 - submitted = 2020-05-23 - paid = 2020-05-01T00:00:00Z - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, " - + "submitted=2020-05-23)"]) - check(""" - [payee] - amount = 123 - submitted = 2020-05-23 - paid = 2020-05-01T00:00:00-07:23 - """, - ["Payment(node=#1, payee_key='payee', " - + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, " - + "submitted=2020-05-23)"]) + check( + """ + person1 = 123 + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, " + "payee_key='person1', amount=123, " + "state=NotYetSubmitted, paid=None, submitted=None)"]) + check( + """ + abc = "123" + """, + [BudgetGraphPayeesParseError, + BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=, payee_key='abc', " + "amount=123, state=NotYetSubmitted, paid=None, " + "submitted=None)"]) + check( + """ + person1 = "123.45" + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, " + "payee_key='person1', amount=123.45, " + "state=NotYetSubmitted, paid=None, submitted=None)"]) + check( + """ + person1 = "123.45" + "person 2" = "21.35" + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + 'amount=123.45, state=NotYetSubmitted, paid=None, ' + 'submitted=None)', + "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', " + 'amount=21.35, state=NotYetSubmitted, paid=None, ' + 'submitted=None)']) + check( + """ + person1 = "123.45" + "d e f" = "21.35" + """, + [BudgetGraphPayeesParseError, + BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + 'amount=123.45, state=NotYetSubmitted, paid=None, ' + 'submitted=None)', + "Payment(node=#1, payee=, payee_key='d e f', " + 'amount=21.35, state=NotYetSubmitted, paid=None, ' + 'submitted=None)']) + check( + """ + abc = "123.45" + # my comments + "AAA" = "-21.35" + """, + [BudgetGraphPayeesParseError, + BudgetGraphNegativePayeeMoney, + BudgetGraphPayeesParseError, + BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=, payee_key='abc', " + 'amount=123.45, state=NotYetSubmitted, paid=None, ' + 'submitted=None)', + "Payment(node=#1, payee=, payee_key='AAA', " + 'amount=-21.35, state=NotYetSubmitted, paid=None, ' + 'submitted=None)']) + check( + """ + "not-an-email@example.com" = "-2345" + """, + [BudgetGraphNegativePayeeMoney, + BudgetGraphPayeesParseError, + BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ['Payment(node=#1, payee=, ' + "payee_key='not-an-email@example.com', amount=-2345, " + "state=NotYetSubmitted, paid=None, submitted=None)"]) + check( + """ + person1 = { amount = 123 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"]) + check( + """ + person1 = { amount = 123, submitted = 2020-05-01 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Submitted, paid=None, " + + "submitted=2020-05-01)"]) + check( + """ + person1 = { amount = 123, submitted = 2020-05-01T00:00:00 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Submitted, paid=None, " + + "submitted=2020-05-01 00:00:00)"]) + check( + """ + person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Submitted, paid=None, " + + "submitted=2020-05-01 00:00:00+00:00)"]) + check( + """ + person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Submitted, paid=None, " + + "submitted=2020-05-01 00:00:00-07:23)"]) + check( + """ + person1 = { amount = 123, paid = 2020-05-01 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01, " + + "submitted=None)"]) + check( + """ + person1 = { amount = 123, paid = 2020-05-01T00:00:00 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00, " + + "submitted=None)"]) + check( + """ + person1 = { amount = 123, paid = 2020-05-01T00:00:00Z } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, " + + "submitted=None)"]) + check( + """ + person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 } + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, " + + "submitted=None)"]) + check( + """ + [person1] + amount = 123 + submitted = 2020-05-23 + paid = 2020-05-01 + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01, " + + "submitted=2020-05-23)"]) + check( + """ + [person1] + amount = 123 + submitted = 2020-05-23 + paid = 2020-05-01T00:00:00 + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00, " + + "submitted=2020-05-23)"]) + check( + """ + [person1] + amount = 123 + submitted = 2020-05-23 + paid = 2020-05-01T00:00:00Z + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, " + + "submitted=2020-05-23)"]) + check( + """ + [person1] + amount = 123 + submitted = 2020-05-23 + paid = 2020-05-01T00:00:00-07:23 + """, + [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks, + BudgetGraphMoneyMismatchForBudgetIncludingSubtasks], + ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', " + + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, " + + "submitted=2020-05-23)"]) def test_payees_money_mismatch(self): bg = BudgetGraph([ @@ -800,9 +932,9 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="10", cf_total_budget="10", - cf_nlnet_milestone="abc", - cf_payees_list="payee = 5\npayee2 = 10"), - ]) + cf_nlnet_milestone="milestone 1", + cf_payees_list="person1 = 5\nperson2 = 10"), + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [BudgetGraphPayeesMoneyMismatch]) @@ -817,9 +949,9 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="0", cf_total_budget="0", - cf_nlnet_milestone="abc", + cf_nlnet_milestone="milestone 1", cf_payees_list=cf_payees_list), - ]).get_errors() + ], EXAMPLE_CONFIG).get_errors() self.assertErrorTypesMatches(errors, [BudgetGraphPayeesParseError]) self.assertEqual(errors[0].bug_id, 1) @@ -912,37 +1044,109 @@ class TestBudgetGraph(unittest.TestCase): cf_budget_parent=None, cf_budget="10", cf_total_budget="10", - cf_nlnet_milestone="abc", - cf_payees_list="""payee1 = -10"""), - ]) + cf_nlnet_milestone="milestone 1", + cf_payees_list="""person1 = -10"""), + ], EXAMPLE_CONFIG) errors = bg.get_errors() self.assertErrorTypesMatches(errors, [BudgetGraphNegativePayeeMoney, BudgetGraphPayeesMoneyMismatch]) self.assertEqual(errors[0].bug_id, 1) self.assertEqual(errors[0].root_bug_id, 1) - self.assertEqual(errors[0].payee_key, "payee1") + self.assertEqual(errors[0].payee_key, "person1") self.assertEqual(errors[1].bug_id, 1) self.assertEqual(errors[1].root_bug_id, 1) self.assertEqual(errors[1].payees_total, -10) - def test_payee_keys(self): + def test_duplicate_payments(self): + bg = BudgetGraph([ + MockBug(bug_id=1, + cf_budget_parent=None, + cf_budget="10", + cf_total_budget="10", + cf_nlnet_milestone="milestone 1", + cf_payees_list=""" + person1 = 5 + alias1 = 5 + """), + ], EXAMPLE_CONFIG) + errors = bg.get_errors() + self.assertErrorTypesMatches(errors, + [BudgetGraphDuplicatePayeesForTask]) + self.assertEqual(errors[0].bug_id, 1) + self.assertEqual(errors[0].root_bug_id, 1) + self.assertEqual(errors[0].payee1_key, "person1") + self.assertEqual(errors[0].payee2_key, "alias1") + + def test_incorrect_root_for_milestone(self): + bg = BudgetGraph([ + MockBug(bug_id=1, + cf_budget_parent=None, + cf_budget="10", + cf_total_budget="10", + cf_nlnet_milestone="milestone 2", + cf_payees_list=""), + ], EXAMPLE_CONFIG) + errors = bg.get_errors() + self.assertErrorTypesMatches(errors, + [BudgetGraphIncorrectRootForMilestone]) + self.assertEqual(errors[0].bug_id, 1) + self.assertEqual(errors[0].root_bug_id, 1) + self.assertEqual(errors[0].milestone, "milestone 2") + self.assertEqual(errors[0].milestone_canonical_bug_id, 2) + bg = BudgetGraph([ + MockBug(bug_id=1, + cf_budget_parent=None, + cf_budget="0", + cf_total_budget="0", + cf_nlnet_milestone="milestone 2", + cf_payees_list=""), + ], EXAMPLE_CONFIG) + errors = bg.get_errors() + self.assertErrorTypesMatches(errors, []) + + def test_payments(self): bg = BudgetGraph([ MockBug(bug_id=1, cf_budget_parent=None, cf_budget="10", cf_total_budget="10", - cf_nlnet_milestone="abc", - cf_payees_list="payee2 = 3\npayee1 = 7"), + cf_nlnet_milestone="milestone 1", + cf_payees_list="person1 = 3\nperson2 = 7"), MockBug(bug_id=2, cf_budget_parent=None, cf_budget="10", cf_total_budget="10", - cf_nlnet_milestone="def", - cf_payees_list="""payee3 = 5\npayee2 = 5"""), - ]) + cf_nlnet_milestone="milestone 2", + cf_payees_list="person3 = 5\nperson2 = 5"), + ], EXAMPLE_CONFIG) self.assertErrorTypesMatches(bg.get_errors(), []) - self.assertEqual(bg.payee_keys, {"payee1", "payee2", "payee3"}) + person1 = EXAMPLE_CONFIG.people["person1"] + person2 = EXAMPLE_CONFIG.people["person2"] + person3 = EXAMPLE_CONFIG.people["person3"] + milestone1 = EXAMPLE_CONFIG.milestones["milestone 1"] + milestone2 = EXAMPLE_CONFIG.milestones["milestone 2"] + node1: Node = bg.nodes[1] + node2: Node = bg.nodes[2] + node1_payment_person1 = node1.payments["person1"] + node1_payment_person2 = node1.payments["person2"] + node2_payment_person2 = node2.payments["person2"] + node2_payment_person3 = node2.payments["person3"] + self.assertEqual(bg.payments, + { + person1: { + milestone1: [node1_payment_person1], + milestone2: [], + }, + person2: { + milestone1: [node1_payment_person2], + milestone2: [node2_payment_person2], + }, + person3: { + milestone1: [], + milestone2: [node2_payment_person3], + }, + }) if __name__ == "__main__": -- 2.30.2