lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

money.rs (7232B)


      1 mod common;
      2 
      3 use radroots_core::{RadrootsCoreCurrency, RadrootsCoreMoney, RadrootsCoreMoneyInvariantError};
      4 use rust_decimal::RoundingStrategy;
      5 
      6 #[test]
      7 fn zero_and_is_zero() {
      8     let usd = RadrootsCoreCurrency::USD;
      9     let zero = RadrootsCoreMoney::zero(usd);
     10     assert!(zero.is_zero());
     11     assert_eq!(zero.currency, usd);
     12 }
     13 
     14 #[test]
     15 fn ensure_non_negative_rejects_negative_amount() {
     16     let money = RadrootsCoreMoney::new(common::dec("-1"), RadrootsCoreCurrency::USD);
     17     assert_eq!(
     18         money.ensure_non_negative(),
     19         Err(RadrootsCoreMoneyInvariantError::NegativeAmount)
     20     );
     21 }
     22 
     23 #[test]
     24 fn ensure_non_negative_accepts_zero_and_positive() {
     25     let zero = RadrootsCoreMoney::new(common::dec("0"), RadrootsCoreCurrency::USD);
     26     let pos = RadrootsCoreMoney::new(common::dec("1"), RadrootsCoreCurrency::USD);
     27     assert_eq!(zero.ensure_non_negative(), Ok(()));
     28     assert_eq!(pos.ensure_non_negative(), Ok(()));
     29 }
     30 
     31 #[test]
     32 fn quantize_to_currency_rounds_midpoint_away_from_zero() {
     33     let usd = RadrootsCoreCurrency::USD;
     34     let a = RadrootsCoreMoney::new(common::dec("1.234"), usd).quantize_to_currency();
     35     let b = RadrootsCoreMoney::new(common::dec("1.235"), usd).quantize_to_currency();
     36     let c = RadrootsCoreMoney::new(common::dec("-1.235"), usd).quantize_to_currency();
     37 
     38     assert_eq!(a.amount, common::dec("1.23"));
     39     assert_eq!(b.amount, common::dec("1.24"));
     40     assert_eq!(c.amount, common::dec("-1.24"));
     41 }
     42 
     43 #[test]
     44 fn quantize_to_currency_with_strategy_uses_strategy() {
     45     let usd = RadrootsCoreCurrency::USD;
     46     let a = RadrootsCoreMoney::new(common::dec("1.235"), usd)
     47         .quantize_to_currency_with_strategy(RoundingStrategy::MidpointTowardZero);
     48     let b = RadrootsCoreMoney::new(common::dec("-1.235"), usd)
     49         .quantize_to_currency_with_strategy(RoundingStrategy::MidpointTowardZero);
     50     assert_eq!(a.amount, common::dec("1.23"));
     51     assert_eq!(b.amount, common::dec("-1.23"));
     52 }
     53 
     54 #[test]
     55 fn checked_add_and_sub_require_currency_match() {
     56     let usd = RadrootsCoreCurrency::USD;
     57     let eur = RadrootsCoreCurrency::EUR;
     58     let a = RadrootsCoreMoney::new(common::dec("1.00"), usd);
     59     let b = RadrootsCoreMoney::new(common::dec("2.00"), usd);
     60     let c = RadrootsCoreMoney::new(common::dec("3.00"), eur);
     61 
     62     assert_eq!(a.checked_add(&b).unwrap().amount, common::dec("3.00"));
     63     assert_eq!(
     64         a.checked_add(&c),
     65         Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch)
     66     );
     67     assert_eq!(b.checked_sub(&a).unwrap().amount, common::dec("1.00"));
     68 }
     69 
     70 #[test]
     71 fn checked_sub_mismatch_returns_currency_error() {
     72     let usd = RadrootsCoreCurrency::USD;
     73     let eur = RadrootsCoreCurrency::EUR;
     74     let a = RadrootsCoreMoney::new(common::dec("1.00"), usd);
     75     let b = RadrootsCoreMoney::new(common::dec("2.00"), eur);
     76     assert_eq!(
     77         a.checked_sub(&b),
     78         Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch)
     79     );
     80 }
     81 
     82 #[test]
     83 fn minor_units_exact_and_rounded() {
     84     let usd = RadrootsCoreCurrency::USD;
     85     let exact = RadrootsCoreMoney::new(common::dec("1.23"), usd);
     86     let frac = RadrootsCoreMoney::new(common::dec("1.234"), usd);
     87     let rounded = RadrootsCoreMoney::new(common::dec("1.235"), usd);
     88 
     89     assert_eq!(exact.to_minor_units_u64_exact().unwrap(), 123);
     90     assert_eq!(
     91         frac.to_minor_units_u64_exact(),
     92         Err(RadrootsCoreMoneyInvariantError::NotWholeMinorUnits)
     93     );
     94     assert_eq!(
     95         rounded
     96             .to_minor_units_u64_rounded(RoundingStrategy::MidpointAwayFromZero)
     97             .unwrap(),
     98         124
     99     );
    100 }
    101 
    102 #[test]
    103 fn minor_units_cover_additional_currency_exponents() {
    104     let jpy = RadrootsCoreMoney::new(common::dec("123"), common::currency("JPY"));
    105     assert_eq!(jpy.to_minor_units_u64_exact().unwrap(), 123);
    106     assert_eq!(
    107         jpy.to_minor_units_u64_rounded(RoundingStrategy::MidpointAwayFromZero)
    108             .unwrap(),
    109         123
    110     );
    111 
    112     let kwd = RadrootsCoreMoney::new(common::dec("1.234"), common::currency("KWD"));
    113     assert_eq!(kwd.to_minor_units_u64_exact().unwrap(), 1234);
    114 }
    115 
    116 #[test]
    117 fn minor_units_u32_overflow_is_detected() {
    118     let usd = RadrootsCoreCurrency::USD;
    119     let too_large = RadrootsCoreMoney::from_minor_units_u64(u64::from(u32::MAX) + 1, usd);
    120     assert_eq!(
    121         too_large.to_minor_units_u32_exact(),
    122         Err(RadrootsCoreMoneyInvariantError::AmountOverflow)
    123     );
    124 }
    125 
    126 #[test]
    127 fn minor_units_u32_exact_success_path_is_exercised() {
    128     let usd = RadrootsCoreCurrency::USD;
    129     let m = RadrootsCoreMoney::new(common::dec("42.01"), usd);
    130     assert_eq!(m.to_minor_units_u32_exact().unwrap(), 4201);
    131     let fractional = RadrootsCoreMoney::new(common::dec("1.001"), usd);
    132     assert_eq!(
    133         fractional.to_minor_units_u32_exact(),
    134         Err(RadrootsCoreMoneyInvariantError::NotWholeMinorUnits)
    135     );
    136 }
    137 
    138 #[test]
    139 fn from_minor_units_u32_and_u32_rounded_paths_are_exercised() {
    140     let usd = RadrootsCoreCurrency::USD;
    141     let from_u32 = RadrootsCoreMoney::from_minor_units_u32(505, usd);
    142     assert_eq!(from_u32.amount, common::dec("5.05"));
    143     assert_eq!(
    144         from_u32
    145             .to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero)
    146             .unwrap(),
    147         505
    148     );
    149 }
    150 
    151 #[test]
    152 fn minor_units_u32_rounded_overflow_is_detected() {
    153     let usd = RadrootsCoreCurrency::USD;
    154     let too_large = RadrootsCoreMoney::from_minor_units_u64(u64::from(u32::MAX) + 1, usd);
    155     assert_eq!(
    156         too_large.to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero),
    157         Err(RadrootsCoreMoneyInvariantError::AmountOverflow)
    158     );
    159     let negative = RadrootsCoreMoney::new(common::dec("-1.00"), usd);
    160     assert_eq!(
    161         negative.to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero),
    162         Err(RadrootsCoreMoneyInvariantError::AmountOverflow)
    163     );
    164 }
    165 
    166 #[test]
    167 fn with_scale_path_is_exercised() {
    168     let usd = RadrootsCoreCurrency::USD;
    169     let m = RadrootsCoreMoney::new(common::dec("1.2300"), usd).with_scale(1);
    170     assert_eq!(m.amount, common::dec("1.2"));
    171 }
    172 
    173 #[test]
    174 fn from_minor_units_roundtrips() {
    175     let usd = RadrootsCoreCurrency::USD;
    176     let money = RadrootsCoreMoney::from_minor_units_u64(12345, usd);
    177     assert_eq!(money.to_minor_units_u64_exact().unwrap(), 12345);
    178 }
    179 
    180 #[test]
    181 fn display_and_operator_impl_paths_are_exercised() {
    182     let usd = RadrootsCoreCurrency::USD;
    183     let m = RadrootsCoreMoney::new(common::dec("10"), usd);
    184     assert_eq!(m.to_string(), "10 USD");
    185 
    186     let times = m.clone() * common::dec("2");
    187     assert_eq!(times.amount, common::dec("20"));
    188     let divided = m / common::dec("4");
    189     assert_eq!(divided.amount, common::dec("2.5"));
    190 }
    191 
    192 #[test]
    193 fn invariant_error_display_variants_are_exercised() {
    194     assert_eq!(
    195         RadrootsCoreMoneyInvariantError::NegativeAmount.to_string(),
    196         "money amount must be ≥ 0"
    197     );
    198     assert_eq!(
    199         RadrootsCoreMoneyInvariantError::NotWholeMinorUnits.to_string(),
    200         "money not a whole number of minor units"
    201     );
    202     assert_eq!(
    203         RadrootsCoreMoneyInvariantError::AmountOverflow.to_string(),
    204         "money minor-unit conversion overflow"
    205     );
    206     assert_eq!(
    207         RadrootsCoreMoneyInvariantError::CurrencyMismatch.to_string(),
    208         "money currency mismatch"
    209     );
    210 }