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 BudgetGraphIncorrectRootForMilestone
,
11 BudgetGraphUnknownStatus
, BudgetGraphUnknownAssignee
,
12 BudgetGraphRootWithMilestoneNotInMoU
, BudgetGraphInMoUButParentNotInMoU
,
13 BudgetGraphInMoUWithoutMilestone
)
14 from budget_sync
.money
import Money
15 from budget_sync
.util
import BugStatus
16 from typing
import List
, Type
20 class TestErrorFormatting(unittest
.TestCase
):
21 def test_budget_graph_incorrect_root_for_milestone(self
):
22 self
.assertEqual(str(BudgetGraphIncorrectRootForMilestone(
23 2, "milestone 1", 1)),
24 "Bug #2 is not the canonical root bug for assigned milestone "
25 "'milestone 1' but has no parent bug set: the milestone's "
26 "canonical root bug is #1")
28 def test_budget_graph_loop_error(self
):
29 self
.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])),
30 "Detected Loop in Budget Graph: #5 -> #1 "
31 "-> #2 -> #3 -> #4 -> #5")
32 self
.assertEqual(str(BudgetGraphLoopError([1])),
33 "Detected Loop in Budget Graph: #1 -> #1")
35 def test_budget_graph_money_with_no_milestone(self
):
36 self
.assertEqual(str(BudgetGraphMoneyWithNoMilestone(1, 5)),
37 "Bug assigned money but without any assigned "
40 def test_budget_graph_milestone_mismatch(self
):
41 self
.assertEqual(str(BudgetGraphMilestoneMismatch(1, 5)),
42 "Bug's assigned milestone doesn't match the "
43 "milestone assigned to the root bug: descendant "
44 "bug #1, root bug #5")
46 def test_budget_graph_unknown_milestone(self
):
47 self
.assertEqual(str(BudgetGraphUnknownMilestone(
48 123, "fake milestone")),
49 "failed to parse cf_nlnet_milestone field of bug "
50 "#123: unknown milestone: 'fake milestone'")
52 def test_budget_graph_unknown_status(self
):
53 self
.assertEqual(str(BudgetGraphUnknownStatus(
55 "failed to parse status field of bug "
56 "#123: unknown status: 'fake status'")
58 def test_budget_graph_unknown_assignee(self
):
59 self
.assertEqual(str(BudgetGraphUnknownAssignee(
60 123, "unknown@example.com")),
61 "Bug #123 is assigned to an unknown person:"
62 " 'unknown@example.com'")
64 def test_budget_graph_money_mismatch(self
):
66 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
68 "Budget assigned to task excluding subtasks "
69 "(cf_budget field) doesn't match calculated value:"
70 " bug #1, calculated value 123.4")
72 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
74 "Budget assigned to task including subtasks "
75 "(cf_total_budget field) doesn't match calculated value:"
76 " bug #1, calculated value 123.4")
78 def test_budget_graph_negative_money(self
):
79 self
.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
80 "Budget assigned to task is less than zero: bug #1")
82 def test_budget_graph_negative_payee_money(self
):
83 self
.assertEqual(str(BudgetGraphNegativePayeeMoney(1, 5, "payee1")),
84 "Budget assigned to payee for task is less than "
85 "zero: bug #1, payee 'payee1'")
87 def test_budget_graph_payees_parse_error(self
):
89 BudgetGraphPayeesParseError(1, "my fake parse error")),
90 "Failed to parse cf_payees_list field of bug #1: "
91 "my fake parse error")
93 def test_budget_graph_payees_money_mismatch(self
):
95 BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
96 "Total budget assigned to payees (cf_payees_list) doesn't match "
97 "expected value: bug #1, calculated total 123, expected value 456")
99 def test_budget_graph_root_with_milestone_not_in_mou(self
):
100 self
.assertEqual(str(
101 BudgetGraphRootWithMilestoneNotInMoU(1, "milestone 1")),
102 "Bug #1 has no parent bug set and has an assigned milestone "
103 "'milestone 1' but isn't set to be part of the signed MoU")
105 def test_budget_graph_in_mou_but_parent_not_in_mou(self
):
106 self
.assertEqual(str(
107 BudgetGraphInMoUButParentNotInMoU(5, 3, 1, "milestone 1")),
108 "Bug #5 is set to be part of the signed MoU for milestone "
109 "'milestone 1', but its parent bug isn't set to be part of "
112 def test_budget_graph_in_mou_without_milestone(self
):
113 self
.assertEqual(str(
114 BudgetGraphInMoUWithoutMilestone(1, 5)),
115 "Bug #1 is set to be part of a signed MoU but has no "
119 EXAMPLE_BUG1
= MockBug(bug_id
=1,
120 cf_budget_parent
=None,
123 cf_nlnet_milestone
=None,
126 EXAMPLE_LOOP1_BUG1
= MockBug(bug_id
=1,
130 cf_nlnet_milestone
=None,
133 EXAMPLE_LOOP2_BUG1
= MockBug(bug_id
=1,
137 cf_nlnet_milestone
=None,
140 EXAMPLE_LOOP2_BUG2
= MockBug(bug_id
=2,
144 cf_nlnet_milestone
=None,
147 EXAMPLE_PARENT_BUG1
= MockBug(bug_id
=1,
148 cf_budget_parent
=None,
150 cf_total_budget
="20",
151 cf_nlnet_milestone
="milestone 1",
154 cf_is_in_nlnet_mou2
="Yes")
155 EXAMPLE_CHILD_BUG2
= MockBug(bug_id
=2,
158 cf_total_budget
="10",
159 cf_nlnet_milestone
="milestone 1",
163 EXAMPLE_CONFIG
= Config
.from_str(
165 bugzilla_url = "https://bugzilla.example.com/"
167 aliases = ["person1_alias1", "alias1"]
168 full_name = "Person One"
170 email = "person2@example.com"
171 aliases = ["person1_alias2", "alias2", "person 2"]
172 full_name = "Person Two"
174 email = "user@example.com"
175 full_name = "Person Three"
177 "milestone 1" = { canonical_bug_id = 1 }
178 "milestone 2" = { canonical_bug_id = 2 }
182 class TestBudgetGraph(unittest
.TestCase
):
185 def assertErrorTypesMatches(self
, errors
: List
[BudgetGraphBaseError
], template
: List
[Type
]):
186 def wrap_type_list(type_list
: List
[Type
]):
188 def __init__(self
, t
):
192 return self
.t
.__name
__
194 def __eq__(self
, other
):
195 return self
.t
== other
.t
196 return [TypeWrapper(i
) for i
in type_list
]
199 error_types
.append(type(error
))
200 self
.assertEqual(wrap_type_list(error_types
), wrap_type_list(template
))
203 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
207 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
208 "budget_excluding_subtasks=10, budget_including_subtasks=20, "
209 "fixed_budget_excluding_subtasks=10, "
210 "fixed_budget_including_subtasks=20, milestone_str='milestone "
211 "1', is_in_nlnet_mou=True, "
212 "milestone=Milestone(config=..., identifier='milestone 1', "
213 "canonical_bug_id=1), immediate_children=[#2], payments=[], "
214 "status=BugStatus.CONFIRMED, assignee=Person<'person3'>, "
215 "resolved_payments={}, payment_summaries={}), Node(graph=..., "
216 "id=#2, root=#1, parent=#1, budget_excluding_subtasks=10, "
217 "budget_including_subtasks=10, "
218 "fixed_budget_excluding_subtasks=10, "
219 "fixed_budget_including_subtasks=10, milestone_str='milestone "
220 "1', is_in_nlnet_mou=False, "
221 "milestone=Milestone(config=..., identifier='milestone 1', "
222 "canonical_bug_id=1), immediate_children=[], payments=[], "
223 "status=BugStatus.CONFIRMED, assignee=Person<'person3'>, "
224 "resolved_payments={}, payment_summaries={})], roots=[#1], "
225 "assigned_nodes={Person(config=..., identifier='person1', "
226 "full_name='Person One', "
227 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
228 "[], Person(config=..., identifier='person2', "
229 "full_name='Person Two', "
230 "aliases=OrderedSet(['person1_alias2', 'alias2', 'person 2']), "
231 "email='person2@example.com'): [], Person(config=..., "
232 "identifier='person3', full_name='Person Three', "
233 "aliases=OrderedSet(), email='user@example.com'): [#1, #2]}, "
234 "assigned_nodes_for_milestones={Milestone(config=..., "
235 "identifier='milestone 1', canonical_bug_id=1): [#1, #2], "
236 "Milestone(config=..., identifier='milestone 2', "
237 "canonical_bug_id=2): []}, "
238 "milestone_payments={Milestone(config=..., identifier='milestone "
239 "1', canonical_bug_id=1): [], Milestone(config=..., "
240 "identifier='milestone 2', canonical_bug_id=2): []}, "
241 "payments={Person(config=..., identifier='person1', "
242 "full_name='Person One', "
243 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
244 "{Milestone(config=..., identifier='milestone 1', "
245 "canonical_bug_id=1): [], Milestone(config=..., "
246 "identifier='milestone 2', canonical_bug_id=2): []}, "
247 "Person(config=..., identifier='person2', "
248 "full_name='Person Two', "
249 "aliases=OrderedSet(['person1_alias2', 'alias2', 'person 2']), "
250 "email='person2@example.com'): {Milestone(config=..., "
251 "identifier='milestone 1', canonical_bug_id=1): [], "
252 "Milestone(config=..., identifier='milestone 2', "
253 "canonical_bug_id=2): []}, Person(config=..., "
254 "identifier='person3', full_name='Person Three', "
255 "aliases=OrderedSet(), email='user@example.com'): "
256 "{Milestone(config=..., identifier='milestone 1', "
257 "canonical_bug_id=1): [], Milestone(config=..., "
258 "identifier='milestone 2', canonical_bug_id=2): []}}, "
259 "milestone_people={Milestone(config=..., identifier='milestone "
260 "1', canonical_bug_id=1): OrderedSet(), Milestone(config=..., "
261 "identifier='milestone 2', canonical_bug_id=2): OrderedSet()}}")
262 bg
= BudgetGraph([MockBug(bug_id
=1, status
="blah",
263 assigned_to
="unknown@example.com")],
267 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
268 "budget_excluding_subtasks=0, budget_including_subtasks=0, "
269 "fixed_budget_excluding_subtasks=0, "
270 "fixed_budget_including_subtasks=0, milestone_str=None, "
271 "is_in_nlnet_mou=False, "
272 "milestone=None, immediate_children=[], payments=[], "
273 "status=<unknown status: 'blah'>, assignee=<unknown assignee: "
274 "'unknown@example.com'>, resolved_payments={}, "
275 "payment_summaries={})], roots=[#1], assigned_nodes=<failed>, "
276 "assigned_nodes_for_milestones={Milestone(config=..., "
277 "identifier='milestone 1', canonical_bug_id=1): [], "
278 "Milestone(config=..., identifier='milestone 2', "
279 "canonical_bug_id=2): []}, "
280 "milestone_payments={Milestone(config=..., "
281 "identifier='milestone 1', canonical_bug_id=1): [], "
282 "Milestone(config=..., identifier='milestone 2', "
283 "canonical_bug_id=2): []}, payments={Person(config=..., "
284 "identifier='person1', full_name='Person One', "
285 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
286 "{Milestone(config=..., identifier='milestone 1', "
287 "canonical_bug_id=1): [], Milestone(config=..., "
288 "identifier='milestone 2', canonical_bug_id=2): []}, "
289 "Person(config=..., identifier='person2', "
290 "full_name='Person Two', "
291 "aliases=OrderedSet(['person1_alias2', 'alias2', "
292 "'person 2']), email='person2@example.com'): "
293 "{Milestone(config=..., identifier='milestone 1', "
294 "canonical_bug_id=1): [], Milestone(config=..., "
295 "identifier='milestone 2', canonical_bug_id=2): []}, "
296 "Person(config=..., identifier='person3', "
297 "full_name='Person Three', aliases=OrderedSet(), "
298 "email='user@example.com'): {Milestone(config=..., "
299 "identifier='milestone 1', canonical_bug_id=1): [], "
300 "Milestone(config=..., identifier='milestone 2', "
301 "canonical_bug_id=2): []}}, "
302 "milestone_people={Milestone(config=..., identifier='milestone "
303 "1', canonical_bug_id=1): OrderedSet(), Milestone(config=..., "
304 "identifier='milestone 2', canonical_bug_id=2): OrderedSet()}}")
305 bg
= BudgetGraph([MockBug(bug_id
=1, status
="blah",
306 assigned_to
="unknown@example.com",
308 person1 = {paid=2020-03-15,amount=5}
309 alias1 = {paid=2020-03-15,amount=10}
310 person2 = {submitted=2020-03-15,amount=15}
311 alias2 = {paid=2020-03-16,amount=23}
316 "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
317 "budget_excluding_subtasks=0, budget_including_subtasks=0, "
318 "fixed_budget_excluding_subtasks=0, "
319 "fixed_budget_including_subtasks=0, milestone_str=None, "
320 "is_in_nlnet_mou=False, "
321 "milestone=None, immediate_children=[], "
322 "payments=[Payment(node=#1, payee=Person<'person1'>, "
323 "payee_key='person1', amount=5, state=Paid, paid=2020-03-15, "
324 "submitted=None), Payment(node=#1, payee=Person<'person1'>, "
325 "payee_key='alias1', amount=10, state=Paid, paid=2020-03-15, "
326 "submitted=None), Payment(node=#1, payee=Person<'person2'>, "
327 "payee_key='person2', amount=15, state=Submitted, paid=None, "
328 "submitted=2020-03-15), Payment(node=#1, "
329 "payee=Person<'person2'>, payee_key='alias2', amount=23, "
330 "state=Paid, paid=2020-03-16, submitted=None)], status=<unknown "
331 "status: 'blah'>, assignee=<unknown assignee: "
332 "'unknown@example.com'>, resolved_payments={Person(config=..., "
333 "identifier='person1', full_name='Person One', "
334 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
335 "[Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
336 "amount=5, state=Paid, paid=2020-03-15, submitted=None), "
337 "Payment(node=#1, payee=Person<'person1'>, payee_key='alias1', "
338 "amount=10, state=Paid, paid=2020-03-15, submitted=None)], "
339 "Person(config=..., identifier='person2', "
340 "full_name='Person Two', "
341 "aliases=OrderedSet(['person1_alias2', 'alias2', 'person 2']), "
342 "email='person2@example.com'): [Payment(node=#1, "
343 "payee=Person<'person2'>, payee_key='person2', amount=15, "
344 "state=Submitted, paid=None, submitted=2020-03-15), "
345 "Payment(node=#1, payee=Person<'person2'>, payee_key='alias2', "
346 "amount=23, state=Paid, paid=2020-03-16, submitted=None)]}, "
347 "payment_summaries={Person(config=..., identifier='person1', "
348 "full_name='Person One', "
349 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
350 "PaymentSummary(total=15, total_paid=15, total_submitted=15, "
351 "submitted_date=None, paid_date=2020-03-15, "
352 "state=PaymentSummaryState.Paid, payments=(Payment(node=#1, "
353 "payee=Person<'person1'>, payee_key='person1', amount=5, "
354 "state=Paid, paid=2020-03-15, submitted=None), Payment(node=#1, "
355 "payee=Person<'person1'>, payee_key='alias1', amount=10, "
356 "state=Paid, paid=2020-03-15, submitted=None))), "
357 "Person(config=..., identifier='person2', "
358 "full_name='Person Two', "
359 "aliases=OrderedSet(['person1_alias2', 'alias2', 'person 2']), "
360 "email='person2@example.com'): PaymentSummary(total=38, "
361 "total_paid=23, total_submitted=38, submitted_date=None, "
362 "paid_date=None, state=PaymentSummaryState.Inconsistent, "
363 "payments=(Payment(node=#1, payee=Person<'person2'>, "
364 "payee_key='person2', amount=15, state=Submitted, paid=None, "
365 "submitted=2020-03-15), Payment(node=#1, "
366 "payee=Person<'person2'>, payee_key='alias2', amount=23, "
367 "state=Paid, paid=2020-03-16, submitted=None)))})], roots=[#1], "
368 "assigned_nodes=<failed>, "
369 "assigned_nodes_for_milestones={Milestone(config=..., "
370 "identifier='milestone 1', canonical_bug_id=1): [], "
371 "Milestone(config=..., identifier='milestone 2', "
372 "canonical_bug_id=2): []}, "
373 "milestone_payments={Milestone(config=..., identifier='milestone "
374 "1', canonical_bug_id=1): [], Milestone(config=..., "
375 "identifier='milestone 2', canonical_bug_id=2): []}, "
376 "payments={Person(config=..., identifier='person1', "
377 "full_name='Person One', "
378 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
379 "{Milestone(config=..., identifier='milestone 1', "
380 "canonical_bug_id=1): [], Milestone(config=..., "
381 "identifier='milestone 2', canonical_bug_id=2): []}, "
382 "Person(config=..., identifier='person2', "
383 "full_name='Person Two', "
384 "aliases=OrderedSet(['person1_alias2', 'alias2', 'person 2']), "
385 "email='person2@example.com'): {Milestone(config=..., "
386 "identifier='milestone 1', canonical_bug_id=1): [], "
387 "Milestone(config=..., identifier='milestone 2', "
388 "canonical_bug_id=2): []}, Person(config=..., "
389 "identifier='person3', full_name='Person Three', "
390 "aliases=OrderedSet(), email='user@example.com'): "
391 "{Milestone(config=..., identifier='milestone 1', "
392 "canonical_bug_id=1): [], Milestone(config=..., "
393 "identifier='milestone 2', canonical_bug_id=2): []}}, "
394 "milestone_people={Milestone(config=..., identifier='milestone "
395 "1', canonical_bug_id=1): OrderedSet(), Milestone(config=..., "
396 "identifier='milestone 2', canonical_bug_id=2): OrderedSet()}}")
398 def test_empty(self
):
399 bg
= BudgetGraph([], EXAMPLE_CONFIG
)
400 self
.assertEqual(len(bg
.nodes
), 0)
401 self
.assertEqual(len(bg
.roots
), 0)
402 self
.assertIs(bg
.config
, EXAMPLE_CONFIG
)
404 def test_single(self
):
405 bg
= BudgetGraph([EXAMPLE_BUG1
], EXAMPLE_CONFIG
)
406 self
.assertEqual(len(bg
.nodes
), 1)
407 node
: Node
= bg
.nodes
[1]
408 self
.assertEqual(bg
.roots
, {node}
)
409 self
.assertIsInstance(node
, Node
)
410 self
.assertIs(node
.graph
, bg
)
411 self
.assertIs(node
.bug
, EXAMPLE_BUG1
)
412 self
.assertIs(node
.root
, node
)
413 self
.assertIsNone(node
.parent_id
)
414 self
.assertEqual(node
.immediate_children
, set())
415 self
.assertEqual(node
.bug_url
,
416 "https://bugzilla.example.com/show_bug.cgi?id=1")
417 self
.assertEqual(node
.budget_excluding_subtasks
, Money(cents
=0))
418 self
.assertEqual(node
.budget_including_subtasks
, Money(cents
=0))
419 self
.assertIsNone(node
.milestone
)
420 self
.assertEqual(node
.payments
, {})
422 def test_loop1(self
):
423 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
424 BudgetGraph([EXAMPLE_LOOP1_BUG1
], EXAMPLE_CONFIG
).roots
425 self
.assertEqual(cm
.exception
.bug_ids
, [1])
427 def test_loop2(self
):
428 with self
.assertRaises(BudgetGraphLoopError
) as cm
:
429 BudgetGraph([EXAMPLE_LOOP2_BUG1
, EXAMPLE_LOOP2_BUG2
],
430 EXAMPLE_CONFIG
).roots
431 self
.assertEqual(cm
.exception
.bug_ids
, [2, 1])
433 def test_parent_child(self
):
434 bg
= BudgetGraph([EXAMPLE_PARENT_BUG1
, EXAMPLE_CHILD_BUG2
],
436 self
.assertEqual(len(bg
.nodes
), 2)
437 node1
: Node
= bg
.nodes
[1]
438 node2
: Node
= bg
.nodes
[2]
439 self
.assertEqual(bg
.roots
, {node1}
)
440 self
.assertEqual(node1
, node1
)
441 self
.assertEqual(node2
, node2
)
442 self
.assertNotEqual(node1
, node2
)
443 self
.assertNotEqual(node2
, node1
)
444 self
.assertIsInstance(node1
, Node
)
445 self
.assertIs(node1
.graph
, bg
)
446 self
.assertIs(node1
.bug
, EXAMPLE_PARENT_BUG1
)
447 self
.assertIsNone(node1
.parent_id
)
448 self
.assertEqual(node1
.root
, node1
)
449 self
.assertEqual(node1
.immediate_children
, {node2}
)
450 self
.assertEqual(node1
.budget_excluding_subtasks
, Money(cents
=1000))
451 self
.assertEqual(node1
.budget_including_subtasks
, Money(cents
=2000))
452 self
.assertEqual(node1
.milestone_str
, "milestone 1")
453 self
.assertEqual(node1
.bug_url
,
454 "https://bugzilla.example.com/show_bug.cgi?id=1")
455 self
.assertEqual(list(node1
.children()), [node2
])
456 self
.assertEqual(list(node1
.children_breadth_first()), [node2
])
457 self
.assertEqual(node1
.payments
, {})
458 self
.assertIsInstance(node2
, Node
)
459 self
.assertIs(node2
.graph
, bg
)
460 self
.assertIs(node2
.bug
, EXAMPLE_CHILD_BUG2
)
461 self
.assertEqual(node2
.parent_id
, 1)
462 self
.assertEqual(node2
.root
, node1
)
463 self
.assertEqual(node2
.immediate_children
, set())
464 self
.assertEqual(node2
.budget_excluding_subtasks
, Money(cents
=1000))
465 self
.assertEqual(node2
.budget_including_subtasks
, Money(cents
=1000))
466 self
.assertEqual(node2
.milestone_str
, "milestone 1")
467 self
.assertEqual(node2
.payments
, {})
468 self
.assertEqual(node2
.bug_url
,
469 "https://bugzilla.example.com/show_bug.cgi?id=2")
471 def test_children(self
):
474 cf_budget_parent
=None,
477 cf_nlnet_milestone
=None,
484 cf_nlnet_milestone
=None,
491 cf_nlnet_milestone
=None,
498 cf_nlnet_milestone
=None,
505 cf_nlnet_milestone
=None,
512 cf_nlnet_milestone
=None,
519 cf_nlnet_milestone
=None,
523 self
.assertEqual(len(bg
.nodes
), 7)
524 node1
: Node
= bg
.nodes
[1]
525 node2
: Node
= bg
.nodes
[2]
526 node3
: Node
= bg
.nodes
[3]
527 node4
: Node
= bg
.nodes
[4]
528 node5
: Node
= bg
.nodes
[5]
529 node6
: Node
= bg
.nodes
[6]
530 node7
: Node
= bg
.nodes
[7]
531 self
.assertEqual(bg
.roots
, {node1}
)
532 self
.assertEqual(list(node1
.children()),
533 [node2
, node3
, node5
, node7
, node6
, node4
])
534 self
.assertEqual(list(node1
.children_breadth_first()),
535 [node2
, node3
, node4
, node5
, node6
, node7
])
537 def test_money_with_no_milestone(self
):
540 cf_budget_parent
=None,
542 cf_total_budget
="10",
543 cf_nlnet_milestone
=None,
547 errors
= bg
.get_errors()
548 self
.assertErrorTypesMatches(errors
, [
549 BudgetGraphMoneyWithNoMilestone
,
550 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
])
551 self
.assertEqual(errors
[0].bug_id
, 1)
552 self
.assertEqual(errors
[0].root_bug_id
, 1)
555 cf_budget_parent
=None,
558 cf_nlnet_milestone
=None,
562 errors
= bg
.get_errors()
563 self
.assertErrorTypesMatches(errors
, [
564 BudgetGraphMoneyWithNoMilestone
,
565 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
566 self
.assertEqual(errors
[0].bug_id
, 1)
567 self
.assertEqual(errors
[0].root_bug_id
, 1)
570 cf_budget_parent
=None,
572 cf_total_budget
="10",
573 cf_nlnet_milestone
=None,
577 errors
= bg
.get_errors()
578 self
.assertErrorTypesMatches(errors
, [BudgetGraphMoneyWithNoMilestone
])
579 self
.assertEqual(errors
[0].bug_id
, 1)
580 self
.assertEqual(errors
[0].root_bug_id
, 1)
582 def test_money_mismatch(self
):
583 def helper(budget
, total_budget
, payees_list
, child_budget
,
584 expected_errors
, expected_fixed_error_types
=None):
585 if expected_fixed_error_types
is None:
586 expected_fixed_error_types
= []
589 cf_budget_parent
=None,
591 cf_total_budget
=total_budget
,
592 cf_nlnet_milestone
="milestone 1",
593 cf_payees_list
=payees_list
,
595 cf_is_in_nlnet_mou2
="Yes"),
598 cf_budget
=child_budget
,
599 cf_total_budget
=child_budget
,
600 cf_nlnet_milestone
="milestone 1",
603 node1
: Node
= bg
.nodes
[1]
604 errors
= bg
.get_errors()
605 self
.assertErrorTypesMatches(errors
,
606 [type(i
) for i
in expected_errors
])
607 self
.assertEqual([str(i
) for i
in errors
],
608 [str(i
) for i
in expected_errors
])
611 cf_budget_parent
=None,
612 cf_budget
=str(node1
.fixed_budget_excluding_subtasks
),
614 node1
.fixed_budget_including_subtasks
),
615 cf_nlnet_milestone
="milestone 1",
616 cf_payees_list
=payees_list
,
618 cf_is_in_nlnet_mou2
="Yes"),
621 cf_budget
=child_budget
,
622 cf_total_budget
=child_budget
,
623 cf_nlnet_milestone
="milestone 1",
627 errors
= bg
.get_errors()
628 self
.assertErrorTypesMatches(errors
,
629 expected_fixed_error_types
)
640 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
645 payees_list
="person1=1",
648 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
650 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
655 payees_list
="person1=1",
658 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
660 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
665 payees_list
="person1=10",
668 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
670 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
675 payees_list
="person1=10",
678 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
680 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
688 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
696 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
701 payees_list
="person1=1",
704 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
706 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
708 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
711 payees_list
="person1=1",
714 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
716 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
718 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
721 payees_list
="person1=10",
724 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
726 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
728 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
731 payees_list
="person1=10",
734 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
736 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
738 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
746 payees_list
="person1=1",
749 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
751 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
754 payees_list
="person1=10",
757 BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
759 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
765 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
773 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
778 payees_list
="person1=1",
781 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
783 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
785 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
788 payees_list
="person1=1",
791 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
793 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
795 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
798 payees_list
="person1=10",
801 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
806 payees_list
="person1=10",
809 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
819 payees_list
="person1=1",
822 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
824 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
827 payees_list
="person1=10",
835 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
843 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
848 payees_list
="person1=1",
851 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
853 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
855 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
858 payees_list
="person1=1",
861 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
863 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
865 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
868 payees_list
="person1=10",
871 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
876 payees_list
="person1=10",
879 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
889 payees_list
="person1=1",
892 BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
894 expected_fixed_error_types
=[BudgetGraphPayeesMoneyMismatch
])
897 payees_list
="person1=10",
903 payees_list
="person1=10",
906 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
910 def test_negative_money(self
):
913 cf_budget_parent
=None,
915 cf_total_budget
="-10",
916 cf_nlnet_milestone
="milestone 1",
919 cf_is_in_nlnet_mou2
="Yes"),
921 errors
= bg
.get_errors()
922 self
.assertErrorTypesMatches(errors
, [
923 BudgetGraphNegativeMoney
,
924 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
925 self
.assertEqual(errors
[0].bug_id
, 1)
926 self
.assertEqual(errors
[0].root_bug_id
, 1)
927 self
.assertEqual(errors
[1].bug_id
, 1)
928 self
.assertEqual(errors
[1].root_bug_id
, 1)
929 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, 0)
932 cf_budget_parent
=None,
935 cf_nlnet_milestone
="milestone 1",
938 cf_is_in_nlnet_mou2
="Yes"),
940 errors
= bg
.get_errors()
941 self
.assertErrorTypesMatches(errors
, [
942 BudgetGraphNegativeMoney
,
943 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
])
944 self
.assertEqual(errors
[0].bug_id
, 1)
945 self
.assertEqual(errors
[0].root_bug_id
, 1)
946 self
.assertEqual(errors
[1].bug_id
, 1)
947 self
.assertEqual(errors
[1].root_bug_id
, 1)
948 self
.assertEqual(errors
[1].expected_budget_including_subtasks
, -10)
951 cf_budget_parent
=None,
953 cf_total_budget
="-10",
954 cf_nlnet_milestone
="milestone 1",
957 cf_is_in_nlnet_mou2
="Yes"),
959 errors
= bg
.get_errors()
960 self
.assertErrorTypesMatches(errors
,
961 [BudgetGraphNegativeMoney
])
962 self
.assertEqual(errors
[0].bug_id
, 1)
963 self
.assertEqual(errors
[0].root_bug_id
, 1)
965 def test_payees_parse(self
):
966 def check(cf_payees_list
, error_types
, expected_payments
):
967 bg
= BudgetGraph([MockBug(bug_id
=1,
968 cf_budget_parent
=None,
971 cf_nlnet_milestone
="milestone 1",
972 cf_payees_list
=cf_payees_list
,
974 cf_is_in_nlnet_mou2
="Yes"),
976 self
.assertErrorTypesMatches(bg
.get_errors(), error_types
)
977 self
.assertEqual(len(bg
.nodes
), 1)
978 node
: Node
= bg
.nodes
[1]
979 self
.assertEqual([str(i
) for i
in node
.payments
.values()],
986 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
987 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
988 ["Payment(node=#1, payee=Person<'person1'>, "
989 "payee_key='person1', amount=123, "
990 "state=NotYetSubmitted, paid=None, submitted=None)"])
995 [BudgetGraphPayeesParseError
,
996 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
997 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
998 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
999 "amount=123, state=NotYetSubmitted, paid=None, "
1005 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1006 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1007 ["Payment(node=#1, payee=Person<'person1'>, "
1008 "payee_key='person1', amount=123.45, "
1009 "state=NotYetSubmitted, paid=None, submitted=None)"])
1013 "person 2" = "21.35"
1015 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1016 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1017 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1018 'amount=123.45, state=NotYetSubmitted, paid=None, '
1020 "Payment(node=#1, payee=Person<'person2'>, payee_key='person 2', "
1021 'amount=21.35, state=NotYetSubmitted, paid=None, '
1028 [BudgetGraphPayeesParseError
,
1029 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1030 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1031 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1032 'amount=123.45, state=NotYetSubmitted, paid=None, '
1034 "Payment(node=#1, payee=<unknown person>, payee_key='d e f', "
1035 'amount=21.35, state=NotYetSubmitted, paid=None, '
1043 [BudgetGraphPayeesParseError
,
1044 BudgetGraphNegativePayeeMoney
,
1045 BudgetGraphPayeesParseError
,
1046 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1047 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1048 ["Payment(node=#1, payee=<unknown person>, payee_key='abc', "
1049 'amount=123.45, state=NotYetSubmitted, paid=None, '
1051 "Payment(node=#1, payee=<unknown person>, payee_key='AAA', "
1052 'amount=-21.35, state=NotYetSubmitted, paid=None, '
1056 "not-an-email@example.com" = "-2345"
1058 [BudgetGraphNegativePayeeMoney
,
1059 BudgetGraphPayeesParseError
,
1060 BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1061 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1062 ['Payment(node=#1, payee=<unknown person>, '
1063 "payee_key='not-an-email@example.com', amount=-2345, "
1064 "state=NotYetSubmitted, paid=None, submitted=None)"])
1067 person1 = { amount = 123 }
1069 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1070 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1071 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1072 "amount=123, state=NotYetSubmitted, paid=None, submitted=None)"])
1075 person1 = { amount = 123, submitted = 2020-05-01 }
1077 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1078 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1079 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1080 + "amount=123, state=Submitted, paid=None, "
1081 + "submitted=2020-05-01)"])
1084 person1 = { amount = 123, submitted = 2020-05-01T00:00:00 }
1086 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1087 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1088 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1089 + "amount=123, state=Submitted, paid=None, "
1090 + "submitted=2020-05-01 00:00:00)"])
1093 person1 = { amount = 123, submitted = 2020-05-01T00:00:00Z }
1095 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1096 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1097 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1098 + "amount=123, state=Submitted, paid=None, "
1099 + "submitted=2020-05-01 00:00:00+00:00)"])
1102 person1 = { amount = 123, submitted = 2020-05-01T00:00:00-07:23 }
1104 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1105 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1106 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1107 + "amount=123, state=Submitted, paid=None, "
1108 + "submitted=2020-05-01 00:00:00-07:23)"])
1111 person1 = { amount = 123, paid = 2020-05-01 }
1113 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1114 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1115 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1116 + "amount=123, state=Paid, paid=2020-05-01, "
1117 + "submitted=None)"])
1120 person1 = { amount = 123, paid = 2020-05-01T00:00:00 }
1122 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1123 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1124 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1125 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
1126 + "submitted=None)"])
1129 person1 = { amount = 123, paid = 2020-05-01T00:00:00Z }
1131 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1132 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1133 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1134 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
1135 + "submitted=None)"])
1138 person1 = { amount = 123, paid = 2020-05-01T00:00:00-07:23 }
1140 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1141 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1142 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1143 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
1144 + "submitted=None)"])
1149 submitted = 2020-05-23
1152 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1153 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1154 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1155 + "amount=123, state=Paid, paid=2020-05-01, "
1156 + "submitted=2020-05-23)"])
1161 submitted = 2020-05-23
1162 paid = 2020-05-01T00:00:00
1164 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1165 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1166 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1167 + "amount=123, state=Paid, paid=2020-05-01 00:00:00, "
1168 + "submitted=2020-05-23)"])
1173 submitted = 2020-05-23
1174 paid = 2020-05-01T00:00:00Z
1176 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1177 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1178 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1179 + "amount=123, state=Paid, paid=2020-05-01 00:00:00+00:00, "
1180 + "submitted=2020-05-23)"])
1185 submitted = 2020-05-23
1186 paid = 2020-05-01T00:00:00-07:23
1188 [BudgetGraphMoneyMismatchForBudgetExcludingSubtasks
,
1189 BudgetGraphMoneyMismatchForBudgetIncludingSubtasks
],
1190 ["Payment(node=#1, payee=Person<'person1'>, payee_key='person1', "
1191 + "amount=123, state=Paid, paid=2020-05-01 00:00:00-07:23, "
1192 + "submitted=2020-05-23)"])
1194 def test_payees_money_mismatch(self
):
1197 cf_budget_parent
=None,
1199 cf_total_budget
="10",
1200 cf_nlnet_milestone
="milestone 1",
1201 cf_payees_list
="person1 = 5\nperson2 = 10",
1203 cf_is_in_nlnet_mou2
="Yes"),
1205 errors
= bg
.get_errors()
1206 self
.assertErrorTypesMatches(errors
,
1207 [BudgetGraphPayeesMoneyMismatch
])
1208 self
.assertEqual(errors
[0].bug_id
, 1)
1209 self
.assertEqual(errors
[0].root_bug_id
, 1)
1210 self
.assertEqual(errors
[0].payees_total
, 15)
1212 def test_payees_parse_error(self
):
1213 def check_parse_error(cf_payees_list
, expected_msg
):
1214 errors
= BudgetGraph([
1216 cf_budget_parent
=None,
1218 cf_total_budget
="0",
1219 cf_nlnet_milestone
="milestone 1",
1220 cf_payees_list
=cf_payees_list
,
1222 cf_is_in_nlnet_mou2
="Yes"),
1223 ], EXAMPLE_CONFIG
).get_errors()
1224 self
.assertErrorTypesMatches(errors
,
1225 [BudgetGraphPayeesParseError
])
1226 self
.assertEqual(errors
[0].bug_id
, 1)
1227 self
.assertEqual(errors
[0].msg
, expected_msg
)
1229 check_parse_error("""
1232 "value for key 'payee 1' is invalid -- it should "
1233 "either be a monetary value or a table")
1235 check_parse_error("""
1238 "failed to parse monetary amount for key 'payee': "
1239 "invalid Money string: characters after sign and "
1240 "before first `.` must be ascii digits")
1242 check_parse_error("""
1246 "TOML parse error: Duplicate keys! (line 3"
1247 " column 1 char 39)")
1249 check_parse_error("""
1252 "failed to parse monetary amount for key 'payee': "
1253 "monetary amount is not a string or integer (to "
1254 "use fractional amounts such as 123.45, write "
1255 "\"123.45\"): 123.45")
1257 check_parse_error("""
1260 "value for key 'payee' is missing the `amount` "
1261 "field which is required")
1263 check_parse_error("""
1264 payee = { amount = 123.45 }
1266 "failed to parse monetary amount for key 'payee': "
1267 "monetary amount is not a string or integer (to "
1268 "use fractional amounts such as 123.45, write "
1269 "\"123.45\"): 123.45")
1271 check_parse_error("""
1272 payee = { amount = 123, blah = false }
1274 "value for key 'payee' has an unknown field: `blah`")
1276 check_parse_error("""
1277 payee = { amount = 123, submitted = false }
1279 "failed to parse `submitted` field for key "
1280 "'payee': invalid date: false")
1282 check_parse_error("""
1283 payee = { amount = 123, submitted = 123 }
1285 "failed to parse `submitted` field for key 'payee':"
1286 " invalid date: 123")
1290 payee = { amount = 123, paid = 2020-01-01, submitted = "abc" }
1292 "failed to parse `submitted` field for key 'payee': "
1293 "invalid date: 'abc'")
1297 payee = { amount = 123, paid = 12:34:56 }
1299 "failed to parse `paid` field for key 'payee': just a time of "
1300 "day by itself is not enough, a date must be included: 12:34:56")
1304 payee = { amount = 123, submitted = 12:34:56.123456 }
1306 "failed to parse `submitted` field for key 'payee': just a time "
1307 "of day by itself is not enough, a date must be included: "
1310 def test_negative_payee_money(self
):
1313 cf_budget_parent
=None,
1315 cf_total_budget
="10",
1316 cf_nlnet_milestone
="milestone 1",
1317 cf_payees_list
="""person1 = -10""",
1319 cf_is_in_nlnet_mou2
="Yes"),
1321 errors
= bg
.get_errors()
1322 self
.assertErrorTypesMatches(errors
,
1323 [BudgetGraphNegativePayeeMoney
,
1324 BudgetGraphPayeesMoneyMismatch
])
1325 self
.assertEqual(errors
[0].bug_id
, 1)
1326 self
.assertEqual(errors
[0].root_bug_id
, 1)
1327 self
.assertEqual(errors
[0].payee_key
, "person1")
1328 self
.assertEqual(errors
[1].bug_id
, 1)
1329 self
.assertEqual(errors
[1].root_bug_id
, 1)
1330 self
.assertEqual(errors
[1].payees_total
, -10)
1332 def test_duplicate_payments(self
):
1335 cf_budget_parent
=None,
1337 cf_total_budget
="10",
1338 cf_nlnet_milestone
="milestone 1",
1344 cf_is_in_nlnet_mou2
="Yes"),
1346 errors
= bg
.get_errors()
1347 self
.assertErrorTypesMatches(errors
, [])
1348 person1
= EXAMPLE_CONFIG
.people
["person1"]
1349 person2
= EXAMPLE_CONFIG
.people
["person2"]
1350 person3
= EXAMPLE_CONFIG
.people
["person3"]
1351 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1352 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1353 node1
: Node
= bg
.nodes
[1]
1354 node1_payment_person1
= node1
.payments
["person1"]
1355 node1_payment_alias1
= node1
.payments
["alias1"]
1356 self
.assertEqual(bg
.payments
, {
1358 milestone1
: [node1_payment_person1
, node1_payment_alias1
],
1361 person2
: {milestone1
: [], milestone2
: []},
1362 person3
: {milestone1
: [], milestone2
: []},
1365 repr(node1
.payment_summaries
),
1366 "{Person(config=..., identifier='person1', "
1367 "full_name='Person One', "
1368 "aliases=OrderedSet(['person1_alias1', 'alias1']), email=None): "
1369 "PaymentSummary(total=10, total_paid=0, total_submitted=0, "
1370 "submitted_date=None, paid_date=None, "
1371 "state=PaymentSummaryState.NotYetSubmitted, "
1372 "payments=(Payment(node=#1, payee=Person<'person1'>, "
1373 "payee_key='person1', amount=5, state=NotYetSubmitted, "
1374 "paid=None, submitted=None), Payment(node=#1, "
1375 "payee=Person<'person1'>, payee_key='alias1', amount=5, "
1376 "state=NotYetSubmitted, paid=None, submitted=None)))}")
1378 def test_incorrect_root_for_milestone(self
):
1381 cf_budget_parent
=None,
1383 cf_total_budget
="10",
1384 cf_nlnet_milestone
="milestone 2",
1387 cf_is_in_nlnet_mou2
="Yes"),
1389 errors
= bg
.get_errors()
1390 self
.assertErrorTypesMatches(errors
,
1391 [BudgetGraphIncorrectRootForMilestone
])
1392 self
.assertEqual(errors
[0].bug_id
, 1)
1393 self
.assertEqual(errors
[0].root_bug_id
, 1)
1394 self
.assertEqual(errors
[0].milestone
, "milestone 2")
1395 self
.assertEqual(errors
[0].milestone_canonical_bug_id
, 2)
1398 cf_budget_parent
=None,
1400 cf_total_budget
="0",
1401 cf_nlnet_milestone
="milestone 2",
1404 cf_is_in_nlnet_mou2
="Yes"),
1406 errors
= bg
.get_errors()
1407 self
.assertErrorTypesMatches(errors
, [])
1409 def test_payments(self
):
1412 cf_budget_parent
=None,
1414 cf_total_budget
="10",
1415 cf_nlnet_milestone
="milestone 1",
1416 cf_payees_list
="person1 = 3\nperson2 = 7",
1418 cf_is_in_nlnet_mou2
="Yes"),
1420 cf_budget_parent
=None,
1422 cf_total_budget
="10",
1423 cf_nlnet_milestone
="milestone 2",
1424 cf_payees_list
="person3 = 5\nperson2 = 5",
1426 cf_is_in_nlnet_mou2
="Yes"),
1428 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1429 person1
= EXAMPLE_CONFIG
.people
["person1"]
1430 person2
= EXAMPLE_CONFIG
.people
["person2"]
1431 person3
= EXAMPLE_CONFIG
.people
["person3"]
1432 milestone1
= EXAMPLE_CONFIG
.milestones
["milestone 1"]
1433 milestone2
= EXAMPLE_CONFIG
.milestones
["milestone 2"]
1434 node1
: Node
= bg
.nodes
[1]
1435 node2
: Node
= bg
.nodes
[2]
1436 node1_payment_person1
= node1
.payments
["person1"]
1437 node1_payment_person2
= node1
.payments
["person2"]
1438 node2_payment_person2
= node2
.payments
["person2"]
1439 node2_payment_person3
= node2
.payments
["person3"]
1440 self
.assertEqual(bg
.payments
,
1443 milestone1
: [node1_payment_person1
],
1447 milestone1
: [node1_payment_person2
],
1448 milestone2
: [node2_payment_person2
],
1452 milestone2
: [node2_payment_person3
],
1456 def test_status(self
):
1457 bg
= BudgetGraph([MockBug(bug_id
=1, status
="blah")],
1459 errors
= bg
.get_errors()
1460 self
.assertErrorTypesMatches(errors
,
1461 [BudgetGraphUnknownStatus
])
1462 self
.assertEqual(errors
[0].bug_id
, 1)
1463 self
.assertEqual(errors
[0].status_str
, "blah")
1464 for status
in BugStatus
:
1465 bg
= BudgetGraph([MockBug(bug_id
=1, status
=status
)],
1467 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1468 self
.assertEqual(bg
.nodes
[1].status
, status
)
1470 def test_assignee(self
):
1471 bg
= BudgetGraph([MockBug(bug_id
=1, assigned_to
="blah")],
1473 errors
= bg
.get_errors()
1474 self
.assertErrorTypesMatches(errors
,
1475 [BudgetGraphUnknownAssignee
])
1476 self
.assertEqual(errors
[0].bug_id
, 1)
1477 self
.assertEqual(errors
[0].assignee
, "blah")
1478 bg
= BudgetGraph([MockBug(bug_id
=1,
1479 assigned_to
="person2@example.com")],
1481 self
.assertErrorTypesMatches(bg
.get_errors(), [])
1482 self
.assertEqual(bg
.nodes
[1].assignee
,
1483 EXAMPLE_CONFIG
.people
["person2"])
1485 def test_closest_bug_in_mou(self
):
1487 MockBug(bug_id
=1, cf_nlnet_milestone
="milestone 1",
1488 cf_is_in_nlnet_mou2
="Yes"),
1489 MockBug(bug_id
=2, cf_budget_parent
=1,
1490 cf_nlnet_milestone
="milestone 1",
1491 cf_is_in_nlnet_mou2
="Yes"),
1492 MockBug(bug_id
=3, cf_budget_parent
=2,
1493 cf_nlnet_milestone
="milestone 1",
1494 cf_is_in_nlnet_mou2
="Yes"),
1495 MockBug(bug_id
=4, cf_budget_parent
=2,
1496 cf_nlnet_milestone
="milestone 1"),
1497 MockBug(bug_id
=5, cf_budget_parent
=4,
1498 cf_nlnet_milestone
="milestone 1"),
1501 errors
= bg
.get_errors()
1502 self
.assertErrorTypesMatches(errors
, [])
1503 self
.assertEqual(bg
.nodes
[1].closest_bug_in_mou
, bg
.nodes
[1])
1504 self
.assertEqual(bg
.nodes
[2].closest_bug_in_mou
, bg
.nodes
[2])
1505 self
.assertEqual(bg
.nodes
[3].closest_bug_in_mou
, bg
.nodes
[3])
1506 self
.assertEqual(bg
.nodes
[4].closest_bug_in_mou
, bg
.nodes
[2])
1507 self
.assertEqual(bg
.nodes
[5].closest_bug_in_mou
, bg
.nodes
[2])
1508 self
.assertEqual(bg
.nodes
[6].closest_bug_in_mou
, None)
1510 def test_root_with_milestone_not_in_mou(self
):
1512 MockBug(bug_id
=1, cf_nlnet_milestone
="milestone 1"),
1514 errors
= bg
.get_errors()
1515 self
.assertErrorTypesMatches(errors
,
1516 [BudgetGraphRootWithMilestoneNotInMoU
])
1517 self
.assertEqual(errors
[0].bug_id
, 1)
1518 self
.assertEqual(errors
[0].root_bug_id
, 1)
1519 self
.assertEqual(errors
[0].milestone
, "milestone 1")
1521 def test_budget_graph_in_mou_without_milestone(self
):
1523 MockBug(bug_id
=1, cf_is_in_nlnet_mou2
="Yes"),
1525 errors
= bg
.get_errors()
1526 self
.assertErrorTypesMatches(errors
,
1527 [BudgetGraphInMoUWithoutMilestone
])
1528 self
.assertEqual(errors
[0].bug_id
, 1)
1529 self
.assertEqual(errors
[0].root_bug_id
, 1)
1531 def test_in_mou_but_parent_not_in_mou(self
):
1533 MockBug(bug_id
=1, cf_nlnet_milestone
="milestone 1",
1534 cf_is_in_nlnet_mou2
="Yes"),
1535 MockBug(bug_id
=2, cf_nlnet_milestone
="milestone 1",
1536 cf_budget_parent
=1),
1537 MockBug(bug_id
=3, cf_nlnet_milestone
="milestone 1",
1538 cf_budget_parent
=2, cf_is_in_nlnet_mou2
="Yes"),
1540 errors
= bg
.get_errors()
1541 self
.assertErrorTypesMatches(errors
,
1542 [BudgetGraphInMoUButParentNotInMoU
])
1543 self
.assertEqual(errors
[0].bug_id
, 3)
1544 self
.assertEqual(errors
[0].root_bug_id
, 1)
1545 self
.assertEqual(errors
[0].parent_bug_id
, 2)
1548 if __name__
== "__main__":