diff options
Diffstat (limited to 'beancount_extras_kris7t/plugins/selective_implicit_prices_test.py')
-rw-r--r-- | beancount_extras_kris7t/plugins/selective_implicit_prices_test.py | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/beancount_extras_kris7t/plugins/selective_implicit_prices_test.py b/beancount_extras_kris7t/plugins/selective_implicit_prices_test.py new file mode 100644 index 0000000..6ead45d --- /dev/null +++ b/beancount_extras_kris7t/plugins/selective_implicit_prices_test.py | |||
@@ -0,0 +1,410 @@ | |||
1 | __copyright__ = "Copyright (C) 2015-2017 Martin Blais, " + \ | ||
2 | "2020 Kristóf Marussy <kristof@marussy.com>" | ||
3 | __license__ = "GNU GPLv2" | ||
4 | |||
5 | import unittest | ||
6 | |||
7 | from beancount.core.number import D | ||
8 | from beancount.core import data | ||
9 | from beancount.parser import cmptest | ||
10 | from beancount import loader | ||
11 | |||
12 | from beancount_extras_kris7t.plugins import selective_implicit_prices as implicit_prices | ||
13 | |||
14 | |||
15 | class TestImplicitPrices(cmptest.TestCase): | ||
16 | |||
17 | @loader.load_doc() | ||
18 | def test_add_implicit_prices__all_cases(self, entries, _, options_map): | ||
19 | """ | ||
20 | 1702-04-02 commodity USD | ||
21 | implicit-prices: TRUE | ||
22 | |||
23 | 2013-01-01 commodity HOOL | ||
24 | implicit-prices: TRUE | ||
25 | |||
26 | 2013-01-01 open Assets:Account1 | ||
27 | 2013-01-01 open Assets:Account2 | ||
28 | 2013-01-01 open Assets:Other | ||
29 | |||
30 | ;; An explicit price directive. | ||
31 | 2013-02-01 price USD 1.10 CAD | ||
32 | |||
33 | 2013-04-01 * "A transaction with a price conversion." | ||
34 | Assets:Account1 150 USD @ 1.12 CAD | ||
35 | Assets:Other | ||
36 | |||
37 | ;; This should book at price at the cost. | ||
38 | 2013-04-02 * "A transaction with a cost." | ||
39 | Assets:Account1 1500 HOOL {520 USD} | ||
40 | Assets:Other | ||
41 | |||
42 | ;; This one should be IGNORED because it books against the above. | ||
43 | 2013-04-03 * "A transaction with a cost that reduces an existing position" | ||
44 | Assets:Account1 -500 HOOL {520 USD} | ||
45 | Assets:Other | ||
46 | |||
47 | ;; This one should generate the price, even if it is reducing. | ||
48 | 2013-04-04 * "A transaction with a cost that reduces existing position, with price" | ||
49 | Assets:Account1 -100 HOOL {520 USD} @ 530 USD | ||
50 | Assets:Other | ||
51 | |||
52 | ;; This is not reducing and should also book a price at cost. | ||
53 | 2013-04-05 * "A transaction with another cost that is not reducing." | ||
54 | Assets:Account1 500 HOOL {540 USD} | ||
55 | Assets:Other | ||
56 | |||
57 | ;; The price here overrides the cost and should create an entry. | ||
58 | 2013-04-06 * "A transaction with a cost and a price." | ||
59 | Assets:Account1 500 HOOL {540 USD} @ 560 USD | ||
60 | Assets:Other | ||
61 | """ | ||
62 | self.assertEqual(12, len(entries)) | ||
63 | new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map) | ||
64 | price_entries = [entry for entry in new_entries if isinstance(entry, data.Price)] | ||
65 | |||
66 | self.assertEqualEntries(""" | ||
67 | 1702-04-02 commodity USD | ||
68 | implicit-prices: TRUE | ||
69 | |||
70 | 2013-01-01 commodity HOOL | ||
71 | implicit-prices: TRUE | ||
72 | |||
73 | 2013-01-01 open Assets:Account1 | ||
74 | 2013-01-01 open Assets:Account2 | ||
75 | 2013-01-01 open Assets:Other | ||
76 | |||
77 | 2013-02-01 price USD 1.10 CAD | ||
78 | |||
79 | 2013-04-01 * "A transaction with a price conversion." | ||
80 | Assets:Account1 150 USD @ 1.12 CAD | ||
81 | Assets:Other -168.00 CAD | ||
82 | |||
83 | 2013-04-01 price USD 1.12 CAD | ||
84 | |||
85 | 2013-04-02 * "A transaction with a cost." | ||
86 | Assets:Account1 1500 HOOL {520 USD} | ||
87 | Assets:Other -780000 USD | ||
88 | |||
89 | 2013-04-02 price HOOL 520 USD | ||
90 | |||
91 | 2013-04-03 * "A transaction with a cost that reduces an existing position" | ||
92 | Assets:Account1 -500 HOOL {520 USD} | ||
93 | Assets:Other 260000 USD | ||
94 | |||
95 | 2013-04-04 * "A transaction with a cost that reduces existing position, with price" | ||
96 | Assets:Account1 -100 HOOL {520 USD} @ 530 USD | ||
97 | Assets:Other 52000 USD | ||
98 | |||
99 | 2013-04-04 price HOOL 530 USD | ||
100 | |||
101 | 2013-04-05 * "A transaction with another cost that is not reducing." | ||
102 | Assets:Account1 500 HOOL {540 USD} | ||
103 | Assets:Other -270000 USD | ||
104 | |||
105 | 2013-04-05 price HOOL 540 USD | ||
106 | |||
107 | 2013-04-06 * "A transaction with a cost and a price." | ||
108 | Assets:Account1 500 HOOL {540 USD} @ 560 USD | ||
109 | Assets:Other -270000 USD | ||
110 | |||
111 | 2013-04-06 price HOOL 560 USD | ||
112 | """, new_entries) | ||
113 | |||
114 | self.assertEqual(6, len(price_entries)) | ||
115 | expected_values = [(x[0], x[1], D(x[2])) for x in [ | ||
116 | ('USD', 'CAD', '1.10'), | ||
117 | ('USD', 'CAD', '1.12'), | ||
118 | ('HOOL', 'USD', '520.00'), | ||
119 | ('HOOL', 'USD', '530.00'), | ||
120 | ('HOOL', 'USD', '540.00'), | ||
121 | ('HOOL', 'USD', '560.00') | ||
122 | ]] | ||
123 | for expected, price in zip(expected_values, price_entries): | ||
124 | actual = (price.currency, price.amount.currency, price.amount.number) | ||
125 | self.assertEqual(expected, actual) | ||
126 | |||
127 | @loader.load_doc() | ||
128 | def test_add_implicit_prices__other_account(self, entries, errors, options_map): | ||
129 | """ | ||
130 | 2013-01-01 commodity HOOL | ||
131 | implicit-prices: TRUE | ||
132 | |||
133 | 2013-01-01 open Assets:Account1 | ||
134 | 2013-01-01 open Assets:Account2 "NONE" | ||
135 | 2013-01-01 open Assets:Other | ||
136 | |||
137 | 2013-04-01 * | ||
138 | Assets:Account1 1500 HOOL {520 USD} | ||
139 | Assets:Other | ||
140 | |||
141 | 2013-04-02 * | ||
142 | Assets:Account2 1500 HOOL {530 USD} | ||
143 | Assets:Other | ||
144 | |||
145 | 2013-04-10 * "Reduces existing position in account 1" | ||
146 | Assets:Account1 -100 HOOL {520 USD} | ||
147 | Assets:Other 52000 USD | ||
148 | |||
149 | 2013-04-11 * "Does not find an existing position in account 2" | ||
150 | Assets:Account2 -200 HOOL {531 USD} | ||
151 | Assets:Other 106200 USD | ||
152 | |||
153 | """ | ||
154 | new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map) | ||
155 | self.assertEqualEntries(""" | ||
156 | 2013-01-01 commodity HOOL | ||
157 | implicit-prices: TRUE | ||
158 | |||
159 | 2013-01-01 open Assets:Account1 | ||
160 | 2013-01-01 open Assets:Account2 "NONE" | ||
161 | 2013-01-01 open Assets:Other | ||
162 | |||
163 | 2013-04-01 * | ||
164 | Assets:Account1 1500 HOOL {520 USD} | ||
165 | Assets:Other -780000 USD | ||
166 | |||
167 | 2013-04-02 * | ||
168 | Assets:Account2 1500 HOOL {530 USD} | ||
169 | Assets:Other -795000 USD | ||
170 | |||
171 | 2013-04-01 price HOOL 520 USD | ||
172 | |||
173 | 2013-04-02 price HOOL 530 USD | ||
174 | |||
175 | 2013-04-10 * "Reduces existing position in account 1" | ||
176 | Assets:Account1 -100 HOOL {520 USD} | ||
177 | Assets:Other 52000 USD | ||
178 | |||
179 | 2013-04-11 * "Does not find an existing position in account 2" | ||
180 | Assets:Account2 -200 HOOL {531 USD} | ||
181 | Assets:Other 106200 USD | ||
182 | |||
183 | ;; Because a match was not found against the inventory, a price will be added. | ||
184 | 2013-04-11 price HOOL 531 USD | ||
185 | |||
186 | """, new_entries) | ||
187 | |||
188 | @loader.load_doc() | ||
189 | def test_add_implicit_prices__duplicates_on_same_transaction(self, | ||
190 | entries, _, options_map): | ||
191 | """ | ||
192 | 2013-01-01 commodity HOOL | ||
193 | implicit-prices: TRUE | ||
194 | |||
195 | 2013-01-01 open Assets:Account1 | ||
196 | 2013-01-01 open Assets:Account2 | ||
197 | 2013-01-01 open Assets:Other | ||
198 | |||
199 | 2013-04-01 * "Allowed because of same price" | ||
200 | Assets:Account1 1500 HOOL {520 USD} | ||
201 | Assets:Account2 1500 HOOL {520 USD} | ||
202 | Assets:Other | ||
203 | |||
204 | 2013-04-02 * "Second one is disallowed because of different price" | ||
205 | Assets:Account1 1500 HOOL {520 USD} | ||
206 | Assets:Account2 1500 HOOL {530 USD} | ||
207 | Assets:Other | ||
208 | |||
209 | """ | ||
210 | new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map) | ||
211 | self.assertEqual([], [type(error) for error in errors]) | ||
212 | self.assertEqualEntries(""" | ||
213 | 2013-01-01 commodity HOOL | ||
214 | implicit-prices: TRUE | ||
215 | |||
216 | 2013-01-01 open Assets:Account1 | ||
217 | 2013-01-01 open Assets:Account2 | ||
218 | 2013-01-01 open Assets:Other | ||
219 | |||
220 | 2013-04-01 * "Allowed because of same price" | ||
221 | Assets:Account1 1500 HOOL {520 USD} | ||
222 | Assets:Account2 1500 HOOL {520 USD} | ||
223 | Assets:Other -1560000 USD | ||
224 | |||
225 | 2013-04-01 price HOOL 520 USD | ||
226 | |||
227 | 2013-04-02 * "Second one is disallowed because of different price" | ||
228 | Assets:Account1 1500 HOOL {520 USD} | ||
229 | Assets:Account2 1500 HOOL {530 USD} | ||
230 | Assets:Other -1575000 USD | ||
231 | |||
232 | 2013-04-02 price HOOL 520 USD | ||
233 | 2013-04-02 price HOOL 530 USD ;; Allowed for now. | ||
234 | |||
235 | """, new_entries) | ||
236 | |||
237 | @loader.load_doc() | ||
238 | def test_add_implicit_prices__duplicates_on_different_transactions(self, | ||
239 | entries, _, | ||
240 | options_map): | ||
241 | """ | ||
242 | 2013-01-01 commodity HOOL | ||
243 | implicit-prices: TRUE | ||
244 | |||
245 | 2013-01-01 open Assets:Account1 | ||
246 | 2013-01-01 open Assets:Account2 | ||
247 | 2013-01-01 open Assets:Other | ||
248 | |||
249 | 2013-04-01 * "Allowed because of same price #1" | ||
250 | Assets:Account1 1500 HOOL {520 USD} | ||
251 | Assets:Other | ||
252 | |||
253 | 2013-04-01 * "Allowed because of same price #2" | ||
254 | Assets:Account2 1500 HOOL {520 USD} | ||
255 | Assets:Other | ||
256 | |||
257 | 2013-04-02 * "Second one is disallowed because of different price #1" | ||
258 | Assets:Account1 1500 HOOL {520 USD} | ||
259 | Assets:Other | ||
260 | |||
261 | 2013-04-02 * "Second one is disallowed because of different price #2" | ||
262 | Assets:Account2 1500 HOOL {530 USD} | ||
263 | Assets:Other | ||
264 | |||
265 | """ | ||
266 | new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map) | ||
267 | self.assertEqual([], [type(error) for error in errors]) | ||
268 | self.assertEqualEntries(""" | ||
269 | 2013-01-01 commodity HOOL | ||
270 | implicit-prices: TRUE | ||
271 | |||
272 | 2013-01-01 open Assets:Account1 | ||
273 | 2013-01-01 open Assets:Account2 | ||
274 | 2013-01-01 open Assets:Other | ||
275 | |||
276 | 2013-04-01 * "Allowed because of same price #1" | ||
277 | Assets:Account1 1500 HOOL {520 USD} | ||
278 | Assets:Other -780000 USD | ||
279 | |||
280 | 2013-04-01 * "Allowed because of same price #2" | ||
281 | Assets:Account2 1500 HOOL {520 USD} | ||
282 | Assets:Other -780000 USD | ||
283 | |||
284 | 2013-04-01 price HOOL 520 USD | ||
285 | |||
286 | 2013-04-02 * "Second one is disallowed because of different price #1" | ||
287 | Assets:Account1 1500 HOOL {520 USD} | ||
288 | Assets:Other -780000 USD | ||
289 | |||
290 | 2013-04-02 * "Second one is disallowed because of different price #2" | ||
291 | Assets:Account2 1500 HOOL {530 USD} | ||
292 | Assets:Other -795000 USD | ||
293 | |||
294 | 2013-04-02 price HOOL 520 USD | ||
295 | 2013-04-02 price HOOL 530 USD ;; Allowed for now. | ||
296 | |||
297 | """, new_entries) | ||
298 | |||
299 | @loader.load_doc() | ||
300 | def test_add_implicit_prices__duplicates_overloaded(self, entries, _, options_map): | ||
301 | """ | ||
302 | 2013-01-01 commodity HOOL | ||
303 | implicit-prices: TRUE | ||
304 | |||
305 | 2013-01-01 open Assets:Account1 | ||
306 | 2013-01-01 open Assets:Other | ||
307 | |||
308 | 2013-04-01 * "Allowed, sets the price for that day" | ||
309 | Assets:Account1 1500 HOOL {520 USD} | ||
310 | Assets:Other | ||
311 | |||
312 | 2013-04-01 * "Will be ignored, price for the day already set" | ||
313 | Assets:Account1 1500 HOOL {530 USD} | ||
314 | Assets:Other | ||
315 | |||
316 | 2013-04-01 * "Should be ignored too, price for the day already set" | ||
317 | Assets:Account1 1500 HOOL {530 USD} | ||
318 | Assets:Other | ||
319 | """ | ||
320 | new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map) | ||
321 | self.assertEqual([], [type(error) for error in errors]) | ||
322 | self.assertEqualEntries(""" | ||
323 | 2013-01-01 commodity HOOL | ||
324 | implicit-prices: TRUE | ||
325 | |||
326 | 2013-01-01 open Assets:Account1 | ||
327 | 2013-01-01 open Assets:Other | ||
328 | |||
329 | 2013-04-01 * "Allowed, sets the price for that day" | ||
330 | Assets:Account1 1500 HOOL {520 USD} | ||
331 | Assets:Other -780000 USD | ||
332 | |||
333 | 2013-04-01 * "Will be ignored, price for the day already set" | ||
334 | Assets:Account1 1500 HOOL {530 USD} | ||
335 | Assets:Other -795000 USD | ||
336 | |||
337 | 2013-04-01 * "Should be ignored too, price for the day already set" | ||
338 | Assets:Account1 1500 HOOL {530 USD} | ||
339 | Assets:Other -795000 USD | ||
340 | |||
341 | 2013-04-01 price HOOL 520 USD | ||
342 | 2013-04-01 price HOOL 530 USD | ||
343 | |||
344 | """, new_entries) | ||
345 | |||
346 | @loader.load_doc() | ||
347 | def test_add_implicit_prices__not_enabled(self, entries, errors, options_map): | ||
348 | """ | ||
349 | 2013-01-01 open Assets:Account1 | ||
350 | 2013-01-01 open Assets:Other | ||
351 | |||
352 | 2013-04-01 * | ||
353 | Assets:Account1 1500 HOOL {520 USD} | ||
354 | Assets:Other | ||
355 | """ | ||
356 | new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map) | ||
357 | self.assertEqualEntries(""" | ||
358 | 2013-01-01 open Assets:Account1 | ||
359 | 2013-01-01 open Assets:Other | ||
360 | |||
361 | 2013-04-01 * | ||
362 | Assets:Account1 1500 HOOL {520 USD} | ||
363 | Assets:Other -780000 USD | ||
364 | """, new_entries) | ||
365 | |||
366 | @loader.load_doc() | ||
367 | def test_add_implicit_prices__disabled(self, entries, errors, options_map): | ||
368 | """ | ||
369 | 2013-01-01 commodity HOOL | ||
370 | implicit-prices: FALSE | ||
371 | |||
372 | 2013-01-01 open Assets:Account1 | ||
373 | 2013-01-01 open Assets:Other | ||
374 | |||
375 | 2013-04-01 * | ||
376 | Assets:Account1 1500 HOOL {520 USD} | ||
377 | Assets:Other | ||
378 | """ | ||
379 | new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map) | ||
380 | self.assertEqualEntries(""" | ||
381 | 2013-01-01 commodity HOOL | ||
382 | implicit-prices: FALSE | ||
383 | |||
384 | 2013-01-01 open Assets:Account1 | ||
385 | 2013-01-01 open Assets:Other | ||
386 | |||
387 | 2013-04-01 * | ||
388 | Assets:Account1 1500 HOOL {520 USD} | ||
389 | Assets:Other -780000 USD | ||
390 | """, new_entries) | ||
391 | |||
392 | @loader.load_doc() | ||
393 | def test_add_implicit_prices__invalid(self, entries, errors, options_map): | ||
394 | """ | ||
395 | 2013-01-01 commodity HOOL | ||
396 | implicit-prices: "yes" | ||
397 | |||
398 | 2013-01-01 open Assets:Account1 | ||
399 | 2013-01-01 open Assets:Other | ||
400 | |||
401 | 2013-04-01 * | ||
402 | Assets:Account1 1500 HOOL {520 USD} | ||
403 | Assets:Other | ||
404 | """ | ||
405 | _, new_errors = implicit_prices.add_implicit_prices(entries, options_map) | ||
406 | self.assertRegex(new_errors[0].message, '^implicit-prices must be Boolean') | ||
407 | |||
408 | |||
409 | if __name__ == '__main__': | ||
410 | unittest.main() | ||