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}