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 BudgetGraphUnknownStatus
)
12 from budget_sync
.money
import Money
13 from budget_sync
.util
import BugStatus
14 from typing
import List
, Type
18 class TestErrorFormatting(unittest
.TestCase
):
19 def test_budget_graph_incorrect_root_for_milestone(self
):
20 self
.assertEqual(str(BudgetGraphIncorrectRootForMilestone(
21 2, "milestone 1", 1)),
22 "Bug #2 is not the canonical root bug for assigned milestone "
23 "'milestone 1' but has no parent bug set: the milestone's "
24 "canonical root bug is #1")
26 def test_budget_graph_duplicate_payees_for_task(self
):
27 self
.assertEqual(str(BudgetGraphDuplicatePayeesForTask(
28 2, 1, "alias1", "alias2")),
29 "Budget assigned to multiple aliases of the same person in a "
30 "single task: bug #2, budget assigned to both 'alias1' "
33 def test_budget_graph_loop_error(self
):
34 self
.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])),
35 "Detected Loop in Budget Graph: #5 -> #1 "
36 "-> #2 -> #3 -> #4 -> #5")
37 self
.assertEqual(str(BudgetGraphLoopError([1])),
38 "Detected Loop in Budget Graph: #1 -> #1")
40 def test_budget_graph_money_with_no_milestone(self
):
41 self
.assertEqual(str(BudgetGraphMoneyWithNoMilestone(1, 5)),
42 "Bug assigned money but without any assigned "
45 def test_budget_graph_milestone_mismatch(self
):
46 self
.assertEqual(str(BudgetGraphMilestoneMismatch(1, 5)),
47 "Bug's assigned milestone doesn't match the "
48 "milestone assigned to the root bug: descendant "
49 "bug #1, root bug #5")
51 def test_budget_graph_unknown_milestone(self
):
52 self
.assertEqual(str(BudgetGraphUnknownMilestone(
53 123, "fake milestone")),
54 "failed to parse cf_nlnet_milestone field of bug "
55 "#123: unknown milestone: 'fake milestone'")
57 def test_budget_graph_unknown_status(self
):
58 self
.assertEqual(str(BudgetGraphUnknownStatus(
60 "failed to parse status field of bug "
61 "#123: unknown status: 'fake status'")
63 def test_budget_graph_money_mismatch(self
):
65 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
67 "Budget assigned to task excluding subtasks "
68 "(cf_budget field) doesn't match calculated value:"
69 " bug #1, calculated value 123.4")
71 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
73 "Budget assigned to task including subtasks "
74 "(cf_total_budget field) doesn't match calculated value:"
75 " bug #1, calculated value 123.4")
77 def test_budget_graph_negative_money(self
):
78 self
.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
79 "Budget assigned to task is less than zero: bug #1")
81 def test_budget_graph_negative_payee_money(self
):
82 self
.assertEqual(str(BudgetGraphNegativePayeeMoney(1, 5, "payee1")),
83 "Budget assigned to payee for task is less than "
84 "zero: bug #1, payee 'payee1'")
86 def test_budget_graph_payees_parse_error(self
):
88 BudgetGraphPayeesParseError(1, "my fake parse error")),
89 "Failed to parse cf_payees_list field of bug #1: "
90 "my fake parse error")
92 def test_budget_graph_payees_money_mismatch(self
):
94 BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
95 "Total budget assigned to payees (cf_payees_list) doesn't match "
96 "expected value: bug #1, calculated total 123, expected value 456")
99 EXAMPLE_BUG1
= MockBug(bug_id
=1,
100 cf_budget_parent
=None,
103 cf_nlnet_milestone
=None,
106 EXAMPLE_LOOP1_BUG1
= MockBug(bug_id
=1,
110 cf_nlnet_milestone
=None,
113 EXAMPLE_LOOP2_BUG1
= MockBug(bug_id
=1,
117 cf_nlnet_milestone
=None,
120 EXAMPLE_LOOP2_BUG2
= MockBug(bug_id
=2,
124 cf_nlnet_milestone
=None,
127 EXAMPLE_PARENT_BUG1
= MockBug(bug_id
=1,
128 cf_budget_parent
=None,
130 cf_total_budget
="20",
131 cf_nlnet_milestone
="milestone 1",
134 EXAMPLE_CHILD_BUG2
= MockBug(bug_id
=2,
137 cf_total_budget
="10",
138 cf_nlnet_milestone
="milestone 1",
142 EXAMPLE_CONFIG
= Config
.from_str(
144 bugzilla_url = "https://bugzilla.example.com/"
146 aliases = ["person1_alias1", "alias1"]
147 output_markdown_file = "person1.mdwn"
149 aliases = ["person1_alias2", "alias2", "person 2"]
150 output_markdown_file = "person2.mdwn"
152 output_markdown_file = "person3.mdwn"
154 "milestone 1" = { canonical_bug_id = 1 }
155 "milestone 2" = { canonical_bug_id = 2 }
159 class TestBudgetGraph(unittest
.TestCase
):
162 def assertErrorTypesMatches(self
, errors
: List
[BudgetGraphBaseError
], template
: List
[Type
]):
163 def wrap_type_list(type_list
: List
[Type
]):
165 def __init__(self
, t
):
169 return self
.t
.__name
__
171 def __eq__(self
, other
):
172 return self
.t
== other
.t
173 return [TypeWrapper(i
) for i
in type_list
]
176 error_types
.append(type(error
))
177 self
.assertEqual(wrap_type_list(error_types
), wrap_type_list(template
))
180 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
184 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
185 "budget_excluding_subtasks=10, budget_including_subtasks=20, "
186 "fixed_budget_excluding_subtasks=10, "
187 "fixed_budget_including_subtasks=20, "
188 "milestone_str='milestone 1', milestone=Milestone(config=..., "
189 "identifier='milestone 1', canonical_bug_id=1), "
190 "immediate_children=[#2], payments=[], "
191 "status=BugStatus.CONFIRMED), Node(graph=..., id=#2, root=#1, "
192 "parent=#1, budget_excluding_subtasks=10, "
193 "budget_including_subtasks=10, "
194 "fixed_budget_excluding_subtasks=10, "
195 "fixed_budget_including_subtasks=10, "
196 "milestone_str='milestone 1', milestone=Milestone(config=..., "
197 "identifier='milestone 1', canonical_bug_id=1), "
198 "immediate_children=[], payments=[], "
199 "status=BugStatus.CONFIRMED)], roots=[#1]}")
200 bg
= BudgetGraph([MockBug(bug_id
=1, status
="blah")],
204 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
205 "budget_excluding_subtasks=0, budget_including_subtasks=0, "
206 "fixed_budget_excluding_subtasks=0, "
207 "fixed_budget_including_subtasks=0, milestone_str=None, "
208 "milestone=None, immediate_children=[], payments=[], "
209 "status=<unknown status: 'blah'>)], roots=[#1]}")
211 def test_empty(self
):
212 bg
= BudgetGraph([], EXAMPLE_CONFIG
)
213 self
.assertEqual(len(bg
.nodes
), 0)
214 self
.assertEqual(len(bg
.roots
), 0)
215 self
.assertIs(bg
.config
, EXAMPLE_CONFIG
)
217 def test_single(self
):
218 bg
= BudgetGraph([EXAMPLE_BUG1
], EXAMPLE_CONFIG
)
219 self
.assertEqual(len(bg
.nodes
), 1)
220 node
: Node
= bg
.nodes
[1]
221 self
.assertEqual(bg
.roots
, {node}
)
222 self
.assertIsInstance(node
, Node
)
223 self
.assertIs(node
.graph
, bg
)
224 self
.assertIs(node
.bug
, EXAMPLE_BUG1
)
225 self
.assertIs(node
.root
, node
)
226 self
.assertIsNone(node
.parent_id
)
227 self
.assertEqual(node
.immediate_children
, set())
228 self
.assertEqual(node
.bug_url
,
229 "https://bugzilla.example.com/show_bug.cgi?id=1")
230 self
.assertEqual(node
.budget_excluding_subtasks
, Money(cents
=0))
231 self
.assertEqual(node
.budget_including_subtasks
, Money(cents
=0))
232 self
.assertIsNone(node
.milestone
)
233 self
.assertEqual(node
.payments
, {})
235 def test_loop1(self
):
236 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
237 BudgetGraph([EXAMPLE_LOOP1_BUG1
], EXAMPLE_CONFIG
).roots
238 self
.assertEqual(cm
.exception
.bug_ids
, [1])
240 def test_loop2(self
):
241 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
242 BudgetGraph([EXAMPLE_LOOP2_BUG1
, EXAMPLE_LOOP2_BUG2
],
243 EXAMPLE_CONFIG
).roots
244 self
.assertEqual(cm
.exception
.bug_ids
, [2, 1])
246 def test_parent_child(self
):
247 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
249 self
.assertEqual(len(bg
.nodes
), 2)
250 node1
: Node
= bg
.nodes
[1]
251 node2
: Node
= bg
.nodes
[2]
252 self
.assertEqual(bg
.roots
, {node1}
)
253 self
.assertEqual(node1
, node1
)
254 self
.assertEqual(node2
, node2
)
255 self
.assertNotEqual(node1
, node2
)
256 self
.assertNotEqual(node2
, node1
)
257 self
.assertIsInstance(node1
, Node
)
258 self
.assertIs(node1
.graph
, bg
)
259 self
.assertIs(node1
.bug
, EXAMPLE_PARENT_BUG1
)
260 self
.assertIsNone(node1
.parent_id
)
261 self
.assertEqual(node1
.root
, node1
)
262 self
.assertEqual(node1
.immediate_children
, {node2}
)
263 self
.assertEqual(node1
.budget_excluding_subtasks
, Money(cents
=1000))
264 self
.assertEqual(node1
.budget_including_subtasks
, Money(cents
=2000))
265 self
.assertEqual(node1
.milestone_str
, "milestone 1")
266 self
.assertEqual(node1
.bug_url
,
267 "https://bugzilla.example.com/show_bug.cgi?id=1")
268 self
.assertEqual(list(node1
.children()), [node2
])
269 self
.assertEqual(list(node1
.children_breadth_first()), [node2
])
270 self
.assertEqual(node1
.payments
, {})
271 self
.assertIsInstance(node2
, Node
)
272 self
.assertIs(node2
.graph
, bg
)
273 self
.assertIs(node2
.bug
, EXAMPLE_CHILD_BUG2
)
274 self
.assertEqual(node2
.parent_id
, 1)
275 self
.assertEqual(node2
.root
, node1
)
276 self
.assertEqual(node2
.immediate_children
, set())
277 self
.assertEqual(node2
.budget_excluding_subtasks
, Money(cents
=1000))
278 self
.assertEqual(node2
.budget_including_subtasks
, Money(cents
=1000))
279 self
.assertEqual(node2
.milestone_str
, "milestone 1")
280 self
.assertEqual(node2
.payments
, {})
281 self
.assertEqual(node2
.bug_url
,
282 "https://bugzilla.example.com/show_bug.cgi?id=2")
284 def test_children(self
):
287 cf_budget_parent
=None,
290 cf_nlnet_milestone
=None,
297 cf_nlnet_milestone
=None,
304 cf_nlnet_milestone
=None,
311 cf_nlnet_milestone
=None,
318 cf_nlnet_milestone
=None,
325 cf_nlnet_milestone
=None,
332 cf_nlnet_milestone
=None,
336 self
.assertEqual(len(bg
.nodes
), 7)
337 node1
: Node
= bg
.nodes
[1]
338 node2
: Node
= bg
.nodes
[2]
339 node3
: Node
= bg
.nodes
[3]
340 node4
: Node
= bg
.nodes
[4]
341 node5
: Node
= bg
.nodes
[5]
342 node6
: Node
= bg
.nodes
[6]
343 node7
: Node
= bg
.nodes
[7]
344 self
.assertEqual(bg
.roots
, {node1}
)
345 self
.assertEqual(list(node1
.children()),
346 [node2
, node3
, node5
, node7
, node6
, node4
])
347 self
.assertEqual(list(node1
.children_breadth_first()),
348 [node2
, node3
, node4
, node5
, node6
, node7
])
350 def test_money_with_no_milestone(self
):
353 cf_budget_parent
=None,
355 cf_total_budget
="10",
356 cf_nlnet_milestone
=None,
360 errors
= bg
.get_errors()
361 self
.assertErrorTypesMatches(errors
, [
362 BudgetGraphMoneyWithNoMilestone
,
363 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
364 self
.assertEqual(errors
[0].bug_id
, 1)
365 self
.assertEqual(errors
[0].root_bug_id
, 1)
368 cf_budget_parent
=None,
371 cf_nlnet_milestone
=None,
375 errors
= bg
.get_errors()
376 self
.assertErrorTypesMatches(errors
, [
377 BudgetGraphMoneyWithNoMilestone
,
378 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
379 self
.assertEqual(errors
[0].bug_id
, 1)
380 self
.assertEqual(errors
[0].root_bug_id
, 1)
383 cf_budget_parent
=None,
385 cf_total_budget
="10",
386 cf_nlnet_milestone
=None,
390 errors
= bg
.get_errors()
391 self
.assertErrorTypesMatches(errors
, [BudgetGraphMoneyWithNoMilestone
])
392 self
.assertEqual(errors
[0].bug_id
, 1)
393 self
.assertEqual(errors
[0].root_bug_id
, 1)
395 def test_money_mismatch(self
):
396 def helper(budget
, total_budget
, payees_list
, child_budget
,
397 expected_errors
, expected_fixed_error_types
=None):
398 if expected_fixed_error_types
is None:
399 expected_fixed_error_types
= []
402 cf_budget_parent
=None,
404 cf_total_budget
=total_budget
,
405 cf_nlnet_milestone
="milestone 1",
406 cf_payees_list
=payees_list
,
410 cf_budget
=child_budget
,
411 cf_total_budget
=child_budget
,
412 cf_nlnet_milestone
="milestone 1",
416 node1
: Node
= bg
.nodes
[1]
417 errors
= bg
.get_errors()
418 self
.assertErrorTypesMatches(errors
,
419 [type(i
) for i
in expected_errors
])
420 self
.assertEqual([str(i
) for i
in errors
],
421 [str(i
) for i
in expected_errors
])
424 cf_budget_parent
=None,
425 cf_budget
=str(node1
.fixed_budget_excluding_subtasks
),
427 node1
.fixed_budget_including_subtasks
),
428 cf_nlnet_milestone
="milestone 1",
429 cf_payees_list
=payees_list
,
433 cf_budget
=child_budget
,
434 cf_total_budget
=child_budget
,
435 cf_nlnet_milestone
="milestone 1",
439 errors
= bg
.get_errors()
440 self
.assertErrorTypesMatches(errors
,
441 expected_fixed_error_types
)
452 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
457 payees_list
="person1=1",
460 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
462 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
467 payees_list
="person1=1",
470 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
472 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
477 payees_list
="person1=10",
480 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
482 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
487 payees_list
="person1=10",
490 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
492 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
500 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
508 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
513 payees_list
="person1=1",
516 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
518 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
520 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
523 payees_list
="person1=1",
526 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
528 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
530 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
533 payees_list
="person1=10",
536 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
538 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
540 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
543 payees_list
="person1=10",
546 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
548 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
550 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
558 payees_list
="person1=1",
561 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
563 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
566 payees_list
="person1=10",
569 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
571 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
577 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
585 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
590 payees_list
="person1=1",
593 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
595 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
597 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
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=10",
613 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
618 payees_list
="person1=10",
621 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
631 payees_list
="person1=1",
634 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
636 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
639 payees_list
="person1=10",
647 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
655 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
660 payees_list
="person1=1",
663 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
665 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
667 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
670 payees_list
="person1=1",
673 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
675 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
677 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
680 payees_list
="person1=10",
683 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
688 payees_list
="person1=10",
691 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
701 payees_list
="person1=1",
704 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
706 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
709 payees_list
="person1=10",
715 payees_list
="person1=10",
718 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
722 def test_negative_money(self
):
725 cf_budget_parent
=None,
727 cf_total_budget
="-10",
728 cf_nlnet_milestone
="milestone 1",
732 errors
= bg
.get_errors()
733 self
.assertErrorTypesMatches(errors
, [
734 BudgetGraphNegativeMoney
,
735 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
736 self
.assertEqual(errors
[0].bug_id
, 1)
737 self
.assertEqual(errors
[0].root_bug_id
, 1)
738 self
.assertEqual(errors
[1].bug_id
, 1)
739 self
.assertEqual(errors
[1].root_bug_id
, 1)
740 self
.assertEqual(errors
[1].expected_budget_excluding_subtasks
, -10)
743 cf_budget_parent
=None,
746 cf_nlnet_milestone
="milestone 1",
750 errors
= bg
.get_errors()
751 self
.assertErrorTypesMatches(errors
, [
752 BudgetGraphNegativeMoney
,
753 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
754 self
.assertEqual(errors
[0].bug_id
, 1)
755 self
.assertEqual(errors
[0].root_bug_id
, 1)
756 self
.assertEqual(errors
[1].bug_id
, 1)
757 self
.assertEqual(errors
[1].root_bug_id
, 1)
758 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, -10)
761 cf_budget_parent
=None,
763 cf_total_budget
="-10",
764 cf_nlnet_milestone
="milestone 1",
768 errors
= bg
.get_errors()
769 self
.assertErrorTypesMatches(errors
,
770 [BudgetGraphNegativeMoney
])
771 self
.assertEqual(errors
[0].bug_id
, 1)
772 self
.assertEqual(errors
[0].root_bug_id
, 1)
774 def test_payees_parse(self
):
775 def check(cf_payees_list
, error_types
, expected_payments
):
776 bg
= BudgetGraph([MockBug(bug_id
=1,
777 cf_budget_parent
=None,
780 cf_nlnet_milestone
="milestone 1",
781 cf_payees_list
=cf_payees_list
,
784 self
.assertErrorTypesMatches(bg
.get_errors(), error_types
)
785 self
.assertEqual(len(bg
.nodes
), 1)
786 node
: Node
= bg
.nodes
[1]
787 self
.assertEqual([str(i
) for i
in node
.payments
.values()],
794 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
795 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
796 ["Payment(node=#1, payee=Person<'person1'>, "
797 "payee_key='person1', amount=123, "
798 "state=NotYetSubmitted, paid=None, submitted=None)"])
803 [BudgetGraphPayeesParseError
,
804 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
805 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
806 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
807 "amount=123, state=NotYetSubmitted, paid=None, "
813 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
814 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
815 ["Payment(node=#1, payee=Person<'person1'>, "
816 "payee_key='person1', amount=123.45, "
817 "state=NotYetSubmitted, paid=None, submitted=None)"])
823 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
824 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
825 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
826 'amount=123.45, state=NotYetSubmitted, paid=None, '
828 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
829 'amount=21.35, state=NotYetSubmitted, paid=None, '
836 [BudgetGraphPayeesParseError
,
837 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
838 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
839 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
840 'amount=123.45, state=NotYetSubmitted, paid=None, '
842 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
843 'amount=21.35, state=NotYetSubmitted, paid=None, '
851 [BudgetGraphPayeesParseError
,
852 BudgetGraphNegativePayeeMoney
,
853 BudgetGraphPayeesParseError
,
854 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
855 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
856 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
857 'amount=123.45, state=NotYetSubmitted, paid=None, '
859 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
860 'amount=-21.35, state=NotYetSubmitted, paid=None, '
864 "not-an-email@example.com" = "-2345"
866 [BudgetGraphNegativePayeeMoney
,
867 BudgetGraphPayeesParseError
,
868 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
869 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
870 ['Payment(node=#1, payee=<unknown person>, '
871 "payee_key='not-an-email@example.com', amount=-2345, "
872 "state=NotYetSubmitted, paid=None, submitted=None)"])
875 person1 = { amount = 123 }
877 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
878 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
879 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
880 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
883 person1 = { amount = 123, submitted = 2020-05-01 }
885 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
886 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
887 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
888 + "amount=123, state=Submitted, paid=None, "
889 + "submitted=2020-05-01)"])
892 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
894 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
895 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
896 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
897 + "amount=123, state=Submitted, paid=None, "
898 + "submitted=2020-05-01 00:00:00)"])
901 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
903 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
904 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
905 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
906 + "amount=123, state=Submitted, paid=None, "
907 + "submitted=2020-05-01 00:00:00+00:00)"])
910 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
912 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
913 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
914 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
915 + "amount=123, state=Submitted, paid=None, "
916 + "submitted=2020-05-01 00:00:00-07:23)"])
919 person1 = { amount = 123, paid = 2020-05-01 }
921 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
922 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
923 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
924 + "amount=123, state=Paid, paid=2020-05-01, "
925 + "submitted=None)"])
928 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
930 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
931 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
932 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
933 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
934 + "submitted=None)"])
937 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
939 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
940 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
941 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
942 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
943 + "submitted=None)"])
946 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
948 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
949 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
950 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
951 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
952 + "submitted=None)"])
957 submitted = 2020-05-23
960 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
961 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
962 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
963 + "amount=123, state=Paid, paid=2020-05-01, "
964 + "submitted=2020-05-23)"])
969 submitted = 2020-05-23
970 paid = 2020-05-01T00:00:00
972 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
973 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
974 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
975 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
976 + "submitted=2020-05-23)"])
981 submitted = 2020-05-23
982 paid = 2020-05-01T00:00:00Z
984 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
985 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
986 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
987 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
988 + "submitted=2020-05-23)"])
993 submitted = 2020-05-23
994 paid = 2020-05-01T00:00:00-07:23
996 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
997 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
998 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
999 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
1000 + "submitted=2020-05-23)"])
1002 def test_payees_money_mismatch(self
):
1005 cf_budget_parent
=None,
1007 cf_total_budget
="10",
1008 cf_nlnet_milestone
="milestone 1",
1009 cf_payees_list
="person1 = 5\nperson2 = 10",
1012 errors
= bg
.get_errors()
1013 self
.assertErrorTypesMatches(errors
,
1014 [BudgetGraphPayeesMoneyMismatch
])
1015 self
.assertEqual(errors
[0].bug_id
, 1)
1016 self
.assertEqual(errors
[0].root_bug_id
, 1)
1017 self
.assertEqual(errors
[0].payees_total
, 15)
1019 def test_payees_parse_error(self
):
1020 def check_parse_error(cf_payees_list
, expected_msg
):
1021 errors
= BudgetGraph([
1023 cf_budget_parent
=None,
1025 cf_total_budget
="0",
1026 cf_nlnet_milestone
="milestone 1",
1027 cf_payees_list
=cf_payees_list
,
1029 ], EXAMPLE_CONFIG
).get_errors()
1030 self
.assertErrorTypesMatches(errors
,
1031 [BudgetGraphPayeesParseError
])
1032 self
.assertEqual(errors
[0].bug_id
, 1)
1033 self
.assertEqual(errors
[0].msg
, expected_msg
)
1035 check_parse_error("""
1038 "value for key 'payee 1' is invalid -- it should "
1039 "either be a monetary value or a table")
1041 check_parse_error("""
1044 "failed to parse monetary amount for key 'payee': "
1045 "invalid Money string: characters after sign and "
1046 "before first `.` must be ascii digits")
1048 check_parse_error("""
1052 "TOML parse error: Duplicate keys! (line 3"
1053 " column 1 char 39)")
1055 check_parse_error("""
1058 "failed to parse monetary amount for key 'payee': "
1059 "monetary amount is not a string or integer (to "
1060 "use fractional amounts such as 123.45, write "
1061 "\"123.45\"): 123.45")
1063 check_parse_error("""
1066 "value for key 'payee' is missing the `amount` "
1067 "field which is required")
1069 check_parse_error("""
1070 payee = { amount = 123.45 }
1072 "failed to parse monetary amount for key 'payee': "
1073 "monetary amount is not a string or integer (to "
1074 "use fractional amounts such as 123.45, write "
1075 "\"123.45\"): 123.45")
1077 check_parse_error("""
1078 payee = { amount = 123, blah = false }
1080 "value for key 'payee' has an unknown field: `blah`")
1082 check_parse_error("""
1083 payee = { amount = 123, submitted = false }
1085 "failed to parse `submitted` field for key "
1086 "'payee': invalid date: false")
1088 check_parse_error("""
1089 payee = { amount = 123, submitted = 123 }
1091 "failed to parse `submitted` field for key 'payee':"
1092 " invalid date: 123")
1096 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1098 "failed to parse `submitted` field for key 'payee': "
1099 "invalid date: 'abc'")
1103 payee = { amount = 123, paid = 12:34:56 }
1105 "failed to parse `paid` field for key 'payee': just a time of "
1106 "day by itself is not enough, a date must be included: 12:34:56")
1110 payee = { amount = 123, submitted = 12:34:56.123456 }
1112 "failed to parse `submitted` field for key 'payee': just a time "
1113 "of day by itself is not enough, a date must be included: "
1116 def test_negative_payee_money(self
):
1119 cf_budget_parent
=None,
1121 cf_total_budget
="10",
1122 cf_nlnet_milestone
="milestone 1",
1123 cf_payees_list
="""person1 = -10""",
1126 errors
= bg
.get_errors()
1127 self
.assertErrorTypesMatches(errors
,
1128 [BudgetGraphNegativePayeeMoney
,
1129 BudgetGraphPayeesMoneyMismatch
])
1130 self
.assertEqual(errors
[0].bug_id
, 1)
1131 self
.assertEqual(errors
[0].root_bug_id
, 1)
1132 self
.assertEqual(errors
[0].payee_key
, "person1")
1133 self
.assertEqual(errors
[1].bug_id
, 1)
1134 self
.assertEqual(errors
[1].root_bug_id
, 1)
1135 self
.assertEqual(errors
[1].payees_total
, -10)
1137 def test_duplicate_payments(self
):
1140 cf_budget_parent
=None,
1142 cf_total_budget
="10",
1143 cf_nlnet_milestone
="milestone 1",
1150 errors
= bg
.get_errors()
1151 self
.assertErrorTypesMatches(errors
,
1152 [BudgetGraphDuplicatePayeesForTask
])
1153 self
.assertEqual(errors
[0].bug_id
, 1)
1154 self
.assertEqual(errors
[0].root_bug_id
, 1)
1155 self
.assertEqual(errors
[0].payee1_key
, "person1")
1156 self
.assertEqual(errors
[0].payee2_key
, "alias1")
1158 def test_incorrect_root_for_milestone(self
):
1161 cf_budget_parent
=None,
1163 cf_total_budget
="10",
1164 cf_nlnet_milestone
="milestone 2",
1168 errors
= bg
.get_errors()
1169 self
.assertErrorTypesMatches(errors
,
1170 [BudgetGraphIncorrectRootForMilestone
])
1171 self
.assertEqual(errors
[0].bug_id
, 1)
1172 self
.assertEqual(errors
[0].root_bug_id
, 1)
1173 self
.assertEqual(errors
[0].milestone
, "milestone 2")
1174 self
.assertEqual(errors
[0].milestone_canonical_bug_id
, 2)
1177 cf_budget_parent
=None,
1179 cf_total_budget
="0",
1180 cf_nlnet_milestone
="milestone 2",
1184 errors
= bg
.get_errors()
1185 self
.assertErrorTypesMatches(errors
, [])
1187 def test_payments(self
):
1190 cf_budget_parent
=None,
1192 cf_total_budget
="10",
1193 cf_nlnet_milestone
="milestone 1",
1194 cf_payees_list
="person1 = 3\nperson2 = 7",
1197 cf_budget_parent
=None,
1199 cf_total_budget
="10",
1200 cf_nlnet_milestone
="milestone 2",
1201 cf_payees_list
="person3 = 5\nperson2 = 5",
1204 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1205 person1
= EXAMPLE_CONFIG
.people
["person1"]
1206 person2
= EXAMPLE_CONFIG
.people
["person2"]
1207 person3
= EXAMPLE_CONFIG
.people
["person3"]
1208 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1209 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1210 node1
: Node
= bg
.nodes
[1]
1211 node2
: Node
= bg
.nodes
[2]
1212 node1_payment_person1
= node1
.payments
["person1"]
1213 node1_payment_person2
= node1
.payments
["person2"]
1214 node2_payment_person2
= node2
.payments
["person2"]
1215 node2_payment_person3
= node2
.payments
["person3"]
1216 self
.assertEqual(bg
.payments
,
1219 milestone1
: [node1_payment_person1
],
1223 milestone1
: [node1_payment_person2
],
1224 milestone2
: [node2_payment_person2
],
1228 milestone2
: [node2_payment_person3
],
1232 def test_status(self
):
1233 bg
= BudgetGraph([MockBug(bug_id
=1, status
="blah")],
1235 errors
= bg
.get_errors()
1236 self
.assertErrorTypesMatches(errors
,
1237 [BudgetGraphUnknownStatus
])
1238 self
.assertEqual(errors
[0].bug_id
, 1)
1239 self
.assertEqual(errors
[0].status_str
, "blah")
1240 for status
in BugStatus
:
1241 bg
= BudgetGraph([MockBug(bug_id
=1, status
=status
)],
1243 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1244 self
.assertEqual(bg
.nodes
[1].status
, status
)
1247 if __name__
== "__main__":