]> Git — Sourcephile - comptalang.git/blob - lib/Hcompta/Balance/Test.hs
Épure hcompta-lib.
[comptalang.git] / lib / Hcompta / Balance / Test.hs
1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
3 {-# LANGUAGE TupleSections #-}
4
5 module Balance.Test where
6 import Control.Arrow ((***))
7 import Data.Bool
8 import Data.Data ()
9 import Data.Either (Either(..))
10 import Data.Function (($), (.), id, const, flip)
11 import qualified Data.List as List
12 import Data.List.NonEmpty (NonEmpty(..))
13 import qualified Data.Map.Strict as Map
14 import Data.Ord (Ord(..))
15 import Data.Text (Text)
16 import Data.Tuple (snd)
17 import Prelude (Integer)
18 import Test.Tasty
19 import Test.Tasty.HUnit
20
21 import qualified Data.TreeMap.Strict as TreeMap
22 import Hcompta.Balance
23 import qualified Hcompta.Lib.Strict as Strict
24 import Hcompta.Polarize
25 import Hcompta.Quantity
26 -- {-# ANN module "HLint: ignore Use second" #-}
27 -- {-# ANN module "HLint: ignore Redundant bracket" #-}
28 -- {-# ANN module "HLint: ignore Redundant $" #-}
29
30 amounts :: (Addable q, Ord u) => [(u, q)] -> Map.Map u q
31 amounts = Map.fromListWith quantity_add
32 amount_usd :: t -> (Text, t)
33 amount_usd = ("$",)
34 amount_eur :: t -> (Text, t)
35 amount_eur = ("€",)
36 amount_gbp :: t -> (Text, t)
37 amount_gbp = ("£",)
38
39 tests :: TestTree
40 tests = testGroup "Balance"
41 [ testGroup "balance_cons"
42 [ testCase "[A+$1] = {A+$1, $+1}" $
43 (balance_cons
44 ( (("A"::Text):|[])
45 , Map.map polarize $ amounts [ amount_usd $ (1::Integer) ]
46 )
47 balance_empty)
48 @?=
49 (Balance
50 { balance_by_account =
51 TreeMap.from_List const $
52 List.map (id *** Balance_by_Account_Sum . Map.map polarize) $
53 [ ("A":|[], amounts [ amount_usd $ 1 ]) ]
54 , balance_by_unit =
55 Balance_by_Unit $
56 Map.fromList $
57 [ amount_usd $ Balance_by_Unit_Sum
58 { balance_by_unit_sum_quantity = polarize $ 1
59 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
60 [ "A":|[] ]
61 }
62 ]
63 }
64 )
65 , testCase "[A+$1, A-$1] = {A+$0, $+0}" $
66 List.foldl (flip balance_cons)
67 balance_empty
68 [ ( (("A"::Text):|[])
69 , Map.map polarize $ amounts [ amount_usd $ (1::Integer) ]
70 )
71 , ( ("A":|[])
72 , Map.map polarize $ amounts [ amount_usd $ -1 ]
73 )
74 ]
75 @?=
76 Balance
77 { balance_by_account =
78 TreeMap.from_List const $
79 [ ( "A":|[]
80 , Balance_by_Account_Sum $
81 Map.fromListWith const $
82 [ amount_usd $ Polarized_Both (-1) ( 1)
83 ]
84 ) ]
85 , balance_by_unit =
86 Balance_by_Unit $ Map.fromList $
87 [ amount_usd $ Balance_by_Unit_Sum
88 { balance_by_unit_sum_quantity = Polarized_Both (-1) ( 1)
89 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
90 [ "A":|[] ]
91 }
92 ]
93 }
94 , testCase "[A+$1, A-€1] = {A+$1-€1, $+1 €-1}" $
95 List.foldl (flip balance_cons)
96 balance_empty
97 [ ( (("A"::Text):|[])
98 , Map.map polarize $ amounts [ amount_usd $ (1::Integer) ]
99 )
100 , ( ("A":|[])
101 , Map.map polarize $ amounts [ amount_eur $ -1 ]
102 )
103 ]
104 @?=
105 Balance
106 { balance_by_account =
107 TreeMap.from_List const $
108 List.map (id *** Balance_by_Account_Sum . Map.map polarize) $
109 [ ("A":|[], amounts [ amount_usd $ 1, amount_eur $ -1 ]) ]
110 , balance_by_unit =
111 Balance_by_Unit $ Map.fromList $
112 [ amount_usd $ Balance_by_Unit_Sum
113 { balance_by_unit_sum_quantity = Polarized_Positive 1
114 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
115 [ "A":|[] ]
116 }
117 , amount_eur $ Balance_by_Unit_Sum
118 { balance_by_unit_sum_quantity = Polarized_Negative (-1)
119 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
120 [ "A":|[] ]
121 }
122 ]
123 }
124 , testCase "[A+$1, B-$1] = {A+$1 B-$1, $+0}" $
125 List.foldl (flip balance_cons)
126 balance_empty
127 [ ( (("A"::Text):|[])
128 , Map.map polarize $ amounts [ amount_usd $ (1::Integer) ]
129 )
130 , ( ("B":|[])
131 , Map.map polarize $ amounts [ amount_usd $ -1 ]
132 )
133 ]
134 @?=
135 Balance
136 { balance_by_account =
137 TreeMap.from_List const $
138 List.map (id *** Balance_by_Account_Sum . Map.map polarize) $
139 [ ("A":|[], amounts [ amount_usd $ 1 ])
140 , ("B":|[], amounts [ amount_usd $ -1 ])
141 ]
142 , balance_by_unit =
143 Balance_by_Unit $ Map.fromList $
144 [ amount_usd $ Balance_by_Unit_Sum
145 { balance_by_unit_sum_quantity = Polarized_Both (-1) 1
146 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
147 [ "A":|[]
148 , "B":|[] ]
149 }
150 ]
151 }
152 , testCase "[A+$1, B+$1]" $
153 List.foldl (flip balance_cons)
154 balance_empty
155 [ ( (("A"::Text):|[])
156 , Map.map polarize $ amounts [ amount_usd $ (1::Integer) ]
157 )
158 , ( ("B":|[])
159 , Map.map polarize $ amounts [ amount_usd $ 1 ]
160 )
161 ]
162 @?=
163 Balance
164 { balance_by_account =
165 TreeMap.from_List const $
166 List.map (id *** Balance_by_Account_Sum . Map.map polarize) $
167 [ ("A":|[], amounts [ amount_usd $ 1 ])
168 , ("B":|[], amounts [ amount_usd $ 1 ])
169 ]
170 , balance_by_unit =
171 Balance_by_Unit $ Map.fromList $
172 [ amount_usd $ Balance_by_Unit_Sum
173 { balance_by_unit_sum_quantity = polarize 2
174 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
175 [ "A":|[]
176 , "B":|[] ]
177 }
178 ]
179 }
180 , testCase "[A+$1+€2, A-$1-€2] = {A+$0+€0, $+0 €+0}" $
181 List.foldl (flip balance_cons)
182 balance_empty
183 [ ( (("A"::Text):|[])
184 , Map.map polarize $ amounts [ amount_usd $ 1, amount_eur $ (2::Integer) ]
185 )
186 , ( ("A":|[])
187 , Map.map polarize $ amounts [ amount_usd $ -1, amount_eur $ -2 ]
188 )
189 ]
190 @?=
191 Balance
192 { balance_by_account =
193 TreeMap.from_List const $
194 [ ("A":|[]
195 , Balance_by_Account_Sum $
196 Map.fromListWith const $
197 [ amount_usd $ Polarized_Both (-1) 1
198 , amount_eur $ Polarized_Both (-2) 2
199 ]
200 )
201 ]
202 , balance_by_unit =
203 Balance_by_Unit $ Map.fromList $
204 [ amount_usd $ Balance_by_Unit_Sum
205 { balance_by_unit_sum_quantity = Polarized_Both (-1) 1
206 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
207 [ "A":|[] ]
208 }
209 , amount_eur $ Balance_by_Unit_Sum
210 { balance_by_unit_sum_quantity = Polarized_Both (-2) 2
211 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
212 [ "A":|[] ]
213 }
214 ]
215 }
216 , testCase "[A+$1+€2+£3, B-$1-2€-£3] = {A+$1+€2+£3 B-$1-€2-£3, $+0 €+0 £+0}" $
217 List.foldl (flip balance_cons)
218 balance_empty
219 [ ( (("A"::Text):|[])
220 , Map.map polarize $ amounts [ amount_usd $ (1::Integer), amount_eur $ 2, amount_gbp $ 3 ]
221 )
222 , ( ("B":|[])
223 , Map.map polarize $ amounts [ amount_usd $ -1, amount_eur $ -2, amount_gbp $ -3 ]
224 )
225 ]
226 @?=
227 Balance
228 { balance_by_account =
229 TreeMap.from_List const $
230 List.map (id *** Balance_by_Account_Sum . Map.map polarize) $
231 [ ("A":|[], amounts [ amount_usd $ 1, amount_eur $ 2, amount_gbp $ 3 ])
232 , ("B":|[], amounts [ amount_usd $ -1, amount_eur $ -2, amount_gbp $ -3 ])
233 ]
234 , balance_by_unit =
235 Balance_by_Unit $
236 Map.fromList $
237 [ amount_usd $ Balance_by_Unit_Sum
238 { balance_by_unit_sum_quantity = Polarized_Both (-1) 1
239 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
240 [ "A":|[]
241 , "B":|[] ]
242 }
243 , amount_eur $ Balance_by_Unit_Sum
244 { balance_by_unit_sum_quantity = Polarized_Both (-2) 2
245 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
246 [ "A":|[]
247 , "B":|[] ]
248 }
249 , amount_gbp $ Balance_by_Unit_Sum
250 { balance_by_unit_sum_quantity = Polarized_Both (-3) 3
251 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
252 [ "A":|[]
253 , "B":|[] ]
254 }
255 ]
256 }
257 ]
258 , testGroup "balance_union" $
259 [ testCase "{A+$1, $+1} {A+$1, $+1} = {A+$2, $+2}" $
260 balance_union
261 Balance
262 { balance_by_account =
263 TreeMap.from_List const $
264 [ ( ("A"::Text):|[]
265 , Balance_by_Account_Sum $
266 Map.fromListWith const $
267 [ amount_usd $ polarize (1::Integer) ]
268 )
269 ]
270 , balance_by_unit =
271 Balance_by_Unit $ Map.fromList $
272 [ amount_usd $ Balance_by_Unit_Sum
273 { balance_by_unit_sum_quantity = polarize 1
274 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
275 [ "A":|[] ]
276 }
277 ]
278 }
279 (Balance
280 { balance_by_account =
281 TreeMap.from_List const $
282 [ ( "A":|[]
283 , Balance_by_Account_Sum $
284 Map.fromListWith const $
285 [ amount_usd $ polarize 1 ]
286 )
287 ]
288 , balance_by_unit =
289 Balance_by_Unit $ Map.fromList $
290 [ amount_usd $ Balance_by_Unit_Sum
291 { balance_by_unit_sum_quantity = polarize 1
292 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
293 [ "A":|[] ]
294 }
295 ]
296 })
297 @?=
298 Balance
299 { balance_by_account =
300 TreeMap.from_List const $
301 [ ( ("A":|[])
302 , Balance_by_Account_Sum $
303 Map.fromListWith const $
304 [ amount_usd $ polarize 2 ]
305 )
306 ]
307 , balance_by_unit =
308 Balance_by_Unit $ Map.fromList $
309 [ amount_usd $ Balance_by_Unit_Sum
310 { balance_by_unit_sum_quantity = polarize 2
311 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
312 [ "A":|[] ]
313 }
314 ]
315 }
316 , testCase "{A+$1, $+1} {B+$1, $+1} = {A+$1 B+$1, $+2}" $
317 balance_union
318 Balance
319 { balance_by_account =
320 TreeMap.from_List const $
321 [ ( (("A"::Text):|[])
322 , Balance_by_Account_Sum $
323 Map.fromListWith const $
324 [ amount_usd $ polarize (1::Integer) ]
325 )
326 ]
327 , balance_by_unit =
328 Balance_by_Unit $ Map.fromList $
329 [ amount_usd $ Balance_by_Unit_Sum
330 { balance_by_unit_sum_quantity = polarize 1
331 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
332 [ "A":|[] ]
333 }
334 ]
335 }
336 Balance
337 { balance_by_account =
338 TreeMap.from_List const $
339 [ ( ("B":|[])
340 , Balance_by_Account_Sum $
341 Map.fromListWith const $
342 [ amount_usd $ polarize 1 ]
343 )
344 ]
345 , balance_by_unit =
346 Balance_by_Unit $ Map.fromList $
347 [ amount_usd $ Balance_by_Unit_Sum
348 { balance_by_unit_sum_quantity = polarize 1
349 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
350 [ "B":|[] ]
351 }
352 ]
353 }
354 @?=
355 Balance
356 { balance_by_account =
357 TreeMap.from_List const $
358 [ ( ("A":|[])
359 , Balance_by_Account_Sum $
360 Map.fromListWith const $
361 [ amount_usd $ polarize 1 ]
362 )
363 , ( ("B":|[])
364 , Balance_by_Account_Sum $
365 Map.fromListWith const $
366 [ amount_usd $ polarize 1 ]
367 )
368 ]
369 , balance_by_unit =
370 Balance_by_Unit $ Map.fromList $
371 [ amount_usd $ Balance_by_Unit_Sum
372 { balance_by_unit_sum_quantity = polarize 2
373 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
374 [ "A":|[]
375 , "B":|[] ]
376 }
377 ]
378 }
379 , testCase "{A+$1, $+1} {B+€1, €+1} = {A+$1 B+€1, $+1 €+1}" $
380 balance_union
381 Balance
382 { balance_by_account =
383 TreeMap.from_List const $
384 [ ( (("A"::Text):|[])
385 , Balance_by_Account_Sum $
386 Map.fromListWith const $
387 [ amount_usd $ polarize (1::Integer) ]
388 )
389 ]
390 , balance_by_unit =
391 Balance_by_Unit $ Map.fromList $
392 [ amount_usd $ Balance_by_Unit_Sum
393 { balance_by_unit_sum_quantity = polarize 1
394 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
395 [ "A":|[] ]
396 }
397 ]
398 }
399 Balance
400 { balance_by_account =
401 TreeMap.from_List const $
402 [ ( ("B":|[])
403 , Balance_by_Account_Sum $
404 Map.fromListWith const $
405 [ amount_eur $ polarize 1 ]
406 )
407 ]
408 , balance_by_unit =
409 Balance_by_Unit $ Map.fromList $
410 [ amount_eur $ Balance_by_Unit_Sum
411 { balance_by_unit_sum_quantity = polarize 1
412 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
413 [ "B":|[] ]
414 }
415 ]
416 }
417 @?=
418 Balance
419 { balance_by_account =
420 TreeMap.from_List const $
421 [ ( ("A":|[])
422 , Balance_by_Account_Sum $
423 Map.fromListWith const $
424 [ amount_usd $ polarize 1 ]
425 )
426 , ( ("B":|[])
427 , Balance_by_Account_Sum $
428 Map.fromListWith const $
429 [ amount_eur $ polarize 1 ]
430 )
431 ]
432 , balance_by_unit =
433 Balance_by_Unit $ Map.fromList $
434 [ amount_usd $ Balance_by_Unit_Sum
435 { balance_by_unit_sum_quantity = polarize 1
436 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
437 [ "A":|[] ]
438 }
439 , amount_eur $ Balance_by_Unit_Sum
440 { balance_by_unit_sum_quantity = polarize 1
441 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
442 [ "B":|[] ]
443 }
444 ]
445 }
446 ]
447 , testGroup "balance_expanded"
448 [ testCase "empty" $
449 balance_expanded TreeMap.empty
450 @?=
451 (TreeMap.empty::Balance_Expanded (NonEmpty Text) Text Integer)
452 , testCase "A+$1 = A+$1" $
453 balance_expanded
454 (TreeMap.from_List const $
455 [ ( ("A":|[])
456 , Balance_by_Account_Sum $
457 Map.fromListWith const $
458 [ amount_usd $ polarize 1 ]
459 )
460 ]::Balance_by_Account Text Text (Polarized Integer))
461 @?=
462 TreeMap.from_List const
463 [ ("A":|[], Strict.Clusive
464 { Strict.inclusive =
465 Balance_by_Account_Sum $
466 Map.map polarize $
467 amounts [ amount_usd $ 1 ]
468 , Strict.exclusive =
469 Balance_by_Account_Sum $
470 Map.map polarize $
471 amounts [ amount_usd $ 1 ]
472 })
473 ]
474 , testCase "A/A+$1 = A+$1 A/A+$1" $
475 balance_expanded
476 (TreeMap.from_List const $
477 [ ( ("A":|["A"])
478 , Balance_by_Account_Sum $
479 Map.fromListWith const $
480 [ amount_usd $ polarize 1 ]
481 )
482 ]::Balance_by_Account Text Text (Polarized Integer))
483 @?=
484 TreeMap.from_List const
485 [ ("A":|[], Strict.Clusive
486 { Strict.inclusive =
487 Balance_by_Account_Sum $
488 Map.map polarize $
489 amounts [ amount_usd $ 1 ]
490 , Strict.exclusive =
491 Balance_by_Account_Sum $
492 Map.map polarize $
493 amounts []
494 })
495 , ("A":|["A"], Strict.Clusive
496 { Strict.inclusive =
497 Balance_by_Account_Sum $
498 Map.map polarize $
499 amounts [ amount_usd $ 1 ]
500 , Strict.exclusive =
501 Balance_by_Account_Sum $
502 Map.map polarize $
503 amounts [ amount_usd $ 1 ]
504 })
505 ]
506 , testCase "A/B+$1 = A+$1 A/B+$1" $
507 balance_expanded
508 (TreeMap.from_List const $
509 [ ( ("A":|["B"])
510 , Balance_by_Account_Sum $
511 Map.fromListWith const $
512 [ amount_usd $ polarize 1 ]
513 )
514 ]::Balance_by_Account Text Text (Polarized Integer))
515 @?=
516 TreeMap.from_List const
517 [ ("A":|[], Strict.Clusive
518 { Strict.inclusive =
519 Balance_by_Account_Sum $
520 Map.map polarize $
521 amounts [ amount_usd $ 1 ]
522 , Strict.exclusive =
523 Balance_by_Account_Sum $
524 Map.map polarize $
525 amounts []
526 })
527 , ("A":|["B"], Strict.Clusive
528 { Strict.inclusive =
529 Balance_by_Account_Sum $
530 Map.map polarize $
531 amounts [ amount_usd $ 1 ]
532 , Strict.exclusive =
533 Balance_by_Account_Sum $
534 Map.map polarize $
535 amounts [ amount_usd $ 1 ]
536 })
537 ]
538 , testCase "A/B/C+$1 = A+$1 A/B+$1 A/B/C+$1" $
539 balance_expanded
540 (TreeMap.from_List const $
541 [ ( ("A":|["B", "C"])
542 , Balance_by_Account_Sum $
543 Map.fromListWith const $
544 [ amount_usd $ polarize 1 ]
545 )
546 ]::Balance_by_Account Text Text (Polarized Integer))
547 @?=
548 TreeMap.from_List const
549 [ ("A":|[], Strict.Clusive
550 { Strict.inclusive =
551 Balance_by_Account_Sum $
552 Map.map polarize $
553 amounts [ amount_usd $ 1 ]
554 , Strict.exclusive =
555 Balance_by_Account_Sum $
556 Map.map polarize $
557 amounts []
558 })
559 , ("A":|["B"], Strict.Clusive
560 { Strict.inclusive =
561 Balance_by_Account_Sum $
562 Map.map polarize $
563 amounts [ amount_usd $ 1 ]
564 , Strict.exclusive =
565 Balance_by_Account_Sum $
566 Map.map polarize $
567 amounts []
568 })
569 , ("A":|["B", "C"], Strict.Clusive
570 { Strict.inclusive =
571 Balance_by_Account_Sum $
572 Map.map polarize $
573 amounts [ amount_usd $ 1 ]
574 , Strict.exclusive =
575 Balance_by_Account_Sum $
576 Map.map polarize $
577 amounts [ amount_usd $ 1 ]
578 })
579 ]
580 , testCase "A+$1 A/B+$1 = A+$2 A/B+$1" $
581 balance_expanded
582 (TreeMap.from_List const
583 [ ( ("A":|[])
584 , Balance_by_Account_Sum $
585 Map.fromListWith const $
586 [ amount_usd $ polarize 1 ]
587 )
588 , ( ("A":|["B"])
589 , Balance_by_Account_Sum $
590 Map.fromListWith const $
591 [ amount_usd $ polarize 1 ]
592 )
593 ]::Balance_by_Account Text Text (Polarized Integer))
594 @?=
595 TreeMap.from_List const
596 [ ("A":|[], Strict.Clusive
597 { Strict.inclusive =
598 Balance_by_Account_Sum $
599 Map.map polarize $
600 amounts [ amount_usd $ 2 ]
601 , Strict.exclusive =
602 Balance_by_Account_Sum $
603 Map.map polarize $
604 amounts [ amount_usd $ 1 ]
605 })
606 , ("A":|["B"], Strict.Clusive
607 { Strict.inclusive =
608 Balance_by_Account_Sum $
609 Map.map polarize $
610 amounts [ amount_usd $ 1 ]
611 , Strict.exclusive =
612 Balance_by_Account_Sum $
613 Map.map polarize $
614 amounts [ amount_usd $ 1 ]
615 })
616 ]
617 , testCase "A+$1 A/B+$1 A/B/C+$1 = A+$3 A/B+$2 A/B/C+$1" $
618 balance_expanded
619 (TreeMap.from_List const $
620 [ ( ("A":|[])
621 , Balance_by_Account_Sum $
622 Map.fromListWith const $
623 [ amount_usd $ polarize 1 ]
624 )
625 , ( ("A":|["B"])
626 , Balance_by_Account_Sum $
627 Map.fromListWith const $
628 [ amount_usd $ polarize 1 ]
629 )
630 , ( ("A":|["B", "C"])
631 , Balance_by_Account_Sum $
632 Map.fromListWith const $
633 [ amount_usd $ polarize 1 ]
634 )
635 ]::Balance_by_Account Text Text (Polarized Integer))
636 @?=
637 TreeMap.from_List const
638 [ ("A":|[], Strict.Clusive
639 { Strict.inclusive =
640 Balance_by_Account_Sum $
641 Map.map polarize $
642 amounts [ amount_usd $ 3 ]
643 , Strict.exclusive =
644 Balance_by_Account_Sum $
645 Map.map polarize $
646 amounts [ amount_usd $ 1 ]
647 })
648 , ("A":|["B"], Strict.Clusive
649 { Strict.inclusive =
650 Balance_by_Account_Sum $
651 Map.map polarize $
652 amounts [ amount_usd $ 2 ]
653 , Strict.exclusive =
654 Balance_by_Account_Sum $
655 Map.map polarize $
656 amounts [ amount_usd $ 1 ]
657 })
658 , ("A":|["B", "C"], Strict.Clusive
659 { Strict.inclusive =
660 Balance_by_Account_Sum $
661 Map.map polarize $
662 amounts [ amount_usd $ 1 ]
663 , Strict.exclusive =
664 Balance_by_Account_Sum $
665 Map.map polarize $
666 amounts [ amount_usd $ 1 ]
667 })
668 ]
669 , testCase "A+$1 A/B+$1 A/B/C+$1 A/B/C/D+$1 = A+$4 A/B+$3 A/B/C+$2 A/B/C/D+$1" $
670 balance_expanded
671 (TreeMap.from_List const
672 [ ( ("A":|[])
673 , Balance_by_Account_Sum $
674 Map.fromListWith const $
675 [ amount_usd $ polarize 1 ]
676 )
677 , ( ("A":|["B"])
678 , Balance_by_Account_Sum $
679 Map.fromListWith const $
680 [ amount_usd $ polarize 1 ]
681 )
682 , ( ("A":|["B", "C"])
683 , Balance_by_Account_Sum $
684 Map.fromListWith const $
685 [ amount_usd $ polarize 1 ]
686 )
687 , ( ("A":|["B", "C", "D"])
688 , Balance_by_Account_Sum $
689 Map.fromListWith const $
690 [ amount_usd $ polarize 1 ]
691 )
692 ]::Balance_by_Account Text Text (Polarized Integer))
693 @?=
694 TreeMap.from_List const
695 [ ("A":|[], Strict.Clusive
696 { Strict.inclusive =
697 Balance_by_Account_Sum $
698 Map.map polarize $
699 amounts [ amount_usd $ 4 ]
700 , Strict.exclusive =
701 Balance_by_Account_Sum $
702 Map.map polarize $
703 amounts [ amount_usd $ 1 ]
704 })
705 , ("A":|["B"], Strict.Clusive
706 { Strict.inclusive =
707 Balance_by_Account_Sum $
708 Map.map polarize $
709 amounts [ amount_usd $ 3 ]
710 , Strict.exclusive =
711 Balance_by_Account_Sum $
712 Map.map polarize $
713 amounts [ amount_usd $ 1 ]
714 })
715 , ("A":|["B", "C"], Strict.Clusive
716 { Strict.inclusive =
717 Balance_by_Account_Sum $
718 Map.map polarize $
719 amounts [ amount_usd $ 2 ]
720 , Strict.exclusive =
721 Balance_by_Account_Sum $
722 Map.map polarize $
723 amounts [ amount_usd $ 1 ]
724 })
725 , ("A":|["B", "C", "D"], Strict.Clusive
726 { Strict.inclusive =
727 Balance_by_Account_Sum $
728 Map.map polarize $
729 amounts [ amount_usd $ 1 ]
730 , Strict.exclusive =
731 Balance_by_Account_Sum $
732 Map.map polarize $
733 amounts [ amount_usd $ 1 ]
734 })
735 ]
736 , testCase "A+$1 A/B+$1 A/BB+$1 AA/B+$1 = A+$3 A/B+$1 A/BB+$1 AA+$1 AA/B+$1" $
737 balance_expanded
738 (TreeMap.from_List const
739 [ ( ("A":|[])
740 , Balance_by_Account_Sum $
741 Map.fromListWith const $
742 [ amount_usd $ polarize 1 ]
743 )
744 , ( ("A":|["B"])
745 , Balance_by_Account_Sum $
746 Map.fromListWith const $
747 [ amount_usd $ polarize 1 ]
748 )
749 , ( ("A":|["BB"])
750 , Balance_by_Account_Sum $
751 Map.fromListWith const $
752 [ amount_usd $ polarize 1 ]
753 )
754 , ( ("AA":|["B"])
755 , Balance_by_Account_Sum $
756 Map.fromListWith const $
757 [ amount_usd $ polarize 1 ]
758 )
759 ]::Balance_by_Account Text Text (Polarized Integer))
760 @?=
761 TreeMap.from_List const
762 [ ("A":|[], Strict.Clusive
763 { Strict.inclusive =
764 Balance_by_Account_Sum $
765 Map.map polarize $
766 amounts [ amount_usd $ 3 ]
767 , Strict.exclusive =
768 Balance_by_Account_Sum $
769 Map.map polarize $
770 amounts [ amount_usd $ 1 ]
771 })
772 , ("A":|["B"], Strict.Clusive
773 { Strict.inclusive =
774 Balance_by_Account_Sum $
775 Map.map polarize $
776 amounts [ amount_usd $ 1 ]
777 , Strict.exclusive =
778 Balance_by_Account_Sum $
779 Map.map polarize $
780 amounts [ amount_usd $ 1 ]
781 })
782 , ("A":|["BB"], Strict.Clusive
783 { Strict.inclusive =
784 Balance_by_Account_Sum $
785 Map.map polarize $
786 amounts [ amount_usd $ 1 ]
787 , Strict.exclusive =
788 Balance_by_Account_Sum $
789 Map.map polarize $
790 amounts [ amount_usd $ 1 ]
791 })
792 , ("AA":|[], Strict.Clusive
793 { Strict.inclusive =
794 Balance_by_Account_Sum $
795 Map.map polarize $
796 amounts [ amount_usd $ 1 ]
797 , Strict.exclusive =
798 Balance_by_Account_Sum $
799 Map.map polarize $
800 amounts []
801 })
802 , ("AA":|["B"], Strict.Clusive
803 { Strict.inclusive =
804 Balance_by_Account_Sum $
805 Map.map polarize $
806 amounts [ amount_usd $ 1 ]
807 , Strict.exclusive =
808 Balance_by_Account_Sum $
809 Map.map polarize $
810 amounts [ amount_usd $ 1 ]
811 })
812 ]
813 ]
814 , testGroup "balance_deviation"
815 [ testCase "{A+$1, $1}" $
816 balance_deviation
817 Balance
818 { balance_by_account =
819 TreeMap.from_List const
820 [ ( (("A"::Text):|[])
821 , Balance_by_Account_Sum $
822 Map.fromListWith const $
823 [ amount_usd $ polarize (1::Integer) ]
824 )
825 , ( ("B":|[])
826 , Balance_by_Account_Sum $
827 Map.fromListWith const $
828 [ ]
829 )
830 ]
831 , balance_by_unit =
832 Balance_by_Unit $ Map.fromList $
833 [ amount_usd $ Balance_by_Unit_Sum
834 { balance_by_unit_sum_quantity = polarize 1
835 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
836 [ "A":|[] ]
837 }
838 ]
839 }
840 @?=
841 Balance_Deviation
842 (Balance_by_Unit $ Map.fromList
843 [ amount_usd $ Balance_by_Unit_Sum
844 { balance_by_unit_sum_quantity = polarize 1
845 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
846 [ "B":|[] ]
847 }
848 ])
849 , testCase "{A+$1 B+$1, $2}" $
850 balance_deviation
851 Balance
852 { balance_by_account =
853 TreeMap.from_List const $
854 [ ( (("A"::Text):|[])
855 , Balance_by_Account_Sum $
856 Map.fromListWith const $
857 [ amount_usd $ polarize (1::Integer) ]
858 )
859 , ( ("B":|[])
860 , Balance_by_Account_Sum $
861 Map.fromListWith const $
862 [ amount_usd $ polarize 1 ]
863 )
864 ]
865 , balance_by_unit =
866 Balance_by_Unit $ Map.fromList $
867 [ amount_usd $ Balance_by_Unit_Sum
868 { balance_by_unit_sum_quantity = polarize 2
869 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
870 [ "A":|[]
871 , "B":|[]
872 ]
873 }
874 ]
875 }
876 @?=
877 Balance_Deviation
878 (Balance_by_Unit $ Map.fromList $
879 [ amount_usd $ Balance_by_Unit_Sum
880 { balance_by_unit_sum_quantity = polarize 2
881 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
882 [
883 ]
884 }
885 ])
886 ]
887 , testGroup "is_balance_equilibrium_inferrable"
888 [ testCase "empty" $
889 (@=?) True $
890 is_balance_equilibrium_inferrable $
891 balance_deviation $
892 (balance_empty::Balance Text Text Integer)
893 , testCase "{A+$0, $+0}" $
894 (@=?) True $
895 is_balance_equilibrium_inferrable $
896 balance_deviation
897 Balance
898 { balance_by_account =
899 TreeMap.from_List const $
900 [ ( ("A":|[])
901 , Balance_by_Account_Sum $
902 Map.fromListWith const $
903 [ amount_usd $ polarize (0::Integer) ]
904 )
905 ]
906 , balance_by_unit =
907 Balance_by_Unit $ Map.fromList $
908 [ amount_usd $ Balance_by_Unit_Sum
909 { balance_by_unit_sum_quantity = polarize (0::Integer)
910 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
911 [ ("A"::Text):|[] ]
912 }
913 ]
914 }
915 , testCase "{A+$1, $+1}" $
916 (@=?) False $
917 is_balance_equilibrium_inferrable $
918 balance_deviation
919 Balance
920 { balance_by_account =
921 TreeMap.from_List const $
922 [ ( (("A"::Text):|[])
923 , Balance_by_Account_Sum $
924 Map.fromListWith const $
925 [ amount_usd $ polarize (1::Integer) ]
926 )
927 ]
928 , balance_by_unit =
929 Balance_by_Unit $ Map.fromList $
930 [ amount_usd $ Balance_by_Unit_Sum
931 { balance_by_unit_sum_quantity = polarize 1
932 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
933 [ "A":|[] ]
934 }
935 ]
936 }
937 , testCase "{A+$0+€0, $0 €+0}" $
938 (@=?) True $
939 is_balance_equilibrium_inferrable $
940 balance_deviation
941 Balance
942 { balance_by_account =
943 TreeMap.from_List const $
944 [ ( (("A"::Text):|[])
945 , Balance_by_Account_Sum $
946 Map.fromListWith const $
947 [ amount_usd $ polarize (0::Integer)
948 , amount_eur $ polarize 0
949 ]
950 )
951 ]
952 , balance_by_unit =
953 Balance_by_Unit $ Map.fromList $
954 [ amount_usd $ Balance_by_Unit_Sum
955 { balance_by_unit_sum_quantity = polarize 0
956 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
957 [ "A":|[] ]
958 }
959 , amount_eur $ Balance_by_Unit_Sum
960 { balance_by_unit_sum_quantity = polarize 0
961 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
962 [ "A":|[] ]
963 }
964 ]
965 }
966 , testCase "{A+$1 B-$1, $+0}" $
967 (@=?) True $
968 is_balance_equilibrium_inferrable $
969 balance_deviation
970 Balance
971 { balance_by_account =
972 TreeMap.from_List const $
973 [ ( (("A"::Text):|[])
974 , Balance_by_Account_Sum $
975 Map.fromListWith const $
976 [ amount_usd $ polarize (1::Integer) ]
977 )
978 , ( ("B":|[])
979 , Balance_by_Account_Sum $
980 Map.fromListWith const $
981 [ amount_usd $ polarize (-1) ]
982 )
983 ]
984 , balance_by_unit =
985 Balance_by_Unit $ Map.fromList $
986 [ amount_usd $ Balance_by_Unit_Sum
987 { balance_by_unit_sum_quantity = polarize 0
988 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
989 [ "A":|[]
990 , "B":|[] ]
991 }
992 ]
993 }
994 , testCase "{A+$1 B, $+1}" $
995 (@=?) True $
996 is_balance_equilibrium_inferrable $
997 balance_deviation
998 Balance
999 { balance_by_account =
1000 TreeMap.from_List const $
1001 [ ( (("A"::Text):|[])
1002 , Balance_by_Account_Sum $
1003 Map.fromListWith const $
1004 [ amount_usd $ polarize (1::Integer) ]
1005 )
1006 , ( ("B":|[])
1007 , Balance_by_Account_Sum $
1008 Map.fromListWith const $
1009 [ ]
1010 )
1011 ]
1012 , balance_by_unit =
1013 Balance_by_Unit $ Map.fromList $
1014 [ amount_usd $ Balance_by_Unit_Sum
1015 { balance_by_unit_sum_quantity = polarize 1
1016 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1017 [ "A":|[] ]
1018 }
1019 ]
1020 }
1021 , testCase "{A+$1 B+€1, $+1 €+1}" $
1022 (@=?) True $
1023 is_balance_equilibrium_inferrable $
1024 balance_deviation
1025 Balance
1026 { balance_by_account =
1027 TreeMap.from_List const $
1028 [ ( (("A"::Text):|[])
1029 , Balance_by_Account_Sum $
1030 Map.fromListWith const $
1031 [ amount_usd $ polarize (1::Integer) ]
1032 )
1033 , ( ("B":|[])
1034 , Balance_by_Account_Sum $
1035 Map.fromListWith const $
1036 [ amount_eur $ polarize 1 ]
1037 )
1038 ]
1039 , balance_by_unit =
1040 Balance_by_Unit $ Map.fromList $
1041 [ amount_usd $ Balance_by_Unit_Sum
1042 { balance_by_unit_sum_quantity = polarize 1
1043 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1044 [ "A":|[] ]
1045 }
1046 , amount_eur $ Balance_by_Unit_Sum
1047 { balance_by_unit_sum_quantity = polarize 1
1048 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1049 [ "B":|[] ]
1050 }
1051 ]
1052 }
1053 , testCase "{A+$1 B-$1+€1, $+0 €+1}" $
1054 (@=?) True $
1055 is_balance_equilibrium_inferrable $
1056 balance_deviation
1057 Balance
1058 { balance_by_account =
1059 TreeMap.from_List const $
1060 [ ( (("A"::Text):|[])
1061 , Balance_by_Account_Sum $
1062 Map.fromListWith const $
1063 [ amount_usd $ polarize (1::Integer) ]
1064 )
1065 , ( ("B":|[])
1066 , Balance_by_Account_Sum $
1067 Map.fromListWith const $
1068 [ amount_usd $ polarize (-1)
1069 , amount_eur $ polarize 1
1070 ]
1071 )
1072 ]
1073 , balance_by_unit =
1074 Balance_by_Unit $ Map.fromList $
1075 [ amount_usd $ Balance_by_Unit_Sum
1076 { balance_by_unit_sum_quantity = polarize 0
1077 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1078 [ "A":|[]
1079 , "B":|[] ]
1080 }
1081 , amount_eur $ Balance_by_Unit_Sum
1082 { balance_by_unit_sum_quantity = polarize 1
1083 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1084 [ "B":|[] ]
1085 }
1086 ]
1087 }
1088 , testCase "{A+$1+€2+£3 B-$1-€2-£3, $+0 €+0 £+0}" $
1089 (@=?) True $
1090 is_balance_equilibrium_inferrable $
1091 balance_deviation $
1092 Balance
1093 { balance_by_account =
1094 TreeMap.from_List const $
1095 [ ( (("A"::Text):|[])
1096 , Balance_by_Account_Sum $
1097 Map.fromListWith const $
1098 [ amount_usd $ polarize (1::Integer)
1099 , amount_eur $ polarize 2
1100 , amount_gbp $ polarize 3
1101 ]
1102 )
1103 , ( ("B":|[])
1104 , Balance_by_Account_Sum $
1105 Map.fromListWith const $
1106 [ amount_usd $ polarize (-1)
1107 , amount_eur $ polarize (-2)
1108 , amount_gbp $ polarize (-3)
1109 ]
1110 )
1111 ]
1112 , balance_by_unit =
1113 Balance_by_Unit $ Map.fromList $
1114 [ amount_usd $ Balance_by_Unit_Sum
1115 { balance_by_unit_sum_quantity = polarize 0
1116 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1117 [ "A":|[]
1118 , "B":|[] ]
1119 }
1120 , amount_eur $ Balance_by_Unit_Sum
1121 { balance_by_unit_sum_quantity = polarize 0
1122 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1123 [ "A":|[]
1124 , "B":|[] ]
1125 }
1126 , amount_gbp $ Balance_by_Unit_Sum
1127 { balance_by_unit_sum_quantity = polarize 0
1128 , balance_by_unit_sum_accounts = Map.fromList $ List.map (,())
1129 [ "A":|[]
1130 , "B":|[] ]
1131 }
1132 ]
1133 }
1134 ]
1135 , testGroup "balance_infer_equilibrium"
1136 [ testCase "{A+$1 B}" $
1137 snd (balance_infer_equilibrium $
1138 Map.fromList $
1139 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1140 [ ( ("A"::Text):|[]
1141 , amounts [ amount_usd $ (1::Integer) ] )
1142 , ( "B":|[]
1143 , amounts [] )
1144 ])
1145 @?=
1146 (Right $
1147 Map.fromList $
1148 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1149 [ ( "A":|[]
1150 , amounts [ amount_usd $ 1 ] )
1151 , ( "B":|[]
1152 , amounts [ amount_usd $ -1 ] )
1153 ])
1154 , testCase "{A+$1 B-1€}" $
1155 snd (balance_infer_equilibrium $
1156 Map.fromList $
1157 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1158 [ ( ("A"::Text):|[]
1159 , amounts [ amount_usd $ (1::Integer) ] )
1160 , ( "B":|[]
1161 , amounts [ amount_eur $ -1 ] )
1162 ])
1163 @?=
1164 (Right $
1165 Map.fromList $
1166 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1167 [ ( ("A"::Text):|[]
1168 , amounts [ amount_usd $ 1, amount_eur $ (1::Integer)] )
1169 , ( "B":|[]
1170 , amounts [ amount_eur $ -1, amount_usd $ -1 ] )
1171 ])
1172 , testCase "{A+$1 B+$1}" $
1173 snd (balance_infer_equilibrium $
1174 Map.fromList $
1175 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1176 [ ( ("A"::Text):|[]
1177 , amounts [ amount_usd $ (1::Integer) ] )
1178 , ( "B":|[]
1179 , amounts [ amount_usd $ 1 ] )
1180 ])
1181 @?=
1182 (Left
1183 [ amount_usd $ Balance_by_Unit_Sum
1184 { balance_by_unit_sum_quantity = 2
1185 , balance_by_unit_sum_accounts = Map.fromList []}
1186 ])
1187 , testCase "{A+$1 B-$1 B-1€}" $
1188 snd (balance_infer_equilibrium $
1189 Map.fromList $
1190 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1191 [ ( ("A"::Text):|[]
1192 , amounts [ amount_usd $ (1::Integer) ] )
1193 , ( "B":|[]
1194 , amounts [ amount_usd $ -1, amount_eur $ -1 ] )
1195 ])
1196 @?=
1197 (Right $
1198 Map.fromList $
1199 List.map (\(acct, amts) -> (acct, [(acct, amts)])) $
1200 [ ( "A":|[]
1201 , amounts [ amount_usd $ 1, amount_eur $ 1 ] )
1202 , ( "B":|[]
1203 , amounts [ amount_usd $ -1, amount_eur $ -1 ] )
1204 ])
1205 ]
1206 ]