remove unnecessary __ne__ implementation
[utils.git] / src / budget_sync / test / test_budget_graph.py
1 from budget_sync.test.mock_bug import MockBug
2 from budget_sync.budget_graph import (BudgetGraphLoopError, BudgetGraph,
3 Node, BudgetGraphMoneyWithNoMilestone,
4 BudgetGraphBaseError,
5 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
6 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks,
7 BudgetGraphNegativeMoney,
8 BudgetGraphMilestoneMismatch,
9 BudgetGraphNegativePayeeMoney,
10 BudgetGraphPayeesParseError,
11 BudgetGraphPayeesMoneyMismatch)
12 from budget_sync.money import Money
13 from typing import List, Type
14 import unittest
15
16
17 class TestErrorFormatting(unittest.TestCase):
18 def test_budget_graph_loop_error(self):
19 self.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])),
20 "Detected Loop in Budget Graph: #5 -> #1 "
21 "-> #2 -> #3 -> #4 -> #5")
22 self.assertEqual(str(BudgetGraphLoopError([1])),
23 "Detected Loop in Budget Graph: #1 -> #1")
24
25 def test_budget_graph_money_with_no_milestone(self):
26 self.assertEqual(str(BudgetGraphMoneyWithNoMilestone(1, 5)),
27 "Bug assigned money but without any assigned "
28 "milestone: #1")
29
30 def test_budget_graph_milestone_mismatch(self):
31 self.assertEqual(str(BudgetGraphMilestoneMismatch(1, 5)),
32 "Bug's assigned milestone doesn't match the "
33 "milestone assigned to the root bug: descendant "
34 "bug #1, root bug #5")
35
36 def test_budget_graph_money_mismatch(self):
37 self.assertEqual(str(
38 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
39 1, 5, "123.4")),
40 "Budget assigned to task excluding subtasks "
41 "(cf_budget field) doesn't match calculated value:"
42 " bug #1, calculated value 123.4")
43 self.assertEqual(str(
44 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
45 1, 5, "123.4")),
46 "Budget assigned to task including subtasks "
47 "(cf_total_budget field) doesn't match calculated value:"
48 " bug #1, calculated value 123.4")
49
50 def test_budget_graph_negative_money(self):
51 self.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
52 "Budget assigned to task is less than zero: bug #1")
53
54 def test_budget_graph_negative_payee_money(self):
55 self.assertEqual(str(BudgetGraphNegativePayeeMoney(1, 5, "payee1")),
56 "Budget assigned to payee for task is less than "
57 "zero: bug #1, payee 'payee1'")
58
59 def test_budget_graph_payees_parse_error(self):
60 self.assertEqual(str(
61 BudgetGraphPayeesParseError(1, "my fake parse error")),
62 "Failed to parse cf_payees_list field of bug #1: "
63 "my fake parse error")
64
65 def test_budget_graph_payees_money_mismatch(self):
66 self.assertEqual(str(
67 BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
68 "Total budget assigned to payees (cf_payees_list) doesn't match "
69 "expected value: bug #1, calculated total 123, expected value 456")
70
71
72 EXAMPLE_BUG1 = MockBug(bug_id=1,
73 cf_budget_parent=None,
74 cf_budget="0",
75 cf_total_budget="0",
76 cf_nlnet_milestone=None,
77 cf_payees_list="")
78 EXAMPLE_LOOP1_BUG1 = MockBug(bug_id=1,
79 cf_budget_parent=1,
80 cf_budget="0",
81 cf_total_budget="0",
82 cf_nlnet_milestone=None,
83 cf_payees_list="")
84 EXAMPLE_LOOP2_BUG1 = MockBug(bug_id=1,
85 cf_budget_parent=2,
86 cf_budget="0",
87 cf_total_budget="0",
88 cf_nlnet_milestone=None,
89 cf_payees_list="")
90 EXAMPLE_LOOP2_BUG2 = MockBug(bug_id=2,
91 cf_budget_parent=1,
92 cf_budget="0",
93 cf_total_budget="0",
94 cf_nlnet_milestone=None,
95 cf_payees_list="")
96 EXAMPLE_PARENT_BUG1 = MockBug(bug_id=1,
97 cf_budget_parent=None,
98 cf_budget="10",
99 cf_total_budget="20",
100 cf_nlnet_milestone="abc",
101 cf_payees_list="")
102 EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2,
103 cf_budget_parent=1,
104 cf_budget="10",
105 cf_total_budget="10",
106 cf_nlnet_milestone="abc",
107 cf_payees_list="")
108
109
110 class TestBudgetGraph(unittest.TestCase):
111 def assertErrorTypesMatches(self, errors: List[BudgetGraphBaseError], template: List[Type]):
112 error_types = []
113 for error in errors:
114 error_types.append(type(error))
115 self.assertEqual(error_types, template)
116
117 def test_empty(self):
118 bg = BudgetGraph([])
119 self.assertEqual(len(bg.nodes), 0)
120 self.assertEqual(len(bg.roots), 0)
121
122 def test_single(self):
123 bg = BudgetGraph([EXAMPLE_BUG1])
124 self.assertEqual(len(bg.nodes), 1)
125 node: Node = bg.nodes[1]
126 self.assertEqual(bg.roots, {node})
127 self.assertIsInstance(node, Node)
128 self.assertIs(node.graph, bg)
129 self.assertIs(node.bug, EXAMPLE_BUG1)
130 self.assertIs(node.root, node)
131 self.assertIsNone(node.parent_id)
132 self.assertEqual(node.immediate_children, set())
133 self.assertEqual(node.budget_excluding_subtasks, Money(cents=0))
134 self.assertEqual(node.budget_including_subtasks, Money(cents=0))
135 self.assertIsNone(node.nlnet_milestone)
136 self.assertEqual(node.payments, {})
137
138 def test_loop1(self):
139 with self.assertRaises(BudgetGraphLoopError) as cm:
140 BudgetGraph([EXAMPLE_LOOP1_BUG1]).roots
141 self.assertEqual(cm.exception.bug_ids, [1])
142
143 def test_loop2(self):
144 with self.assertRaises(BudgetGraphLoopError) as cm:
145 BudgetGraph([EXAMPLE_LOOP2_BUG1, EXAMPLE_LOOP2_BUG2]).roots
146 self.assertEqual(cm.exception.bug_ids, [2, 1])
147
148 def test_parent_child(self):
149 bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2])
150 self.assertEqual(len(bg.nodes), 2)
151 node1: Node = bg.nodes[1]
152 node2: Node = bg.nodes[2]
153 self.assertEqual(bg.roots, {node1})
154 self.assertEqual(node1, node1)
155 self.assertEqual(node2, node2)
156 self.assertNotEqual(node1, node2)
157 self.assertNotEqual(node2, node1)
158 self.assertIsInstance(node1, Node)
159 self.assertIs(node1.graph, bg)
160 self.assertIs(node1.bug, EXAMPLE_PARENT_BUG1)
161 self.assertIsNone(node1.parent_id)
162 self.assertEqual(node1.root, node1)
163 self.assertEqual(node1.immediate_children, {node2})
164 self.assertEqual(node1.budget_excluding_subtasks, Money(cents=1000))
165 self.assertEqual(node1.budget_including_subtasks, Money(cents=2000))
166 self.assertEqual(node1.nlnet_milestone, "abc")
167 self.assertEqual(list(node1.children()), [node2])
168 self.assertEqual(list(node1.children_breadth_first()), [node2])
169 self.assertEqual(node1.payments, {})
170 self.assertIsInstance(node2, Node)
171 self.assertIs(node2.graph, bg)
172 self.assertIs(node2.bug, EXAMPLE_CHILD_BUG2)
173 self.assertEqual(node2.parent_id, 1)
174 self.assertEqual(node2.root, node1)
175 self.assertEqual(node2.immediate_children, set())
176 self.assertEqual(node2.budget_excluding_subtasks, Money(cents=1000))
177 self.assertEqual(node2.budget_including_subtasks, Money(cents=1000))
178 self.assertEqual(node2.nlnet_milestone, "abc")
179 self.assertEqual(node2.payments, {})
180
181 def test_children(self):
182 bg = BudgetGraph([
183 MockBug(bug_id=1,
184 cf_budget_parent=None,
185 cf_budget="0",
186 cf_total_budget="0",
187 cf_nlnet_milestone=None,
188 cf_payees_list=""),
189 MockBug(bug_id=2,
190 cf_budget_parent=1,
191 cf_budget="0",
192 cf_total_budget="0",
193 cf_nlnet_milestone=None,
194 cf_payees_list=""),
195 MockBug(bug_id=3,
196 cf_budget_parent=1,
197 cf_budget="0",
198 cf_total_budget="0",
199 cf_nlnet_milestone=None,
200 cf_payees_list=""),
201 MockBug(bug_id=4,
202 cf_budget_parent=1,
203 cf_budget="0",
204 cf_total_budget="0",
205 cf_nlnet_milestone=None,
206 cf_payees_list=""),
207 MockBug(bug_id=5,
208 cf_budget_parent=3,
209 cf_budget="0",
210 cf_total_budget="0",
211 cf_nlnet_milestone=None,
212 cf_payees_list=""),
213 MockBug(bug_id=6,
214 cf_budget_parent=3,
215 cf_budget="0",
216 cf_total_budget="0",
217 cf_nlnet_milestone=None,
218 cf_payees_list=""),
219 MockBug(bug_id=7,
220 cf_budget_parent=5,
221 cf_budget="0",
222 cf_total_budget="0",
223 cf_nlnet_milestone=None,
224 cf_payees_list=""),
225 ])
226 self.assertEqual(len(bg.nodes), 7)
227 node1: Node = bg.nodes[1]
228 node2: Node = bg.nodes[2]
229 node3: Node = bg.nodes[3]
230 node4: Node = bg.nodes[4]
231 node5: Node = bg.nodes[5]
232 node6: Node = bg.nodes[6]
233 node7: Node = bg.nodes[7]
234 self.assertEqual(bg.roots, {node1})
235 self.assertEqual(list(node1.children()),
236 [node2, node3, node5, node7, node6, node4])
237 self.assertEqual(list(node1.children_breadth_first()),
238 [node2, node3, node4, node5, node6, node7])
239
240 def test_money_with_no_milestone(self):
241 bg = BudgetGraph([
242 MockBug(bug_id=1,
243 cf_budget_parent=None,
244 cf_budget="0",
245 cf_total_budget="10",
246 cf_nlnet_milestone=None,
247 cf_payees_list=""),
248 ])
249 errors = bg.get_errors()
250 self.assertErrorTypesMatches(errors, [
251 BudgetGraphMoneyWithNoMilestone,
252 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
253 self.assertEqual(errors[0].bug_id, 1)
254 self.assertEqual(errors[0].root_bug_id, 1)
255 bg = BudgetGraph([
256 MockBug(bug_id=1,
257 cf_budget_parent=None,
258 cf_budget="10",
259 cf_total_budget="0",
260 cf_nlnet_milestone=None,
261 cf_payees_list=""),
262 ])
263 errors = bg.get_errors()
264 self.assertErrorTypesMatches(errors, [
265 BudgetGraphMoneyWithNoMilestone,
266 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
267 self.assertEqual(errors[0].bug_id, 1)
268 self.assertEqual(errors[0].root_bug_id, 1)
269 bg = BudgetGraph([
270 MockBug(bug_id=1,
271 cf_budget_parent=None,
272 cf_budget="10",
273 cf_total_budget="10",
274 cf_nlnet_milestone=None,
275 cf_payees_list=""),
276 ])
277 errors = bg.get_errors()
278 self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone])
279 self.assertEqual(errors[0].bug_id, 1)
280 self.assertEqual(errors[0].root_bug_id, 1)
281
282 def test_money_mismatch(self):
283 def helper(budget, total_budget, payees_list, child_budget,
284 expected_errors, expected_fixed_error_types=None):
285 if expected_fixed_error_types is None:
286 expected_fixed_error_types = []
287 bg = BudgetGraph([
288 MockBug(bug_id=1,
289 cf_budget_parent=None,
290 cf_budget=budget,
291 cf_total_budget=total_budget,
292 cf_nlnet_milestone="abc",
293 cf_payees_list=payees_list),
294 MockBug(bug_id=2,
295 cf_budget_parent=1,
296 cf_budget=child_budget,
297 cf_total_budget=child_budget,
298 cf_nlnet_milestone="abc",
299 cf_payees_list=""),
300 ])
301 node1: Node = bg.nodes[1]
302 errors = bg.get_errors()
303 self.assertErrorTypesMatches(errors,
304 [type(i) for i in expected_errors])
305 self.assertEqual([str(i) for i in errors],
306 [str(i) for i in expected_errors])
307 bg = BudgetGraph([
308 MockBug(bug_id=1,
309 cf_budget_parent=None,
310 cf_budget=str(node1.fixed_budget_excluding_subtasks),
311 cf_total_budget=str(
312 node1.fixed_budget_including_subtasks),
313 cf_nlnet_milestone="abc",
314 cf_payees_list=payees_list),
315 MockBug(bug_id=2,
316 cf_budget_parent=1,
317 cf_budget=child_budget,
318 cf_total_budget=child_budget,
319 cf_nlnet_milestone="abc",
320 cf_payees_list=""),
321 ])
322 errors = bg.get_errors()
323 self.assertErrorTypesMatches(errors,
324 expected_fixed_error_types)
325 helper(budget="0",
326 total_budget="0",
327 payees_list="",
328 child_budget="0",
329 expected_errors=[])
330 helper(budget="0",
331 total_budget="0",
332 payees_list="",
333 child_budget="5",
334 expected_errors=[
335 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
336 1, 1, Money(5)),
337 ])
338 helper(budget="0",
339 total_budget="0",
340 payees_list="a=1",
341 child_budget="0",
342 expected_errors=[
343 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
344 1, 1, Money(1)),
345 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
346 1, 1, Money(1)),
347 ])
348 helper(budget="0",
349 total_budget="0",
350 payees_list="a=1",
351 child_budget="5",
352 expected_errors=[
353 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
354 1, 1, Money(1)),
355 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
356 1, 1, Money(6)),
357 ])
358 helper(budget="0",
359 total_budget="0",
360 payees_list="a=10",
361 child_budget="0",
362 expected_errors=[
363 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
364 1, 1, Money(10)),
365 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
366 1, 1, Money(10)),
367 ])
368 helper(budget="0",
369 total_budget="0",
370 payees_list="a=10",
371 child_budget="5",
372 expected_errors=[
373 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
374 1, 1, Money(10)),
375 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
376 1, 1, Money(15)),
377 ])
378 helper(budget="0",
379 total_budget="100",
380 payees_list="",
381 child_budget="0",
382 expected_errors=[
383 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
384 1, 1, Money(100)),
385 ])
386 helper(budget="0",
387 total_budget="100",
388 payees_list="",
389 child_budget="5",
390 expected_errors=[
391 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
392 1, 1, Money(95)),
393 ])
394 helper(budget="0",
395 total_budget="100",
396 payees_list="a=1",
397 child_budget="0",
398 expected_errors=[
399 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
400 1, 1, Money(100)),
401 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
402 ],
403 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
404 helper(budget="0",
405 total_budget="100",
406 payees_list="a=1",
407 child_budget="5",
408 expected_errors=[
409 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
410 1, 1, Money(95)),
411 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
412 ],
413 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
414 helper(budget="0",
415 total_budget="100",
416 payees_list="a=10",
417 child_budget="0",
418 expected_errors=[
419 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
420 1, 1, Money(100)),
421 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
422 ],
423 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
424 helper(budget="0",
425 total_budget="100",
426 payees_list="a=10",
427 child_budget="5",
428 expected_errors=[
429 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
430 1, 1, Money(95)),
431 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
432 ],
433 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
434 helper(budget="0",
435 total_budget="5",
436 payees_list="",
437 child_budget="5",
438 expected_errors=[])
439 helper(budget="0",
440 total_budget="5",
441 payees_list="a=1",
442 child_budget="5",
443 expected_errors=[
444 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
445 ],
446 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
447 helper(budget="0",
448 total_budget="5",
449 payees_list="a=10",
450 child_budget="5",
451 expected_errors=[
452 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
453 ],
454 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
455 helper(budget="10",
456 total_budget="0",
457 payees_list="",
458 child_budget="0",
459 expected_errors=[
460 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
461 1, 1, Money(10)),
462 ])
463 helper(budget="10",
464 total_budget="0",
465 payees_list="",
466 child_budget="5",
467 expected_errors=[
468 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
469 1, 1, Money(15)),
470 ])
471 helper(budget="10",
472 total_budget="0",
473 payees_list="a=1",
474 child_budget="0",
475 expected_errors=[
476 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
477 1, 1, Money(10)),
478 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
479 ],
480 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
481 helper(budget="10",
482 total_budget="0",
483 payees_list="a=1",
484 child_budget="5",
485 expected_errors=[
486 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
487 1, 1, Money(15)),
488 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
489 ],
490 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
491 helper(budget="10",
492 total_budget="0",
493 payees_list="a=10",
494 child_budget="0",
495 expected_errors=[
496 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
497 1, 1, Money(10)),
498 ])
499 helper(budget="10",
500 total_budget="0",
501 payees_list="a=10",
502 child_budget="5",
503 expected_errors=[
504 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
505 1, 1, Money(15)),
506 ])
507 helper(budget="10",
508 total_budget="10",
509 payees_list="",
510 child_budget="0",
511 expected_errors=[])
512 helper(budget="10",
513 total_budget="10",
514 payees_list="a=1",
515 child_budget="0",
516 expected_errors=[
517 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
518 ],
519 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
520 helper(budget="10",
521 total_budget="10",
522 payees_list="a=10",
523 child_budget="0",
524 expected_errors=[])
525 helper(budget="10",
526 total_budget="100",
527 payees_list="",
528 child_budget="0",
529 expected_errors=[
530 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
531 1, 1, Money(100)),
532 ])
533 helper(budget="10",
534 total_budget="100",
535 payees_list="",
536 child_budget="5",
537 expected_errors=[
538 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
539 1, 1, Money(95)),
540 ])
541 helper(budget="10",
542 total_budget="100",
543 payees_list="a=1",
544 child_budget="0",
545 expected_errors=[
546 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
547 1, 1, Money(10)),
548 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
549 ],
550 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
551 helper(budget="10",
552 total_budget="100",
553 payees_list="a=1",
554 child_budget="5",
555 expected_errors=[
556 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
557 1, 1, Money(15)),
558 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
559 ],
560 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
561 helper(budget="10",
562 total_budget="100",
563 payees_list="a=10",
564 child_budget="0",
565 expected_errors=[
566 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
567 1, 1, Money(10)),
568 ])
569 helper(budget="10",
570 total_budget="100",
571 payees_list="a=10",
572 child_budget="5",
573 expected_errors=[
574 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
575 1, 1, Money(15)),
576 ])
577 helper(budget="10",
578 total_budget="15",
579 payees_list="",
580 child_budget="5",
581 expected_errors=[])
582 helper(budget="10",
583 total_budget="15",
584 payees_list="a=1",
585 child_budget="5",
586 expected_errors=[
587 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
588 ],
589 expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
590 helper(budget="10",
591 total_budget="15",
592 payees_list="a=10",
593 child_budget="5",
594 expected_errors=[])
595
596 helper(budget="1",
597 total_budget="15",
598 payees_list="a=10",
599 child_budget="5",
600 expected_errors=[
601 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
602 1, 1, Money("10"))
603 ])
604
605 def test_negative_money(self):
606 bg = BudgetGraph([
607 MockBug(bug_id=1,
608 cf_budget_parent=None,
609 cf_budget="0",
610 cf_total_budget="-10",
611 cf_nlnet_milestone="abc",
612 cf_payees_list=""),
613 ])
614 errors = bg.get_errors()
615 self.assertErrorTypesMatches(errors, [
616 BudgetGraphNegativeMoney,
617 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
618 self.assertEqual(errors[0].bug_id, 1)
619 self.assertEqual(errors[0].root_bug_id, 1)
620 self.assertEqual(errors[1].bug_id, 1)
621 self.assertEqual(errors[1].root_bug_id, 1)
622 self.assertEqual(errors[1].expected_budget_excluding_subtasks, -10)
623 bg = BudgetGraph([
624 MockBug(bug_id=1,
625 cf_budget_parent=None,
626 cf_budget="-10",
627 cf_total_budget="0",
628 cf_nlnet_milestone="abc",
629 cf_payees_list=""),
630 ])
631 errors = bg.get_errors()
632 self.assertErrorTypesMatches(errors, [
633 BudgetGraphNegativeMoney,
634 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
635 self.assertEqual(errors[0].bug_id, 1)
636 self.assertEqual(errors[0].root_bug_id, 1)
637 self.assertEqual(errors[1].bug_id, 1)
638 self.assertEqual(errors[1].root_bug_id, 1)
639 self.assertEqual(errors[1].expected_budget_including_subtasks, -10)
640 bg = BudgetGraph([
641 MockBug(bug_id=1,
642 cf_budget_parent=None,
643 cf_budget="-10",
644 cf_total_budget="-10",
645 cf_nlnet_milestone="abc",
646 cf_payees_list=""),
647 ])
648 errors = bg.get_errors()
649 self.assertErrorTypesMatches(errors,
650 [BudgetGraphNegativeMoney])
651 self.assertEqual(errors[0].bug_id, 1)
652 self.assertEqual(errors[0].root_bug_id, 1)
653
654 def test_payees_parse(self):
655 def check(cf_payees_list, expected_payments):
656 bg = BudgetGraph([MockBug(bug_id=1,
657 cf_budget_parent=None,
658 cf_budget="0",
659 cf_total_budget="0",
660 cf_nlnet_milestone="abc",
661 cf_payees_list=cf_payees_list),
662 ])
663 self.assertEqual(len(bg.nodes), 1)
664 node: Node = bg.nodes[1]
665 self.assertEqual([str(i) for i in node.payments.values()],
666 expected_payments)
667
668 check("""
669 abc = 123
670 """,
671 ["Payment(node=#1, payee_key='abc', amount=123, "
672 + "state=NotYetSubmitted, paid=None, submitted=None)"])
673 check("""
674 abc = "123"
675 """,
676 ["Payment(node=#1, payee_key='abc', amount=123, "
677 + "state=NotYetSubmitted, paid=None, submitted=None)"])
678 check("""
679 abc = "123.45"
680 """,
681 ["Payment(node=#1, payee_key='abc', amount=123.45, "
682 + "state=NotYetSubmitted, paid=None, submitted=None)"])
683 check("""
684 abc = "123.45"
685 "d e f" = "21.35"
686 """,
687 ["Payment(node=#1, payee_key='abc', amount=123.45, "
688 + "state=NotYetSubmitted, paid=None, submitted=None)",
689 "Payment(node=#1, payee_key='d e f', amount=21.35, "
690 + "state=NotYetSubmitted, paid=None, submitted=None)"])
691 check("""
692 abc = "123.45"
693 # my comments
694 "AAA" = "-21.35"
695 """,
696 ["Payment(node=#1, payee_key='abc', amount=123.45, "
697 + "state=NotYetSubmitted, paid=None, submitted=None)",
698 "Payment(node=#1, payee_key='AAA', amount=-21.35, "
699 + "state=NotYetSubmitted, paid=None, submitted=None)"])
700 check("""
701 "not-an-email@example.com" = "-2345"
702 """,
703 ["Payment(node=#1, payee_key='not-an-email@example.com', "
704 + "amount=-2345, state=NotYetSubmitted, paid=None, "
705 + "submitted=None)"])
706 check("""
707 payee = { amount = 123 }
708 """,
709 ["Payment(node=#1, payee_key='payee', "
710 + "amount=123, state=NotYetSubmitted, paid=None, "
711 + "submitted=None)"])
712 check("""
713 payee = { amount = 123, submitted = 2020-05-01 }
714 """,
715 ["Payment(node=#1, payee_key='payee', "
716 + "amount=123, state=Submitted, paid=None, "
717 + "submitted=2020-05-01)"])
718 check("""
719 payee = { amount = 123, submitted = 2020-05-01T00:00:00 }
720 """,
721 ["Payment(node=#1, payee_key='payee', "
722 + "amount=123, state=Submitted, paid=None, "
723 + "submitted=2020-05-01 00:00:00)"])
724 check("""
725 payee = { amount = 123, submitted = 2020-05-01T00:00:00Z }
726 """,
727 ["Payment(node=#1, payee_key='payee', "
728 + "amount=123, state=Submitted, paid=None, "
729 + "submitted=2020-05-01 00:00:00+00:00)"])
730 check("""
731 payee = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
732 """,
733 ["Payment(node=#1, payee_key='payee', "
734 + "amount=123, state=Submitted, paid=None, "
735 + "submitted=2020-05-01 00:00:00-07:23)"])
736 check("""
737 payee = { amount = 123, paid = 2020-05-01 }
738 """,
739 ["Payment(node=#1, payee_key='payee', "
740 + "amount=123, state=Paid, paid=2020-05-01, "
741 + "submitted=None)"])
742 check("""
743 payee = { amount = 123, paid = 2020-05-01T00:00:00 }
744 """,
745 ["Payment(node=#1, payee_key='payee', "
746 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
747 + "submitted=None)"])
748 check("""
749 payee = { amount = 123, paid = 2020-05-01T00:00:00Z }
750 """,
751 ["Payment(node=#1, payee_key='payee', "
752 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
753 + "submitted=None)"])
754 check("""
755 payee = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
756 """,
757 ["Payment(node=#1, payee_key='payee', "
758 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
759 + "submitted=None)"])
760 check("""
761 [payee]
762 amount = 123
763 submitted = 2020-05-23
764 paid = 2020-05-01
765 """,
766 ["Payment(node=#1, payee_key='payee', "
767 + "amount=123, state=Paid, paid=2020-05-01, "
768 + "submitted=2020-05-23)"])
769 check("""
770 [payee]
771 amount = 123
772 submitted = 2020-05-23
773 paid = 2020-05-01T00:00:00
774 """,
775 ["Payment(node=#1, payee_key='payee', "
776 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
777 + "submitted=2020-05-23)"])
778 check("""
779 [payee]
780 amount = 123
781 submitted = 2020-05-23
782 paid = 2020-05-01T00:00:00Z
783 """,
784 ["Payment(node=#1, payee_key='payee', "
785 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
786 + "submitted=2020-05-23)"])
787 check("""
788 [payee]
789 amount = 123
790 submitted = 2020-05-23
791 paid = 2020-05-01T00:00:00-07:23
792 """,
793 ["Payment(node=#1, payee_key='payee', "
794 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
795 + "submitted=2020-05-23)"])
796
797 def test_payees_money_mismatch(self):
798 bg = BudgetGraph([
799 MockBug(bug_id=1,
800 cf_budget_parent=None,
801 cf_budget="10",
802 cf_total_budget="10",
803 cf_nlnet_milestone="abc",
804 cf_payees_list="payee = 5\npayee2 = 10"),
805 ])
806 errors = bg.get_errors()
807 self.assertErrorTypesMatches(errors,
808 [BudgetGraphPayeesMoneyMismatch])
809 self.assertEqual(errors[0].bug_id, 1)
810 self.assertEqual(errors[0].root_bug_id, 1)
811 self.assertEqual(errors[0].payees_total, 15)
812
813 def test_payees_parse_error(self):
814 def check_parse_error(cf_payees_list, expected_msg):
815 errors = BudgetGraph([
816 MockBug(bug_id=1,
817 cf_budget_parent=None,
818 cf_budget="0",
819 cf_total_budget="0",
820 cf_nlnet_milestone="abc",
821 cf_payees_list=cf_payees_list),
822 ]).get_errors()
823 self.assertErrorTypesMatches(errors,
824 [BudgetGraphPayeesParseError])
825 self.assertEqual(errors[0].bug_id, 1)
826 self.assertEqual(errors[0].msg, expected_msg)
827
828 check_parse_error("""
829 "payee 1" = []
830 """,
831 "value for key 'payee 1' is invalid -- it should "
832 "either be a monetary value or a table")
833
834 check_parse_error("""
835 payee = "ashjkf"
836 """,
837 "failed to parse monetary amount for key 'payee': "
838 "invalid Money string: characters after sign and "
839 "before first `.` must be ascii digits")
840
841 check_parse_error("""
842 payee = "1"
843 payee = "1"
844 """,
845 "TOML parse error: Duplicate keys! (line 3"
846 " column 1 char 39)")
847
848 check_parse_error("""
849 payee = 123.45
850 """,
851 "failed to parse monetary amount for key 'payee': "
852 "monetary amount is not a string or integer (to "
853 "use fractional amounts such as 123.45, write "
854 "\"123.45\"): 123.45")
855
856 check_parse_error("""
857 payee = {}
858 """,
859 "value for key 'payee' is missing the `amount` "
860 "field which is required")
861
862 check_parse_error("""
863 payee = { amount = 123.45 }
864 """,
865 "failed to parse monetary amount for key 'payee': "
866 "monetary amount is not a string or integer (to "
867 "use fractional amounts such as 123.45, write "
868 "\"123.45\"): 123.45")
869
870 check_parse_error("""
871 payee = { amount = 123, blah = false }
872 """,
873 "value for key 'payee' has an unknown field: `blah`")
874
875 check_parse_error("""
876 payee = { amount = 123, submitted = false }
877 """,
878 "failed to parse `submitted` field for key "
879 "'payee': invalid date: false")
880
881 check_parse_error("""
882 payee = { amount = 123, submitted = 123 }
883 """,
884 "failed to parse `submitted` field for key 'payee':"
885 " invalid date: 123")
886
887 check_parse_error(
888 """
889 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
890 """,
891 "failed to parse `submitted` field for key 'payee': "
892 "invalid date: 'abc'")
893
894 check_parse_error(
895 """
896 payee = { amount = 123, paid = 12:34:56 }
897 """,
898 "failed to parse `paid` field for key 'payee': just a time of "
899 "day by itself is not enough, a date must be included: 12:34:56")
900
901 check_parse_error(
902 """
903 payee = { amount = 123, submitted = 12:34:56.123456 }
904 """,
905 "failed to parse `submitted` field for key 'payee': just a time "
906 "of day by itself is not enough, a date must be included: "
907 "12:34:56.123456")
908
909 def test_negative_payee_money(self):
910 bg = BudgetGraph([
911 MockBug(bug_id=1,
912 cf_budget_parent=None,
913 cf_budget="10",
914 cf_total_budget="10",
915 cf_nlnet_milestone="abc",
916 cf_payees_list="""payee1 = -10"""),
917 ])
918 errors = bg.get_errors()
919 self.assertErrorTypesMatches(errors,
920 [BudgetGraphNegativePayeeMoney,
921 BudgetGraphPayeesMoneyMismatch])
922 self.assertEqual(errors[0].bug_id, 1)
923 self.assertEqual(errors[0].root_bug_id, 1)
924 self.assertEqual(errors[0].payee_key, "payee1")
925 self.assertEqual(errors[1].bug_id, 1)
926 self.assertEqual(errors[1].root_bug_id, 1)
927 self.assertEqual(errors[1].payees_total, -10)
928
929 def test_payee_keys(self):
930 bg = BudgetGraph([
931 MockBug(bug_id=1,
932 cf_budget_parent=None,
933 cf_budget="10",
934 cf_total_budget="10",
935 cf_nlnet_milestone="abc",
936 cf_payees_list="payee2 = 3\npayee1 = 7"),
937 MockBug(bug_id=2,
938 cf_budget_parent=None,
939 cf_budget="10",
940 cf_total_budget="10",
941 cf_nlnet_milestone="def",
942 cf_payees_list="""payee3 = 5\npayee2 = 5"""),
943 ])
944 self.assertErrorTypesMatches(bg.get_errors(), [])
945 self.assertEqual(bg.payee_keys, {"payee1", "payee2", "payee3"})
946
947
948 if __name__ == "__main__":
949 unittest.main()