tktax_money/
lib.rs

1// ---------------- [ File: tktax-money/src/lib.rs ]
2#![feature(assert_matches)]
3#[macro_use] mod imports; use imports::*;
4
5x!{basic_stats}
6x!{dimensionless}
7x!{price}
8x!{errors}
9x!{correlation}
10x!{simple_linear_regression}
11x!{monetary_amount}
12x!{segment_by}
13x!{deser}
14x!{advanced_stats}
15
16#[cfg(test)]
17mod test_monetary_amount_extended {
18    use super::MonetaryAmount;
19    use super::MonetaryAmountError;
20    use super::*;
21    use std::str::FromStr;
22    use rust_decimal::Decimal;
23
24    fn ma(s: &str) -> MonetaryAmount {
25        MonetaryAmount::from_str(s).unwrap()
26    }
27
28    // --------------------------
29    // 1. Parsing & Edge Cases
30    // --------------------------
31
32    #[test]
33    fn test_parse_valid() {
34        assert_eq!(MonetaryAmount::from_str("0").unwrap(), MonetaryAmount::zero());
35        assert_eq!(MonetaryAmount::from_str("123.456").unwrap(), ma("123.456"));
36        assert_eq!(MonetaryAmount::from_str("-987.654321").unwrap(), ma("-987.654321"));
37    }
38
39    #[test]
40    fn test_parse_invalid() {
41        // Contains non-numeric chars or multiple decimals, etc.
42        let err = MonetaryAmount::from_str("12.34.56").unwrap_err();
43        assert_matches!(err, MonetaryAmountParseError::CouldNotParse { .. });
44
45        let err = MonetaryAmount::from_str("abc").unwrap_err();
46        assert_matches!(err, MonetaryAmountParseError::CouldNotParse { .. });
47
48        let err = MonetaryAmount::from_str("").unwrap_err();
49        assert_matches!(err, MonetaryAmountParseError::CouldNotParse { .. });
50    }
51
52    #[test]
53    fn test_parse_extreme() {
54        // 12 digits before decimal is allowed
55        let x = ma("999999999999.9999999999"); // 12 digits before decimal
56        assert_eq!(format!("{}", x), " $1000000000000.00");  // Rounds up
57
58        // 13 digits => display panic
59        // But from_str is still valid. The panic only triggers *when printing* if >12 digits before decimal.
60        let big_str = "1234567890123.45";
61        let y = MonetaryAmount::from_str(big_str).unwrap();
62        // The next line should panic if displayed:
63        // let _ = format!("{}", y);
64
65        // We can check that we store it, but panic if we do try to display:
66        // So we won't call format!() here to avoid intentional panic in this test.
67        assert_eq!(y, MonetaryAmount::from(Decimal::from_str(big_str).unwrap()));
68    }
69
70    #[test]
71    fn test_from_f64_valid() {
72        let x = MonetaryAmount::from(3.14159_f64);
73        // 3.14159 => check truncated or exact?
74        assert_eq!(x.to_string().trim(), "$3.14");
75    }
76
77    // This scenario might panic if the f64 => Decimal fails.
78    // Usually rust_decimal::from_f64(some_large_value) => Option::None if out of range
79    // so we might wrap that in a custom panic or error. 
80    // For demonstration:
81    #[test]
82    #[should_panic(expected = "Failed to convert from f64 to Decimal")]
83    fn test_from_f64_out_of_range() {
84        // Very large f64 can cause Decimal::from_f64 to return None
85        let _ = MonetaryAmount::from(1e40_f64); 
86    }
87
88    // --------------------------
89    // 2. Display & Rounding
90    // --------------------------
91
92    // Check that trailing decimals get truncated properly
93    #[test]
94    fn test_display_rounding() {
95        let amt = ma("123.4567");
96        // 2 decimal rounding => "123.46"
97        // left-justify in 12 spaces => " $123.46     "
98        assert_eq!(format!("{}", amt), " $123.46     ");
99    }
100
101    #[test]
102    fn test_display_zero() {
103        let amt = ma("0");
104        assert_eq!(format!("{}", amt).trim(), "$0.00");
105    }
106
107    // --------------------------
108    // 3. Arithmetic Edge Cases
109    // --------------------------
110
111    #[test]
112    fn test_negative_division() {
113        let x = ma("-10");
114        let y = ma("5");
115        let res = x / y; // = -2
116        assert_eq!(res, ma("-2"));
117        let res2 = x + y; // -10 + 5 = -5
118        assert_eq!(res2, ma("-5"));
119    }
120
121    #[test]
122    fn test_sqrt_zero() {
123        let z = MonetaryAmount::zero();
124        let root = z.sqrt();
125        assert_eq!(root, MonetaryAmount::zero());
126    }
127
128    // Another corner: exponent 0 => x^0=1
129    #[test]
130    fn test_powu_zero_exponent() {
131        let x = ma("9999.99");
132        let p = x.powu(0);
133        // any non-zero x^0 => 1
134        assert_eq!(p, ma("1"));
135    }
136
137    #[test]
138    fn test_powu_large_exponent() {
139        // test behavior for large exponent that might overflow decimal
140        // e.g. we expect a panic if it's too big, or success if not
141        let x = ma("2");
142        // 2^32=4,294,967,296 => might still be ok for 128-bit decimal
143        let p = x.powu(32);
144        assert_eq!(p, ma("4294967296"));
145    }
146
147    // --------------------------
148    // 4. Comparison & Hashing
149    // --------------------------
150
151    #[test]
152    fn test_partial_eq_and_ordering() {
153        // Check that different decimal scales are recognized as equal
154        let a = ma("10.0");
155        let b = ma("10.00");
156        assert_eq!(a, b);
157
158        let c = ma("10.0001");
159        assert!(c > a);
160        assert!(c >= a);
161        assert!(a < c);
162    }
163
164    #[test]
165    fn test_hash_map_usage() {
166        // confirm we can use MonetaryAmount as key in a HashMap
167        let mut map = std::collections::HashMap::new();
168        map.insert(ma("5"), "five");
169        map.insert(ma("10.000"), "ten");
170
171        assert_eq!(map.get(&ma("5.0")), Some(&"five"));
172        assert_eq!(map.get(&ma("10")), Some(&"ten"));
173    }
174
175    // ----------------------------
176    // 5. (Optional) Property Tests
177    // ----------------------------
178
179    // If you want property-based checks, uncomment and add proptest dev-dependency in Cargo.toml:
180    //
181    // [dev-dependencies]
182    // proptest = "1"
183    //
184    // Then:
185    /*
186    use proptest::prelude::*;
187
188    proptest! {
189        #[test]
190        fn prop_arithmetic_closure(a in -9999i64..9999, b in -9999i64..9999) {
191            let ma_a = MonetaryAmount::from(a);
192            let ma_b = MonetaryAmount::from(b);
193
194            // Check associativity, commutativity, etc.
195            // For example, addition is commutative:
196            prop_assert_eq!(ma_a + ma_b, ma_b + ma_a);
197
198            // Multiplying with 0 yields 0:
199            prop_assert_eq!(ma_a * MonetaryAmount::zero(), MonetaryAmount::zero());
200            prop_assert_eq!(ma_b * MonetaryAmount::zero(), MonetaryAmount::zero());
201        }
202    }
203    */
204}