implement calculating subtotals for MoU Milestones for subsets of bugs
[utils.git] / src / budget_sync / test / test_write_budget_markdown.py
1 import unittest
2 from budget_sync.config import Config
3 from budget_sync.ordered_set import OrderedSet
4 from budget_sync.test.mock_bug import MockBug
5 from budget_sync.test.mock_path import MockFilesystem, MockPath, DIR
6 from budget_sync.test.test_mock_path import make_filesystem_and_report_if_error
7 from budget_sync.budget_graph import BudgetGraph
8 from budget_sync.write_budget_markdown import (
9 write_budget_markdown, DisplayStatus, markdown_escape, markdown_for_person)
10 from budget_sync.util import BugStatus
11
12
13 class TestWriteBudgetMarkdown(unittest.TestCase):
14 maxDiff = None
15
16 def test_display_status(self):
17 for status in BugStatus:
18 DisplayStatus.from_status(status)
19
20 def test_markdown_escape(self):
21 self.assertEqual(markdown_escape("abc * def_k < &k"),
22 r"abc \* def\_k &lt; &amp;k")
23
24 def format_files_dict(self, files):
25 assert isinstance(files, dict)
26 files_list: "list[str]" = []
27 for path, contents in files.items():
28 assert isinstance(path, str)
29 if contents is DIR:
30 files_list.append(f" {path!r}: DIR,")
31 continue
32 assert isinstance(contents, bytes)
33 lines: "list[str]" = []
34 for line in contents.splitlines(keepends=True):
35 lines.append(f" {line!r}")
36 if len(lines) == 0:
37 files_list.append(f" {path!r}: b'',")
38 else:
39 lines_str = '\n'.join(lines)
40 files_list.append(f" {path!r}: (\n{lines_str}\n ),")
41 if len(files_list) == 0:
42 return "{}"
43 return "{\n" + "\n".join(files_list) + "\n}"
44
45 def assertFiles(self, expected_files, filesystem: MockFilesystem):
46 files = filesystem.files
47 self.assertIsInstance(expected_files, dict)
48 if files == expected_files:
49 return
50 files_str = self.format_files_dict(files)
51 expected_files_str = self.format_files_dict(expected_files)
52 self.assertEqual(
53 files, expected_files,
54 msg=f"\nfiles:\n{files_str}\nexpected:\n{expected_files_str}")
55
56 def test(self):
57 config = Config.from_str(
58 """
59 bugzilla_url = "https://bugzilla.example.com/"
60 [milestones]
61 [people."person1"]
62 email = "person1@example.com"
63 full_name = "Person One"
64 [people."person2"]
65 full_name = "Person Two"
66 """)
67 budget_graph = BudgetGraph([
68 MockBug(bug_id=1,
69 cf_budget_parent=None,
70 cf_budget="0",
71 cf_total_budget="0",
72 cf_nlnet_milestone=None,
73 cf_payees_list="",
74 summary="summary1",
75 assigned_to="person1@example.com"),
76 ], config)
77 self.assertEqual([], budget_graph.get_errors())
78 with make_filesystem_and_report_if_error(self) as filesystem:
79 output_dir = MockPath("/output_dir/", filesystem=filesystem)
80 write_budget_markdown(budget_graph, output_dir)
81 self.assertFiles({
82 '/': DIR,
83 '/output_dir': DIR,
84 '/output_dir/person1.mdwn': (
85 b'<!-- autogenerated by budget-sync -->\n'
86 b'\n'
87 b'# Person One (person1)\n'
88 b'\n'
89 b'\n'
90 b'\n'
91 b'# Status Tracking\n'
92 b'\n'
93 ),
94 '/output_dir/person2.mdwn': (
95 b'<!-- autogenerated by budget-sync -->\n'
96 b'\n'
97 b'# Person Two (person2)\n'
98 b'\n'
99 b'\n'
100 b'\n'
101 b'# Status Tracking\n'
102 b'\n'
103 ),
104 }, filesystem)
105
106 def test2(self):
107 config = Config.from_str(
108 """
109 bugzilla_url = "https://bugzilla.example.com/"
110 [milestones]
111 "milestone 1" = { canonical_bug_id = 1 }
112 [people."person1"]
113 email = "person1@example.com"
114 full_name = "Person One"
115 [people."person2"]
116 full_name = "Person Two"
117 """)
118 budget_graph = BudgetGraph([
119 MockBug(bug_id=1,
120 cf_budget_parent=None,
121 cf_budget="700",
122 cf_total_budget="1000",
123 cf_nlnet_milestone="milestone 1",
124 cf_payees_list="",
125 summary="summary1",
126 assigned_to="person1@example.com"),
127 MockBug(bug_id=2,
128 cf_budget_parent=1,
129 cf_budget="100",
130 cf_total_budget="300",
131 cf_nlnet_milestone="milestone 1",
132 cf_payees_list="person2 = 100",
133 summary="summary2",
134 assigned_to="person1@example.com"),
135 MockBug(bug_id=3,
136 cf_budget_parent=2,
137 cf_budget="100",
138 cf_total_budget="200",
139 cf_nlnet_milestone="milestone 1",
140 cf_payees_list="person1 = 100",
141 summary="summary3",
142 assigned_to="person1@example.com"),
143 MockBug(bug_id=4,
144 cf_budget_parent=3,
145 cf_budget="100",
146 cf_total_budget="100",
147 cf_nlnet_milestone="milestone 1",
148 cf_payees_list="person2 = 100",
149 summary="summary4",
150 assigned_to="person1@example.com"),
151 ], config)
152 self.assertEqual([], budget_graph.get_errors())
153 with make_filesystem_and_report_if_error(self) as filesystem:
154 output_dir = MockPath("/output_dir/", filesystem=filesystem)
155 write_budget_markdown(budget_graph, output_dir)
156 self.assertFiles({
157 '/': DIR,
158 '/output_dir': DIR,
159 '/output_dir/person1.mdwn': (
160 b'<!-- autogenerated by budget-sync -->\n'
161 b'\n'
162 b'# Person One (person1)\n'
163 b'\n'
164 b'\n'
165 b'\n'
166 b'# Status Tracking\n'
167 b'\n'
168 b'\n'
169 b'## Payment not yet submitted\n'
170 b'\n'
171 b'\n'
172 b'### milestone 1\n'
173 b'\n'
174 b'* [Bug #3](https://bugzilla.example.com/show_bug.cgi?id=3):\n'
175 b' summary3\n'
176 b' * &euro;100 which is the total amount\n'
177 b' * this task is part of MoU Milestone\n'
178 b' [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2)\n'
179 b'\n'
180 b'#### MoU Milestone subtotals for not yet submitted payments\n'
181 b'\n'
182 b'* [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2):\n'
183 b' summary2\n'
184 b' * subtotal &euro;100 out of total including subtasks of &euro;300\n'
185 ),
186 '/output_dir/person2.mdwn': (
187 b'<!-- autogenerated by budget-sync -->\n'
188 b'\n'
189 b'# Person Two (person2)\n'
190 b'\n'
191 b'\n'
192 b'\n'
193 b'# Status Tracking\n'
194 b'\n'
195 b'\n'
196 b'## Payment not yet submitted\n'
197 b'\n'
198 b'\n'
199 b'### milestone 1\n'
200 b'\n'
201 b'* [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2):\n'
202 b' summary2\n'
203 b' * &euro;100 which is the total amount\n'
204 b' * this task is a MoU Milestone\n'
205 b'* [Bug #4](https://bugzilla.example.com/show_bug.cgi?id=4):\n'
206 b' summary4\n'
207 b' * &euro;100 which is the total amount\n'
208 b' * this task is part of MoU Milestone\n'
209 b' [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2)\n'
210 b'\n'
211 b'#### MoU Milestone subtotals for not yet submitted payments\n'
212 b'\n'
213 b'* [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2):\n'
214 b' summary2\n'
215 b' * subtotal &euro;200 out of total including subtasks of &euro;300\n'
216 ),
217 }, filesystem)
218
219 def test_markdown_for_person(self):
220 config = Config.from_str(
221 """
222 bugzilla_url = "https://bugzilla.example.com/"
223 [milestones]
224 "milestone 1" = { canonical_bug_id = 1 }
225 [people."person1"]
226 email = "person1@example.com"
227 full_name = "Person One"
228 [people."person2"]
229 full_name = "Person Two"
230 """)
231 budget_graph = BudgetGraph([
232 MockBug(bug_id=1,
233 cf_budget_parent=None,
234 cf_budget="600",
235 cf_total_budget="1000",
236 cf_nlnet_milestone="milestone 1",
237 cf_payees_list="",
238 summary="summary1",
239 assigned_to="person1@example.com"),
240 MockBug(bug_id=2,
241 cf_budget_parent=1,
242 cf_budget="100",
243 cf_total_budget="400",
244 cf_nlnet_milestone="milestone 1",
245 cf_payees_list="person2 = 100",
246 summary="summary2",
247 assigned_to="person1@example.com"),
248 MockBug(bug_id=3,
249 cf_budget_parent=2,
250 cf_budget="100",
251 cf_total_budget="300",
252 cf_nlnet_milestone="milestone 1",
253 cf_payees_list="person1 = 100",
254 summary="summary3",
255 assigned_to="person1@example.com"),
256 MockBug(bug_id=4,
257 cf_budget_parent=3,
258 cf_budget="100",
259 cf_total_budget="100",
260 cf_nlnet_milestone="milestone 1",
261 cf_payees_list="person2 = 100",
262 summary="summary4",
263 assigned_to="person1@example.com"),
264 MockBug(bug_id=5,
265 cf_budget_parent=3,
266 cf_budget="100",
267 cf_total_budget="100",
268 cf_nlnet_milestone="milestone 1",
269 cf_payees_list="person2 = 100",
270 summary="summary4",
271 assigned_to="person1@example.com"),
272 ], config)
273 self.assertEqual([], budget_graph.get_errors())
274 person = config.all_names["person2"]
275 nodes_subset = OrderedSet([budget_graph.nodes[2],
276 budget_graph.nodes[3],
277 budget_graph.nodes[4]])
278 expected = [
279 '<!-- autogenerated by budget-sync -->\n',
280 '\n',
281 '# Person Two (person2)\n',
282 '\n',
283 '\n',
284 '\n',
285 '# Status Tracking\n',
286 '\n',
287 '\n',
288 '## Payment not yet submitted\n',
289 '\n',
290 '\n',
291 '### milestone 1\n',
292 '\n',
293 '* [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2):\n',
294 ' summary2\n',
295 ' * &euro;100 which is the total amount\n',
296 ' * this task is a MoU Milestone\n',
297 '* [Bug #4](https://bugzilla.example.com/show_bug.cgi?id=4):\n',
298 ' summary4\n',
299 ' * &euro;100 which is the total amount\n',
300 ' * this task is part of MoU Milestone\n',
301 ' [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2)\n',
302 '\n',
303 '#### MoU Milestone subtotals for not yet submitted payments\n',
304 '\n',
305 '* [Bug #2](https://bugzilla.example.com/show_bug.cgi?id=2):\n',
306 ' summary2\n',
307 ' * subtotal &euro;200 out of total including subtasks of &euro;400\n',
308 ]
309 self.assertEqual(markdown_for_person(
310 budget_graph, person, nodes_subset).splitlines(keepends=True),
311 expected)
312 # TODO: add more test cases
313
314
315 if __name__ == "__main__":
316 unittest.main()