unit.rs (8729B)
1 mod common; 2 3 use core::str::FromStr; 4 5 use radroots_core::{ 6 RadrootsCoreUnit, RadrootsCoreUnitConvertError, RadrootsCoreUnitParseError, 7 convert_mass_decimal, convert_unit_decimal, convert_volume_decimal, parse_mass_unit, 8 parse_volume_unit, 9 }; 10 11 #[test] 12 fn parses_units_and_synonyms() { 13 use RadrootsCoreUnit::*; 14 let cases = [ 15 ("each", Each), 16 ("ea", Each), 17 ("count", Each), 18 ("kg", MassKg), 19 ("kilograms", MassKg), 20 ("g", MassG), 21 ("grams", MassG), 22 ("oz", MassOz), 23 ("ounces", MassOz), 24 ("lb", MassLb), 25 ("pounds", MassLb), 26 ("l", VolumeL), 27 ("liters", VolumeL), 28 ("ml", VolumeMl), 29 ("milliliters", VolumeMl), 30 ]; 31 for (input, expected) in cases { 32 assert_eq!(RadrootsCoreUnit::from_str(input).unwrap(), expected); 33 } 34 } 35 36 #[test] 37 fn rejects_unknown_units() { 38 assert_eq!( 39 RadrootsCoreUnit::from_str("unknown"), 40 Err(RadrootsCoreUnitParseError::UnknownUnit) 41 ); 42 } 43 44 #[test] 45 fn same_dimension_matches_mass_and_volume_groups() { 46 use RadrootsCoreUnit::*; 47 assert!(RadrootsCoreUnit::same_dimension(MassKg, MassG)); 48 assert!(RadrootsCoreUnit::same_dimension(VolumeL, VolumeMl)); 49 assert!(RadrootsCoreUnit::same_dimension(Each, Each)); 50 assert!(!RadrootsCoreUnit::same_dimension(MassKg, VolumeL)); 51 assert!(!RadrootsCoreUnit::same_dimension(Each, MassG)); 52 } 53 54 #[test] 55 fn canonical_unit_maps_dimensions() { 56 use RadrootsCoreUnit::*; 57 assert_eq!(Each.canonical_unit(), Each); 58 assert_eq!(MassKg.canonical_unit(), MassG); 59 assert_eq!(MassLb.canonical_unit(), MassG); 60 assert_eq!(VolumeL.canonical_unit(), VolumeMl); 61 } 62 63 #[test] 64 fn code_and_predicate_helpers_cover_all_variants() { 65 use RadrootsCoreUnit::*; 66 assert_eq!(Each.code(), "each"); 67 assert_eq!(MassKg.code(), "kg"); 68 assert_eq!(MassG.code(), "g"); 69 assert_eq!(MassOz.code(), "oz"); 70 assert_eq!(MassLb.code(), "lb"); 71 assert_eq!(VolumeL.code(), "l"); 72 assert_eq!(VolumeMl.code(), "ml"); 73 74 assert!(Each.is_count()); 75 assert!(!Each.is_mass()); 76 assert!(!Each.is_volume()); 77 assert!(MassLb.is_mass()); 78 assert!(!MassLb.is_count()); 79 assert!(VolumeMl.is_volume()); 80 } 81 82 #[test] 83 fn parse_mass_unit_enforces_mass_only() { 84 assert_eq!(parse_mass_unit("kg"), Ok(RadrootsCoreUnit::MassKg)); 85 assert_eq!( 86 parse_mass_unit("each"), 87 Err(RadrootsCoreUnitParseError::NotAMassUnit) 88 ); 89 assert_eq!( 90 parse_mass_unit("bogus"), 91 Err(RadrootsCoreUnitParseError::UnknownUnit) 92 ); 93 } 94 95 #[test] 96 fn parse_volume_unit_enforces_volume_only() { 97 assert_eq!(parse_volume_unit("l"), Ok(RadrootsCoreUnit::VolumeL)); 98 assert_eq!( 99 parse_volume_unit("kg"), 100 Err(RadrootsCoreUnitParseError::NotAVolumeUnit) 101 ); 102 assert_eq!( 103 parse_volume_unit("bogus"), 104 Err(RadrootsCoreUnitParseError::UnknownUnit) 105 ); 106 } 107 108 #[test] 109 fn convert_mass_decimal_converts_between_mass_units() { 110 use RadrootsCoreUnit::*; 111 let kg_to_g = convert_mass_decimal(common::dec("1"), MassKg, MassG).unwrap(); 112 let g_to_kg = convert_mass_decimal(common::dec("1000"), MassG, MassKg).unwrap(); 113 let lb_to_g = convert_mass_decimal(common::dec("1"), MassLb, MassG).unwrap(); 114 let oz_to_g = convert_mass_decimal(common::dec("1"), MassOz, MassG).unwrap(); 115 let g_to_oz = convert_mass_decimal(common::dec("28.349523125"), MassG, MassOz).unwrap(); 116 let g_to_lb = convert_mass_decimal(common::dec("453.59237"), MassG, MassLb).unwrap(); 117 118 assert_eq!(kg_to_g, common::dec("1000")); 119 assert_eq!(g_to_kg, common::dec("1")); 120 assert_eq!(lb_to_g, common::dec("453.59237")); 121 assert_eq!(oz_to_g, common::dec("28.349523125")); 122 assert_eq!(g_to_oz, common::dec("1")); 123 assert_eq!(g_to_lb, common::dec("1")); 124 } 125 126 #[test] 127 fn convert_mass_decimal_rejects_non_mass_units() { 128 let err = convert_mass_decimal( 129 common::dec("1"), 130 RadrootsCoreUnit::Each, 131 RadrootsCoreUnit::MassG, 132 ) 133 .unwrap_err(); 134 assert_eq!( 135 err, 136 RadrootsCoreUnitConvertError::NotMassUnit { 137 from: RadrootsCoreUnit::Each, 138 to: RadrootsCoreUnit::MassG 139 } 140 ); 141 142 let err = convert_mass_decimal( 143 common::dec("1"), 144 RadrootsCoreUnit::MassKg, 145 RadrootsCoreUnit::Each, 146 ) 147 .unwrap_err(); 148 assert_eq!( 149 err, 150 RadrootsCoreUnitConvertError::NotMassUnit { 151 from: RadrootsCoreUnit::MassKg, 152 to: RadrootsCoreUnit::Each 153 } 154 ); 155 } 156 157 #[test] 158 fn convert_volume_decimal_converts_between_volume_units() { 159 use RadrootsCoreUnit::*; 160 let l_to_ml = convert_volume_decimal(common::dec("1"), VolumeL, VolumeMl).unwrap(); 161 let ml_to_l = convert_volume_decimal(common::dec("250"), VolumeMl, VolumeL).unwrap(); 162 assert_eq!(l_to_ml, common::dec("1000")); 163 assert_eq!(ml_to_l, common::dec("0.25")); 164 } 165 166 #[test] 167 fn convert_volume_decimal_rejects_non_volume_units() { 168 let err = convert_volume_decimal( 169 common::dec("1"), 170 RadrootsCoreUnit::Each, 171 RadrootsCoreUnit::VolumeMl, 172 ) 173 .unwrap_err(); 174 assert_eq!( 175 err, 176 RadrootsCoreUnitConvertError::NotVolumeUnit { 177 from: RadrootsCoreUnit::Each, 178 to: RadrootsCoreUnit::VolumeMl 179 } 180 ); 181 182 let err = convert_volume_decimal( 183 common::dec("1"), 184 RadrootsCoreUnit::VolumeMl, 185 RadrootsCoreUnit::Each, 186 ) 187 .unwrap_err(); 188 assert_eq!( 189 err, 190 RadrootsCoreUnitConvertError::NotVolumeUnit { 191 from: RadrootsCoreUnit::VolumeMl, 192 to: RadrootsCoreUnit::Each 193 } 194 ); 195 } 196 197 #[test] 198 fn convert_unit_decimal_converts_matching_dimensions() { 199 use RadrootsCoreUnit::*; 200 let kg_to_g = convert_unit_decimal(common::dec("1"), MassKg, MassG).unwrap(); 201 let l_to_ml = convert_unit_decimal(common::dec("2"), VolumeL, VolumeMl).unwrap(); 202 let each_to_each = convert_unit_decimal(common::dec("3"), Each, Each).unwrap(); 203 assert_eq!(kg_to_g, common::dec("1000")); 204 assert_eq!(l_to_ml, common::dec("2000")); 205 assert_eq!(each_to_each, common::dec("3")); 206 } 207 208 #[test] 209 fn display_paths_for_unit_and_errors_are_exercised() { 210 assert_eq!(RadrootsCoreUnit::MassOz.to_string(), "oz"); 211 assert_eq!( 212 RadrootsCoreUnitParseError::UnknownUnit.to_string(), 213 "unknown unit string" 214 ); 215 assert_eq!( 216 RadrootsCoreUnitParseError::NotAMassUnit.to_string(), 217 "unit is not a mass unit" 218 ); 219 assert_eq!( 220 RadrootsCoreUnitParseError::NotAVolumeUnit.to_string(), 221 "unit is not a volume unit" 222 ); 223 assert_eq!( 224 RadrootsCoreUnitConvertError::NotMassUnit { 225 from: RadrootsCoreUnit::Each, 226 to: RadrootsCoreUnit::MassG 227 } 228 .to_string(), 229 "unit conversion requires mass units: each -> g" 230 ); 231 assert_eq!( 232 RadrootsCoreUnitConvertError::NotVolumeUnit { 233 from: RadrootsCoreUnit::Each, 234 to: RadrootsCoreUnit::VolumeL 235 } 236 .to_string(), 237 "unit conversion requires volume units: each -> l" 238 ); 239 assert_eq!( 240 RadrootsCoreUnitConvertError::NotConvertibleUnits { 241 from: RadrootsCoreUnit::Each, 242 to: RadrootsCoreUnit::MassG 243 } 244 .to_string(), 245 "unit conversion requires matching dimensions: each -> g" 246 ); 247 } 248 249 #[test] 250 fn convert_unit_decimal_rejects_mismatched_dimensions() { 251 let err = convert_unit_decimal( 252 common::dec("1"), 253 RadrootsCoreUnit::Each, 254 RadrootsCoreUnit::MassG, 255 ) 256 .unwrap_err(); 257 assert_eq!( 258 err, 259 RadrootsCoreUnitConvertError::NotConvertibleUnits { 260 from: RadrootsCoreUnit::Each, 261 to: RadrootsCoreUnit::MassG 262 } 263 ); 264 } 265 266 #[cfg(feature = "serde")] 267 #[test] 268 fn serde_roundtrip_for_unit_paths() { 269 use RadrootsCoreUnit::*; 270 let all = [Each, MassKg, MassG, MassOz, MassLb, VolumeL, VolumeMl]; 271 for unit in all { 272 let json = serde_json::to_string(&unit).unwrap(); 273 let back: RadrootsCoreUnit = serde_json::from_str(&json).unwrap(); 274 assert_eq!(back, unit); 275 } 276 let back: RadrootsCoreUnit = serde_json::from_str("\"kg\"").unwrap(); 277 assert_eq!(back, RadrootsCoreUnit::MassKg); 278 let unknown_err = serde_json::from_str::<RadrootsCoreUnit>("\"bogus\"").unwrap_err(); 279 assert!(unknown_err.to_string().contains("unknown unit")); 280 let err = serde_json::from_str::<RadrootsCoreUnit>("123").unwrap_err(); 281 assert!(err.to_string().contains("invalid type")); 282 let missing_err = serde_json::from_str::<RadrootsCoreUnit>("{}").unwrap_err(); 283 assert!(!missing_err.to_string().is_empty()); 284 }