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:
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"
+ );
+}