lib

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

commit b9dcb9b291d2ae61d0c49b45130026f2d5a1ba82
parent cd6fd08a5a557bf0744ab4e75bf956ae642fb8ce
Author: triesap <tyson@radroots.org>
Date:   Sat, 21 Feb 2026 17:58:31 +0000

core: add error-path tests for money and percent invariants


- add money tests for invariant displays checked-sub mismatch and u32 conversion paths
- add money tests for jpy-kwd exponent paths display and operator impl coverage
- add percent tests for display and parse error display behavior
- run cargo check -q -p `radroots-core` cargo test -q -p `radroots-core` and core coverage remeasure

Diffstat:
Mcrates/core/tests/money.rs | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/core/tests/percent.rs | 10++++++++++
2 files changed, 89 insertions(+), 0 deletions(-)

diff --git a/crates/core/tests/money.rs b/crates/core/tests/money.rs @@ -21,6 +21,14 @@ fn ensure_non_negative_rejects_negative_amount() { } #[test] +fn ensure_non_negative_accepts_zero_and_positive() { + let zero = RadrootsCoreMoney::new(common::dec("0"), RadrootsCoreCurrency::USD); + let pos = RadrootsCoreMoney::new(common::dec("1"), RadrootsCoreCurrency::USD); + assert_eq!(zero.ensure_non_negative(), Ok(())); + assert_eq!(pos.ensure_non_negative(), Ok(())); +} + +#[test] fn quantize_to_currency_rounds_midpoint_away_from_zero() { let usd = RadrootsCoreCurrency::USD; let a = RadrootsCoreMoney::new(common::dec("1.234"), usd).quantize_to_currency(); @@ -60,6 +68,18 @@ fn checked_add_and_sub_require_currency_match() { } #[test] +fn checked_sub_mismatch_returns_currency_error() { + let usd = RadrootsCoreCurrency::USD; + let eur = RadrootsCoreCurrency::EUR; + let a = RadrootsCoreMoney::new(common::dec("1.00"), usd); + let b = RadrootsCoreMoney::new(common::dec("2.00"), eur); + assert_eq!( + a.checked_sub(&b), + Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch) + ); +} + +#[test] fn minor_units_exact_and_rounded() { let usd = RadrootsCoreCurrency::USD; let exact = RadrootsCoreMoney::new(common::dec("1.23"), usd); @@ -80,6 +100,20 @@ fn minor_units_exact_and_rounded() { } #[test] +fn minor_units_cover_additional_currency_exponents() { + let jpy = RadrootsCoreMoney::new(common::dec("123"), common::currency("JPY")); + assert_eq!(jpy.to_minor_units_u64_exact().unwrap(), 123); + assert_eq!( + jpy.to_minor_units_u64_rounded(RoundingStrategy::MidpointAwayFromZero) + .unwrap(), + 123 + ); + + let kwd = RadrootsCoreMoney::new(common::dec("1.234"), common::currency("KWD")); + assert_eq!(kwd.to_minor_units_u64_exact().unwrap(), 1234); +} + +#[test] fn minor_units_u32_overflow_is_detected() { let usd = RadrootsCoreCurrency::USD; let too_large = RadrootsCoreMoney::from_minor_units_u64(u64::from(u32::MAX) + 1, usd); @@ -90,8 +124,53 @@ fn minor_units_u32_overflow_is_detected() { } #[test] +fn from_minor_units_u32_and_u32_rounded_paths_are_exercised() { + let usd = RadrootsCoreCurrency::USD; + let from_u32 = RadrootsCoreMoney::from_minor_units_u32(505, usd); + assert_eq!(from_u32.amount, common::dec("5.05")); + assert_eq!( + from_u32 + .to_minor_units_u32_rounded(RoundingStrategy::MidpointAwayFromZero) + .unwrap(), + 505 + ); +} + +#[test] fn from_minor_units_roundtrips() { let usd = RadrootsCoreCurrency::USD; let money = RadrootsCoreMoney::from_minor_units_u64(12345, usd); assert_eq!(money.to_minor_units_u64_exact().unwrap(), 12345); } + +#[test] +fn display_and_operator_impl_paths_are_exercised() { + let usd = RadrootsCoreCurrency::USD; + let m = RadrootsCoreMoney::new(common::dec("10"), usd); + assert_eq!(m.to_string(), "10 USD"); + + let times = m.clone() * common::dec("2"); + assert_eq!(times.amount, common::dec("20")); + let divided = m / common::dec("4"); + assert_eq!(divided.amount, common::dec("2.5")); +} + +#[test] +fn invariant_error_display_variants_are_exercised() { + assert_eq!( + RadrootsCoreMoneyInvariantError::NegativeAmount.to_string(), + "money amount must be ≥ 0" + ); + assert_eq!( + RadrootsCoreMoneyInvariantError::NotWholeMinorUnits.to_string(), + "money not a whole number of minor units" + ); + assert_eq!( + RadrootsCoreMoneyInvariantError::AmountOverflow.to_string(), + "money minor-unit conversion overflow" + ); + assert_eq!( + RadrootsCoreMoneyInvariantError::CurrencyMismatch.to_string(), + "money currency mismatch" + ); +} diff --git a/crates/core/tests/percent.rs b/crates/core/tests/percent.rs @@ -37,3 +37,13 @@ fn of_money_and_quantized() { let rounded = pct.of_money_quantized(&tiny); assert_eq!(rounded.amount, common::dec("0.01")); } + +#[test] +fn display_and_parse_error_display_paths_are_exercised() { + let pct = RadrootsCorePercent::from_str("12.5%").unwrap(); + assert_eq!(pct.to_string(), "12.5%"); + assert_eq!( + RadrootsCorePercentParseError::InvalidNumber.to_string(), + "invalid percent string" + ); +}