lib

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

quantity_price.rs (7910B)


      1 mod common;
      2 
      3 use radroots_core::{
      4     RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceError, RadrootsCoreQuantityPriceOps,
      5     RadrootsCoreUnit,
      6 };
      7 
      8 #[test]
      9 fn cost_for_scales_by_ratio() {
     10     let price = RadrootsCoreQuantityPrice::new(
     11         common::money("10", "USD"),
     12         common::qty("1", RadrootsCoreUnit::MassKg),
     13     );
     14     let cost = price.cost_for(&common::qty("2", RadrootsCoreUnit::MassKg));
     15     assert_eq!(cost.amount, common::dec("20"));
     16 }
     17 
     18 #[test]
     19 fn cost_for_returns_zero_on_unit_mismatch() {
     20     let price = RadrootsCoreQuantityPrice::new(
     21         common::money("10", "USD"),
     22         common::qty("1", RadrootsCoreUnit::MassKg),
     23     );
     24     let cost = price.cost_for(&common::qty("1", RadrootsCoreUnit::Each));
     25     assert!(cost.amount.is_zero());
     26 }
     27 
     28 #[test]
     29 fn cost_for_rounded_and_quantized_price_differ() {
     30     let price = RadrootsCoreQuantityPrice::new(
     31         common::money("1.005", "USD"),
     32         common::qty("1", RadrootsCoreUnit::Each),
     33     );
     34     let qty = common::qty("2", RadrootsCoreUnit::Each);
     35     let rounded = price.cost_for_rounded(&qty);
     36     let quantized = price.cost_for_with_quantized_price(&qty);
     37 
     38     assert_eq!(rounded.amount, common::dec("2.01"));
     39     assert_eq!(quantized.amount, common::dec("2.02"));
     40 }
     41 
     42 #[test]
     43 fn try_cost_for_validates_quantity_and_units() {
     44     let price = RadrootsCoreQuantityPrice::new(
     45         common::money("10", "USD"),
     46         common::qty("1", RadrootsCoreUnit::Each),
     47     );
     48     let zero_price = RadrootsCoreQuantityPrice::new(
     49         common::money("10", "USD"),
     50         common::qty("0", RadrootsCoreUnit::Each),
     51     );
     52 
     53     assert_eq!(
     54         zero_price.try_cost_for(&common::qty("1", RadrootsCoreUnit::Each)),
     55         Err(RadrootsCoreQuantityPriceError::PerQuantityZero)
     56     );
     57     assert_eq!(
     58         price.try_cost_for(&common::qty("1", RadrootsCoreUnit::MassKg)),
     59         Err(RadrootsCoreQuantityPriceError::UnitMismatch {
     60             have: RadrootsCoreUnit::MassKg,
     61             want: RadrootsCoreUnit::Each
     62         })
     63     );
     64 }
     65 
     66 #[test]
     67 fn try_cost_for_rounded_error_path_is_exercised() {
     68     let price = RadrootsCoreQuantityPrice::new(
     69         common::money("10", "USD"),
     70         common::qty("1", RadrootsCoreUnit::Each),
     71     );
     72     assert_eq!(
     73         price.try_cost_for_rounded(&common::qty("1", RadrootsCoreUnit::MassKg)),
     74         Err(RadrootsCoreQuantityPriceError::UnitMismatch {
     75             have: RadrootsCoreUnit::MassKg,
     76             want: RadrootsCoreUnit::Each
     77         })
     78     );
     79 }
     80 
     81 #[test]
     82 fn try_cost_for_amount_in_converts_mass_units() {
     83     let price = RadrootsCoreQuantityPrice::new(
     84         common::money("10", "USD"),
     85         common::qty("1", RadrootsCoreUnit::MassKg),
     86     );
     87     let cost = price
     88         .try_cost_for_amount_in(common::dec("500"), RadrootsCoreUnit::MassG)
     89         .unwrap();
     90     assert_eq!(cost.amount, common::dec("5"));
     91 }
     92 
     93 #[test]
     94 fn try_cost_for_amount_in_converts_volume_units() {
     95     let price = RadrootsCoreQuantityPrice::new(
     96         common::money("10", "USD"),
     97         common::qty("1", RadrootsCoreUnit::VolumeL),
     98     );
     99     let cost = price
    100         .try_cost_for_amount_in(common::dec("500"), RadrootsCoreUnit::VolumeMl)
    101         .unwrap();
    102     assert_eq!(cost.amount, common::dec("5"));
    103 }
    104 
    105 #[test]
    106 fn try_cost_for_amount_in_rejects_non_convertible_units() {
    107     let price = RadrootsCoreQuantityPrice::new(
    108         common::money("10", "USD"),
    109         common::qty("1", RadrootsCoreUnit::MassKg),
    110     );
    111     assert_eq!(
    112         price.try_cost_for_amount_in(common::dec("1"), RadrootsCoreUnit::Each),
    113         Err(RadrootsCoreQuantityPriceError::NonConvertibleUnits {
    114             from: RadrootsCoreUnit::Each,
    115             to: RadrootsCoreUnit::MassKg
    116         })
    117     );
    118 }
    119 
    120 #[test]
    121 fn try_cost_for_amount_in_same_unit_path_is_exercised() {
    122     let price = RadrootsCoreQuantityPrice::new(
    123         common::money("4", "USD"),
    124         common::qty("1", RadrootsCoreUnit::Each),
    125     );
    126     let out = price
    127         .try_cost_for_amount_in(common::dec("3"), RadrootsCoreUnit::Each)
    128         .unwrap();
    129     assert_eq!(out.amount, common::dec("12"));
    130 }
    131 
    132 #[test]
    133 fn try_cost_for_quantity_in_path_is_exercised() {
    134     let price = RadrootsCoreQuantityPrice::new(
    135         common::money("10", "USD"),
    136         common::qty("1", RadrootsCoreUnit::MassKg),
    137     );
    138     let qty = common::qty("250", RadrootsCoreUnit::MassG);
    139     let out = price.try_cost_for_quantity_in(&qty).unwrap();
    140     assert_eq!(out.amount, common::dec("2.5"));
    141 }
    142 
    143 #[test]
    144 fn try_to_unit_price_error_and_same_unit_paths_are_exercised() {
    145     let zero = RadrootsCoreQuantityPrice::new(
    146         common::money("10", "USD"),
    147         common::qty("0", RadrootsCoreUnit::MassKg),
    148     );
    149     assert_eq!(
    150         zero.try_to_unit_price(RadrootsCoreUnit::MassG),
    151         Err(RadrootsCoreQuantityPriceError::PerQuantityZero)
    152     );
    153 
    154     let base = RadrootsCoreQuantityPrice::new(
    155         common::money("5", "USD"),
    156         common::qty("2", RadrootsCoreUnit::MassKg),
    157     );
    158     let same = base.try_to_unit_price(RadrootsCoreUnit::MassKg).unwrap();
    159     assert_eq!(same.quantity.unit, RadrootsCoreUnit::MassKg);
    160     assert_eq!(same.quantity.amount, common::dec("1"));
    161     assert_eq!(same.amount.amount, common::dec("2.5"));
    162 
    163     let err = base
    164         .try_to_unit_price(RadrootsCoreUnit::VolumeMl)
    165         .unwrap_err();
    166     assert_eq!(
    167         err,
    168         RadrootsCoreQuantityPriceError::NonConvertibleUnits {
    169             from: RadrootsCoreUnit::MassKg,
    170             to: RadrootsCoreUnit::VolumeMl
    171         }
    172     );
    173 }
    174 
    175 #[test]
    176 fn cost_for_and_quantized_price_zero_paths_are_exercised() {
    177     let p = RadrootsCoreQuantityPrice::new(
    178         common::money("3.33", "USD"),
    179         common::qty("1", RadrootsCoreUnit::Each),
    180     );
    181     let zero_qty = common::qty("0", RadrootsCoreUnit::Each);
    182     assert!(p.cost_for(&zero_qty).amount.is_zero());
    183     assert!(p.cost_for_with_quantized_price(&zero_qty).amount.is_zero());
    184 
    185     let zero_per = RadrootsCoreQuantityPrice::new(
    186         common::money("3.33", "USD"),
    187         common::qty("0", RadrootsCoreUnit::Each),
    188     );
    189     assert!(
    190         zero_per
    191             .cost_for(&common::qty("1", RadrootsCoreUnit::Each))
    192             .amount
    193             .is_zero()
    194     );
    195     assert!(
    196         zero_per
    197             .cost_for_with_quantized_price(&common::qty("1", RadrootsCoreUnit::Each))
    198             .amount
    199             .is_zero()
    200     );
    201 
    202     let mismatch_qty = common::qty("1", RadrootsCoreUnit::MassG);
    203     assert!(
    204         p.cost_for_with_quantized_price(&mismatch_qty)
    205             .amount
    206             .is_zero()
    207     );
    208 }
    209 
    210 #[test]
    211 fn try_to_unit_price_detects_underflow_to_zero_normalized_amount() {
    212     let tiny = RadrootsCoreQuantityPrice::new(
    213         common::money("1", "USD"),
    214         common::qty("0.0000000000000000000000000001", RadrootsCoreUnit::VolumeMl),
    215     );
    216     let err = tiny
    217         .try_to_unit_price(RadrootsCoreUnit::VolumeL)
    218         .unwrap_err();
    219     assert_eq!(err, RadrootsCoreQuantityPriceError::PerQuantityZero);
    220 }
    221 
    222 #[test]
    223 fn try_to_canonical_unit_price_converts_units() {
    224     let price = RadrootsCoreQuantityPrice::new(
    225         common::money("6.99", "USD"),
    226         common::qty("1", RadrootsCoreUnit::MassLb),
    227     );
    228     let canonical = price.try_to_canonical_unit_price().unwrap();
    229     assert_eq!(canonical.quantity.unit, RadrootsCoreUnit::MassG);
    230     assert_eq!(canonical.quantity.amount, common::dec("1"));
    231     let expected = common::dec("6.99") / common::dec("453.59237");
    232     assert_eq!(canonical.amount.amount, expected);
    233 }
    234 
    235 #[test]
    236 fn is_price_per_canonical_unit_detects_canonical() {
    237     let price = RadrootsCoreQuantityPrice::new(
    238         common::money("1.00", "USD"),
    239         common::qty("1", RadrootsCoreUnit::MassG),
    240     );
    241     assert!(price.is_price_per_canonical_unit());
    242 
    243     let price = RadrootsCoreQuantityPrice::new(
    244         common::money("1.00", "USD"),
    245         common::qty("1", RadrootsCoreUnit::MassKg),
    246     );
    247     assert!(!price.is_price_per_canonical_unit());
    248 }