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(
130 bugzilla_url = "https://bugzilla.example.com/"
132 aliases = ["person1_alias1", "alias1"]
133 output_markdown_file = "person1.mdwn"
135 aliases = ["person1_alias2", "alias2", "person 2"]
136 output_markdown_file = "person2.mdwn"
138 output_markdown_file = "person3.mdwn"
140 "milestone 1" = { canonical_bug_id = 1 }
141 "milestone 2" = { canonical_bug_id = 2 }
145 class TestBudgetGraph(unittest
.TestCase
):
148 def assertErrorTypesMatches(self
, errors
: List
[BudgetGraphBaseError
], template
: List
[Type
]):
149 def wrap_type_list(type_list
: List
[Type
]):
151 def __init__(self
, t
):
155 return self
.t
.__name
__
157 def __eq__(self
, other
):
158 return self
.t
== other
.t
159 return [TypeWrapper(i
) for i
in type_list
]
162 error_types
.append(type(error
))
163 self
.assertEqual(wrap_type_list(error_types
), wrap_type_list(template
))
165 def test_empty(self
):
166 bg
= BudgetGraph([], EXAMPLE_CONFIG
)
167 self
.assertEqual(len(bg
.nodes
), 0)
168 self
.assertEqual(len(bg
.roots
), 0)
169 self
.assertIs(bg
.config
, EXAMPLE_CONFIG
)
171 def test_single(self
):
172 bg
= BudgetGraph([EXAMPLE_BUG1
], EXAMPLE_CONFIG
)
173 self
.assertEqual(len(bg
.nodes
), 1)
174 node
: Node
= bg
.nodes
[1]
175 self
.assertEqual(bg
.roots
, {node}
)
176 self
.assertIsInstance(node
, Node
)
177 self
.assertIs(node
.graph
, bg
)
178 self
.assertIs(node
.bug
, EXAMPLE_BUG1
)
179 self
.assertIs(node
.root
, node
)
180 self
.assertIsNone(node
.parent_id
)
181 self
.assertEqual(node
.immediate_children
, set())
182 self
.assertEqual(node
.bug_url
,
183 "https://bugzilla.example.com/show_bug.cgi?id=1")
184 self
.assertEqual(node
.budget_excluding_subtasks
, Money(cents
=0))
185 self
.assertEqual(node
.budget_including_subtasks
, Money(cents
=0))
186 self
.assertIsNone(node
.milestone
)
187 self
.assertEqual(node
.payments
, {})
189 def test_loop1(self
):
190 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
191 BudgetGraph([EXAMPLE_LOOP1_BUG1
], EXAMPLE_CONFIG
).roots
192 self
.assertEqual(cm
.exception
.bug_ids
, [1])
194 def test_loop2(self
):
195 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
196 BudgetGraph([EXAMPLE_LOOP2_BUG1
, EXAMPLE_LOOP2_BUG2
],
197 EXAMPLE_CONFIG
).roots
198 self
.assertEqual(cm
.exception
.bug_ids
, [2, 1])
200 def test_parent_child(self
):
201 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
203 self
.assertEqual(len(bg
.nodes
), 2)
204 node1
: Node
= bg
.nodes
[1]
205 node2
: Node
= bg
.nodes
[2]
206 self
.assertEqual(bg
.roots
, {node1}
)
207 self
.assertEqual(node1
, node1
)
208 self
.assertEqual(node2
, node2
)
209 self
.assertNotEqual(node1
, node2
)
210 self
.assertNotEqual(node2
, node1
)
211 self
.assertIsInstance(node1
, Node
)
212 self
.assertIs(node1
.graph
, bg
)
213 self
.assertIs(node1
.bug
, EXAMPLE_PARENT_BUG1
)
214 self
.assertIsNone(node1
.parent_id
)
215 self
.assertEqual(node1
.root
, node1
)
216 self
.assertEqual(node1
.immediate_children
, {node2}
)
217 self
.assertEqual(node1
.budget_excluding_subtasks
, Money(cents
=1000))
218 self
.assertEqual(node1
.budget_including_subtasks
, Money(cents
=2000))
219 self
.assertEqual(node1
.milestone_str
, "milestone 1")
220 self
.assertEqual(node1
.bug_url
,
221 "https://bugzilla.example.com/show_bug.cgi?id=1")
222 self
.assertEqual(list(node1
.children()), [node2
])
223 self
.assertEqual(list(node1
.children_breadth_first()), [node2
])
224 self
.assertEqual(node1
.payments
, {})
225 self
.assertIsInstance(node2
, Node
)
226 self
.assertIs(node2
.graph
, bg
)
227 self
.assertIs(node2
.bug
, EXAMPLE_CHILD_BUG2
)
228 self
.assertEqual(node2
.parent_id
, 1)
229 self
.assertEqual(node2
.root
, node1
)
230 self
.assertEqual(node2
.immediate_children
, set())
231 self
.assertEqual(node2
.budget_excluding_subtasks
, Money(cents
=1000))
232 self
.assertEqual(node2
.budget_including_subtasks
, Money(cents
=1000))
233 self
.assertEqual(node2
.milestone_str
, "milestone 1")
234 self
.assertEqual(node2
.payments
, {})
235 self
.assertEqual(node2
.bug_url
,
236 "https://bugzilla.example.com/show_bug.cgi?id=2")
238 def test_children(self
):
241 cf_budget_parent
=None,
244 cf_nlnet_milestone
=None,
250 cf_nlnet_milestone
=None,
256 cf_nlnet_milestone
=None,
262 cf_nlnet_milestone
=None,
268 cf_nlnet_milestone
=None,
274 cf_nlnet_milestone
=None,
280 cf_nlnet_milestone
=None,
283 self
.assertEqual(len(bg
.nodes
), 7)
284 node1
: Node
= bg
.nodes
[1]
285 node2
: Node
= bg
.nodes
[2]
286 node3
: Node
= bg
.nodes
[3]
287 node4
: Node
= bg
.nodes
[4]
288 node5
: Node
= bg
.nodes
[5]
289 node6
: Node
= bg
.nodes
[6]
290 node7
: Node
= bg
.nodes
[7]
291 self
.assertEqual(bg
.roots
, {node1}
)
292 self
.assertEqual(list(node1
.children()),
293 [node2
, node3
, node5
, node7
, node6
, node4
])
294 self
.assertEqual(list(node1
.children_breadth_first()),
295 [node2
, node3
, node4
, node5
, node6
, node7
])
297 def test_money_with_no_milestone(self
):
300 cf_budget_parent
=None,
302 cf_total_budget
="10",
303 cf_nlnet_milestone
=None,
306 errors
= bg
.get_errors()
307 self
.assertErrorTypesMatches(errors
, [
308 BudgetGraphMoneyWithNoMilestone
,
309 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
310 self
.assertEqual(errors
[0].bug_id
, 1)
311 self
.assertEqual(errors
[0].root_bug_id
, 1)
314 cf_budget_parent
=None,
317 cf_nlnet_milestone
=None,
320 errors
= bg
.get_errors()
321 self
.assertErrorTypesMatches(errors
, [
322 BudgetGraphMoneyWithNoMilestone
,
323 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
324 self
.assertEqual(errors
[0].bug_id
, 1)
325 self
.assertEqual(errors
[0].root_bug_id
, 1)
328 cf_budget_parent
=None,
330 cf_total_budget
="10",
331 cf_nlnet_milestone
=None,
334 errors
= bg
.get_errors()
335 self
.assertErrorTypesMatches(errors
, [BudgetGraphMoneyWithNoMilestone
])
336 self
.assertEqual(errors
[0].bug_id
, 1)
337 self
.assertEqual(errors
[0].root_bug_id
, 1)
339 def test_money_mismatch(self
):
340 def helper(budget
, total_budget
, payees_list
, child_budget
,
341 expected_errors
, expected_fixed_error_types
=None):
342 if expected_fixed_error_types
is None:
343 expected_fixed_error_types
= []
346 cf_budget_parent
=None,
348 cf_total_budget
=total_budget
,
349 cf_nlnet_milestone
="milestone 1",
350 cf_payees_list
=payees_list
),
353 cf_budget
=child_budget
,
354 cf_total_budget
=child_budget
,
355 cf_nlnet_milestone
="milestone 1",
358 node1
: Node
= bg
.nodes
[1]
359 errors
= bg
.get_errors()
360 self
.assertErrorTypesMatches(errors
,
361 [type(i
) for i
in expected_errors
])
362 self
.assertEqual([str(i
) for i
in errors
],
363 [str(i
) for i
in expected_errors
])
366 cf_budget_parent
=None,
367 cf_budget
=str(node1
.fixed_budget_excluding_subtasks
),
369 node1
.fixed_budget_including_subtasks
),
370 cf_nlnet_milestone
="milestone 1",
371 cf_payees_list
=payees_list
),
374 cf_budget
=child_budget
,
375 cf_total_budget
=child_budget
,
376 cf_nlnet_milestone
="milestone 1",
379 errors
= bg
.get_errors()
380 self
.assertErrorTypesMatches(errors
,
381 expected_fixed_error_types
)
392 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
397 payees_list
="person1=1",
400 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
402 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
407 payees_list
="person1=1",
410 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
412 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
417 payees_list
="person1=10",
420 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
422 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
427 payees_list
="person1=10",
430 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
432 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
440 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
448 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
453 payees_list
="person1=1",
456 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
458 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
460 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
463 payees_list
="person1=1",
466 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
468 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
470 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
473 payees_list
="person1=10",
476 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
478 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
480 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
483 payees_list
="person1=10",
486 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
488 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
490 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
498 payees_list
="person1=1",
501 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
503 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
506 payees_list
="person1=10",
509 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
511 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
517 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
525 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
530 payees_list
="person1=1",
533 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
535 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
537 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
540 payees_list
="person1=1",
543 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
545 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
547 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
550 payees_list
="person1=10",
553 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
558 payees_list
="person1=10",
561 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
571 payees_list
="person1=1",
574 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
576 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
579 payees_list
="person1=10",
587 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
595 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
600 payees_list
="person1=1",
603 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
605 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
607 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
610 payees_list
="person1=1",
613 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
615 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
617 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
620 payees_list
="person1=10",
623 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
628 payees_list
="person1=10",
631 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
641 payees_list
="person1=1",
644 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
646 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
649 payees_list
="person1=10",
655 payees_list
="person1=10",
658 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
662 def test_negative_money(self
):
665 cf_budget_parent
=None,
667 cf_total_budget
="-10",
668 cf_nlnet_milestone
="milestone 1",
671 errors
= bg
.get_errors()
672 self
.assertErrorTypesMatches(errors
, [
673 BudgetGraphNegativeMoney
,
674 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
675 self
.assertEqual(errors
[0].bug_id
, 1)
676 self
.assertEqual(errors
[0].root_bug_id
, 1)
677 self
.assertEqual(errors
[1].bug_id
, 1)
678 self
.assertEqual(errors
[1].root_bug_id
, 1)
679 self
.assertEqual(errors
[1].expected_budget_excluding_subtasks
, -10)
682 cf_budget_parent
=None,
685 cf_nlnet_milestone
="milestone 1",
688 errors
= bg
.get_errors()
689 self
.assertErrorTypesMatches(errors
, [
690 BudgetGraphNegativeMoney
,
691 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
692 self
.assertEqual(errors
[0].bug_id
, 1)
693 self
.assertEqual(errors
[0].root_bug_id
, 1)
694 self
.assertEqual(errors
[1].bug_id
, 1)
695 self
.assertEqual(errors
[1].root_bug_id
, 1)
696 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, -10)
699 cf_budget_parent
=None,
701 cf_total_budget
="-10",
702 cf_nlnet_milestone
="milestone 1",
705 errors
= bg
.get_errors()
706 self
.assertErrorTypesMatches(errors
,
707 [BudgetGraphNegativeMoney
])
708 self
.assertEqual(errors
[0].bug_id
, 1)
709 self
.assertEqual(errors
[0].root_bug_id
, 1)
711 def test_payees_parse(self
):
712 def check(cf_payees_list
, error_types
, expected_payments
):
713 bg
= BudgetGraph([MockBug(bug_id
=1,
714 cf_budget_parent
=None,
717 cf_nlnet_milestone
="milestone 1",
718 cf_payees_list
=cf_payees_list
),
720 self
.assertErrorTypesMatches(bg
.get_errors(), error_types
)
721 self
.assertEqual(len(bg
.nodes
), 1)
722 node
: Node
= bg
.nodes
[1]
723 self
.assertEqual([str(i
) for i
in node
.payments
.values()],
730 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
731 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
732 ["Payment(node=#1, payee=Person<'person1'>, "
733 "payee_key='person1', amount=123, "
734 "state=NotYetSubmitted, paid=None, submitted=None)"])
739 [BudgetGraphPayeesParseError
,
740 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
741 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
742 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
743 "amount=123, state=NotYetSubmitted, paid=None, "
749 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
750 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
751 ["Payment(node=#1, payee=Person<'person1'>, "
752 "payee_key='person1', amount=123.45, "
753 "state=NotYetSubmitted, paid=None, submitted=None)"])
759 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
760 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
761 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
762 'amount=123.45, state=NotYetSubmitted, paid=None, '
764 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
765 'amount=21.35, state=NotYetSubmitted, paid=None, '
772 [BudgetGraphPayeesParseError
,
773 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
774 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
775 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
776 'amount=123.45, state=NotYetSubmitted, paid=None, '
778 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
779 'amount=21.35, state=NotYetSubmitted, paid=None, '
787 [BudgetGraphPayeesParseError
,
788 BudgetGraphNegativePayeeMoney
,
789 BudgetGraphPayeesParseError
,
790 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
791 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
792 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
793 'amount=123.45, state=NotYetSubmitted, paid=None, '
795 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
796 'amount=-21.35, state=NotYetSubmitted, paid=None, '
800 "not-an-email@example.com" = "-2345"
802 [BudgetGraphNegativePayeeMoney
,
803 BudgetGraphPayeesParseError
,
804 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
805 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
806 ['Payment(node=#1, payee=<unknown person>, '
807 "payee_key='not-an-email@example.com', amount=-2345, "
808 "state=NotYetSubmitted, paid=None, submitted=None)"])
811 person1 = { amount = 123 }
813 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
814 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
815 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
816 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
819 person1 = { amount = 123, submitted = 2020-05-01 }
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)"])
828 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
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)"])
837 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
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+00:00)"])
846 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
848 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
849 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
850 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
851 + "amount=123, state=Submitted, paid=None, "
852 + "submitted=2020-05-01 00:00:00-07:23)"])
855 person1 = { amount = 123, paid = 2020-05-01 }
857 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
858 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
859 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
860 + "amount=123, state=Paid, paid=2020-05-01, "
861 + "submitted=None)"])
864 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
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, "
870 + "submitted=None)"])
873 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
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+00:00, "
879 + "submitted=None)"])
882 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
884 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
885 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
886 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
887 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
888 + "submitted=None)"])
893 submitted = 2020-05-23
896 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
897 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
898 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
899 + "amount=123, state=Paid, paid=2020-05-01, "
900 + "submitted=2020-05-23)"])
905 submitted = 2020-05-23
906 paid = 2020-05-01T00:00:00
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, "
912 + "submitted=2020-05-23)"])
917 submitted = 2020-05-23
918 paid = 2020-05-01T00:00:00Z
920 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
921 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
922 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
923 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
924 + "submitted=2020-05-23)"])
929 submitted = 2020-05-23
930 paid = 2020-05-01T00:00:00-07:23
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-07:23, "
936 + "submitted=2020-05-23)"])
938 def test_payees_money_mismatch(self
):
941 cf_budget_parent
=None,
943 cf_total_budget
="10",
944 cf_nlnet_milestone
="milestone 1",
945 cf_payees_list
="person1 = 5\nperson2 = 10"),
947 errors
= bg
.get_errors()
948 self
.assertErrorTypesMatches(errors
,
949 [BudgetGraphPayeesMoneyMismatch
])
950 self
.assertEqual(errors
[0].bug_id
, 1)
951 self
.assertEqual(errors
[0].root_bug_id
, 1)
952 self
.assertEqual(errors
[0].payees_total
, 15)
954 def test_payees_parse_error(self
):
955 def check_parse_error(cf_payees_list
, expected_msg
):
956 errors
= BudgetGraph([
958 cf_budget_parent
=None,
961 cf_nlnet_milestone
="milestone 1",
962 cf_payees_list
=cf_payees_list
),
963 ], EXAMPLE_CONFIG
).get_errors()
964 self
.assertErrorTypesMatches(errors
,
965 [BudgetGraphPayeesParseError
])
966 self
.assertEqual(errors
[0].bug_id
, 1)
967 self
.assertEqual(errors
[0].msg
, expected_msg
)
969 check_parse_error("""
972 "value for key 'payee 1' is invalid -- it should "
973 "either be a monetary value or a table")
975 check_parse_error("""
978 "failed to parse monetary amount for key 'payee': "
979 "invalid Money string: characters after sign and "
980 "before first `.` must be ascii digits")
982 check_parse_error("""
986 "TOML parse error: Duplicate keys! (line 3"
987 " column 1 char 39)")
989 check_parse_error("""
992 "failed to parse monetary amount for key 'payee': "
993 "monetary amount is not a string or integer (to "
994 "use fractional amounts such as 123.45, write "
995 "\"123.45\"): 123.45")
997 check_parse_error("""
1000 "value for key 'payee' is missing the `amount` "
1001 "field which is required")
1003 check_parse_error("""
1004 payee = { amount = 123.45 }
1006 "failed to parse monetary amount for key 'payee': "
1007 "monetary amount is not a string or integer (to "
1008 "use fractional amounts such as 123.45, write "
1009 "\"123.45\"): 123.45")
1011 check_parse_error("""
1012 payee = { amount = 123, blah = false }
1014 "value for key 'payee' has an unknown field: `blah`")
1016 check_parse_error("""
1017 payee = { amount = 123, submitted = false }
1019 "failed to parse `submitted` field for key "
1020 "'payee': invalid date: false")
1022 check_parse_error("""
1023 payee = { amount = 123, submitted = 123 }
1025 "failed to parse `submitted` field for key 'payee':"
1026 " invalid date: 123")
1030 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1032 "failed to parse `submitted` field for key 'payee': "
1033 "invalid date: 'abc'")
1037 payee = { amount = 123, paid = 12:34:56 }
1039 "failed to parse `paid` field for key 'payee': just a time of "
1040 "day by itself is not enough, a date must be included: 12:34:56")
1044 payee = { amount = 123, submitted = 12:34:56.123456 }
1046 "failed to parse `submitted` field for key 'payee': just a time "
1047 "of day by itself is not enough, a date must be included: "
1050 def test_negative_payee_money(self
):
1053 cf_budget_parent
=None,
1055 cf_total_budget
="10",
1056 cf_nlnet_milestone
="milestone 1",
1057 cf_payees_list
="""person1 = -10"""),
1059 errors
= bg
.get_errors()
1060 self
.assertErrorTypesMatches(errors
,
1061 [BudgetGraphNegativePayeeMoney
,
1062 BudgetGraphPayeesMoneyMismatch
])
1063 self
.assertEqual(errors
[0].bug_id
, 1)
1064 self
.assertEqual(errors
[0].root_bug_id
, 1)
1065 self
.assertEqual(errors
[0].payee_key
, "person1")
1066 self
.assertEqual(errors
[1].bug_id
, 1)
1067 self
.assertEqual(errors
[1].root_bug_id
, 1)
1068 self
.assertEqual(errors
[1].payees_total
, -10)
1070 def test_duplicate_payments(self
):
1073 cf_budget_parent
=None,
1075 cf_total_budget
="10",
1076 cf_nlnet_milestone
="milestone 1",
1082 errors
= bg
.get_errors()
1083 self
.assertErrorTypesMatches(errors
,
1084 [BudgetGraphDuplicatePayeesForTask
])
1085 self
.assertEqual(errors
[0].bug_id
, 1)
1086 self
.assertEqual(errors
[0].root_bug_id
, 1)
1087 self
.assertEqual(errors
[0].payee1_key
, "person1")
1088 self
.assertEqual(errors
[0].payee2_key
, "alias1")
1090 def test_incorrect_root_for_milestone(self
):
1093 cf_budget_parent
=None,
1095 cf_total_budget
="10",
1096 cf_nlnet_milestone
="milestone 2",
1099 errors
= bg
.get_errors()
1100 self
.assertErrorTypesMatches(errors
,
1101 [BudgetGraphIncorrectRootForMilestone
])
1102 self
.assertEqual(errors
[0].bug_id
, 1)
1103 self
.assertEqual(errors
[0].root_bug_id
, 1)
1104 self
.assertEqual(errors
[0].milestone
, "milestone 2")
1105 self
.assertEqual(errors
[0].milestone_canonical_bug_id
, 2)
1108 cf_budget_parent
=None,
1110 cf_total_budget
="0",
1111 cf_nlnet_milestone
="milestone 2",
1114 errors
= bg
.get_errors()
1115 self
.assertErrorTypesMatches(errors
, [])
1117 def test_payments(self
):
1120 cf_budget_parent
=None,
1122 cf_total_budget
="10",
1123 cf_nlnet_milestone
="milestone 1",
1124 cf_payees_list
="person1 = 3\nperson2 = 7"),
1126 cf_budget_parent
=None,
1128 cf_total_budget
="10",
1129 cf_nlnet_milestone
="milestone 2",
1130 cf_payees_list
="person3 = 5\nperson2 = 5"),
1132 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1133 person1
= EXAMPLE_CONFIG
.people
["person1"]
1134 person2
= EXAMPLE_CONFIG
.people
["person2"]
1135 person3
= EXAMPLE_CONFIG
.people
["person3"]
1136 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1137 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1138 node1
: Node
= bg
.nodes
[1]
1139 node2
: Node
= bg
.nodes
[2]
1140 node1_payment_person1
= node1
.payments
["person1"]
1141 node1_payment_person2
= node1
.payments
["person2"]
1142 node2_payment_person2
= node2
.payments
["person2"]
1143 node2_payment_person3
= node2
.payments
["person3"]
1144 self
.assertEqual(bg
.payments
,
1147 milestone1
: [node1_payment_person1
],
1151 milestone1
: [node1_payment_person2
],
1152 milestone2
: [node2_payment_person2
],
1156 milestone2
: [node2_payment_person3
],
1161 if __name__
== "__main__":