add assignee to budget_graph.Node
[utils.git] / src / budget_sync / test / test_budget_graph.py
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, BudgetGraphUnknownAssignee)
12 from budget_sync.money import Money
13 from budget_sync.util import BugStatus
14 from typing import List, Type
15 import unittest
16
17
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")
25
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' "
31 "and 'alias2'")
32
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")
39
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 "
43 "milestone: #1")
44
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")
50
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'")
56
57 def test_budget_graph_unknown_status(self):
58 self.assertEqual(str(BudgetGraphUnknownStatus(
59 123, "fake status")),
60 "failed to parse status field of bug "
61 "#123: unknown status: 'fake status'")
62
63 def test_budget_graph_unknown_assignee(self):
64 self.assertEqual(str(BudgetGraphUnknownAssignee(
65 123, "unknown@example.com")),
66 "Bug #123 is assigned to an unknown person:"
67 " 'unknown@example.com'")
68
69 def test_budget_graph_money_mismatch(self):
70 self.assertEqual(str(
71 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
72 1, 5, "123.4")),
73 "Budget assigned to task excluding subtasks "
74 "(cf_budget field) doesn't match calculated value:"
75 " bug #1, calculated value 123.4")
76 self.assertEqual(str(
77 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
78 1, 5, "123.4")),
79 "Budget assigned to task including subtasks "
80 "(cf_total_budget field) doesn't match calculated value:"
81 " bug #1, calculated value 123.4")
82
83 def test_budget_graph_negative_money(self):
84 self.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
85 "Budget assigned to task is less than zero: bug #1")
86
87 def test_budget_graph_negative_payee_money(self):
88 self.assertEqual(str(BudgetGraphNegativePayeeMoney(1, 5, "payee1")),
89 "Budget assigned to payee for task is less than "
90 "zero: bug #1, payee 'payee1'")
91
92 def test_budget_graph_payees_parse_error(self):
93 self.assertEqual(str(
94 BudgetGraphPayeesParseError(1, "my fake parse error")),
95 "Failed to parse cf_payees_list field of bug #1: "
96 "my fake parse error")
97
98 def test_budget_graph_payees_money_mismatch(self):
99 self.assertEqual(str(
100 BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
101 "Total budget assigned to payees (cf_payees_list) doesn't match "
102 "expected value: bug #1, calculated total 123, expected value 456")
103
104
105 EXAMPLE_BUG1 = MockBug(bug_id=1,
106 cf_budget_parent=None,
107 cf_budget="0",
108 cf_total_budget="0",
109 cf_nlnet_milestone=None,
110 cf_payees_list="",
111 summary="")
112 EXAMPLE_LOOP1_BUG1 = MockBug(bug_id=1,
113 cf_budget_parent=1,
114 cf_budget="0",
115 cf_total_budget="0",
116 cf_nlnet_milestone=None,
117 cf_payees_list="",
118 summary="")
119 EXAMPLE_LOOP2_BUG1 = MockBug(bug_id=1,
120 cf_budget_parent=2,
121 cf_budget="0",
122 cf_total_budget="0",
123 cf_nlnet_milestone=None,
124 cf_payees_list="",
125 summary="")
126 EXAMPLE_LOOP2_BUG2 = MockBug(bug_id=2,
127 cf_budget_parent=1,
128 cf_budget="0",
129 cf_total_budget="0",
130 cf_nlnet_milestone=None,
131 cf_payees_list="",
132 summary="")
133 EXAMPLE_PARENT_BUG1 = MockBug(bug_id=1,
134 cf_budget_parent=None,
135 cf_budget="10",
136 cf_total_budget="20",
137 cf_nlnet_milestone="milestone 1",
138 cf_payees_list="",
139 summary="")
140 EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2,
141 cf_budget_parent=1,
142 cf_budget="10",
143 cf_total_budget="10",
144 cf_nlnet_milestone="milestone 1",
145 cf_payees_list="",
146 summary="")
147
148 EXAMPLE_CONFIG = Config.from_str(
149 """
150 bugzilla_url = "https://bugzilla.example.com/"
151 [people."person1"]
152 aliases = ["person1_alias1", "alias1"]
153 output_markdown_file = "person1.mdwn"
154 [people."person2"]
155 email = "person2@example.com"
156 aliases = ["person1_alias2", "alias2", "person 2"]
157 output_markdown_file = "person2.mdwn"
158 [people."person3"]
159 email = "user@example.com"
160 output_markdown_file = "person3.mdwn"
161 [milestones]
162 "milestone 1" = { canonical_bug_id = 1 }
163 "milestone 2" = { canonical_bug_id = 2 }
164 """)
165
166
167 class TestBudgetGraph(unittest.TestCase):
168 maxDiff = None
169
170 def assertErrorTypesMatches(self, errors: List[BudgetGraphBaseError], template: List[Type]):
171 def wrap_type_list(type_list: List[Type]):
172 class TypeWrapper:
173 def __init__(self, t):
174 self.t = t
175
176 def __repr__(self):
177 return self.t.__name__
178
179 def __eq__(self, other):
180 return self.t == other.t
181 return [TypeWrapper(i) for i in type_list]
182 error_types = []
183 for error in errors:
184 error_types.append(type(error))
185 self.assertEqual(wrap_type_list(error_types), wrap_type_list(template))
186
187 def test_repr(self):
188 bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2],
189 EXAMPLE_CONFIG)
190 self.assertEqual(
191 repr(bg),
192 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
193 "budget_excluding_subtasks=10, budget_including_subtasks=20, "
194 "fixed_budget_excluding_subtasks=10, "
195 "fixed_budget_including_subtasks=20, "
196 "milestone_str='milestone 1', milestone=Milestone(config=..., "
197 "identifier='milestone 1', canonical_bug_id=1), "
198 "immediate_children=[#2], payments=[], "
199 "status=BugStatus.CONFIRMED, assignee=Person<'person3'>), "
200 "Node(graph=..., id=#2, root=#1, "
201 "parent=#1, budget_excluding_subtasks=10, "
202 "budget_including_subtasks=10, "
203 "fixed_budget_excluding_subtasks=10, "
204 "fixed_budget_including_subtasks=10, "
205 "milestone_str='milestone 1', milestone=Milestone(config=..., "
206 "identifier='milestone 1', canonical_bug_id=1), "
207 "immediate_children=[], payments=[], "
208 "status=BugStatus.CONFIRMED, assignee=Person<'person3'>)], "
209 "roots=[#1]}")
210 bg = BudgetGraph([MockBug(bug_id=1, status="blah",
211 assigned_to="unknown@example.com")],
212 EXAMPLE_CONFIG)
213 self.assertEqual(
214 repr(bg),
215 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
216 "budget_excluding_subtasks=0, budget_including_subtasks=0, "
217 "fixed_budget_excluding_subtasks=0, "
218 "fixed_budget_including_subtasks=0, milestone_str=None, "
219 "milestone=None, immediate_children=[], payments=[], "
220 "status=<unknown status: 'blah'>, "
221 "assignee=<unknown assignee: 'unknown@example.com'>)], "
222 "roots=[#1]}")
223
224 def test_empty(self):
225 bg = BudgetGraph([], EXAMPLE_CONFIG)
226 self.assertEqual(len(bg.nodes), 0)
227 self.assertEqual(len(bg.roots), 0)
228 self.assertIs(bg.config, EXAMPLE_CONFIG)
229
230 def test_single(self):
231 bg = BudgetGraph([EXAMPLE_BUG1], EXAMPLE_CONFIG)
232 self.assertEqual(len(bg.nodes), 1)
233 node: Node = bg.nodes[1]
234 self.assertEqual(bg.roots, {node})
235 self.assertIsInstance(node, Node)
236 self.assertIs(node.graph, bg)
237 self.assertIs(node.bug, EXAMPLE_BUG1)
238 self.assertIs(node.root, node)
239 self.assertIsNone(node.parent_id)
240 self.assertEqual(node.immediate_children, set())
241 self.assertEqual(node.bug_url,
242 "https://bugzilla.example.com/show_bug.cgi?id=1")
243 self.assertEqual(node.budget_excluding_subtasks, Money(cents=0))
244 self.assertEqual(node.budget_including_subtasks, Money(cents=0))
245 self.assertIsNone(node.milestone)
246 self.assertEqual(node.payments, {})
247
248 def test_loop1(self):
249 with self.assertRaises(BudgetGraphLoopError) as cm:
250 BudgetGraph([EXAMPLE_LOOP1_BUG1], EXAMPLE_CONFIG).roots
251 self.assertEqual(cm.exception.bug_ids, [1])
252
253 def test_loop2(self):
254 with self.assertRaises(BudgetGraphLoopError) as cm:
255 BudgetGraph([EXAMPLE_LOOP2_BUG1, EXAMPLE_LOOP2_BUG2],
256 EXAMPLE_CONFIG).roots
257 self.assertEqual(cm.exception.bug_ids, [2, 1])
258
259 def test_parent_child(self):
260 bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2],
261 EXAMPLE_CONFIG)
262 self.assertEqual(len(bg.nodes), 2)
263 node1: Node = bg.nodes[1]
264 node2: Node = bg.nodes[2]
265 self.assertEqual(bg.roots, {node1})
266 self.assertEqual(node1, node1)
267 self.assertEqual(node2, node2)
268 self.assertNotEqual(node1, node2)
269 self.assertNotEqual(node2, node1)
270 self.assertIsInstance(node1, Node)
271 self.assertIs(node1.graph, bg)
272 self.assertIs(node1.bug, EXAMPLE_PARENT_BUG1)
273 self.assertIsNone(node1.parent_id)
274 self.assertEqual(node1.root, node1)
275 self.assertEqual(node1.immediate_children, {node2})
276 self.assertEqual(node1.budget_excluding_subtasks, Money(cents=1000))
277 self.assertEqual(node1.budget_including_subtasks, Money(cents=2000))
278 self.assertEqual(node1.milestone_str, "milestone 1")
279 self.assertEqual(node1.bug_url,
280 "https://bugzilla.example.com/show_bug.cgi?id=1")
281 self.assertEqual(list(node1.children()), [node2])
282 self.assertEqual(list(node1.children_breadth_first()), [node2])
283 self.assertEqual(node1.payments, {})
284 self.assertIsInstance(node2, Node)
285 self.assertIs(node2.graph, bg)
286 self.assertIs(node2.bug, EXAMPLE_CHILD_BUG2)
287 self.assertEqual(node2.parent_id, 1)
288 self.assertEqual(node2.root, node1)
289 self.assertEqual(node2.immediate_children, set())
290 self.assertEqual(node2.budget_excluding_subtasks, Money(cents=1000))
291 self.assertEqual(node2.budget_including_subtasks, Money(cents=1000))
292 self.assertEqual(node2.milestone_str, "milestone 1")
293 self.assertEqual(node2.payments, {})
294 self.assertEqual(node2.bug_url,
295 "https://bugzilla.example.com/show_bug.cgi?id=2")
296
297 def test_children(self):
298 bg = BudgetGraph([
299 MockBug(bug_id=1,
300 cf_budget_parent=None,
301 cf_budget="0",
302 cf_total_budget="0",
303 cf_nlnet_milestone=None,
304 cf_payees_list="",
305 summary=""),
306 MockBug(bug_id=2,
307 cf_budget_parent=1,
308 cf_budget="0",
309 cf_total_budget="0",
310 cf_nlnet_milestone=None,
311 cf_payees_list="",
312 summary=""),
313 MockBug(bug_id=3,
314 cf_budget_parent=1,
315 cf_budget="0",
316 cf_total_budget="0",
317 cf_nlnet_milestone=None,
318 cf_payees_list="",
319 summary=""),
320 MockBug(bug_id=4,
321 cf_budget_parent=1,
322 cf_budget="0",
323 cf_total_budget="0",
324 cf_nlnet_milestone=None,
325 cf_payees_list="",
326 summary=""),
327 MockBug(bug_id=5,
328 cf_budget_parent=3,
329 cf_budget="0",
330 cf_total_budget="0",
331 cf_nlnet_milestone=None,
332 cf_payees_list="",
333 summary=""),
334 MockBug(bug_id=6,
335 cf_budget_parent=3,
336 cf_budget="0",
337 cf_total_budget="0",
338 cf_nlnet_milestone=None,
339 cf_payees_list="",
340 summary=""),
341 MockBug(bug_id=7,
342 cf_budget_parent=5,
343 cf_budget="0",
344 cf_total_budget="0",
345 cf_nlnet_milestone=None,
346 cf_payees_list="",
347 summary=""),
348 ], EXAMPLE_CONFIG)
349 self.assertEqual(len(bg.nodes), 7)
350 node1: Node = bg.nodes[1]
351 node2: Node = bg.nodes[2]
352 node3: Node = bg.nodes[3]
353 node4: Node = bg.nodes[4]
354 node5: Node = bg.nodes[5]
355 node6: Node = bg.nodes[6]
356 node7: Node = bg.nodes[7]
357 self.assertEqual(bg.roots, {node1})
358 self.assertEqual(list(node1.children()),
359 [node2, node3, node5, node7, node6, node4])
360 self.assertEqual(list(node1.children_breadth_first()),
361 [node2, node3, node4, node5, node6, node7])
362
363 def test_money_with_no_milestone(self):
364 bg = BudgetGraph([
365 MockBug(bug_id=1,
366 cf_budget_parent=None,
367 cf_budget="0",
368 cf_total_budget="10",
369 cf_nlnet_milestone=None,
370 cf_payees_list="",
371 summary=""),
372 ], EXAMPLE_CONFIG)
373 errors = bg.get_errors()
374 self.assertErrorTypesMatches(errors, [
375 BudgetGraphMoneyWithNoMilestone,
376 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
377 self.assertEqual(errors[0].bug_id, 1)
378 self.assertEqual(errors[0].root_bug_id, 1)
379 bg = BudgetGraph([
380 MockBug(bug_id=1,
381 cf_budget_parent=None,
382 cf_budget="10",
383 cf_total_budget="0",
384 cf_nlnet_milestone=None,
385 cf_payees_list="",
386 summary=""),
387 ], EXAMPLE_CONFIG)
388 errors = bg.get_errors()
389 self.assertErrorTypesMatches(errors, [
390 BudgetGraphMoneyWithNoMilestone,
391 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
392 self.assertEqual(errors[0].bug_id, 1)
393 self.assertEqual(errors[0].root_bug_id, 1)
394 bg = BudgetGraph([
395 MockBug(bug_id=1,
396 cf_budget_parent=None,
397 cf_budget="10",
398 cf_total_budget="10",
399 cf_nlnet_milestone=None,
400 cf_payees_list="",
401 summary=""),
402 ], EXAMPLE_CONFIG)
403 errors = bg.get_errors()
404 self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone])
405 self.assertEqual(errors[0].bug_id, 1)
406 self.assertEqual(errors[0].root_bug_id, 1)
407
408 def test_money_mismatch(self):
409 def helper(budget, total_budget, payees_list, child_budget,
410 expected_errors, expected_fixed_error_types=None):
411 if expected_fixed_error_types is None:
412 expected_fixed_error_types = []
413 bg = BudgetGraph([
414 MockBug(bug_id=1,
415 cf_budget_parent=None,
416 cf_budget=budget,
417 cf_total_budget=total_budget,
418 cf_nlnet_milestone="milestone 1",
419 cf_payees_list=payees_list,
420 summary=""),
421 MockBug(bug_id=2,
422 cf_budget_parent=1,
423 cf_budget=child_budget,
424 cf_total_budget=child_budget,
425 cf_nlnet_milestone="milestone 1",
426 cf_payees_list="",
427 summary=""),
428 ], EXAMPLE_CONFIG)
429 node1: Node = bg.nodes[1]
430 errors = bg.get_errors()
431 self.assertErrorTypesMatches(errors,
432 [type(i) for i in expected_errors])
433 self.assertEqual([str(i) for i in errors],
434 [str(i) for i in expected_errors])
435 bg = BudgetGraph([
436 MockBug(bug_id=1,
437 cf_budget_parent=None,
438 cf_budget=str(node1.fixed_budget_excluding_subtasks),
439 cf_total_budget=str(
440 node1.fixed_budget_including_subtasks),
441 cf_nlnet_milestone="milestone 1",
442 cf_payees_list=payees_list,
443 summary=""),
444 MockBug(bug_id=2,
445 cf_budget_parent=1,
446 cf_budget=child_budget,
447 cf_total_budget=child_budget,
448 cf_nlnet_milestone="milestone 1",
449 cf_payees_list="",
450 summary=""),
451 ], EXAMPLE_CONFIG)
452 errors = bg.get_errors()
453 self.assertErrorTypesMatches(errors,
454 expected_fixed_error_types)
455 helper(budget="0",
456 total_budget="0",
457 payees_list="",
458 child_budget="0",
459 expected_errors=[])
460 helper(budget="0",
461 total_budget="0",
462 payees_list="",
463 child_budget="5",
464 expected_errors=[
465 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
466 1, 1, Money(5)),
467 ])
468 helper(budget="0",
469 total_budget="0",
470 payees_list="person1=1",
471 child_budget="0",
472 expected_errors=[
473 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
474 1, 1, Money(1)),
475 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
476 1, 1, Money(1)),
477 ])
478 helper(budget="0",
479 total_budget="0",
480 payees_list="person1=1",
481 child_budget="5",
482 expected_errors=[
483 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
484 1, 1, Money(1)),
485 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
486 1, 1, Money(6)),
487 ])
488 helper(budget="0",
489 total_budget="0",
490 payees_list="person1=10",
491 child_budget="0",
492 expected_errors=[
493 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
494 1, 1, Money(10)),
495 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
496 1, 1, Money(10)),
497 ])
498 helper(budget="0",
499 total_budget="0",
500 payees_list="person1=10",
501 child_budget="5",
502 expected_errors=[
503 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
504 1, 1, Money(10)),
505 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
506 1, 1, Money(15)),
507 ])
508 helper(budget="0",
509 total_budget="100",
510 payees_list="",
511 child_budget="0",
512 expected_errors=[
513 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
514 1, 1, Money(100)),
515 ])
516 helper(budget="0",
517 total_budget="100",
518 payees_list="",
519 child_budget="5",
520 expected_errors=[
521 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
522 1, 1, Money(95)),
523 ])
524 helper(budget="0",
525 total_budget="100",
526 payees_list="person1=1",
527 child_budget="0",
528 expected_errors=[
529 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
530 1, 1, Money(100)),
531 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
532 ],
533 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
534 helper(budget="0",
535 total_budget="100",
536 payees_list="person1=1",
537 child_budget="5",
538 expected_errors=[
539 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
540 1, 1, Money(95)),
541 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
542 ],
543 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
544 helper(budget="0",
545 total_budget="100",
546 payees_list="person1=10",
547 child_budget="0",
548 expected_errors=[
549 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
550 1, 1, Money(100)),
551 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
552 ],
553 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
554 helper(budget="0",
555 total_budget="100",
556 payees_list="person1=10",
557 child_budget="5",
558 expected_errors=[
559 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
560 1, 1, Money(95)),
561 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
562 ],
563 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
564 helper(budget="0",
565 total_budget="5",
566 payees_list="",
567 child_budget="5",
568 expected_errors=[])
569 helper(budget="0",
570 total_budget="5",
571 payees_list="person1=1",
572 child_budget="5",
573 expected_errors=[
574 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
575 ],
576 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
577 helper(budget="0",
578 total_budget="5",
579 payees_list="person1=10",
580 child_budget="5",
581 expected_errors=[
582 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
583 ],
584 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
585 helper(budget="10",
586 total_budget="0",
587 payees_list="",
588 child_budget="0",
589 expected_errors=[
590 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
591 1, 1, Money(10)),
592 ])
593 helper(budget="10",
594 total_budget="0",
595 payees_list="",
596 child_budget="5",
597 expected_errors=[
598 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
599 1, 1, Money(15)),
600 ])
601 helper(budget="10",
602 total_budget="0",
603 payees_list="person1=1",
604 child_budget="0",
605 expected_errors=[
606 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
607 1, 1, Money(10)),
608 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
609 ],
610 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
611 helper(budget="10",
612 total_budget="0",
613 payees_list="person1=1",
614 child_budget="5",
615 expected_errors=[
616 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
617 1, 1, Money(15)),
618 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
619 ],
620 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
621 helper(budget="10",
622 total_budget="0",
623 payees_list="person1=10",
624 child_budget="0",
625 expected_errors=[
626 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
627 1, 1, Money(10)),
628 ])
629 helper(budget="10",
630 total_budget="0",
631 payees_list="person1=10",
632 child_budget="5",
633 expected_errors=[
634 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
635 1, 1, Money(15)),
636 ])
637 helper(budget="10",
638 total_budget="10",
639 payees_list="",
640 child_budget="0",
641 expected_errors=[])
642 helper(budget="10",
643 total_budget="10",
644 payees_list="person1=1",
645 child_budget="0",
646 expected_errors=[
647 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
648 ],
649 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
650 helper(budget="10",
651 total_budget="10",
652 payees_list="person1=10",
653 child_budget="0",
654 expected_errors=[])
655 helper(budget="10",
656 total_budget="100",
657 payees_list="",
658 child_budget="0",
659 expected_errors=[
660 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
661 1, 1, Money(100)),
662 ])
663 helper(budget="10",
664 total_budget="100",
665 payees_list="",
666 child_budget="5",
667 expected_errors=[
668 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
669 1, 1, Money(95)),
670 ])
671 helper(budget="10",
672 total_budget="100",
673 payees_list="person1=1",
674 child_budget="0",
675 expected_errors=[
676 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
677 1, 1, Money(10)),
678 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
679 ],
680 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
681 helper(budget="10",
682 total_budget="100",
683 payees_list="person1=1",
684 child_budget="5",
685 expected_errors=[
686 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
687 1, 1, Money(15)),
688 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
689 ],
690 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
691 helper(budget="10",
692 total_budget="100",
693 payees_list="person1=10",
694 child_budget="0",
695 expected_errors=[
696 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
697 1, 1, Money(10)),
698 ])
699 helper(budget="10",
700 total_budget="100",
701 payees_list="person1=10",
702 child_budget="5",
703 expected_errors=[
704 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
705 1, 1, Money(15)),
706 ])
707 helper(budget="10",
708 total_budget="15",
709 payees_list="",
710 child_budget="5",
711 expected_errors=[])
712 helper(budget="10",
713 total_budget="15",
714 payees_list="person1=1",
715 child_budget="5",
716 expected_errors=[
717 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
718 ],
719 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
720 helper(budget="10",
721 total_budget="15",
722 payees_list="person1=10",
723 child_budget="5",
724 expected_errors=[])
725
726 helper(budget="1",
727 total_budget="15",
728 payees_list="person1=10",
729 child_budget="5",
730 expected_errors=[
731 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
732 1, 1, Money("10"))
733 ])
734
735 def test_negative_money(self):
736 bg = BudgetGraph([
737 MockBug(bug_id=1,
738 cf_budget_parent=None,
739 cf_budget="0",
740 cf_total_budget="-10",
741 cf_nlnet_milestone="milestone 1",
742 cf_payees_list="",
743 summary=""),
744 ], EXAMPLE_CONFIG)
745 errors = bg.get_errors()
746 self.assertErrorTypesMatches(errors, [
747 BudgetGraphNegativeMoney,
748 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
749 self.assertEqual(errors[0].bug_id, 1)
750 self.assertEqual(errors[0].root_bug_id, 1)
751 self.assertEqual(errors[1].bug_id, 1)
752 self.assertEqual(errors[1].root_bug_id, 1)
753 self.assertEqual(errors[1].expected_budget_excluding_subtasks, -10)
754 bg = BudgetGraph([
755 MockBug(bug_id=1,
756 cf_budget_parent=None,
757 cf_budget="-10",
758 cf_total_budget="0",
759 cf_nlnet_milestone="milestone 1",
760 cf_payees_list="",
761 summary=""),
762 ], EXAMPLE_CONFIG)
763 errors = bg.get_errors()
764 self.assertErrorTypesMatches(errors, [
765 BudgetGraphNegativeMoney,
766 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
767 self.assertEqual(errors[0].bug_id, 1)
768 self.assertEqual(errors[0].root_bug_id, 1)
769 self.assertEqual(errors[1].bug_id, 1)
770 self.assertEqual(errors[1].root_bug_id, 1)
771 self.assertEqual(errors[1].expected_budget_including_subtasks, -10)
772 bg = BudgetGraph([
773 MockBug(bug_id=1,
774 cf_budget_parent=None,
775 cf_budget="-10",
776 cf_total_budget="-10",
777 cf_nlnet_milestone="milestone 1",
778 cf_payees_list="",
779 summary=""),
780 ], EXAMPLE_CONFIG)
781 errors = bg.get_errors()
782 self.assertErrorTypesMatches(errors,
783 [BudgetGraphNegativeMoney])
784 self.assertEqual(errors[0].bug_id, 1)
785 self.assertEqual(errors[0].root_bug_id, 1)
786
787 def test_payees_parse(self):
788 def check(cf_payees_list, error_types, expected_payments):
789 bg = BudgetGraph([MockBug(bug_id=1,
790 cf_budget_parent=None,
791 cf_budget="0",
792 cf_total_budget="0",
793 cf_nlnet_milestone="milestone 1",
794 cf_payees_list=cf_payees_list,
795 summary=""),
796 ], EXAMPLE_CONFIG)
797 self.assertErrorTypesMatches(bg.get_errors(), error_types)
798 self.assertEqual(len(bg.nodes), 1)
799 node: Node = bg.nodes[1]
800 self.assertEqual([str(i) for i in node.payments.values()],
801 expected_payments)
802
803 check(
804 """
805 person1 = 123
806 """,
807 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
808 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
809 ["Payment(node=#1, payee=Person<'person1'>, "
810 "payee_key='person1', amount=123, "
811 "state=NotYetSubmitted, paid=None, submitted=None)"])
812 check(
813 """
814 abc = "123"
815 """,
816 [BudgetGraphPayeesParseError,
817 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
818 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
819 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
820 "amount=123, state=NotYetSubmitted, paid=None, "
821 "submitted=None)"])
822 check(
823 """
824 person1 = "123.45"
825 """,
826 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
827 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
828 ["Payment(node=#1, payee=Person<'person1'>, "
829 "payee_key='person1', amount=123.45, "
830 "state=NotYetSubmitted, paid=None, submitted=None)"])
831 check(
832 """
833 person1 = "123.45"
834 "person 2" = "21.35"
835 """,
836 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
837 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
838 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
839 'amount=123.45, state=NotYetSubmitted, paid=None, '
840 'submitted=None)',
841 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
842 'amount=21.35, state=NotYetSubmitted, paid=None, '
843 'submitted=None)'])
844 check(
845 """
846 person1 = "123.45"
847 "d e f" = "21.35"
848 """,
849 [BudgetGraphPayeesParseError,
850 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
851 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
852 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
853 'amount=123.45, state=NotYetSubmitted, paid=None, '
854 'submitted=None)',
855 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
856 'amount=21.35, state=NotYetSubmitted, paid=None, '
857 'submitted=None)'])
858 check(
859 """
860 abc = "123.45"
861 # my comments
862 "AAA" = "-21.35"
863 """,
864 [BudgetGraphPayeesParseError,
865 BudgetGraphNegativePayeeMoney,
866 BudgetGraphPayeesParseError,
867 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
868 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
869 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
870 'amount=123.45, state=NotYetSubmitted, paid=None, '
871 'submitted=None)',
872 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
873 'amount=-21.35, state=NotYetSubmitted, paid=None, '
874 'submitted=None)'])
875 check(
876 """
877 "not-an-email@example.com" = "-2345"
878 """,
879 [BudgetGraphNegativePayeeMoney,
880 BudgetGraphPayeesParseError,
881 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
882 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
883 ['Payment(node=#1, payee=<unknown person>, '
884 "payee_key='not-an-email@example.com', amount=-2345, "
885 "state=NotYetSubmitted, paid=None, submitted=None)"])
886 check(
887 """
888 person1 = { amount = 123 }
889 """,
890 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
891 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
892 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
893 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
894 check(
895 """
896 person1 = { amount = 123, submitted = 2020-05-01 }
897 """,
898 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
899 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
900 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
901 + "amount=123, state=Submitted, paid=None, "
902 + "submitted=2020-05-01)"])
903 check(
904 """
905 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
906 """,
907 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
908 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
909 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
910 + "amount=123, state=Submitted, paid=None, "
911 + "submitted=2020-05-01 00:00:00)"])
912 check(
913 """
914 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
915 """,
916 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
917 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
918 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
919 + "amount=123, state=Submitted, paid=None, "
920 + "submitted=2020-05-01 00:00:00+00:00)"])
921 check(
922 """
923 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
924 """,
925 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
926 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
927 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
928 + "amount=123, state=Submitted, paid=None, "
929 + "submitted=2020-05-01 00:00:00-07:23)"])
930 check(
931 """
932 person1 = { amount = 123, paid = 2020-05-01 }
933 """,
934 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
935 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
936 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
937 + "amount=123, state=Paid, paid=2020-05-01, "
938 + "submitted=None)"])
939 check(
940 """
941 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
942 """,
943 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
944 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
945 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
946 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
947 + "submitted=None)"])
948 check(
949 """
950 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
951 """,
952 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
953 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
954 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
955 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
956 + "submitted=None)"])
957 check(
958 """
959 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
960 """,
961 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
962 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
963 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
964 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
965 + "submitted=None)"])
966 check(
967 """
968 [person1]
969 amount = 123
970 submitted = 2020-05-23
971 paid = 2020-05-01
972 """,
973 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
974 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
975 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
976 + "amount=123, state=Paid, paid=2020-05-01, "
977 + "submitted=2020-05-23)"])
978 check(
979 """
980 [person1]
981 amount = 123
982 submitted = 2020-05-23
983 paid = 2020-05-01T00:00:00
984 """,
985 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
986 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
987 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
988 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
989 + "submitted=2020-05-23)"])
990 check(
991 """
992 [person1]
993 amount = 123
994 submitted = 2020-05-23
995 paid = 2020-05-01T00:00:00Z
996 """,
997 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
998 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
999 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1000 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
1001 + "submitted=2020-05-23)"])
1002 check(
1003 """
1004 [person1]
1005 amount = 123
1006 submitted = 2020-05-23
1007 paid = 2020-05-01T00:00:00-07:23
1008 """,
1009 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
1010 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks],
1011 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1012 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
1013 + "submitted=2020-05-23)"])
1014
1015 def test_payees_money_mismatch(self):
1016 bg = BudgetGraph([
1017 MockBug(bug_id=1,
1018 cf_budget_parent=None,
1019 cf_budget="10",
1020 cf_total_budget="10",
1021 cf_nlnet_milestone="milestone 1",
1022 cf_payees_list="person1 = 5\nperson2 = 10",
1023 summary=""),
1024 ], EXAMPLE_CONFIG)
1025 errors = bg.get_errors()
1026 self.assertErrorTypesMatches(errors,
1027 [BudgetGraphPayeesMoneyMismatch])
1028 self.assertEqual(errors[0].bug_id, 1)
1029 self.assertEqual(errors[0].root_bug_id, 1)
1030 self.assertEqual(errors[0].payees_total, 15)
1031
1032 def test_payees_parse_error(self):
1033 def check_parse_error(cf_payees_list, expected_msg):
1034 errors = BudgetGraph([
1035 MockBug(bug_id=1,
1036 cf_budget_parent=None,
1037 cf_budget="0",
1038 cf_total_budget="0",
1039 cf_nlnet_milestone="milestone 1",
1040 cf_payees_list=cf_payees_list,
1041 summary=""),
1042 ], EXAMPLE_CONFIG).get_errors()
1043 self.assertErrorTypesMatches(errors,
1044 [BudgetGraphPayeesParseError])
1045 self.assertEqual(errors[0].bug_id, 1)
1046 self.assertEqual(errors[0].msg, expected_msg)
1047
1048 check_parse_error("""
1049 "payee 1" = []
1050 """,
1051 "value for key 'payee 1' is invalid -- it should "
1052 "either be a monetary value or a table")
1053
1054 check_parse_error("""
1055 payee = "ashjkf"
1056 """,
1057 "failed to parse monetary amount for key 'payee': "
1058 "invalid Money string: characters after sign and "
1059 "before first `.` must be ascii digits")
1060
1061 check_parse_error("""
1062 payee = "1"
1063 payee = "1"
1064 """,
1065 "TOML parse error: Duplicate keys! (line 3"
1066 " column 1 char 39)")
1067
1068 check_parse_error("""
1069 payee = 123.45
1070 """,
1071 "failed to parse monetary amount for key 'payee': "
1072 "monetary amount is not a string or integer (to "
1073 "use fractional amounts such as 123.45, write "
1074 "\"123.45\"): 123.45")
1075
1076 check_parse_error("""
1077 payee = {}
1078 """,
1079 "value for key 'payee' is missing the `amount` "
1080 "field which is required")
1081
1082 check_parse_error("""
1083 payee = { amount = 123.45 }
1084 """,
1085 "failed to parse monetary amount for key 'payee': "
1086 "monetary amount is not a string or integer (to "
1087 "use fractional amounts such as 123.45, write "
1088 "\"123.45\"): 123.45")
1089
1090 check_parse_error("""
1091 payee = { amount = 123, blah = false }
1092 """,
1093 "value for key 'payee' has an unknown field: `blah`")
1094
1095 check_parse_error("""
1096 payee = { amount = 123, submitted = false }
1097 """,
1098 "failed to parse `submitted` field for key "
1099 "'payee': invalid date: false")
1100
1101 check_parse_error("""
1102 payee = { amount = 123, submitted = 123 }
1103 """,
1104 "failed to parse `submitted` field for key 'payee':"
1105 " invalid date: 123")
1106
1107 check_parse_error(
1108 """
1109 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1110 """,
1111 "failed to parse `submitted` field for key 'payee': "
1112 "invalid date: 'abc'")
1113
1114 check_parse_error(
1115 """
1116 payee = { amount = 123, paid = 12:34:56 }
1117 """,
1118 "failed to parse `paid` field for key 'payee': just a time of "
1119 "day by itself is not enough, a date must be included: 12:34:56")
1120
1121 check_parse_error(
1122 """
1123 payee = { amount = 123, submitted = 12:34:56.123456 }
1124 """,
1125 "failed to parse `submitted` field for key 'payee': just a time "
1126 "of day by itself is not enough, a date must be included: "
1127 "12:34:56.123456")
1128
1129 def test_negative_payee_money(self):
1130 bg = BudgetGraph([
1131 MockBug(bug_id=1,
1132 cf_budget_parent=None,
1133 cf_budget="10",
1134 cf_total_budget="10",
1135 cf_nlnet_milestone="milestone 1",
1136 cf_payees_list="""person1 = -10""",
1137 summary=""),
1138 ], EXAMPLE_CONFIG)
1139 errors = bg.get_errors()
1140 self.assertErrorTypesMatches(errors,
1141 [BudgetGraphNegativePayeeMoney,
1142 BudgetGraphPayeesMoneyMismatch])
1143 self.assertEqual(errors[0].bug_id, 1)
1144 self.assertEqual(errors[0].root_bug_id, 1)
1145 self.assertEqual(errors[0].payee_key, "person1")
1146 self.assertEqual(errors[1].bug_id, 1)
1147 self.assertEqual(errors[1].root_bug_id, 1)
1148 self.assertEqual(errors[1].payees_total, -10)
1149
1150 def test_duplicate_payments(self):
1151 bg = BudgetGraph([
1152 MockBug(bug_id=1,
1153 cf_budget_parent=None,
1154 cf_budget="10",
1155 cf_total_budget="10",
1156 cf_nlnet_milestone="milestone 1",
1157 cf_payees_list="""
1158 person1 = 5
1159 alias1 = 5
1160 """,
1161 summary=""),
1162 ], EXAMPLE_CONFIG)
1163 errors = bg.get_errors()
1164 self.assertErrorTypesMatches(errors,
1165 [BudgetGraphDuplicatePayeesForTask])
1166 self.assertEqual(errors[0].bug_id, 1)
1167 self.assertEqual(errors[0].root_bug_id, 1)
1168 self.assertEqual(errors[0].payee1_key, "person1")
1169 self.assertEqual(errors[0].payee2_key, "alias1")
1170
1171 def test_incorrect_root_for_milestone(self):
1172 bg = BudgetGraph([
1173 MockBug(bug_id=1,
1174 cf_budget_parent=None,
1175 cf_budget="10",
1176 cf_total_budget="10",
1177 cf_nlnet_milestone="milestone 2",
1178 cf_payees_list="",
1179 summary=""),
1180 ], EXAMPLE_CONFIG)
1181 errors = bg.get_errors()
1182 self.assertErrorTypesMatches(errors,
1183 [BudgetGraphIncorrectRootForMilestone])
1184 self.assertEqual(errors[0].bug_id, 1)
1185 self.assertEqual(errors[0].root_bug_id, 1)
1186 self.assertEqual(errors[0].milestone, "milestone 2")
1187 self.assertEqual(errors[0].milestone_canonical_bug_id, 2)
1188 bg = BudgetGraph([
1189 MockBug(bug_id=1,
1190 cf_budget_parent=None,
1191 cf_budget="0",
1192 cf_total_budget="0",
1193 cf_nlnet_milestone="milestone 2",
1194 cf_payees_list="",
1195 summary=""),
1196 ], EXAMPLE_CONFIG)
1197 errors = bg.get_errors()
1198 self.assertErrorTypesMatches(errors, [])
1199
1200 def test_payments(self):
1201 bg = BudgetGraph([
1202 MockBug(bug_id=1,
1203 cf_budget_parent=None,
1204 cf_budget="10",
1205 cf_total_budget="10",
1206 cf_nlnet_milestone="milestone 1",
1207 cf_payees_list="person1 = 3\nperson2 = 7",
1208 summary=""),
1209 MockBug(bug_id=2,
1210 cf_budget_parent=None,
1211 cf_budget="10",
1212 cf_total_budget="10",
1213 cf_nlnet_milestone="milestone 2",
1214 cf_payees_list="person3 = 5\nperson2 = 5",
1215 summary=""),
1216 ], EXAMPLE_CONFIG)
1217 self.assertErrorTypesMatches(bg.get_errors(), [])
1218 person1 = EXAMPLE_CONFIG.people["person1"]
1219 person2 = EXAMPLE_CONFIG.people["person2"]
1220 person3 = EXAMPLE_CONFIG.people["person3"]
1221 milestone1 = EXAMPLE_CONFIG.milestones["milestone 1"]
1222 milestone2 = EXAMPLE_CONFIG.milestones["milestone 2"]
1223 node1: Node = bg.nodes[1]
1224 node2: Node = bg.nodes[2]
1225 node1_payment_person1 = node1.payments["person1"]
1226 node1_payment_person2 = node1.payments["person2"]
1227 node2_payment_person2 = node2.payments["person2"]
1228 node2_payment_person3 = node2.payments["person3"]
1229 self.assertEqual(bg.payments,
1230 {
1231 person1: {
1232 milestone1: [node1_payment_person1],
1233 milestone2: [],
1234 },
1235 person2: {
1236 milestone1: [node1_payment_person2],
1237 milestone2: [node2_payment_person2],
1238 },
1239 person3: {
1240 milestone1: [],
1241 milestone2: [node2_payment_person3],
1242 },
1243 })
1244
1245 def test_status(self):
1246 bg = BudgetGraph([MockBug(bug_id=1, status="blah")],
1247 EXAMPLE_CONFIG)
1248 errors = bg.get_errors()
1249 self.assertErrorTypesMatches(errors,
1250 [BudgetGraphUnknownStatus])
1251 self.assertEqual(errors[0].bug_id, 1)
1252 self.assertEqual(errors[0].status_str, "blah")
1253 for status in BugStatus:
1254 bg = BudgetGraph([MockBug(bug_id=1, status=status)],
1255 EXAMPLE_CONFIG)
1256 self.assertErrorTypesMatches(bg.get_errors(), [])
1257 self.assertEqual(bg.nodes[1].status, status)
1258
1259 def test_assignee(self):
1260 bg = BudgetGraph([MockBug(bug_id=1, assigned_to="blah")],
1261 EXAMPLE_CONFIG)
1262 errors = bg.get_errors()
1263 self.assertErrorTypesMatches(errors,
1264 [BudgetGraphUnknownAssignee])
1265 self.assertEqual(errors[0].bug_id, 1)
1266 self.assertEqual(errors[0].assignee, "blah")
1267 bg = BudgetGraph([MockBug(bug_id=1,
1268 assigned_to="person2@example.com")],
1269 EXAMPLE_CONFIG)
1270 self.assertErrorTypesMatches(bg.get_errors(), [])
1271 self.assertEqual(bg.nodes[1].assignee,
1272 EXAMPLE_CONFIG.people["person2"])
1273
1274
1275 if __name__ == "__main__":
1276 unittest.main()