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,
98 EXAMPLE_LOOP1_BUG1
= MockBug(bug_id
=1,
102 cf_nlnet_milestone
=None,
105 EXAMPLE_LOOP2_BUG1
= MockBug(bug_id
=1,
109 cf_nlnet_milestone
=None,
112 EXAMPLE_LOOP2_BUG2
= MockBug(bug_id
=2,
116 cf_nlnet_milestone
=None,
119 EXAMPLE_PARENT_BUG1
= MockBug(bug_id
=1,
120 cf_budget_parent
=None,
122 cf_total_budget
="20",
123 cf_nlnet_milestone
="milestone 1",
126 EXAMPLE_CHILD_BUG2
= MockBug(bug_id
=2,
129 cf_total_budget
="10",
130 cf_nlnet_milestone
="milestone 1",
134 EXAMPLE_CONFIG
= Config
.from_str(
136 bugzilla_url = "https://bugzilla.example.com/"
138 aliases = ["person1_alias1", "alias1"]
139 output_markdown_file = "person1.mdwn"
141 aliases = ["person1_alias2", "alias2", "person 2"]
142 output_markdown_file = "person2.mdwn"
144 output_markdown_file = "person3.mdwn"
146 "milestone 1" = { canonical_bug_id = 1 }
147 "milestone 2" = { canonical_bug_id = 2 }
151 class TestBudgetGraph(unittest
.TestCase
):
154 def assertErrorTypesMatches(self
, errors
: List
[BudgetGraphBaseError
], template
: List
[Type
]):
155 def wrap_type_list(type_list
: List
[Type
]):
157 def __init__(self
, t
):
161 return self
.t
.__name
__
163 def __eq__(self
, other
):
164 return self
.t
== other
.t
165 return [TypeWrapper(i
) for i
in type_list
]
168 error_types
.append(type(error
))
169 self
.assertEqual(wrap_type_list(error_types
), wrap_type_list(template
))
171 def test_empty(self
):
172 bg
= BudgetGraph([], EXAMPLE_CONFIG
)
173 self
.assertEqual(len(bg
.nodes
), 0)
174 self
.assertEqual(len(bg
.roots
), 0)
175 self
.assertIs(bg
.config
, EXAMPLE_CONFIG
)
177 def test_single(self
):
178 bg
= BudgetGraph([EXAMPLE_BUG1
], EXAMPLE_CONFIG
)
179 self
.assertEqual(len(bg
.nodes
), 1)
180 node
: Node
= bg
.nodes
[1]
181 self
.assertEqual(bg
.roots
, {node}
)
182 self
.assertIsInstance(node
, Node
)
183 self
.assertIs(node
.graph
, bg
)
184 self
.assertIs(node
.bug
, EXAMPLE_BUG1
)
185 self
.assertIs(node
.root
, node
)
186 self
.assertIsNone(node
.parent_id
)
187 self
.assertEqual(node
.immediate_children
, set())
188 self
.assertEqual(node
.bug_url
,
189 "https://bugzilla.example.com/show_bug.cgi?id=1")
190 self
.assertEqual(node
.budget_excluding_subtasks
, Money(cents
=0))
191 self
.assertEqual(node
.budget_including_subtasks
, Money(cents
=0))
192 self
.assertIsNone(node
.milestone
)
193 self
.assertEqual(node
.payments
, {})
195 def test_loop1(self
):
196 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
197 BudgetGraph([EXAMPLE_LOOP1_BUG1
], EXAMPLE_CONFIG
).roots
198 self
.assertEqual(cm
.exception
.bug_ids
, [1])
200 def test_loop2(self
):
201 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
202 BudgetGraph([EXAMPLE_LOOP2_BUG1
, EXAMPLE_LOOP2_BUG2
],
203 EXAMPLE_CONFIG
).roots
204 self
.assertEqual(cm
.exception
.bug_ids
, [2, 1])
206 def test_parent_child(self
):
207 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
209 self
.assertEqual(len(bg
.nodes
), 2)
210 node1
: Node
= bg
.nodes
[1]
211 node2
: Node
= bg
.nodes
[2]
212 self
.assertEqual(bg
.roots
, {node1}
)
213 self
.assertEqual(node1
, node1
)
214 self
.assertEqual(node2
, node2
)
215 self
.assertNotEqual(node1
, node2
)
216 self
.assertNotEqual(node2
, node1
)
217 self
.assertIsInstance(node1
, Node
)
218 self
.assertIs(node1
.graph
, bg
)
219 self
.assertIs(node1
.bug
, EXAMPLE_PARENT_BUG1
)
220 self
.assertIsNone(node1
.parent_id
)
221 self
.assertEqual(node1
.root
, node1
)
222 self
.assertEqual(node1
.immediate_children
, {node2}
)
223 self
.assertEqual(node1
.budget_excluding_subtasks
, Money(cents
=1000))
224 self
.assertEqual(node1
.budget_including_subtasks
, Money(cents
=2000))
225 self
.assertEqual(node1
.milestone_str
, "milestone 1")
226 self
.assertEqual(node1
.bug_url
,
227 "https://bugzilla.example.com/show_bug.cgi?id=1")
228 self
.assertEqual(list(node1
.children()), [node2
])
229 self
.assertEqual(list(node1
.children_breadth_first()), [node2
])
230 self
.assertEqual(node1
.payments
, {})
231 self
.assertIsInstance(node2
, Node
)
232 self
.assertIs(node2
.graph
, bg
)
233 self
.assertIs(node2
.bug
, EXAMPLE_CHILD_BUG2
)
234 self
.assertEqual(node2
.parent_id
, 1)
235 self
.assertEqual(node2
.root
, node1
)
236 self
.assertEqual(node2
.immediate_children
, set())
237 self
.assertEqual(node2
.budget_excluding_subtasks
, Money(cents
=1000))
238 self
.assertEqual(node2
.budget_including_subtasks
, Money(cents
=1000))
239 self
.assertEqual(node2
.milestone_str
, "milestone 1")
240 self
.assertEqual(node2
.payments
, {})
241 self
.assertEqual(node2
.bug_url
,
242 "https://bugzilla.example.com/show_bug.cgi?id=2")
244 def test_children(self
):
247 cf_budget_parent
=None,
250 cf_nlnet_milestone
=None,
257 cf_nlnet_milestone
=None,
264 cf_nlnet_milestone
=None,
271 cf_nlnet_milestone
=None,
278 cf_nlnet_milestone
=None,
285 cf_nlnet_milestone
=None,
292 cf_nlnet_milestone
=None,
296 self
.assertEqual(len(bg
.nodes
), 7)
297 node1
: Node
= bg
.nodes
[1]
298 node2
: Node
= bg
.nodes
[2]
299 node3
: Node
= bg
.nodes
[3]
300 node4
: Node
= bg
.nodes
[4]
301 node5
: Node
= bg
.nodes
[5]
302 node6
: Node
= bg
.nodes
[6]
303 node7
: Node
= bg
.nodes
[7]
304 self
.assertEqual(bg
.roots
, {node1}
)
305 self
.assertEqual(list(node1
.children()),
306 [node2
, node3
, node5
, node7
, node6
, node4
])
307 self
.assertEqual(list(node1
.children_breadth_first()),
308 [node2
, node3
, node4
, node5
, node6
, node7
])
310 def test_money_with_no_milestone(self
):
313 cf_budget_parent
=None,
315 cf_total_budget
="10",
316 cf_nlnet_milestone
=None,
320 errors
= bg
.get_errors()
321 self
.assertErrorTypesMatches(errors
, [
322 BudgetGraphMoneyWithNoMilestone
,
323 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
324 self
.assertEqual(errors
[0].bug_id
, 1)
325 self
.assertEqual(errors
[0].root_bug_id
, 1)
328 cf_budget_parent
=None,
331 cf_nlnet_milestone
=None,
335 errors
= bg
.get_errors()
336 self
.assertErrorTypesMatches(errors
, [
337 BudgetGraphMoneyWithNoMilestone
,
338 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
339 self
.assertEqual(errors
[0].bug_id
, 1)
340 self
.assertEqual(errors
[0].root_bug_id
, 1)
343 cf_budget_parent
=None,
345 cf_total_budget
="10",
346 cf_nlnet_milestone
=None,
350 errors
= bg
.get_errors()
351 self
.assertErrorTypesMatches(errors
, [BudgetGraphMoneyWithNoMilestone
])
352 self
.assertEqual(errors
[0].bug_id
, 1)
353 self
.assertEqual(errors
[0].root_bug_id
, 1)
355 def test_money_mismatch(self
):
356 def helper(budget
, total_budget
, payees_list
, child_budget
,
357 expected_errors
, expected_fixed_error_types
=None):
358 if expected_fixed_error_types
is None:
359 expected_fixed_error_types
= []
362 cf_budget_parent
=None,
364 cf_total_budget
=total_budget
,
365 cf_nlnet_milestone
="milestone 1",
366 cf_payees_list
=payees_list
,
370 cf_budget
=child_budget
,
371 cf_total_budget
=child_budget
,
372 cf_nlnet_milestone
="milestone 1",
376 node1
: Node
= bg
.nodes
[1]
377 errors
= bg
.get_errors()
378 self
.assertErrorTypesMatches(errors
,
379 [type(i
) for i
in expected_errors
])
380 self
.assertEqual([str(i
) for i
in errors
],
381 [str(i
) for i
in expected_errors
])
384 cf_budget_parent
=None,
385 cf_budget
=str(node1
.fixed_budget_excluding_subtasks
),
387 node1
.fixed_budget_including_subtasks
),
388 cf_nlnet_milestone
="milestone 1",
389 cf_payees_list
=payees_list
,
393 cf_budget
=child_budget
,
394 cf_total_budget
=child_budget
,
395 cf_nlnet_milestone
="milestone 1",
399 errors
= bg
.get_errors()
400 self
.assertErrorTypesMatches(errors
,
401 expected_fixed_error_types
)
412 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
417 payees_list
="person1=1",
420 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
422 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
427 payees_list
="person1=1",
430 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
432 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
437 payees_list
="person1=10",
440 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
442 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
447 payees_list
="person1=10",
450 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
452 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
460 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
468 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
473 payees_list
="person1=1",
476 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
478 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
480 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
483 payees_list
="person1=1",
486 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
488 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
490 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
493 payees_list
="person1=10",
496 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
498 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
500 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
503 payees_list
="person1=10",
506 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
508 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
510 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
518 payees_list
="person1=1",
521 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
523 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
526 payees_list
="person1=10",
529 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
531 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
537 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
545 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
550 payees_list
="person1=1",
553 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
555 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
557 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
560 payees_list
="person1=1",
563 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
565 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
567 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
570 payees_list
="person1=10",
573 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
578 payees_list
="person1=10",
581 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
591 payees_list
="person1=1",
594 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
596 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
599 payees_list
="person1=10",
607 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
615 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
620 payees_list
="person1=1",
623 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
625 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
627 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
630 payees_list
="person1=1",
633 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
635 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
637 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
640 payees_list
="person1=10",
643 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
648 payees_list
="person1=10",
651 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
661 payees_list
="person1=1",
664 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
666 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
669 payees_list
="person1=10",
675 payees_list
="person1=10",
678 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
682 def test_negative_money(self
):
685 cf_budget_parent
=None,
687 cf_total_budget
="-10",
688 cf_nlnet_milestone
="milestone 1",
692 errors
= bg
.get_errors()
693 self
.assertErrorTypesMatches(errors
, [
694 BudgetGraphNegativeMoney
,
695 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
696 self
.assertEqual(errors
[0].bug_id
, 1)
697 self
.assertEqual(errors
[0].root_bug_id
, 1)
698 self
.assertEqual(errors
[1].bug_id
, 1)
699 self
.assertEqual(errors
[1].root_bug_id
, 1)
700 self
.assertEqual(errors
[1].expected_budget_excluding_subtasks
, -10)
703 cf_budget_parent
=None,
706 cf_nlnet_milestone
="milestone 1",
710 errors
= bg
.get_errors()
711 self
.assertErrorTypesMatches(errors
, [
712 BudgetGraphNegativeMoney
,
713 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
714 self
.assertEqual(errors
[0].bug_id
, 1)
715 self
.assertEqual(errors
[0].root_bug_id
, 1)
716 self
.assertEqual(errors
[1].bug_id
, 1)
717 self
.assertEqual(errors
[1].root_bug_id
, 1)
718 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, -10)
721 cf_budget_parent
=None,
723 cf_total_budget
="-10",
724 cf_nlnet_milestone
="milestone 1",
728 errors
= bg
.get_errors()
729 self
.assertErrorTypesMatches(errors
,
730 [BudgetGraphNegativeMoney
])
731 self
.assertEqual(errors
[0].bug_id
, 1)
732 self
.assertEqual(errors
[0].root_bug_id
, 1)
734 def test_payees_parse(self
):
735 def check(cf_payees_list
, error_types
, expected_payments
):
736 bg
= BudgetGraph([MockBug(bug_id
=1,
737 cf_budget_parent
=None,
740 cf_nlnet_milestone
="milestone 1",
741 cf_payees_list
=cf_payees_list
,
744 self
.assertErrorTypesMatches(bg
.get_errors(), error_types
)
745 self
.assertEqual(len(bg
.nodes
), 1)
746 node
: Node
= bg
.nodes
[1]
747 self
.assertEqual([str(i
) for i
in node
.payments
.values()],
754 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
755 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
756 ["Payment(node=#1, payee=Person<'person1'>, "
757 "payee_key='person1', amount=123, "
758 "state=NotYetSubmitted, paid=None, submitted=None)"])
763 [BudgetGraphPayeesParseError
,
764 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
765 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
766 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
767 "amount=123, state=NotYetSubmitted, paid=None, "
773 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
774 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
775 ["Payment(node=#1, payee=Person<'person1'>, "
776 "payee_key='person1', amount=123.45, "
777 "state=NotYetSubmitted, paid=None, submitted=None)"])
783 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
784 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
785 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
786 'amount=123.45, state=NotYetSubmitted, paid=None, '
788 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
789 'amount=21.35, state=NotYetSubmitted, paid=None, '
796 [BudgetGraphPayeesParseError
,
797 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
798 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
799 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
800 'amount=123.45, state=NotYetSubmitted, paid=None, '
802 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
803 'amount=21.35, state=NotYetSubmitted, paid=None, '
811 [BudgetGraphPayeesParseError
,
812 BudgetGraphNegativePayeeMoney
,
813 BudgetGraphPayeesParseError
,
814 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
815 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
816 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
817 'amount=123.45, state=NotYetSubmitted, paid=None, '
819 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
820 'amount=-21.35, state=NotYetSubmitted, paid=None, '
824 "not-an-email@example.com" = "-2345"
826 [BudgetGraphNegativePayeeMoney
,
827 BudgetGraphPayeesParseError
,
828 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
829 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
830 ['Payment(node=#1, payee=<unknown person>, '
831 "payee_key='not-an-email@example.com', amount=-2345, "
832 "state=NotYetSubmitted, paid=None, submitted=None)"])
835 person1 = { amount = 123 }
837 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
838 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
839 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
840 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
843 person1 = { amount = 123, submitted = 2020-05-01 }
845 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
846 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
847 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
848 + "amount=123, state=Submitted, paid=None, "
849 + "submitted=2020-05-01)"])
852 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
854 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
855 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
856 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
857 + "amount=123, state=Submitted, paid=None, "
858 + "submitted=2020-05-01 00:00:00)"])
861 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
863 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
864 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
865 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
866 + "amount=123, state=Submitted, paid=None, "
867 + "submitted=2020-05-01 00:00:00+00:00)"])
870 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
872 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
873 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
874 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
875 + "amount=123, state=Submitted, paid=None, "
876 + "submitted=2020-05-01 00:00:00-07:23)"])
879 person1 = { amount = 123, paid = 2020-05-01 }
881 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
882 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
883 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
884 + "amount=123, state=Paid, paid=2020-05-01, "
885 + "submitted=None)"])
888 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
890 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
891 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
892 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
893 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
894 + "submitted=None)"])
897 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
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+00:00, "
903 + "submitted=None)"])
906 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
908 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
909 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
910 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
911 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
912 + "submitted=None)"])
917 submitted = 2020-05-23
920 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
921 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
922 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
923 + "amount=123, state=Paid, paid=2020-05-01, "
924 + "submitted=2020-05-23)"])
929 submitted = 2020-05-23
930 paid = 2020-05-01T00:00:00
932 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
933 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
934 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
935 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
936 + "submitted=2020-05-23)"])
941 submitted = 2020-05-23
942 paid = 2020-05-01T00:00:00Z
944 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
945 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
946 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
947 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
948 + "submitted=2020-05-23)"])
953 submitted = 2020-05-23
954 paid = 2020-05-01T00:00:00-07:23
956 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
957 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
958 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
959 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
960 + "submitted=2020-05-23)"])
962 def test_payees_money_mismatch(self
):
965 cf_budget_parent
=None,
967 cf_total_budget
="10",
968 cf_nlnet_milestone
="milestone 1",
969 cf_payees_list
="person1 = 5\nperson2 = 10",
972 errors
= bg
.get_errors()
973 self
.assertErrorTypesMatches(errors
,
974 [BudgetGraphPayeesMoneyMismatch
])
975 self
.assertEqual(errors
[0].bug_id
, 1)
976 self
.assertEqual(errors
[0].root_bug_id
, 1)
977 self
.assertEqual(errors
[0].payees_total
, 15)
979 def test_payees_parse_error(self
):
980 def check_parse_error(cf_payees_list
, expected_msg
):
981 errors
= BudgetGraph([
983 cf_budget_parent
=None,
986 cf_nlnet_milestone
="milestone 1",
987 cf_payees_list
=cf_payees_list
,
989 ], EXAMPLE_CONFIG
).get_errors()
990 self
.assertErrorTypesMatches(errors
,
991 [BudgetGraphPayeesParseError
])
992 self
.assertEqual(errors
[0].bug_id
, 1)
993 self
.assertEqual(errors
[0].msg
, expected_msg
)
995 check_parse_error("""
998 "value for key 'payee 1' is invalid -- it should "
999 "either be a monetary value or a table")
1001 check_parse_error("""
1004 "failed to parse monetary amount for key 'payee': "
1005 "invalid Money string: characters after sign and "
1006 "before first `.` must be ascii digits")
1008 check_parse_error("""
1012 "TOML parse error: Duplicate keys! (line 3"
1013 " column 1 char 39)")
1015 check_parse_error("""
1018 "failed to parse monetary amount for key 'payee': "
1019 "monetary amount is not a string or integer (to "
1020 "use fractional amounts such as 123.45, write "
1021 "\"123.45\"): 123.45")
1023 check_parse_error("""
1026 "value for key 'payee' is missing the `amount` "
1027 "field which is required")
1029 check_parse_error("""
1030 payee = { amount = 123.45 }
1032 "failed to parse monetary amount for key 'payee': "
1033 "monetary amount is not a string or integer (to "
1034 "use fractional amounts such as 123.45, write "
1035 "\"123.45\"): 123.45")
1037 check_parse_error("""
1038 payee = { amount = 123, blah = false }
1040 "value for key 'payee' has an unknown field: `blah`")
1042 check_parse_error("""
1043 payee = { amount = 123, submitted = false }
1045 "failed to parse `submitted` field for key "
1046 "'payee': invalid date: false")
1048 check_parse_error("""
1049 payee = { amount = 123, submitted = 123 }
1051 "failed to parse `submitted` field for key 'payee':"
1052 " invalid date: 123")
1056 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1058 "failed to parse `submitted` field for key 'payee': "
1059 "invalid date: 'abc'")
1063 payee = { amount = 123, paid = 12:34:56 }
1065 "failed to parse `paid` field for key 'payee': just a time of "
1066 "day by itself is not enough, a date must be included: 12:34:56")
1070 payee = { amount = 123, submitted = 12:34:56.123456 }
1072 "failed to parse `submitted` field for key 'payee': just a time "
1073 "of day by itself is not enough, a date must be included: "
1076 def test_negative_payee_money(self
):
1079 cf_budget_parent
=None,
1081 cf_total_budget
="10",
1082 cf_nlnet_milestone
="milestone 1",
1083 cf_payees_list
="""person1 = -10""",
1086 errors
= bg
.get_errors()
1087 self
.assertErrorTypesMatches(errors
,
1088 [BudgetGraphNegativePayeeMoney
,
1089 BudgetGraphPayeesMoneyMismatch
])
1090 self
.assertEqual(errors
[0].bug_id
, 1)
1091 self
.assertEqual(errors
[0].root_bug_id
, 1)
1092 self
.assertEqual(errors
[0].payee_key
, "person1")
1093 self
.assertEqual(errors
[1].bug_id
, 1)
1094 self
.assertEqual(errors
[1].root_bug_id
, 1)
1095 self
.assertEqual(errors
[1].payees_total
, -10)
1097 def test_duplicate_payments(self
):
1100 cf_budget_parent
=None,
1102 cf_total_budget
="10",
1103 cf_nlnet_milestone
="milestone 1",
1110 errors
= bg
.get_errors()
1111 self
.assertErrorTypesMatches(errors
,
1112 [BudgetGraphDuplicatePayeesForTask
])
1113 self
.assertEqual(errors
[0].bug_id
, 1)
1114 self
.assertEqual(errors
[0].root_bug_id
, 1)
1115 self
.assertEqual(errors
[0].payee1_key
, "person1")
1116 self
.assertEqual(errors
[0].payee2_key
, "alias1")
1118 def test_incorrect_root_for_milestone(self
):
1121 cf_budget_parent
=None,
1123 cf_total_budget
="10",
1124 cf_nlnet_milestone
="milestone 2",
1128 errors
= bg
.get_errors()
1129 self
.assertErrorTypesMatches(errors
,
1130 [BudgetGraphIncorrectRootForMilestone
])
1131 self
.assertEqual(errors
[0].bug_id
, 1)
1132 self
.assertEqual(errors
[0].root_bug_id
, 1)
1133 self
.assertEqual(errors
[0].milestone
, "milestone 2")
1134 self
.assertEqual(errors
[0].milestone_canonical_bug_id
, 2)
1137 cf_budget_parent
=None,
1139 cf_total_budget
="0",
1140 cf_nlnet_milestone
="milestone 2",
1144 errors
= bg
.get_errors()
1145 self
.assertErrorTypesMatches(errors
, [])
1147 def test_payments(self
):
1150 cf_budget_parent
=None,
1152 cf_total_budget
="10",
1153 cf_nlnet_milestone
="milestone 1",
1154 cf_payees_list
="person1 = 3\nperson2 = 7",
1157 cf_budget_parent
=None,
1159 cf_total_budget
="10",
1160 cf_nlnet_milestone
="milestone 2",
1161 cf_payees_list
="person3 = 5\nperson2 = 5",
1164 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1165 person1
= EXAMPLE_CONFIG
.people
["person1"]
1166 person2
= EXAMPLE_CONFIG
.people
["person2"]
1167 person3
= EXAMPLE_CONFIG
.people
["person3"]
1168 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1169 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1170 node1
: Node
= bg
.nodes
[1]
1171 node2
: Node
= bg
.nodes
[2]
1172 node1_payment_person1
= node1
.payments
["person1"]
1173 node1_payment_person2
= node1
.payments
["person2"]
1174 node2_payment_person2
= node2
.payments
["person2"]
1175 node2_payment_person3
= node2
.payments
["person3"]
1176 self
.assertEqual(bg
.payments
,
1179 milestone1
: [node1_payment_person1
],
1183 milestone1
: [node1_payment_person2
],
1184 milestone2
: [node2_payment_person2
],
1188 milestone2
: [node2_payment_person3
],
1193 if __name__
== "__main__":