all unit tests pass
[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 BudgetGraphMoneyMismatch,
6 BudgetGraphNegativeMoney,
7 BudgetGraphMilestoneMismatch)
8 from budget_sync.money import Money
9 from typing import List, Type
10 import unittest
11
12
13 class TestErrorFormatting(unittest.TestCase):
14 def test_budget_graph_loop_error(self):
15 self.assertEqual(str(BudgetGraphLoopError([1, 2, 3, 4, 5])),
16 "Detected Loop in Budget Graph: #5 -> #1 "
17 "-> #2 -> #3 -> #4 -> #5")
18 self.assertEqual(str(BudgetGraphLoopError([1])),
19 "Detected Loop in Budget Graph: #1 -> #1")
20
21 def test_budget_graph_money_with_no_milestone(self):
22 self.assertEqual(str(BudgetGraphMoneyWithNoMilestone(1, 5)),
23 "Bug assigned money but without any assigned "
24 "milestone: #1")
25
26 def test_budget_graph_milestone_mismatch(self):
27 self.assertEqual(str(BudgetGraphMilestoneMismatch(1, 5)),
28 "Bug's assigned milestone doesn't match the "
29 "milestone assigned to the root bug: descendant "
30 "bug #1, root bug #5")
31
32 def test_budget_graph_money_mismatch(self):
33 self.assertEqual(str(BudgetGraphMoneyMismatch(1, 5, "123.4")),
34 "Budget assigned to task excluding subtasks "
35 "(cf_budget field) doesn't match calculated value:"
36 " bug #1, calculated value 123.4")
37
38 def test_budget_graph_negative_money(self):
39 self.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
40 "Budget assigned to task is less than zero: bug #1")
41
42
43 EXAMPLE_BUG1 = MockBug(bug_id=1,
44 cf_budget_parent=None,
45 cf_budget="0",
46 cf_total_budget="0",
47 cf_nlnet_milestone=None)
48 EXAMPLE_LOOP1_BUG1 = MockBug(bug_id=1,
49 cf_budget_parent=1,
50 cf_budget="0",
51 cf_total_budget="0",
52 cf_nlnet_milestone=None)
53 EXAMPLE_LOOP2_BUG1 = MockBug(bug_id=1,
54 cf_budget_parent=2,
55 cf_budget="0",
56 cf_total_budget="0",
57 cf_nlnet_milestone=None)
58 EXAMPLE_LOOP2_BUG2 = MockBug(bug_id=2,
59 cf_budget_parent=1,
60 cf_budget="0",
61 cf_total_budget="0",
62 cf_nlnet_milestone=None)
63 EXAMPLE_PARENT_BUG1 = MockBug(bug_id=1,
64 cf_budget_parent=None,
65 cf_budget="10",
66 cf_total_budget="20",
67 cf_nlnet_milestone="abc")
68 EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2,
69 cf_budget_parent=1,
70 cf_budget="10",
71 cf_total_budget="10",
72 cf_nlnet_milestone="abc")
73
74
75 class TestBudgetGraph(unittest.TestCase):
76 def assertErrorTypesMatches(self, errors: List[BudgetGraphBaseError], template: List[Type]):
77 error_types = []
78 for error in errors:
79 error_types.append(type(error))
80 self.assertEqual(error_types, template)
81
82 def test_empty(self):
83 bg = BudgetGraph([])
84 self.assertEqual(len(bg.nodes), 0)
85 self.assertEqual(len(bg.roots), 0)
86
87 def test_single(self):
88 bg = BudgetGraph([EXAMPLE_BUG1])
89 self.assertEqual(len(bg.nodes), 1)
90 node: Node = bg.nodes[1]
91 self.assertEqual(bg.roots, {node})
92 self.assertIsInstance(node, Node)
93 self.assertIs(node.graph, bg)
94 self.assertIs(node.bug, EXAMPLE_BUG1)
95 self.assertIs(node.root, node)
96 self.assertIsNone(node.parent_id)
97 self.assertEqual(node.immediate_children, set())
98 self.assertEqual(node.budget_excluding_subtasks, Money(cents=0))
99 self.assertEqual(node.budget_including_subtasks, Money(cents=0))
100 self.assertIsNone(node.nlnet_milestone)
101
102 def test_loop1(self):
103 with self.assertRaises(BudgetGraphLoopError) as cm:
104 BudgetGraph([EXAMPLE_LOOP1_BUG1]).roots
105 self.assertEqual(cm.exception.bug_ids, [1])
106
107 def test_loop2(self):
108 with self.assertRaises(BudgetGraphLoopError) as cm:
109 BudgetGraph([EXAMPLE_LOOP2_BUG1, EXAMPLE_LOOP2_BUG2]).roots
110 self.assertEqual(cm.exception.bug_ids, [2, 1])
111
112 def test_parent_child(self):
113 bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2])
114 self.assertEqual(len(bg.nodes), 2)
115 node1: Node = bg.nodes[1]
116 node2: Node = bg.nodes[2]
117 self.assertEqual(bg.roots, {node1})
118 self.assertEqual(node1, node1)
119 self.assertEqual(node2, node2)
120 self.assertNotEqual(node1, node2)
121 self.assertNotEqual(node2, node1)
122 self.assertIsInstance(node1, Node)
123 self.assertIs(node1.graph, bg)
124 self.assertIs(node1.bug, EXAMPLE_PARENT_BUG1)
125 self.assertIsNone(node1.parent_id)
126 self.assertEqual(node1.root, node1)
127 self.assertEqual(node1.immediate_children, {node2})
128 self.assertEqual(node1.budget_excluding_subtasks, Money(cents=1000))
129 self.assertEqual(node1.budget_including_subtasks, Money(cents=2000))
130 self.assertEqual(node1.nlnet_milestone, "abc")
131 self.assertEqual(list(node1.children()), [node2])
132 self.assertIsInstance(node2, Node)
133 self.assertIs(node2.graph, bg)
134 self.assertIs(node2.bug, EXAMPLE_CHILD_BUG2)
135 self.assertEqual(node2.parent_id, 1)
136 self.assertEqual(node2.root, node1)
137 self.assertEqual(node2.immediate_children, set())
138 self.assertEqual(node2.budget_excluding_subtasks, Money(cents=1000))
139 self.assertEqual(node2.budget_including_subtasks, Money(cents=1000))
140 self.assertEqual(node2.nlnet_milestone, "abc")
141
142 def test_money_with_no_milestone(self):
143 bg = BudgetGraph([
144 MockBug(bug_id=1,
145 cf_budget_parent=None,
146 cf_budget="0",
147 cf_total_budget="10",
148 cf_nlnet_milestone=None),
149 ])
150 errors = bg.get_errors()
151 self.assertErrorTypesMatches(errors,
152 [BudgetGraphMoneyWithNoMilestone,
153 BudgetGraphMoneyMismatch])
154 self.assertEqual(errors[0].bug_id, 1)
155 self.assertEqual(errors[0].root_bug_id, 1)
156 bg = BudgetGraph([
157 MockBug(bug_id=1,
158 cf_budget_parent=None,
159 cf_budget="10",
160 cf_total_budget="0",
161 cf_nlnet_milestone=None),
162 ])
163 errors = bg.get_errors()
164 self.assertErrorTypesMatches(errors,
165 [BudgetGraphMoneyWithNoMilestone,
166 BudgetGraphMoneyMismatch])
167 self.assertEqual(errors[0].bug_id, 1)
168 self.assertEqual(errors[0].root_bug_id, 1)
169 bg = BudgetGraph([
170 MockBug(bug_id=1,
171 cf_budget_parent=None,
172 cf_budget="10",
173 cf_total_budget="10",
174 cf_nlnet_milestone=None),
175 ])
176 errors = bg.get_errors()
177 self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone])
178 self.assertEqual(errors[0].bug_id, 1)
179 self.assertEqual(errors[0].root_bug_id, 1)
180
181 def test_money_mismatch(self):
182 bg = BudgetGraph([
183 MockBug(bug_id=1,
184 cf_budget_parent=None,
185 cf_budget="0",
186 cf_total_budget="10",
187 cf_nlnet_milestone="abc"),
188 ])
189 errors = bg.get_errors()
190 self.assertErrorTypesMatches(errors,
191 [BudgetGraphMoneyMismatch])
192 self.assertEqual(errors[0].bug_id, 1)
193 self.assertEqual(errors[0].root_bug_id, 1)
194 self.assertEqual(errors[0].expected_budget_excluding_subtasks, 10)
195 bg = BudgetGraph([
196 MockBug(bug_id=1,
197 cf_budget_parent=None,
198 cf_budget="10",
199 cf_total_budget="0",
200 cf_nlnet_milestone="abc"),
201 ])
202 errors = bg.get_errors()
203 self.assertErrorTypesMatches(errors,
204 [BudgetGraphMoneyMismatch])
205 self.assertEqual(errors[0].bug_id, 1)
206 self.assertEqual(errors[0].root_bug_id, 1)
207 self.assertEqual(errors[0].expected_budget_excluding_subtasks, 0)
208 bg = BudgetGraph([
209 MockBug(bug_id=1,
210 cf_budget_parent=None,
211 cf_budget="10",
212 cf_total_budget="10",
213 cf_nlnet_milestone="abc"),
214 ])
215 errors = bg.get_errors()
216 self.assertEqual(errors, [])
217 bg = BudgetGraph([
218 MockBug(bug_id=1,
219 cf_budget_parent=None,
220 cf_budget="10",
221 cf_total_budget="10",
222 cf_nlnet_milestone="abc"),
223 MockBug(bug_id=2,
224 cf_budget_parent=1,
225 cf_budget="10",
226 cf_total_budget="10",
227 cf_nlnet_milestone="abc"),
228 MockBug(bug_id=3,
229 cf_budget_parent=1,
230 cf_budget="1",
231 cf_total_budget="10",
232 cf_nlnet_milestone="abc"),
233 ])
234 errors = bg.get_errors()
235 self.assertErrorTypesMatches(errors,
236 [BudgetGraphMoneyMismatch,
237 BudgetGraphMoneyMismatch])
238 self.assertEqual(errors[0].bug_id, 1)
239 self.assertEqual(errors[0].root_bug_id, 1)
240 self.assertEqual(errors[0].expected_budget_excluding_subtasks, -10)
241 self.assertEqual(errors[1].bug_id, 3)
242 self.assertEqual(errors[1].root_bug_id, 1)
243 self.assertEqual(errors[1].expected_budget_excluding_subtasks, 10)
244
245 def test_negative_money(self):
246 bg = BudgetGraph([
247 MockBug(bug_id=1,
248 cf_budget_parent=None,
249 cf_budget="0",
250 cf_total_budget="-10",
251 cf_nlnet_milestone="abc"),
252 ])
253 errors = bg.get_errors()
254 self.assertErrorTypesMatches(errors,
255 [BudgetGraphNegativeMoney,
256 BudgetGraphMoneyMismatch])
257 self.assertEqual(errors[0].bug_id, 1)
258 self.assertEqual(errors[0].root_bug_id, 1)
259 self.assertEqual(errors[1].bug_id, 1)
260 self.assertEqual(errors[1].root_bug_id, 1)
261 self.assertEqual(errors[1].expected_budget_excluding_subtasks, -10)
262 bg = BudgetGraph([
263 MockBug(bug_id=1,
264 cf_budget_parent=None,
265 cf_budget="-10",
266 cf_total_budget="0",
267 cf_nlnet_milestone="abc"),
268 ])
269 errors = bg.get_errors()
270 self.assertErrorTypesMatches(errors,
271 [BudgetGraphNegativeMoney,
272 BudgetGraphMoneyMismatch])
273 self.assertEqual(errors[0].bug_id, 1)
274 self.assertEqual(errors[0].root_bug_id, 1)
275 self.assertEqual(errors[1].bug_id, 1)
276 self.assertEqual(errors[1].root_bug_id, 1)
277 self.assertEqual(errors[1].expected_budget_excluding_subtasks, 0)
278 bg = BudgetGraph([
279 MockBug(bug_id=1,
280 cf_budget_parent=None,
281 cf_budget="-10",
282 cf_total_budget="-10",
283 cf_nlnet_milestone="abc"),
284 ])
285 errors = bg.get_errors()
286 self.assertErrorTypesMatches(errors,
287 [BudgetGraphNegativeMoney])
288 self.assertEqual(errors[0].bug_id, 1)
289 self.assertEqual(errors[0].root_bug_id, 1)
290
291
292 if __name__ == "__main__":
293 unittest.main()