lib

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

commit 6bab64bdbb98ddcde1b66b8cc7a45b9800d2973b
parent bbab2a137b050748b66055c067cd785977d30090
Author: triesap <tyson@radroots.org>
Date:   Thu,  5 Mar 2026 02:53:27 +0000

trade: close strict coverage gaps in listing parsers

- align codec reject-path assertions with parser error tags
- refactor display-tag and image-size parsing to remove hidden region misses
- consolidate dvm envelope serialization tests to cover both serde_json paths in one instantiation
- run radroots-trade check, tests, and strict xtask coverage gate at 100/100/100/100

Diffstat:
Mcrates/trade/src/listing/codec.rs | 364++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/trade/src/listing/dvm.rs | 136++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mcrates/trade/src/listing/price_ext.rs | 29+++++++++++++++++++++++++++++
Mcrates/trade/src/listing/validation.rs | 22+++++++++-------------
4 files changed, 461 insertions(+), 90 deletions(-)

diff --git a/crates/trade/src/listing/codec.rs b/crates/trade/src/listing/codec.rs @@ -325,19 +325,23 @@ fn listing_from_tags( } bin.quantity = Some(RadrootsCoreQuantity::new(amount, unit)); - if tag.len() >= 5 { - if tag.len() < 6 { + match tag.as_slice() { + [_, _, _, _, display_amount_raw, display_unit_raw] + | [_, _, _, _, display_amount_raw, display_unit_raw, _] => { + let display_amount = parse_decimal(display_amount_raw, TAG_RADROOTS_BIN)?; + let display_unit = parse_unit(display_unit_raw)?; + bin.display_amount = Some(display_amount); + bin.display_unit = Some(display_unit); + if let [_, _, _, _, _, _, label] = tag.as_slice() { + bin.display_label = clean_value(label); + } + } + [_, _, _, _, _] => { return Err(TradeListingParseError::InvalidTag( TAG_RADROOTS_BIN.to_string(), )); } - let display_amount = parse_decimal(&tag[4], TAG_RADROOTS_BIN)?; - let display_unit = parse_unit(&tag[5])?; - bin.display_amount = Some(display_amount); - bin.display_unit = Some(display_unit); - if tag.len() == 7 { - bin.display_label = clean_value(&tag[6]); - } + _ => {} } } TAG_RADROOTS_PRICE => { @@ -375,16 +379,19 @@ fn listing_from_tags( } bin.price_per_canonical_unit = Some(price_per_canonical_unit); - if tag.len() == 7 { - return Err(TradeListingParseError::InvalidTag( - TAG_RADROOTS_PRICE.to_string(), - )); - } - if tag.len() == 8 { - let display_price = parse_decimal(&tag[6], TAG_RADROOTS_PRICE)?; - let display_unit = parse_unit(&tag[7])?; - bin.display_price = Some(RadrootsCoreMoney::new(display_price, currency)); - bin.display_price_unit = Some(display_unit); + match tag.as_slice() { + [_, _, _, _, _, _, _] => { + return Err(TradeListingParseError::InvalidTag( + TAG_RADROOTS_PRICE.to_string(), + )); + } + [_, _, _, _, _, _, display_price_raw, display_unit_raw] => { + let display_price = parse_decimal(display_price_raw, TAG_RADROOTS_PRICE)?; + let display_unit = parse_unit(display_unit_raw)?; + bin.display_price = Some(RadrootsCoreMoney::new(display_price, currency)); + bin.display_price_unit = Some(display_unit); + } + _ => {} } } TAG_RADROOTS_DISCOUNT => { @@ -655,9 +662,7 @@ mod tests { RadrootsCoreDiscountThreshold, RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; - use radroots_events::listing::{ - RadrootsListing, RadrootsListingFarmRef, RadrootsListingStatus, - }; + use radroots_events::listing::{RadrootsListing, RadrootsListingFarmRef}; fn farm_ref() -> RadrootsListingFarmRef { RadrootsListingFarmRef { @@ -1021,6 +1026,27 @@ mod tests { } #[test] + fn listing_from_event_parts_rejects_missing_reference_tags() { + let mut missing_farm_ref = base_event_tags(); + missing_farm_ref.extend(base_trade_tags()); + missing_farm_ref.retain(|tag| tag.first().map(|v| v.as_str()) != Some(TAG_A)); + let err = listing_from_event_parts(&missing_farm_ref, "").unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_A.to_string()); + + let mut missing_farm_pubkey = base_event_tags(); + missing_farm_pubkey.extend(base_trade_tags()); + missing_farm_pubkey.retain(|tag| tag.first().map(|v| v.as_str()) != Some(TAG_P)); + let err = listing_from_event_parts(&missing_farm_pubkey, "").unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_P.to_string()); + + let mut invalid_resource_area = base_event_tags(); + invalid_resource_area.extend(base_trade_tags()); + invalid_resource_area.push(vec![TAG_RADROOTS_RESOURCE_AREA.into(), "bad".into()]); + let err = listing_from_event_parts(&invalid_resource_area, "").unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_RESOURCE_AREA.to_string()); + } + + #[test] fn listing_tags_build_and_error_mapping_cover_paths() { let listing = parse_base_listing_from_tags(); let built = listing_tags_build(&listing).expect("build tags"); @@ -1045,6 +1071,10 @@ mod tests { let mut tags = base_trade_tags(); tags.push(Vec::new()); tags.push(vec![TAG_PRICE.into(), "ignored".into()]); + tags.push(vec!["process".into(), "washed".into()]); + tags.push(vec!["lot".into(), "lot-7".into()]); + tags.push(vec!["profile".into(), "fruity".into()]); + tags.push(vec!["year".into(), "2024".into()]); tags.push(vec![TAG_RADROOTS_PRIMARY_BIN.into(), "bin-1".into()]); tags.push(vec![ TAG_LOCATION.into(), @@ -1101,6 +1131,10 @@ mod tests { listing.location.as_ref().unwrap().geohash.as_deref(), Some("u6se") ); + assert_eq!(listing.product.process.as_deref(), Some("washed")); + assert_eq!(listing.product.lot.as_deref(), Some("lot-7")); + assert_eq!(listing.product.profile.as_deref(), Some("fruity")); + assert_eq!(listing.product.year.as_deref(), Some("2024")); assert_eq!(listing.images.as_ref().unwrap().len(), 1); assert_eq!(listing.discounts.as_ref().unwrap().len(), 1); } @@ -1130,6 +1164,38 @@ mod tests { } #[test] + fn listing_from_tags_parses_delivery_enum_variants() { + let mut local_delivery = base_trade_tags(); + local_delivery.push(vec![TAG_DELIVERY.into(), "local_delivery".into()]); + let listing = listing_from_tags( + &local_delivery, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .expect("local delivery parse"); + assert_eq!( + format!("{:?}", listing.delivery_method), + "Some(LocalDelivery)" + ); + + let mut shipping = base_trade_tags(); + shipping.push(vec![TAG_DELIVERY.into(), "shipping".into()]); + let listing = listing_from_tags( + &shipping, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .expect("shipping parse"); + assert_eq!(format!("{:?}", listing.delivery_method), "Some(Shipping)"); + } + + #[test] fn listing_from_tags_rejects_empty_structured_location_primary() { let mut tags = base_trade_tags(); tags.push(vec![ @@ -1522,6 +1588,219 @@ mod tests { ) .unwrap_err(); assert_eq!(parse_error_tag(err), TAG_IMAGE.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![TAG_INVENTORY.into(), "bad".into()]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_INVENTORY.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_BIN.into(), + "bin-1".into(), + "bad".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_BIN.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_BIN.into(), + "bin-1".into(), + "500".into(), + "bad".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), "unit".to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_BIN.into(), + "bin-2".into(), + "500".into(), + "g".into(), + "bad".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_BIN.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_BIN.into(), + "bin-2".into(), + "500".into(), + "g".into(), + "1".into(), + "bad".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), "unit".to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-1".into(), + "bad".into(), + "USD".into(), + "1".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_PRICE.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-1".into(), + "10".into(), + "US".into(), + "1".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), "currency".to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-1".into(), + "10".into(), + "USD".into(), + "bad".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_PRICE.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-1".into(), + "10".into(), + "USD".into(), + "1".into(), + "bad".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), "unit".to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-2".into(), + "10".into(), + "USD".into(), + "1".into(), + "g".into(), + "bad".into(), + "g".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_PRICE.to_string()); + + let mut tags = base_trade_tags(); + tags.push(vec![ + TAG_RADROOTS_PRICE.into(), + "bin-2".into(), + "10".into(), + "USD".into(), + "1".into(), + "g".into(), + "12".into(), + "bad".into(), + ]); + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), "unit".to_string()); } #[test] @@ -1635,6 +1914,32 @@ mod tests { } #[test] + fn listing_from_tags_rejects_incomplete_bin_draft() { + let tags = vec![ + vec!["key".into(), "coffee".into()], + vec!["title".into(), "Coffee".into()], + vec!["category".into(), "coffee".into()], + vec![TAG_RADROOTS_PRIMARY_BIN.into(), "bin-1".into()], + vec![ + TAG_RADROOTS_BIN.into(), + "bin-1".into(), + "500".into(), + "g".into(), + ], + ]; + let err = listing_from_tags( + &tags, + listing_d_tag(), + farm_ref(), + "seller".to_string(), + None, + None, + ) + .unwrap_err(); + assert_eq!(parse_error_tag(err), TAG_RADROOTS_PRICE.to_string()); + } + + #[test] fn parse_farm_and_reference_helpers_cover_all_paths() { let valid_farm_tags = vec![vec![ TAG_A.into(), @@ -1865,18 +2170,17 @@ mod tests { set_optional(&mut opt_none, Some(&blank)); assert_eq!(opt_none, None); - assert!(matches!( - parse_status("ACTIVE"), - RadrootsListingStatus::Active - )); - assert!(matches!(parse_status("sold"), RadrootsListingStatus::Sold)); + assert_eq!(format!("{:?}", parse_status("ACTIVE")), "Active"); + assert_eq!(format!("{:?}", parse_status("sold")), "Sold"); assert_eq!( format!("{:?}", parse_status("queued")), "Other { value: \"queued\" }" ); assert_eq!(parse_image_size("100x200").unwrap().w, 100); + assert!(parse_image_size("100").is_none()); assert!(parse_image_size("invalid").is_none()); + assert!(parse_image_size("badx100").is_none()); assert!(parse_image_size("100xbad").is_none()); } @@ -2050,9 +2354,9 @@ fn parse_status(value: &str) -> RadrootsListingStatus { } fn parse_image_size(value: &str) -> Option<RadrootsListingImageSize> { - let mut parts = value.split('x'); - let w = parts.next()?.parse::<u32>().ok()?; - let h = parts.next()?.parse::<u32>().ok()?; + let (w_raw, h_raw) = value.split_once('x')?; + let w = w_raw.parse::<u32>().ok()?; + let h = h_raw.parse::<u32>().ok()?; Some(RadrootsListingImageSize { w, h }) } diff --git a/crates/trade/src/listing/dvm.rs b/crates/trade/src/listing/dvm.rs @@ -241,23 +241,20 @@ pub struct TradeListingAddress { impl TradeListingAddress { pub fn parse(addr: &str) -> Result<Self, TradeListingAddressError> { - let mut parts = addr.split(':'); - let kind = parts - .next() - .ok_or(TradeListingAddressError::InvalidFormat)? - .parse::<u16>() - .map_err(|_| TradeListingAddressError::InvalidFormat)?; - let seller_pubkey = parts - .next() - .ok_or(TradeListingAddressError::InvalidFormat)? - .to_string(); - let listing_id = parts - .next() - .ok_or(TradeListingAddressError::InvalidFormat)? - .to_string(); - if parts.next().is_some() { + let (kind_raw, seller_and_listing) = addr + .split_once(':') + .ok_or(TradeListingAddressError::InvalidFormat)?; + let (seller_pubkey_raw, listing_id_raw) = seller_and_listing + .split_once(':') + .ok_or(TradeListingAddressError::InvalidFormat)?; + if listing_id_raw.contains(':') { return Err(TradeListingAddressError::InvalidFormat); } + let kind = kind_raw + .parse::<u16>() + .map_err(|_| TradeListingAddressError::InvalidFormat)?; + let seller_pubkey = seller_pubkey_raw.to_string(); + let listing_id = listing_id_raw.to_string(); if kind == KIND_PROFILE as u16 || seller_pubkey.trim().is_empty() || listing_id.trim().is_empty() @@ -456,30 +453,29 @@ mod tests { #[test] fn message_type_kind_and_request_flags_cover_all_variants() { let expected_kinds = crate::listing::kinds::TRADE_LISTING_KINDS; - let cases = [ - (TradeListingMessageType::ListingValidateRequest, true, false), - (TradeListingMessageType::ListingValidateResult, false, true), - (TradeListingMessageType::OrderRequest, true, false), - (TradeListingMessageType::OrderResponse, false, true), - (TradeListingMessageType::OrderRevision, true, false), - (TradeListingMessageType::OrderRevisionAccept, false, true), - (TradeListingMessageType::OrderRevisionDecline, false, true), - (TradeListingMessageType::Question, true, false), - (TradeListingMessageType::Answer, false, true), - (TradeListingMessageType::DiscountRequest, true, false), - (TradeListingMessageType::DiscountOffer, false, true), - (TradeListingMessageType::DiscountAccept, true, false), - (TradeListingMessageType::DiscountDecline, true, false), - (TradeListingMessageType::Cancel, true, false), - (TradeListingMessageType::FulfillmentUpdate, true, false), - (TradeListingMessageType::Receipt, true, false), - ]; - - for (message_type, is_request, is_result) in cases { - assert_eq!(message_type.is_request(), is_request); - assert_eq!(message_type.is_result(), is_result); - assert!(expected_kinds.contains(&message_type.kind())); - } + let assert_case = + |message_type: TradeListingMessageType, is_request: bool, is_result: bool| { + assert_eq!(message_type.is_request(), is_request); + assert_eq!(message_type.is_result(), is_result); + assert!(expected_kinds.contains(&message_type.kind())); + }; + + assert_case(TradeListingMessageType::ListingValidateRequest, true, false); + assert_case(TradeListingMessageType::ListingValidateResult, false, true); + assert_case(TradeListingMessageType::OrderRequest, true, false); + assert_case(TradeListingMessageType::OrderResponse, false, true); + assert_case(TradeListingMessageType::OrderRevision, true, false); + assert_case(TradeListingMessageType::OrderRevisionAccept, false, true); + assert_case(TradeListingMessageType::OrderRevisionDecline, false, true); + assert_case(TradeListingMessageType::Question, true, false); + assert_case(TradeListingMessageType::Answer, false, true); + assert_case(TradeListingMessageType::DiscountRequest, true, false); + assert_case(TradeListingMessageType::DiscountOffer, false, true); + assert_case(TradeListingMessageType::DiscountAccept, true, false); + assert_case(TradeListingMessageType::DiscountDecline, true, false); + assert_case(TradeListingMessageType::Cancel, true, false); + assert_case(TradeListingMessageType::FulfillmentUpdate, true, false); + assert_case(TradeListingMessageType::Receipt, true, false); } #[test] @@ -539,6 +535,10 @@ mod tests { TradeListingAddressError::InvalidFormat ); assert_eq!( + TradeListingAddress::parse("30340").unwrap_err(), + TradeListingAddressError::InvalidFormat + ); + assert_eq!( TradeListingAddress::parse("30340:seller").unwrap_err(), TradeListingAddressError::InvalidFormat ); @@ -573,12 +573,40 @@ mod tests { } #[cfg(feature = "serde_json")] + #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] + struct EnvelopePayload { + fail: bool, + } + + #[cfg(feature = "serde_json")] + impl EnvelopePayload { + fn ok() -> Self { + Self { fail: false } + } + + fn fail() -> Self { + Self { fail: true } + } + } + + #[cfg(feature = "serde_json")] + impl serde::Serialize for EnvelopePayload { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + if self.fail { + return Err(serde::ser::Error::custom("intentional")); + } + serializer.serialize_str("ok") + } + } + + #[cfg(feature = "serde_json")] #[test] fn envelope_event_build_includes_order_tag() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let payload = TradeListingValidateRequest { - listing_event: None, - }; + let payload = EnvelopePayload::ok(); let built = super::trade_listing_envelope_event_build( "pubkey", TradeListingMessageType::OrderRequest, @@ -590,7 +618,7 @@ mod tests { assert_eq!(built.kind, TradeListingMessageType::OrderRequest.kind()); - let envelope: TradeListingEnvelope<TradeListingValidateRequest> = + let envelope: TradeListingEnvelope<serde_json::Value> = serde_json::from_str(&built.content).unwrap(); assert_eq!(envelope.listing_addr, listing_addr.clone()); assert_eq!(envelope.order_id.as_deref(), Some("order-1")); @@ -601,9 +629,7 @@ mod tests { #[test] fn envelope_event_build_omits_order_tag_when_missing() { let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); - let payload = TradeListingValidateRequest { - listing_event: None, - }; + let payload = EnvelopePayload::ok(); let built = super::trade_listing_envelope_event_build( "pubkey", TradeListingMessageType::ListingValidateRequest, @@ -618,10 +644,26 @@ mod tests { TradeListingMessageType::ListingValidateRequest.kind() ); - let envelope: TradeListingEnvelope<TradeListingValidateRequest> = + let envelope: TradeListingEnvelope<serde_json::Value> = serde_json::from_str(&built.content).unwrap(); assert_eq!(envelope.listing_addr, listing_addr); assert!(envelope.order_id.is_none()); assert_eq!(built.tags.len(), 2); } + + #[cfg(feature = "serde_json")] + #[test] + fn envelope_event_build_propagates_payload_serialization_error() { + let listing_addr = format!("{KIND_LISTING}:pubkey:AAAAAAAAAAAAAAAAAAAAAg"); + let payload = EnvelopePayload::fail(); + let err = super::trade_listing_envelope_event_build( + "pubkey", + TradeListingMessageType::ListingValidateRequest, + listing_addr, + None, + &payload, + ) + .unwrap_err(); + assert!(err.to_string().contains("intentional")); + } } diff --git a/crates/trade/src/listing/price_ext.rs b/crates/trade/src/listing/price_ext.rs @@ -178,4 +178,33 @@ mod tests { assert_eq!(total.quantity_amount, subtotal.quantity_amount); assert_eq!(total.price_amount, subtotal.price_amount); } + + #[test] + fn try_total_for_count_propagates_subtotal_errors() { + let bin = RadrootsListingBin { + bin_id: "bin-1".into(), + quantity: RadrootsCoreQuantity::new( + RadrootsCoreDecimal::from(1u32), + RadrootsCoreUnit::MassG, + ), + price_per_canonical_unit: RadrootsCoreQuantityPrice::new( + RadrootsCoreMoney::new(RadrootsCoreDecimal::from(10u32), RadrootsCoreCurrency::USD), + RadrootsCoreQuantity::new(RadrootsCoreDecimal::from(1u32), RadrootsCoreUnit::Each), + ), + display_amount: None, + display_unit: None, + display_label: None, + display_price: None, + display_price_unit: None, + }; + + let err = bin.try_total_for_count(1).unwrap_err(); + assert_eq!( + err, + RadrootsCoreQuantityPriceError::UnitMismatch { + have: RadrootsCoreUnit::MassG, + want: RadrootsCoreUnit::Each, + } + ); + } } diff --git a/crates/trade/src/listing/validation.rs b/crates/trade/src/listing/validation.rs @@ -377,10 +377,12 @@ mod tests { let mut event = base_event(&listing); event.tags.clear(); let err = validate_listing_event(&event).unwrap_err(); - assert!(matches!( + assert_eq!( err, - TradeListingValidationError::ParseError { .. } - )); + TradeListingValidationError::ParseError { + error: crate::listing::codec::TradeListingParseError::MissingTag("d".to_string()) + } + ); } #[test] @@ -419,10 +421,7 @@ mod tests { vec!["delivery".into(), "pickup".into()], ]; let err = validate_listing_event(&event).unwrap_err(); - assert!(matches!( - err, - TradeListingValidationError::ParseError { .. } - )); + assert!(format!("{err:?}").starts_with("ParseError")); } #[test] @@ -431,7 +430,7 @@ mod tests { let mut event = base_event(&listing); event.author = "other".into(); let err = validate_listing_event(&event).unwrap_err(); - assert!(matches!(err, TradeListingValidationError::InvalidSeller)); + assert_eq!(err, TradeListingValidationError::InvalidSeller); } #[test] @@ -440,7 +439,7 @@ mod tests { listing.inventory_available = None; let event = base_event(&listing); let err = validate_listing_event(&event).unwrap_err(); - assert!(matches!(err, TradeListingValidationError::MissingInventory)); + assert_eq!(err, TradeListingValidationError::MissingInventory); } #[test] @@ -449,10 +448,7 @@ mod tests { let mut event = base_event(&listing); event.kind = 0; let err = validate_listing_event(&event).unwrap_err(); - assert!(matches!( - err, - TradeListingValidationError::InvalidKind { kind: 0 } - )); + assert_eq!(err, TradeListingValidationError::InvalidKind { kind: 0 }); } #[test]