1 from budget_sync
.test
.mock_bug
import MockBug
2 from budget_sync
.config
import Config
3 from budget_sync
.budget_graph
import (
4 BudgetGraphLoopError
, BudgetGraph
, Node
, BudgetGraphMoneyWithNoMilestone
,
5 BudgetGraphBaseError
, BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
6 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
,
7 BudgetGraphNegativeMoney
, BudgetGraphMilestoneMismatch
,
8 BudgetGraphNegativePayeeMoney
, BudgetGraphPayeesParseError
,
9 BudgetGraphPayeesMoneyMismatch
, BudgetGraphUnknownMilestone
,
10 BudgetGraphDuplicatePayeesForTask
, BudgetGraphIncorrectRootForMilestone
)
11 from budget_sync
.money
import Money
12 from typing
import List
, Type
16 class TestErrorFormatting(unittest
.TestCase
):
17 def test_budget_graph_incorrect_root_for_milestone(self
):
18 self
.assertEqual(str(BudgetGraphIncorrectRootForMilestone(
19 2, "milestone 1", 1)),
20 "Bug #2 is not the canonical root bug for assigned milestone "
21 "'milestone 1' but has no parent bug set: the milestone's "
22 "canonical root bug is #1")
24 def test_budget_graph_duplicate_payees_for_task(self
):
25 self
.assertEqual(str(BudgetGraphDuplicatePayeesForTask(
26 2, 1, "alias1", "alias2")),
27 "Budget assigned to multiple aliases of the same person in a "
28 "single task: bug #2, budget assigned to both 'alias1' "
31 def test_budget_graph_loop_error(self
):
32 self
.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])),
33 "Detected Loop in Budget Graph: #5 -> #1 "
34 "-> #2 -> #3 -> #4 -> #5")
35 self
.assertEqual(str(BudgetGraphLoopError([1])),
36 "Detected Loop in Budget Graph: #1 -> #1")
38 def test_budget_graph_money_with_no_milestone(self
):
39 self
.assertEqual(str(BudgetGraphMoneyWithNoMilestone(1, 5)),
40 "Bug assigned money but without any assigned "
43 def test_budget_graph_milestone_mismatch(self
):
44 self
.assertEqual(str(BudgetGraphMilestoneMismatch(1, 5)),
45 "Bug's assigned milestone doesn't match the "
46 "milestone assigned to the root bug: descendant "
47 "bug #1, root bug #5")
49 def test_budget_graph_unknown_milestone(self
):
50 self
.assertEqual(str(BudgetGraphUnknownMilestone(
51 123, "fake milestone")),
52 "failed to parse cf_nlnet_milestone field of bug "
53 "#123: unknown milestone: 'fake milestone'")
55 def test_budget_graph_money_mismatch(self
):
57 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
59 "Budget assigned to task excluding subtasks "
60 "(cf_budget field) doesn't match calculated value:"
61 " bug #1, calculated value 123.4")
63 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
65 "Budget assigned to task including subtasks "
66 "(cf_total_budget field) doesn't match calculated value:"
67 " bug #1, calculated value 123.4")
69 def test_budget_graph_negative_money(self
):
70 self
.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
71 "Budget assigned to task is less than zero: bug #1")
73 def test_budget_graph_negative_payee_money(self
):
74 self
.assertEqual(str(BudgetGraphNegativePayeeMoney(1, 5, "payee1")),
75 "Budget assigned to payee for task is less than "
76 "zero: bug #1, payee 'payee1'")
78 def test_budget_graph_payees_parse_error(self
):
80 BudgetGraphPayeesParseError(1, "my fake parse error")),
81 "Failed to parse cf_payees_list field of bug #1: "
82 "my fake parse error")
84 def test_budget_graph_payees_money_mismatch(self
):
86 BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
87 "Total budget assigned to payees (cf_payees_list) doesn't match "
88 "expected value: bug #1, calculated total 123, expected value 456")
91 EXAMPLE_BUG1
= MockBug(bug_id
=1,
92 cf_budget_parent
=None,
95 cf_nlnet_milestone
=None,
97 EXAMPLE_LOOP1_BUG1
= MockBug(bug_id
=1,
101 cf_nlnet_milestone
=None,
103 EXAMPLE_LOOP2_BUG1
= MockBug(bug_id
=1,
107 cf_nlnet_milestone
=None,
109 EXAMPLE_LOOP2_BUG2
= MockBug(bug_id
=2,
113 cf_nlnet_milestone
=None,
115 EXAMPLE_PARENT_BUG1
= MockBug(bug_id
=1,
116 cf_budget_parent
=None,
118 cf_total_budget
="20",
119 cf_nlnet_milestone
="milestone 1",
121 EXAMPLE_CHILD_BUG2
= MockBug(bug_id
=2,
124 cf_total_budget
="10",
125 cf_nlnet_milestone
="milestone 1",
128 EXAMPLE_CONFIG
= Config
.from_str(
132 aliases = ["person1_alias1", "alias1"]
134 aliases = ["person1_alias2", "alias2", "person 2"]
137 "milestone 1" = { canonical_bug_id = 1 }
138 "milestone 2" = { canonical_bug_id = 2 }
142 class TestBudgetGraph(unittest
.TestCase
):
145 def assertErrorTypesMatches(self
, errors
: List
[BudgetGraphBaseError
], template
: List
[Type
]):
146 def wrap_type_list(type_list
: List
[Type
]):
148 def __init__(self
, t
):
152 return self
.t
.__name
__
154 def __eq__(self
, other
):
155 return self
.t
== other
.t
156 return [TypeWrapper(i
) for i
in type_list
]
159 error_types
.append(type(error
))
160 self
.assertEqual(wrap_type_list(error_types
), wrap_type_list(template
))
162 def test_empty(self
):
163 bg
= BudgetGraph([], EXAMPLE_CONFIG
)
164 self
.assertEqual(len(bg
.nodes
), 0)
165 self
.assertEqual(len(bg
.roots
), 0)
166 self
.assertIs(bg
.config
, EXAMPLE_CONFIG
)
168 def test_single(self
):
169 bg
= BudgetGraph([EXAMPLE_BUG1
], EXAMPLE_CONFIG
)
170 self
.assertEqual(len(bg
.nodes
), 1)
171 node
: Node
= bg
.nodes
[1]
172 self
.assertEqual(bg
.roots
, {node}
)
173 self
.assertIsInstance(node
, Node
)
174 self
.assertIs(node
.graph
, bg
)
175 self
.assertIs(node
.bug
, EXAMPLE_BUG1
)
176 self
.assertIs(node
.root
, node
)
177 self
.assertIsNone(node
.parent_id
)
178 self
.assertEqual(node
.immediate_children
, set())
179 self
.assertEqual(node
.budget_excluding_subtasks
, Money(cents
=0))
180 self
.assertEqual(node
.budget_including_subtasks
, Money(cents
=0))
181 self
.assertIsNone(node
.milestone
)
182 self
.assertEqual(node
.payments
, {})
184 def test_loop1(self
):
185 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
186 BudgetGraph([EXAMPLE_LOOP1_BUG1
], EXAMPLE_CONFIG
).roots
187 self
.assertEqual(cm
.exception
.bug_ids
, [1])
189 def test_loop2(self
):
190 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
191 BudgetGraph([EXAMPLE_LOOP2_BUG1
, EXAMPLE_LOOP2_BUG2
],
192 EXAMPLE_CONFIG
).roots
193 self
.assertEqual(cm
.exception
.bug_ids
, [2, 1])
195 def test_parent_child(self
):
196 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
198 self
.assertEqual(len(bg
.nodes
), 2)
199 node1
: Node
= bg
.nodes
[1]
200 node2
: Node
= bg
.nodes
[2]
201 self
.assertEqual(bg
.roots
, {node1}
)
202 self
.assertEqual(node1
, node1
)
203 self
.assertEqual(node2
, node2
)
204 self
.assertNotEqual(node1
, node2
)
205 self
.assertNotEqual(node2
, node1
)
206 self
.assertIsInstance(node1
, Node
)
207 self
.assertIs(node1
.graph
, bg
)
208 self
.assertIs(node1
.bug
, EXAMPLE_PARENT_BUG1
)
209 self
.assertIsNone(node1
.parent_id
)
210 self
.assertEqual(node1
.root
, node1
)
211 self
.assertEqual(node1
.immediate_children
, {node2}
)
212 self
.assertEqual(node1
.budget_excluding_subtasks
, Money(cents
=1000))
213 self
.assertEqual(node1
.budget_including_subtasks
, Money(cents
=2000))
214 self
.assertEqual(node1
.milestone_str
, "milestone 1")
215 self
.assertEqual(list(node1
.children()), [node2
])
216 self
.assertEqual(list(node1
.children_breadth_first()), [node2
])
217 self
.assertEqual(node1
.payments
, {})
218 self
.assertIsInstance(node2
, Node
)
219 self
.assertIs(node2
.graph
, bg
)
220 self
.assertIs(node2
.bug
, EXAMPLE_CHILD_BUG2
)
221 self
.assertEqual(node2
.parent_id
, 1)
222 self
.assertEqual(node2
.root
, node1
)
223 self
.assertEqual(node2
.immediate_children
, set())
224 self
.assertEqual(node2
.budget_excluding_subtasks
, Money(cents
=1000))
225 self
.assertEqual(node2
.budget_including_subtasks
, Money(cents
=1000))
226 self
.assertEqual(node2
.milestone_str
, "milestone 1")
227 self
.assertEqual(node2
.payments
, {})
229 def test_children(self
):
232 cf_budget_parent
=None,
235 cf_nlnet_milestone
=None,
241 cf_nlnet_milestone
=None,
247 cf_nlnet_milestone
=None,
253 cf_nlnet_milestone
=None,
259 cf_nlnet_milestone
=None,
265 cf_nlnet_milestone
=None,
271 cf_nlnet_milestone
=None,
274 self
.assertEqual(len(bg
.nodes
), 7)
275 node1
: Node
= bg
.nodes
[1]
276 node2
: Node
= bg
.nodes
[2]
277 node3
: Node
= bg
.nodes
[3]
278 node4
: Node
= bg
.nodes
[4]
279 node5
: Node
= bg
.nodes
[5]
280 node6
: Node
= bg
.nodes
[6]
281 node7
: Node
= bg
.nodes
[7]
282 self
.assertEqual(bg
.roots
, {node1}
)
283 self
.assertEqual(list(node1
.children()),
284 [node2
, node3
, node5
, node7
, node6
, node4
])
285 self
.assertEqual(list(node1
.children_breadth_first()),
286 [node2
, node3
, node4
, node5
, node6
, node7
])
288 def test_money_with_no_milestone(self
):
291 cf_budget_parent
=None,
293 cf_total_budget
="10",
294 cf_nlnet_milestone
=None,
297 errors
= bg
.get_errors()
298 self
.assertErrorTypesMatches(errors
, [
299 BudgetGraphMoneyWithNoMilestone
,
300 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
301 self
.assertEqual(errors
[0].bug_id
, 1)
302 self
.assertEqual(errors
[0].root_bug_id
, 1)
305 cf_budget_parent
=None,
308 cf_nlnet_milestone
=None,
311 errors
= bg
.get_errors()
312 self
.assertErrorTypesMatches(errors
, [
313 BudgetGraphMoneyWithNoMilestone
,
314 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
315 self
.assertEqual(errors
[0].bug_id
, 1)
316 self
.assertEqual(errors
[0].root_bug_id
, 1)
319 cf_budget_parent
=None,
321 cf_total_budget
="10",
322 cf_nlnet_milestone
=None,
325 errors
= bg
.get_errors()
326 self
.assertErrorTypesMatches(errors
, [BudgetGraphMoneyWithNoMilestone
])
327 self
.assertEqual(errors
[0].bug_id
, 1)
328 self
.assertEqual(errors
[0].root_bug_id
, 1)
330 def test_money_mismatch(self
):
331 def helper(budget
, total_budget
, payees_list
, child_budget
,
332 expected_errors
, expected_fixed_error_types
=None):
333 if expected_fixed_error_types
is None:
334 expected_fixed_error_types
= []
337 cf_budget_parent
=None,
339 cf_total_budget
=total_budget
,
340 cf_nlnet_milestone
="milestone 1",
341 cf_payees_list
=payees_list
),
344 cf_budget
=child_budget
,
345 cf_total_budget
=child_budget
,
346 cf_nlnet_milestone
="milestone 1",
349 node1
: Node
= bg
.nodes
[1]
350 errors
= bg
.get_errors()
351 self
.assertErrorTypesMatches(errors
,
352 [type(i
) for i
in expected_errors
])
353 self
.assertEqual([str(i
) for i
in errors
],
354 [str(i
) for i
in expected_errors
])
357 cf_budget_parent
=None,
358 cf_budget
=str(node1
.fixed_budget_excluding_subtasks
),
360 node1
.fixed_budget_including_subtasks
),
361 cf_nlnet_milestone
="milestone 1",
362 cf_payees_list
=payees_list
),
365 cf_budget
=child_budget
,
366 cf_total_budget
=child_budget
,
367 cf_nlnet_milestone
="milestone 1",
370 errors
= bg
.get_errors()
371 self
.assertErrorTypesMatches(errors
,
372 expected_fixed_error_types
)
383 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
388 payees_list
="person1=1",
391 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
393 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
398 payees_list
="person1=1",
401 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
403 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
408 payees_list
="person1=10",
411 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
413 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
418 payees_list
="person1=10",
421 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
423 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
431 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
439 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
444 payees_list
="person1=1",
447 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
449 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
451 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
454 payees_list
="person1=1",
457 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
459 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
461 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
464 payees_list
="person1=10",
467 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
469 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
471 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
474 payees_list
="person1=10",
477 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
479 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
481 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
489 payees_list
="person1=1",
492 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
494 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
497 payees_list
="person1=10",
500 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
502 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
508 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
516 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
521 payees_list
="person1=1",
524 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
526 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
528 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
531 payees_list
="person1=1",
534 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
536 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
538 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
541 payees_list
="person1=10",
544 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
549 payees_list
="person1=10",
552 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
562 payees_list
="person1=1",
565 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
567 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
570 payees_list
="person1=10",
578 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
586 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
591 payees_list
="person1=1",
594 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
596 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
598 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
601 payees_list
="person1=1",
604 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
606 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
608 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
611 payees_list
="person1=10",
614 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
619 payees_list
="person1=10",
622 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
632 payees_list
="person1=1",
635 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
637 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
640 payees_list
="person1=10",
646 payees_list
="person1=10",
649 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
653 def test_negative_money(self
):
656 cf_budget_parent
=None,
658 cf_total_budget
="-10",
659 cf_nlnet_milestone
="milestone 1",
662 errors
= bg
.get_errors()
663 self
.assertErrorTypesMatches(errors
, [
664 BudgetGraphNegativeMoney
,
665 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
666 self
.assertEqual(errors
[0].bug_id
, 1)
667 self
.assertEqual(errors
[0].root_bug_id
, 1)
668 self
.assertEqual(errors
[1].bug_id
, 1)
669 self
.assertEqual(errors
[1].root_bug_id
, 1)
670 self
.assertEqual(errors
[1].expected_budget_excluding_subtasks
, -10)
673 cf_budget_parent
=None,
676 cf_nlnet_milestone
="milestone 1",
679 errors
= bg
.get_errors()
680 self
.assertErrorTypesMatches(errors
, [
681 BudgetGraphNegativeMoney
,
682 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
683 self
.assertEqual(errors
[0].bug_id
, 1)
684 self
.assertEqual(errors
[0].root_bug_id
, 1)
685 self
.assertEqual(errors
[1].bug_id
, 1)
686 self
.assertEqual(errors
[1].root_bug_id
, 1)
687 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, -10)
690 cf_budget_parent
=None,
692 cf_total_budget
="-10",
693 cf_nlnet_milestone
="milestone 1",
696 errors
= bg
.get_errors()
697 self
.assertErrorTypesMatches(errors
,
698 [BudgetGraphNegativeMoney
])
699 self
.assertEqual(errors
[0].bug_id
, 1)
700 self
.assertEqual(errors
[0].root_bug_id
, 1)
702 def test_payees_parse(self
):
703 def check(cf_payees_list
, error_types
, expected_payments
):
704 bg
= BudgetGraph([MockBug(bug_id
=1,
705 cf_budget_parent
=None,
708 cf_nlnet_milestone
="milestone 1",
709 cf_payees_list
=cf_payees_list
),
711 self
.assertErrorTypesMatches(bg
.get_errors(), error_types
)
712 self
.assertEqual(len(bg
.nodes
), 1)
713 node
: Node
= bg
.nodes
[1]
714 self
.assertEqual([str(i
) for i
in node
.payments
.values()],
721 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
722 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
723 ["Payment(node=#1, payee=Person<'person1'>, "
724 "payee_key='person1', amount=123, "
725 "state=NotYetSubmitted, paid=None, submitted=None)"])
730 [BudgetGraphPayeesParseError
,
731 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
732 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
733 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
734 "amount=123, state=NotYetSubmitted, paid=None, "
740 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
741 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
742 ["Payment(node=#1, payee=Person<'person1'>, "
743 "payee_key='person1', amount=123.45, "
744 "state=NotYetSubmitted, paid=None, submitted=None)"])
750 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
751 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
752 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
753 'amount=123.45, state=NotYetSubmitted, paid=None, '
755 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
756 'amount=21.35, state=NotYetSubmitted, paid=None, '
763 [BudgetGraphPayeesParseError
,
764 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
765 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
766 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
767 'amount=123.45, state=NotYetSubmitted, paid=None, '
769 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
770 'amount=21.35, state=NotYetSubmitted, paid=None, '
778 [BudgetGraphPayeesParseError
,
779 BudgetGraphNegativePayeeMoney
,
780 BudgetGraphPayeesParseError
,
781 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
782 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
783 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
784 'amount=123.45, state=NotYetSubmitted, paid=None, '
786 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
787 'amount=-21.35, state=NotYetSubmitted, paid=None, '
791 "not-an-email@example.com" = "-2345"
793 [BudgetGraphNegativePayeeMoney
,
794 BudgetGraphPayeesParseError
,
795 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
796 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
797 ['Payment(node=#1, payee=<unknown person>, '
798 "payee_key='not-an-email@example.com', amount=-2345, "
799 "state=NotYetSubmitted, paid=None, submitted=None)"])
802 person1 = { amount = 123 }
804 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
805 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
806 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
807 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
810 person1 = { amount = 123, submitted = 2020-05-01 }
812 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
813 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
814 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
815 + "amount=123, state=Submitted, paid=None, "
816 + "submitted=2020-05-01)"])
819 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
821 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
822 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
823 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
824 + "amount=123, state=Submitted, paid=None, "
825 + "submitted=2020-05-01 00:00:00)"])
828 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
830 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
831 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
832 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
833 + "amount=123, state=Submitted, paid=None, "
834 + "submitted=2020-05-01 00:00:00+00:00)"])
837 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
839 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
840 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
841 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
842 + "amount=123, state=Submitted, paid=None, "
843 + "submitted=2020-05-01 00:00:00-07:23)"])
846 person1 = { amount = 123, paid = 2020-05-01 }
848 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
849 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
850 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
851 + "amount=123, state=Paid, paid=2020-05-01, "
852 + "submitted=None)"])
855 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
857 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
858 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
859 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
860 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
861 + "submitted=None)"])
864 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
866 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
867 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
868 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
869 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
870 + "submitted=None)"])
873 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
875 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
876 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
877 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
878 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
879 + "submitted=None)"])
884 submitted = 2020-05-23
887 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
888 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
889 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
890 + "amount=123, state=Paid, paid=2020-05-01, "
891 + "submitted=2020-05-23)"])
896 submitted = 2020-05-23
897 paid = 2020-05-01T00:00:00
899 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
900 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
901 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
902 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
903 + "submitted=2020-05-23)"])
908 submitted = 2020-05-23
909 paid = 2020-05-01T00:00:00Z
911 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
912 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
913 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
914 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
915 + "submitted=2020-05-23)"])
920 submitted = 2020-05-23
921 paid = 2020-05-01T00:00:00-07:23
923 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
924 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
925 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
926 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
927 + "submitted=2020-05-23)"])
929 def test_payees_money_mismatch(self
):
932 cf_budget_parent
=None,
934 cf_total_budget
="10",
935 cf_nlnet_milestone
="milestone 1",
936 cf_payees_list
="person1 = 5\nperson2 = 10"),
938 errors
= bg
.get_errors()
939 self
.assertErrorTypesMatches(errors
,
940 [BudgetGraphPayeesMoneyMismatch
])
941 self
.assertEqual(errors
[0].bug_id
, 1)
942 self
.assertEqual(errors
[0].root_bug_id
, 1)
943 self
.assertEqual(errors
[0].payees_total
, 15)
945 def test_payees_parse_error(self
):
946 def check_parse_error(cf_payees_list
, expected_msg
):
947 errors
= BudgetGraph([
949 cf_budget_parent
=None,
952 cf_nlnet_milestone
="milestone 1",
953 cf_payees_list
=cf_payees_list
),
954 ], EXAMPLE_CONFIG
).get_errors()
955 self
.assertErrorTypesMatches(errors
,
956 [BudgetGraphPayeesParseError
])
957 self
.assertEqual(errors
[0].bug_id
, 1)
958 self
.assertEqual(errors
[0].msg
, expected_msg
)
960 check_parse_error("""
963 "value for key 'payee 1' is invalid -- it should "
964 "either be a monetary value or a table")
966 check_parse_error("""
969 "failed to parse monetary amount for key 'payee': "
970 "invalid Money string: characters after sign and "
971 "before first `.` must be ascii digits")
973 check_parse_error("""
977 "TOML parse error: Duplicate keys! (line 3"
978 " column 1 char 39)")
980 check_parse_error("""
983 "failed to parse monetary amount for key 'payee': "
984 "monetary amount is not a string or integer (to "
985 "use fractional amounts such as 123.45, write "
986 "\"123.45\"): 123.45")
988 check_parse_error("""
991 "value for key 'payee' is missing the `amount` "
992 "field which is required")
994 check_parse_error("""
995 payee = { amount = 123.45 }
997 "failed to parse monetary amount for key 'payee': "
998 "monetary amount is not a string or integer (to "
999 "use fractional amounts such as 123.45, write "
1000 "\"123.45\"): 123.45")
1002 check_parse_error("""
1003 payee = { amount = 123, blah = false }
1005 "value for key 'payee' has an unknown field: `blah`")
1007 check_parse_error("""
1008 payee = { amount = 123, submitted = false }
1010 "failed to parse `submitted` field for key "
1011 "'payee': invalid date: false")
1013 check_parse_error("""
1014 payee = { amount = 123, submitted = 123 }
1016 "failed to parse `submitted` field for key 'payee':"
1017 " invalid date: 123")
1021 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1023 "failed to parse `submitted` field for key 'payee': "
1024 "invalid date: 'abc'")
1028 payee = { amount = 123, paid = 12:34:56 }
1030 "failed to parse `paid` field for key 'payee': just a time of "
1031 "day by itself is not enough, a date must be included: 12:34:56")
1035 payee = { amount = 123, submitted = 12:34:56.123456 }
1037 "failed to parse `submitted` field for key 'payee': just a time "
1038 "of day by itself is not enough, a date must be included: "
1041 def test_negative_payee_money(self
):
1044 cf_budget_parent
=None,
1046 cf_total_budget
="10",
1047 cf_nlnet_milestone
="milestone 1",
1048 cf_payees_list
="""person1 = -10"""),
1050 errors
= bg
.get_errors()
1051 self
.assertErrorTypesMatches(errors
,
1052 [BudgetGraphNegativePayeeMoney
,
1053 BudgetGraphPayeesMoneyMismatch
])
1054 self
.assertEqual(errors
[0].bug_id
, 1)
1055 self
.assertEqual(errors
[0].root_bug_id
, 1)
1056 self
.assertEqual(errors
[0].payee_key
, "person1")
1057 self
.assertEqual(errors
[1].bug_id
, 1)
1058 self
.assertEqual(errors
[1].root_bug_id
, 1)
1059 self
.assertEqual(errors
[1].payees_total
, -10)
1061 def test_duplicate_payments(self
):
1064 cf_budget_parent
=None,
1066 cf_total_budget
="10",
1067 cf_nlnet_milestone
="milestone 1",
1073 errors
= bg
.get_errors()
1074 self
.assertErrorTypesMatches(errors
,
1075 [BudgetGraphDuplicatePayeesForTask
])
1076 self
.assertEqual(errors
[0].bug_id
, 1)
1077 self
.assertEqual(errors
[0].root_bug_id
, 1)
1078 self
.assertEqual(errors
[0].payee1_key
, "person1")
1079 self
.assertEqual(errors
[0].payee2_key
, "alias1")
1081 def test_incorrect_root_for_milestone(self
):
1084 cf_budget_parent
=None,
1086 cf_total_budget
="10",
1087 cf_nlnet_milestone
="milestone 2",
1090 errors
= bg
.get_errors()
1091 self
.assertErrorTypesMatches(errors
,
1092 [BudgetGraphIncorrectRootForMilestone
])
1093 self
.assertEqual(errors
[0].bug_id
, 1)
1094 self
.assertEqual(errors
[0].root_bug_id
, 1)
1095 self
.assertEqual(errors
[0].milestone
, "milestone 2")
1096 self
.assertEqual(errors
[0].milestone_canonical_bug_id
, 2)
1099 cf_budget_parent
=None,
1101 cf_total_budget
="0",
1102 cf_nlnet_milestone
="milestone 2",
1105 errors
= bg
.get_errors()
1106 self
.assertErrorTypesMatches(errors
, [])
1108 def test_payments(self
):
1111 cf_budget_parent
=None,
1113 cf_total_budget
="10",
1114 cf_nlnet_milestone
="milestone 1",
1115 cf_payees_list
="person1 = 3\nperson2 = 7"),
1117 cf_budget_parent
=None,
1119 cf_total_budget
="10",
1120 cf_nlnet_milestone
="milestone 2",
1121 cf_payees_list
="person3 = 5\nperson2 = 5"),
1123 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1124 person1
= EXAMPLE_CONFIG
.people
["person1"]
1125 person2
= EXAMPLE_CONFIG
.people
["person2"]
1126 person3
= EXAMPLE_CONFIG
.people
["person3"]
1127 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1128 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1129 node1
: Node
= bg
.nodes
[1]
1130 node2
: Node
= bg
.nodes
[2]
1131 node1_payment_person1
= node1
.payments
["person1"]
1132 node1_payment_person2
= node1
.payments
["person2"]
1133 node2_payment_person2
= node2
.payments
["person2"]
1134 node2_payment_person3
= node2
.payments
["person3"]
1135 self
.assertEqual(bg
.payments
,
1138 milestone1
: [node1_payment_person1
],
1142 milestone1
: [node1_payment_person2
],
1143 milestone2
: [node2_payment_person2
],
1147 milestone2
: [node2_payment_person3
],
1152 if __name__
== "__main__":